sun4i-drm changes for 4.13

An unusually big pull request for this merge window, with three notable
 features:
   - V3s display engine support. This is especially notable because it uses
     a different display engine used on the newer Allwinner SoCs (H3, A64
     and the likes) that will be quite easily supported now.
   - HDMI support for the old Allwinner SoCs. This is enabled only on the
     A10s for now, but should be really easy to extend to deal with A10, A20
     and A31
   - Preliminary work to deal with dual-pipeline SoCs (A10, A20, A31, H3,
     etc.). It currently ignores the second pipeline, but we can use the
     dual-pipelines bindings. This will be useful to enable the display
     pipeline while we work on the dual-pipeline.
 -----BEGIN PGP SIGNATURE-----
 
 iQIcBAABAgAGBQJZQZ40AAoJEBx+YmzsjxAgEDgP+wd29aHXZPIp7EGlCR9L1qPS
 62Ps5lrbgwxGJx8Nf7T51tQLl0SDBHrI9xgrQLla1bdqItAMyIkJG2NQ+INN2ZSt
 Yz2aUZL3Xx9DE3dUhbLdfUejOCQZqopn8pPe2egeMYNXS5hsAhwwLB8AYcX3LLCN
 WFpbC3hcd4pih/teDGyJ4bn4I/teyA3qWleq73f+A/eBBbCI4aRZWvx34/G/pzUL
 KeHryE35evukgfOJmZ9wAYMbj8BWHV3t6xObk9TqYrgQ/Vzh7IkAWl1wkcd2qRSQ
 RVhfCI4s0jkd44EHMQLxqh27LQakSviIqZWsJEf91qvi5g9A4ZyIS/H4/dGusfbP
 59QkREjOdnlVqTSfP5E0QzZbzdcbZhV2YoPp73Rv319lif4HqV/9Z7IhDBmNC2IZ
 g2O4J3DKvDjtP4A2s3CoGRVgxf1ag/K+Ig6AEQZL+ShrcHwPEBrVqfqp/MnV+Qg2
 gRv0YimGMKReHaMYkT8NdDtqlAzaLWUFnEYRO92X0B4m3NZENMeeDZ8wMy26/yxo
 TJxwzxxsNON59mZESS0Ci88q/bMnFkyCYvuWIeHol2GgUK7yzN2ePf9p/eUPOziK
 gDbJaKhSZxH67Lerqg+zCFbuPGDu7ZGyOHWMgfG6Xh69bCJptJAJNK03TEvoBvpX
 CSdBObTExOj5S/m64+SI
 =HtFh
 -----END PGP SIGNATURE-----

Merge tag 'sunxi-drm-for-4.13' of https://git.kernel.org/pub/scm/linux/kernel/git/mripard/linux into drm-next

sun4i-drm changes for 4.13

An unusually big pull request for this merge window, with three notable
features:
  - V3s display engine support. This is especially notable because it uses
    a different display engine used on the newer Allwinner SoCs (H3, A64
    and the likes) that will be quite easily supported now.
  - HDMI support for the old Allwinner SoCs. This is enabled only on the
    A10s for now, but should be really easy to extend to deal with A10, A20
    and A31
  - Preliminary work to deal with dual-pipeline SoCs (A10, A20, A31, H3,
    etc.). It currently ignores the second pipeline, but we can use the
    dual-pipelines bindings. This will be useful to enable the display
    pipeline while we work on the dual-pipeline.

* tag 'sunxi-drm-for-4.13' of https://git.kernel.org/pub/scm/linux/kernel/git/mripard/linux: (27 commits)
  drm/sun4i: Add compatible for the A10s pipeline
  drm/sun4i: Add HDMI support
  dt-bindings: display: sun4i: Add allwinner,tcon-channel property
  dt-bindings: display: sun4i: Add HDMI display bindings
  drm/sun4i: Ignore the generic connectors for components
  drm/sun4i: tcon: multiply the vtotal when not in interlace
  drm/sun4i: tcon: Change vertical total size computation inconsistency
  drm/sun4i: tcon: Fix tcon channel 1 backporch calculation
  drm/sun4i: tcon: Switch mux on only for composite
  drm/sun4i: tcon: Move the muxing out of the mode set function
  drm/sun4i: tcon: Add channel debug
  drm/sun4i: tcon: add support for V3s TCON
  drm/sun4i: Add compatible string for V3s display engine
  drm/sun4i: add support for Allwinner DE2 mixers
  drm/sun4i: add a Kconfig option for sun4i-backend
  drm/sun4i: abstract a engine type
  drm/sun4i: return only planes for layers created
  dt-bindings: add bindings for DE2 on V3s SoC
  drm/sun4i: backend: Clarify sun4i_backend_layer_enable debug message
  drm/sun4i: Set TCON clock inside sun4i_tconX_mode_set
  ...
This commit is contained in:
Dave Airlie 2017-06-16 10:02:35 +10:00
commit 7249e3d64e
24 changed files with 2285 additions and 105 deletions

View file

@ -4,6 +4,44 @@ Allwinner A10 Display Pipeline
The Allwinner A10 Display pipeline is composed of several components
that are going to be documented below:
For the input port of all components up to the TCON in the display
pipeline, if there are multiple components, the local endpoint IDs
must correspond to the index of the upstream block. For example, if
the remote endpoint is Frontend 1, then the local endpoint ID must
be 1.
Conversely, for the output ports of the same group, the remote endpoint
ID must be the index of the local hardware block. If the local backend
is backend 1, then the remote endpoint ID must be 1.
HDMI Encoder
------------
The HDMI Encoder supports the HDMI video and audio outputs, and does
CEC. It is one end of the pipeline.
Required properties:
- compatible: value must be one of:
* allwinner,sun5i-a10s-hdmi
- reg: base address and size of memory-mapped region
- interrupts: interrupt associated to this IP
- clocks: phandles to the clocks feeding the HDMI encoder
* ahb: the HDMI interface clock
* mod: the HDMI module clock
* pll-0: the first video PLL
* pll-1: the second video PLL
- clock-names: the clock names mentioned above
- dmas: phandles to the DMA channels used by the HDMI encoder
* ddc-tx: The channel for DDC transmission
* ddc-rx: The channel for DDC reception
* audio-tx: The channel used for audio transmission
- dma-names: the channel names mentioned above
- ports: A ports node with endpoint definitions as defined in
Documentation/devicetree/bindings/media/video-interfaces.txt. The
first port should be the input endpoint. The second should be the
output, usually to an HDMI connector.
TV Encoder
----------
@ -31,6 +69,7 @@ Required properties:
* allwinner,sun6i-a31-tcon
* allwinner,sun6i-a31s-tcon
* allwinner,sun8i-a33-tcon
* allwinner,sun8i-v3s-tcon
- reg: base address and size of memory-mapped region
- interrupts: interrupt associated to this IP
- clocks: phandles to the clocks feeding the TCON. Three are needed:
@ -47,12 +86,15 @@ Required properties:
Documentation/devicetree/bindings/media/video-interfaces.txt. The
first port should be the input endpoint, the second one the output
The output should have two endpoints. The first is the block
connected to the TCON channel 0 (usually a panel or a bridge), the
second the block connected to the TCON channel 1 (usually the TV
encoder)
The output may have multiple endpoints. The TCON has two channels,
usually with the first channel being used for the panels interfaces
(RGB, LVDS, etc.), and the second being used for the outputs that
require another controller (TV Encoder, HDMI, etc.). The endpoints
will take an extra property, allwinner,tcon-channel, to specify the
channel the endpoint is associated to. If that property is not
present, the endpoint number will be used as the channel number.
On SoCs other than the A33, there is one more clock required:
On SoCs other than the A33 and V3s, there is one more clock required:
- 'tcon-ch1': The clock driving the TCON channel 1
DRC
@ -138,6 +180,26 @@ Required properties:
Documentation/devicetree/bindings/media/video-interfaces.txt. The
first port should be the input endpoints, the second one the outputs
Display Engine 2.0 Mixer
------------------------
The DE2 mixer have many functionalities, currently only layer blending is
supported.
Required properties:
- compatible: value must be one of:
* allwinner,sun8i-v3s-de2-mixer
- reg: base address and size of the memory-mapped region.
- clocks: phandles to the clocks feeding the mixer
* bus: the mixer interface clock
* mod: the mixer module clock
- clock-names: the clock names mentioned above
- resets: phandles to the reset controllers driving the mixer
- ports: A ports node with endpoint definitions as defined in
Documentation/devicetree/bindings/media/video-interfaces.txt. The
first port should be the input endpoints, the second one the output
Display Engine Pipeline
-----------------------
@ -148,13 +210,15 @@ extra node.
Required properties:
- compatible: value must be one of:
* allwinner,sun5i-a10s-display-engine
* allwinner,sun5i-a13-display-engine
* allwinner,sun6i-a31-display-engine
* allwinner,sun6i-a31s-display-engine
* allwinner,sun8i-a33-display-engine
* allwinner,sun8i-v3s-display-engine
- allwinner,pipelines: list of phandle to the display engine
frontends available.
frontends (DE 1.0) or mixers (DE 2.0) available.
Example:
@ -173,6 +237,57 @@ panel: panel {
};
};
connector {
compatible = "hdmi-connector";
type = "a";
port {
hdmi_con_in: endpoint {
remote-endpoint = <&hdmi_out_con>;
};
};
};
hdmi: hdmi@01c16000 {
compatible = "allwinner,sun5i-a10s-hdmi";
reg = <0x01c16000 0x1000>;
interrupts = <58>;
clocks = <&ccu CLK_AHB_HDMI>, <&ccu CLK_HDMI>,
<&ccu CLK_PLL_VIDEO0_2X>,
<&ccu CLK_PLL_VIDEO1_2X>;
clock-names = "ahb", "mod", "pll-0", "pll-1";
dmas = <&dma SUN4I_DMA_NORMAL 16>,
<&dma SUN4I_DMA_NORMAL 16>,
<&dma SUN4I_DMA_DEDICATED 24>;
dma-names = "ddc-tx", "ddc-rx", "audio-tx";
status = "disabled";
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
hdmi_in_tcon0: endpoint {
remote-endpoint = <&tcon0_out_hdmi>;
};
};
port@1 {
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
hdmi_out_con: endpoint {
remote-endpoint = <&hdmi_con_in>;
};
};
};
};
tve0: tv-encoder@01c0a000 {
compatible = "allwinner,sun4i-a10-tv-encoder";
reg = <0x01c0a000 0x1000>;

View file

@ -12,3 +12,31 @@ config DRM_SUN4I
Choose this option if you have an Allwinner SoC with a
Display Engine. If M is selected the module will be called
sun4i-drm.
config DRM_SUN4I_HDMI
tristate "Allwinner A10 HDMI Controller Support"
depends on DRM_SUN4I
default DRM_SUN4I
help
Choose this option if you have an Allwinner SoC with an HDMI
controller.
config DRM_SUN4I_BACKEND
tristate "Support for Allwinner A10 Display Engine Backend"
depends on DRM_SUN4I
default DRM_SUN4I
help
Choose this option if you have an Allwinner SoC with the
original Allwinner Display Engine, which has a backend to
do some alpha blending and feed graphics to TCON. If M is
selected the module will be called sun4i-backend.
config DRM_SUN8I_MIXER
tristate "Support for Allwinner Display Engine 2.0 Mixer"
depends on DRM_SUN4I
default MACH_SUN8I
help
Choose this option if you have an Allwinner SoC with the
Allwinner Display Engine 2.0, which has a mixer to do some
graphics mixture and feed graphics to TCON, If M is
selected the module will be called sun8i-mixer.

View file

@ -1,13 +1,23 @@
sun4i-drm-y += sun4i_drv.o
sun4i-drm-y += sun4i_framebuffer.o
sun4i-drm-hdmi-y += sun4i_hdmi_enc.o
sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o
sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o
sun4i-tcon-y += sun4i_tcon.o
sun4i-tcon-y += sun4i_rgb.o
sun4i-tcon-y += sun4i_dotclock.o
sun4i-tcon-y += sun4i_crtc.o
sun4i-tcon-y += sun4i_layer.o
sun4i-backend-y += sun4i_backend.o sun4i_layer.o
sun8i-mixer-y += sun8i_mixer.o sun8i_layer.o
obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o sun4i-tcon.o
obj-$(CONFIG_DRM_SUN4I) += sun4i_backend.o
obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o
obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o
obj-$(CONFIG_DRM_SUN4I_BACKEND) += sun4i-backend.o
obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o
obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o

View file

@ -19,10 +19,14 @@
#include <drm/drm_plane_helper.h>
#include <linux/component.h>
#include <linux/list.h>
#include <linux/of_graph.h>
#include <linux/reset.h>
#include "sun4i_backend.h"
#include "sun4i_drv.h"
#include "sun4i_layer.h"
#include "sunxi_engine.h"
static const u32 sunxi_rgb2yuv_coef[12] = {
0x00000107, 0x00000204, 0x00000064, 0x00000108,
@ -30,58 +34,55 @@ static const u32 sunxi_rgb2yuv_coef[12] = {
0x000001c1, 0x00003e88, 0x00003fb8, 0x00000808
};
void sun4i_backend_apply_color_correction(struct sun4i_backend *backend)
static void sun4i_backend_apply_color_correction(struct sunxi_engine *engine)
{
int i;
DRM_DEBUG_DRIVER("Applying RGB to YUV color correction\n");
/* Set color correction */
regmap_write(backend->regs, SUN4I_BACKEND_OCCTL_REG,
regmap_write(engine->regs, SUN4I_BACKEND_OCCTL_REG,
SUN4I_BACKEND_OCCTL_ENABLE);
for (i = 0; i < 12; i++)
regmap_write(backend->regs, SUN4I_BACKEND_OCRCOEF_REG(i),
regmap_write(engine->regs, SUN4I_BACKEND_OCRCOEF_REG(i),
sunxi_rgb2yuv_coef[i]);
}
EXPORT_SYMBOL(sun4i_backend_apply_color_correction);
void sun4i_backend_disable_color_correction(struct sun4i_backend *backend)
static void sun4i_backend_disable_color_correction(struct sunxi_engine *engine)
{
DRM_DEBUG_DRIVER("Disabling color correction\n");
/* Disable color correction */
regmap_update_bits(backend->regs, SUN4I_BACKEND_OCCTL_REG,
regmap_update_bits(engine->regs, SUN4I_BACKEND_OCCTL_REG,
SUN4I_BACKEND_OCCTL_ENABLE, 0);
}
EXPORT_SYMBOL(sun4i_backend_disable_color_correction);
void sun4i_backend_commit(struct sun4i_backend *backend)
static void sun4i_backend_commit(struct sunxi_engine *engine)
{
DRM_DEBUG_DRIVER("Committing changes\n");
regmap_write(backend->regs, SUN4I_BACKEND_REGBUFFCTL_REG,
regmap_write(engine->regs, SUN4I_BACKEND_REGBUFFCTL_REG,
SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS |
SUN4I_BACKEND_REGBUFFCTL_LOADCTL);
}
EXPORT_SYMBOL(sun4i_backend_commit);
void sun4i_backend_layer_enable(struct sun4i_backend *backend,
int layer, bool enable)
{
u32 val;
DRM_DEBUG_DRIVER("Enabling layer %d\n", layer);
DRM_DEBUG_DRIVER("%sabling layer %d\n", enable ? "En" : "Dis",
layer);
if (enable)
val = SUN4I_BACKEND_MODCTL_LAY_EN(layer);
else
val = 0;
regmap_update_bits(backend->regs, SUN4I_BACKEND_MODCTL_REG,
regmap_update_bits(backend->engine.regs, SUN4I_BACKEND_MODCTL_REG,
SUN4I_BACKEND_MODCTL_LAY_EN(layer), val);
}
EXPORT_SYMBOL(sun4i_backend_layer_enable);
static int sun4i_backend_drm_format_to_layer(struct drm_plane *plane,
u32 format, u32 *mode)
@ -141,33 +142,33 @@ int sun4i_backend_update_layer_coord(struct sun4i_backend *backend,
if (plane->type == DRM_PLANE_TYPE_PRIMARY) {
DRM_DEBUG_DRIVER("Primary layer, updating global size W: %u H: %u\n",
state->crtc_w, state->crtc_h);
regmap_write(backend->regs, SUN4I_BACKEND_DISSIZE_REG,
regmap_write(backend->engine.regs, SUN4I_BACKEND_DISSIZE_REG,
SUN4I_BACKEND_DISSIZE(state->crtc_w,
state->crtc_h));
}
/* Set the line width */
DRM_DEBUG_DRIVER("Layer line width: %d bits\n", fb->pitches[0] * 8);
regmap_write(backend->regs, SUN4I_BACKEND_LAYLINEWIDTH_REG(layer),
regmap_write(backend->engine.regs,
SUN4I_BACKEND_LAYLINEWIDTH_REG(layer),
fb->pitches[0] * 8);
/* Set height and width */
DRM_DEBUG_DRIVER("Layer size W: %u H: %u\n",
state->crtc_w, state->crtc_h);
regmap_write(backend->regs, SUN4I_BACKEND_LAYSIZE_REG(layer),
regmap_write(backend->engine.regs, SUN4I_BACKEND_LAYSIZE_REG(layer),
SUN4I_BACKEND_LAYSIZE(state->crtc_w,
state->crtc_h));
/* Set base coordinates */
DRM_DEBUG_DRIVER("Layer coordinates X: %d Y: %d\n",
state->crtc_x, state->crtc_y);
regmap_write(backend->regs, SUN4I_BACKEND_LAYCOOR_REG(layer),
regmap_write(backend->engine.regs, SUN4I_BACKEND_LAYCOOR_REG(layer),
SUN4I_BACKEND_LAYCOOR(state->crtc_x,
state->crtc_y));
return 0;
}
EXPORT_SYMBOL(sun4i_backend_update_layer_coord);
int sun4i_backend_update_layer_formats(struct sun4i_backend *backend,
int layer, struct drm_plane *plane)
@ -182,7 +183,7 @@ int sun4i_backend_update_layer_formats(struct sun4i_backend *backend,
interlaced = plane->state->crtc->state->adjusted_mode.flags
& DRM_MODE_FLAG_INTERLACE;
regmap_update_bits(backend->regs, SUN4I_BACKEND_MODCTL_REG,
regmap_update_bits(backend->engine.regs, SUN4I_BACKEND_MODCTL_REG,
SUN4I_BACKEND_MODCTL_ITLMOD_EN,
interlaced ? SUN4I_BACKEND_MODCTL_ITLMOD_EN : 0);
@ -196,12 +197,12 @@ int sun4i_backend_update_layer_formats(struct sun4i_backend *backend,
return ret;
}
regmap_update_bits(backend->regs, SUN4I_BACKEND_ATTCTL_REG1(layer),
regmap_update_bits(backend->engine.regs,
SUN4I_BACKEND_ATTCTL_REG1(layer),
SUN4I_BACKEND_ATTCTL_REG1_LAY_FBFMT, val);
return 0;
}
EXPORT_SYMBOL(sun4i_backend_update_layer_formats);
int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend,
int layer, struct drm_plane *plane)
@ -229,19 +230,19 @@ int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend,
/* Write the 32 lower bits of the address (in bits) */
lo_paddr = paddr << 3;
DRM_DEBUG_DRIVER("Setting address lower bits to 0x%x\n", lo_paddr);
regmap_write(backend->regs, SUN4I_BACKEND_LAYFB_L32ADD_REG(layer),
regmap_write(backend->engine.regs,
SUN4I_BACKEND_LAYFB_L32ADD_REG(layer),
lo_paddr);
/* And the upper bits */
hi_paddr = paddr >> 29;
DRM_DEBUG_DRIVER("Setting address high bits to 0x%x\n", hi_paddr);
regmap_update_bits(backend->regs, SUN4I_BACKEND_LAYFB_H4ADD_REG,
regmap_update_bits(backend->engine.regs, SUN4I_BACKEND_LAYFB_H4ADD_REG,
SUN4I_BACKEND_LAYFB_H4ADD_MSK(layer),
SUN4I_BACKEND_LAYFB_H4ADD(layer, hi_paddr));
return 0;
}
EXPORT_SYMBOL(sun4i_backend_update_layer_buffer);
static int sun4i_backend_init_sat(struct device *dev) {
struct sun4i_backend *backend = dev_get_drvdata(dev);
@ -288,6 +289,52 @@ static int sun4i_backend_free_sat(struct device *dev) {
return 0;
}
/*
* The display backend can take video output from the display frontend, or
* the display enhancement unit on the A80, as input for one it its layers.
* This relationship within the display pipeline is encoded in the device
* tree with of_graph, and we use it here to figure out which backend, if
* there are 2 or more, we are currently probing. The number would be in
* the "reg" property of the upstream output port endpoint.
*/
static int sun4i_backend_of_get_id(struct device_node *node)
{
struct device_node *port, *ep;
int ret = -EINVAL;
/* input is port 0 */
port = of_graph_get_port_by_id(node, 0);
if (!port)
return -EINVAL;
/* try finding an upstream endpoint */
for_each_available_child_of_node(port, ep) {
struct device_node *remote;
u32 reg;
remote = of_parse_phandle(ep, "remote-endpoint", 0);
if (!remote)
continue;
ret = of_property_read_u32(remote, "reg", &reg);
if (ret)
continue;
ret = reg;
}
of_node_put(port);
return ret;
}
static const struct sunxi_engine_ops sun4i_backend_engine_ops = {
.commit = sun4i_backend_commit,
.layers_init = sun4i_layers_init,
.apply_color_correction = sun4i_backend_apply_color_correction,
.disable_color_correction = sun4i_backend_disable_color_correction,
};
static struct regmap_config sun4i_backend_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
@ -310,18 +357,23 @@ static int sun4i_backend_bind(struct device *dev, struct device *master,
if (!backend)
return -ENOMEM;
dev_set_drvdata(dev, backend);
drv->backend = backend;
backend->engine.node = dev->of_node;
backend->engine.ops = &sun4i_backend_engine_ops;
backend->engine.id = sun4i_backend_of_get_id(dev->of_node);
if (backend->engine.id < 0)
return backend->engine.id;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
regs = devm_ioremap_resource(dev, res);
if (IS_ERR(regs))
return PTR_ERR(regs);
backend->regs = devm_regmap_init_mmio(dev, regs,
&sun4i_backend_regmap_config);
if (IS_ERR(backend->regs)) {
dev_err(dev, "Couldn't create the backend0 regmap\n");
return PTR_ERR(backend->regs);
backend->engine.regs = devm_regmap_init_mmio(dev, regs,
&sun4i_backend_regmap_config);
if (IS_ERR(backend->engine.regs)) {
dev_err(dev, "Couldn't create the backend regmap\n");
return PTR_ERR(backend->engine.regs);
}
backend->reset = devm_reset_control_get(dev, NULL);
@ -369,16 +421,18 @@ static int sun4i_backend_bind(struct device *dev, struct device *master,
}
}
list_add_tail(&backend->engine.list, &drv->engine_list);
/* Reset the registers */
for (i = 0x800; i < 0x1000; i += 4)
regmap_write(backend->regs, i, 0);
regmap_write(backend->engine.regs, i, 0);
/* Disable registers autoloading */
regmap_write(backend->regs, SUN4I_BACKEND_REGBUFFCTL_REG,
regmap_write(backend->engine.regs, SUN4I_BACKEND_REGBUFFCTL_REG,
SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS);
/* Enable the backend */
regmap_write(backend->regs, SUN4I_BACKEND_MODCTL_REG,
regmap_write(backend->engine.regs, SUN4I_BACKEND_MODCTL_REG,
SUN4I_BACKEND_MODCTL_DEBE_EN |
SUN4I_BACKEND_MODCTL_START_CTL);
@ -400,6 +454,8 @@ static void sun4i_backend_unbind(struct device *dev, struct device *master,
{
struct sun4i_backend *backend = dev_get_drvdata(dev);
list_del(&backend->engine.list);
if (of_device_is_compatible(dev->of_node,
"allwinner,sun8i-a33-display-backend"))
sun4i_backend_free_sat(dev);

View file

@ -14,9 +14,13 @@
#define _SUN4I_BACKEND_H_
#include <linux/clk.h>
#include <linux/list.h>
#include <linux/of.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include "sunxi_engine.h"
#define SUN4I_BACKEND_MODCTL_REG 0x800
#define SUN4I_BACKEND_MODCTL_LINE_SEL BIT(29)
#define SUN4I_BACKEND_MODCTL_ITLMOD_EN BIT(28)
@ -139,7 +143,7 @@
#define SUN4I_BACKEND_PIPE_OFF(p) (0x5000 + (0x400 * (p)))
struct sun4i_backend {
struct regmap *regs;
struct sunxi_engine engine;
struct reset_control *reset;
@ -151,10 +155,11 @@ struct sun4i_backend {
struct reset_control *sat_reset;
};
void sun4i_backend_apply_color_correction(struct sun4i_backend *backend);
void sun4i_backend_disable_color_correction(struct sun4i_backend *backend);
void sun4i_backend_commit(struct sun4i_backend *backend);
static inline struct sun4i_backend *
engine_to_sun4i_backend(struct sunxi_engine *engine)
{
return container_of(engine, struct sun4i_backend, engine);
}
void sun4i_backend_layer_enable(struct sun4i_backend *backend,
int layer, bool enable);

View file

@ -25,10 +25,9 @@
#include <video/videomode.h>
#include "sun4i_backend.h"
#include "sun4i_crtc.h"
#include "sun4i_drv.h"
#include "sun4i_layer.h"
#include "sunxi_engine.h"
#include "sun4i_tcon.h"
static void sun4i_crtc_atomic_begin(struct drm_crtc *crtc,
@ -56,7 +55,7 @@ static void sun4i_crtc_atomic_flush(struct drm_crtc *crtc,
DRM_DEBUG_DRIVER("Committing plane changes\n");
sun4i_backend_commit(scrtc->backend);
sunxi_engine_commit(scrtc->engine);
if (event) {
crtc->state->event = NULL;
@ -135,36 +134,37 @@ static const struct drm_crtc_funcs sun4i_crtc_funcs = {
};
struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm,
struct sun4i_backend *backend,
struct sunxi_engine *engine,
struct sun4i_tcon *tcon)
{
struct sun4i_crtc *scrtc;
struct drm_plane **planes;
struct drm_plane *primary = NULL, *cursor = NULL;
int ret, i;
scrtc = devm_kzalloc(drm->dev, sizeof(*scrtc), GFP_KERNEL);
if (!scrtc)
return ERR_PTR(-ENOMEM);
scrtc->backend = backend;
scrtc->engine = engine;
scrtc->tcon = tcon;
/* Create our layers */
scrtc->layers = sun4i_layers_init(drm, scrtc->backend);
if (IS_ERR(scrtc->layers)) {
planes = sunxi_engine_layers_init(drm, engine);
if (IS_ERR(planes)) {
dev_err(drm->dev, "Couldn't create the planes\n");
return NULL;
}
/* find primary and cursor planes for drm_crtc_init_with_planes */
for (i = 0; scrtc->layers[i]; i++) {
struct sun4i_layer *layer = scrtc->layers[i];
for (i = 0; planes[i]; i++) {
struct drm_plane *plane = planes[i];
switch (layer->plane.type) {
switch (plane->type) {
case DRM_PLANE_TYPE_PRIMARY:
primary = &layer->plane;
primary = plane;
break;
case DRM_PLANE_TYPE_CURSOR:
cursor = &layer->plane;
cursor = plane;
break;
default:
break;
@ -188,12 +188,12 @@ struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm,
1);
/* Set possible_crtcs to this crtc for overlay planes */
for (i = 0; scrtc->layers[i]; i++) {
for (i = 0; planes[i]; i++) {
uint32_t possible_crtcs = BIT(drm_crtc_index(&scrtc->crtc));
struct sun4i_layer *layer = scrtc->layers[i];
struct drm_plane *plane = planes[i];
if (layer->plane.type == DRM_PLANE_TYPE_OVERLAY)
layer->plane.possible_crtcs = possible_crtcs;
if (plane->type == DRM_PLANE_TYPE_OVERLAY)
plane->possible_crtcs = possible_crtcs;
}
return scrtc;

View file

@ -17,9 +17,8 @@ struct sun4i_crtc {
struct drm_crtc crtc;
struct drm_pending_vblank_event *event;
struct sun4i_backend *backend;
struct sunxi_engine *engine;
struct sun4i_tcon *tcon;
struct sun4i_layer **layers;
};
static inline struct sun4i_crtc *drm_crtc_to_sun4i_crtc(struct drm_crtc *crtc)
@ -28,7 +27,7 @@ static inline struct sun4i_crtc *drm_crtc_to_sun4i_crtc(struct drm_crtc *crtc)
}
struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm,
struct sun4i_backend *backend,
struct sunxi_engine *engine,
struct sun4i_tcon *tcon);
#endif /* _SUN4I_CRTC_H_ */

View file

@ -91,6 +91,8 @@ static int sun4i_drv_bind(struct device *dev)
goto free_drm;
}
drm->dev_private = drv;
INIT_LIST_HEAD(&drv->engine_list);
INIT_LIST_HEAD(&drv->tcon_list);
ret = of_reserved_mem_device_init(dev);
if (ret && ret != -ENODEV) {
@ -162,6 +164,11 @@ static const struct component_master_ops sun4i_drv_master_ops = {
.unbind = sun4i_drv_unbind,
};
static bool sun4i_drv_node_is_connector(struct device_node *node)
{
return of_device_is_compatible(node, "hdmi-connector");
}
static bool sun4i_drv_node_is_frontend(struct device_node *node)
{
return of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") ||
@ -174,7 +181,8 @@ static bool sun4i_drv_node_is_tcon(struct device_node *node)
return of_device_is_compatible(node, "allwinner,sun5i-a13-tcon") ||
of_device_is_compatible(node, "allwinner,sun6i-a31-tcon") ||
of_device_is_compatible(node, "allwinner,sun6i-a31s-tcon") ||
of_device_is_compatible(node, "allwinner,sun8i-a33-tcon");
of_device_is_compatible(node, "allwinner,sun8i-a33-tcon") ||
of_device_is_compatible(node, "allwinner,sun8i-v3s-tcon");
}
static int compare_of(struct device *dev, void *data)
@ -202,6 +210,13 @@ static int sun4i_drv_add_endpoints(struct device *dev,
!of_device_is_available(node))
return 0;
/*
* The connectors will be the last nodes in our pipeline, we
* can just bail out.
*/
if (sun4i_drv_node_is_connector(node))
return 0;
if (!sun4i_drv_node_is_frontend(node)) {
/* Add current component */
DRM_DEBUG_DRIVER("Adding component %s\n",
@ -288,10 +303,12 @@ static int sun4i_drv_remove(struct platform_device *pdev)
}
static const struct of_device_id sun4i_drv_of_table[] = {
{ .compatible = "allwinner,sun5i-a10s-display-engine" },
{ .compatible = "allwinner,sun5i-a13-display-engine" },
{ .compatible = "allwinner,sun6i-a31-display-engine" },
{ .compatible = "allwinner,sun6i-a31s-display-engine" },
{ .compatible = "allwinner,sun8i-a33-display-engine" },
{ .compatible = "allwinner,sun8i-v3s-display-engine" },
{ }
};
MODULE_DEVICE_TABLE(of, sun4i_drv_of_table);

View file

@ -14,11 +14,12 @@
#define _SUN4I_DRV_H_
#include <linux/clk.h>
#include <linux/list.h>
#include <linux/regmap.h>
struct sun4i_drv {
struct sun4i_backend *backend;
struct sun4i_tcon *tcon;
struct list_head engine_list;
struct list_head tcon_list;
struct drm_fbdev_cma *fbdev;
};

View file

@ -0,0 +1,157 @@
/*
* Copyright (C) 2016 Maxime Ripard
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*/
#ifndef _SUN4I_HDMI_H_
#define _SUN4I_HDMI_H_
#include <drm/drm_connector.h>
#include <drm/drm_encoder.h>
#define SUN4I_HDMI_CTRL_REG 0x004
#define SUN4I_HDMI_CTRL_ENABLE BIT(31)
#define SUN4I_HDMI_IRQ_REG 0x008
#define SUN4I_HDMI_IRQ_STA_MASK 0x73
#define SUN4I_HDMI_IRQ_STA_FIFO_OF BIT(1)
#define SUN4I_HDMI_IRQ_STA_FIFO_UF BIT(0)
#define SUN4I_HDMI_HPD_REG 0x00c
#define SUN4I_HDMI_HPD_HIGH BIT(0)
#define SUN4I_HDMI_VID_CTRL_REG 0x010
#define SUN4I_HDMI_VID_CTRL_ENABLE BIT(31)
#define SUN4I_HDMI_VID_CTRL_HDMI_MODE BIT(30)
#define SUN4I_HDMI_VID_TIMING_ACT_REG 0x014
#define SUN4I_HDMI_VID_TIMING_BP_REG 0x018
#define SUN4I_HDMI_VID_TIMING_FP_REG 0x01c
#define SUN4I_HDMI_VID_TIMING_SPW_REG 0x020
#define SUN4I_HDMI_VID_TIMING_X(x) ((((x) - 1) & GENMASK(11, 0)))
#define SUN4I_HDMI_VID_TIMING_Y(y) ((((y) - 1) & GENMASK(11, 0)) << 16)
#define SUN4I_HDMI_VID_TIMING_POL_REG 0x024
#define SUN4I_HDMI_VID_TIMING_POL_TX_CLK (0x3e0 << 16)
#define SUN4I_HDMI_VID_TIMING_POL_VSYNC BIT(1)
#define SUN4I_HDMI_VID_TIMING_POL_HSYNC BIT(0)
#define SUN4I_HDMI_AVI_INFOFRAME_REG(n) (0x080 + (n))
#define SUN4I_HDMI_PAD_CTRL0_REG 0x200
#define SUN4I_HDMI_PAD_CTRL0_BIASEN BIT(31)
#define SUN4I_HDMI_PAD_CTRL0_LDOCEN BIT(30)
#define SUN4I_HDMI_PAD_CTRL0_LDODEN BIT(29)
#define SUN4I_HDMI_PAD_CTRL0_PWENC BIT(28)
#define SUN4I_HDMI_PAD_CTRL0_PWEND BIT(27)
#define SUN4I_HDMI_PAD_CTRL0_PWENG BIT(26)
#define SUN4I_HDMI_PAD_CTRL0_CKEN BIT(25)
#define SUN4I_HDMI_PAD_CTRL0_TXEN BIT(23)
#define SUN4I_HDMI_PAD_CTRL1_REG 0x204
#define SUN4I_HDMI_PAD_CTRL1_AMP_OPT BIT(23)
#define SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT BIT(22)
#define SUN4I_HDMI_PAD_CTRL1_EMP_OPT BIT(20)
#define SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT BIT(19)
#define SUN4I_HDMI_PAD_CTRL1_REG_DEN BIT(15)
#define SUN4I_HDMI_PAD_CTRL1_REG_DENCK BIT(14)
#define SUN4I_HDMI_PAD_CTRL1_REG_EMP(n) (((n) & 7) << 10)
#define SUN4I_HDMI_PAD_CTRL1_HALVE_CLK BIT(6)
#define SUN4I_HDMI_PAD_CTRL1_REG_AMP(n) (((n) & 7) << 3)
#define SUN4I_HDMI_PLL_CTRL_REG 0x208
#define SUN4I_HDMI_PLL_CTRL_PLL_EN BIT(31)
#define SUN4I_HDMI_PLL_CTRL_BWS BIT(30)
#define SUN4I_HDMI_PLL_CTRL_HV_IS_33 BIT(29)
#define SUN4I_HDMI_PLL_CTRL_LDO1_EN BIT(28)
#define SUN4I_HDMI_PLL_CTRL_LDO2_EN BIT(27)
#define SUN4I_HDMI_PLL_CTRL_SDIV2 BIT(25)
#define SUN4I_HDMI_PLL_CTRL_VCO_GAIN(n) (((n) & 7) << 20)
#define SUN4I_HDMI_PLL_CTRL_S(n) (((n) & 7) << 17)
#define SUN4I_HDMI_PLL_CTRL_CP_S(n) (((n) & 0x1f) << 12)
#define SUN4I_HDMI_PLL_CTRL_CS(n) (((n) & 0xf) << 8)
#define SUN4I_HDMI_PLL_CTRL_DIV(n) (((n) & 0xf) << 4)
#define SUN4I_HDMI_PLL_CTRL_DIV_MASK GENMASK(7, 4)
#define SUN4I_HDMI_PLL_CTRL_VCO_S(n) ((n) & 0xf)
#define SUN4I_HDMI_PLL_DBG0_REG 0x20c
#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(n) (((n) & 1) << 21)
#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK BIT(21)
#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT 21
#define SUN4I_HDMI_PKT_CTRL_REG(n) (0x2f0 + (4 * (n)))
#define SUN4I_HDMI_PKT_CTRL_TYPE(n, t) ((t) << (((n) % 4) * 4))
#define SUN4I_HDMI_UNKNOWN_REG 0x300
#define SUN4I_HDMI_UNKNOWN_INPUT_SYNC BIT(27)
#define SUN4I_HDMI_DDC_CTRL_REG 0x500
#define SUN4I_HDMI_DDC_CTRL_ENABLE BIT(31)
#define SUN4I_HDMI_DDC_CTRL_START_CMD BIT(30)
#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK BIT(8)
#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ (0 << 8)
#define SUN4I_HDMI_DDC_CTRL_RESET BIT(0)
#define SUN4I_HDMI_DDC_ADDR_REG 0x504
#define SUN4I_HDMI_DDC_ADDR_SEGMENT(seg) (((seg) & 0xff) << 24)
#define SUN4I_HDMI_DDC_ADDR_EDDC(addr) (((addr) & 0xff) << 16)
#define SUN4I_HDMI_DDC_ADDR_OFFSET(off) (((off) & 0xff) << 8)
#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr) ((addr) & 0xff)
#define SUN4I_HDMI_DDC_FIFO_CTRL_REG 0x510
#define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(31)
#define SUN4I_HDMI_DDC_FIFO_DATA_REG 0x518
#define SUN4I_HDMI_DDC_BYTE_COUNT_REG 0x51c
#define SUN4I_HDMI_DDC_CMD_REG 0x520
#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ 6
#define SUN4I_HDMI_DDC_CLK_REG 0x528
#define SUN4I_HDMI_DDC_CLK_M(m) (((m) & 0x7) << 3)
#define SUN4I_HDMI_DDC_CLK_N(n) ((n) & 0x7)
#define SUN4I_HDMI_DDC_LINE_CTRL_REG 0x540
#define SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE BIT(9)
#define SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE BIT(8)
#define SUN4I_HDMI_DDC_FIFO_SIZE 16
enum sun4i_hdmi_pkt_type {
SUN4I_HDMI_PKT_AVI = 2,
SUN4I_HDMI_PKT_END = 15,
};
struct sun4i_hdmi {
struct drm_connector connector;
struct drm_encoder encoder;
struct device *dev;
void __iomem *base;
/* Parent clocks */
struct clk *bus_clk;
struct clk *mod_clk;
struct clk *pll0_clk;
struct clk *pll1_clk;
/* And the clocks we create */
struct clk *ddc_clk;
struct clk *tmds_clk;
struct sun4i_drv *drv;
bool hdmi_monitor;
};
int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
int sun4i_tmds_create(struct sun4i_hdmi *hdmi);
#endif /* _SUN4I_HDMI_H_ */

View file

@ -0,0 +1,127 @@
/*
* Copyright (C) 2016 Free Electrons
* Copyright (C) 2016 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*/
#include <linux/clk-provider.h>
#include "sun4i_tcon.h"
#include "sun4i_hdmi.h"
struct sun4i_ddc {
struct clk_hw hw;
struct sun4i_hdmi *hdmi;
};
static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw)
{
return container_of(hw, struct sun4i_ddc, hw);
}
static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
unsigned long parent_rate,
u8 *m, u8 *n)
{
unsigned long best_rate = 0;
u8 best_m = 0, best_n = 0, _m, _n;
for (_m = 0; _m < 8; _m++) {
for (_n = 0; _n < 8; _n++) {
unsigned long tmp_rate;
tmp_rate = (((parent_rate / 2) / 10) >> _n) / (_m + 1);
if (tmp_rate > rate)
continue;
if (abs(rate - tmp_rate) < abs(rate - best_rate)) {
best_rate = tmp_rate;
best_m = _m;
best_n = _n;
}
}
}
if (m && n) {
*m = best_m;
*n = best_n;
}
return best_rate;
}
static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
return sun4i_ddc_calc_divider(rate, *prate, NULL, NULL);
}
static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct sun4i_ddc *ddc = hw_to_ddc(hw);
u32 reg;
u8 m, n;
reg = readl(ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
m = (reg >> 3) & 0x7;
n = reg & 0x7;
return (((parent_rate / 2) / 10) >> n) / (m + 1);
}
static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct sun4i_ddc *ddc = hw_to_ddc(hw);
u8 div_m, div_n;
sun4i_ddc_calc_divider(rate, parent_rate, &div_m, &div_n);
writel(SUN4I_HDMI_DDC_CLK_M(div_m) | SUN4I_HDMI_DDC_CLK_N(div_n),
ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
return 0;
}
static const struct clk_ops sun4i_ddc_ops = {
.recalc_rate = sun4i_ddc_recalc_rate,
.round_rate = sun4i_ddc_round_rate,
.set_rate = sun4i_ddc_set_rate,
};
int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent)
{
struct clk_init_data init;
struct sun4i_ddc *ddc;
const char *parent_name;
parent_name = __clk_get_name(parent);
if (!parent_name)
return -ENODEV;
ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL);
if (!ddc)
return -ENOMEM;
init.name = "hdmi-ddc";
init.ops = &sun4i_ddc_ops;
init.parent_names = &parent_name;
init.num_parents = 1;
ddc->hdmi = hdmi;
ddc->hw.init = &init;
hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw);
if (IS_ERR(hdmi->ddc_clk))
return PTR_ERR(hdmi->ddc_clk);
return 0;
}

View file

@ -0,0 +1,501 @@
/*
* Copyright (C) 2016 Maxime Ripard
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*/
#include <drm/drmP.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_encoder.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/iopoll.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include "sun4i_backend.h"
#include "sun4i_crtc.h"
#include "sun4i_drv.h"
#include "sun4i_hdmi.h"
#include "sun4i_tcon.h"
#define DDC_SEGMENT_ADDR 0x30
static inline struct sun4i_hdmi *
drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder)
{
return container_of(encoder, struct sun4i_hdmi,
encoder);
}
static inline struct sun4i_hdmi *
drm_connector_to_sun4i_hdmi(struct drm_connector *connector)
{
return container_of(connector, struct sun4i_hdmi,
connector);
}
static int sun4i_hdmi_setup_avi_infoframes(struct sun4i_hdmi *hdmi,
struct drm_display_mode *mode)
{
struct hdmi_avi_infoframe frame;
u8 buffer[17];
int i, ret;
ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode);
if (ret < 0) {
DRM_ERROR("Failed to get infoframes from mode\n");
return ret;
}
ret = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer));
if (ret < 0) {
DRM_ERROR("Failed to pack infoframes\n");
return ret;
}
for (i = 0; i < sizeof(buffer); i++)
writeb(buffer[i], hdmi->base + SUN4I_HDMI_AVI_INFOFRAME_REG(i));
return 0;
}
static int sun4i_hdmi_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct drm_display_mode *mode = &crtc_state->mode;
if (mode->flags & DRM_MODE_FLAG_DBLCLK)
return -EINVAL;
return 0;
}
static void sun4i_hdmi_disable(struct drm_encoder *encoder)
{
struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
struct sun4i_tcon *tcon = crtc->tcon;
u32 val;
DRM_DEBUG_DRIVER("Disabling the HDMI Output\n");
val = readl(hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
val &= ~SUN4I_HDMI_VID_CTRL_ENABLE;
writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
sun4i_tcon_channel_disable(tcon, 1);
}
static void sun4i_hdmi_enable(struct drm_encoder *encoder)
{
struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
struct sun4i_tcon *tcon = crtc->tcon;
u32 val = 0;
DRM_DEBUG_DRIVER("Enabling the HDMI Output\n");
sun4i_tcon_channel_enable(tcon, 1);
sun4i_hdmi_setup_avi_infoframes(hdmi, mode);
val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI);
val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END);
writel(val, hdmi->base + SUN4I_HDMI_PKT_CTRL_REG(0));
val = SUN4I_HDMI_VID_CTRL_ENABLE;
if (hdmi->hdmi_monitor)
val |= SUN4I_HDMI_VID_CTRL_HDMI_MODE;
writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
}
static void sun4i_hdmi_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
struct sun4i_tcon *tcon = crtc->tcon;
unsigned int x, y;
u32 val;
sun4i_tcon1_mode_set(tcon, mode);
sun4i_tcon_set_mux(tcon, 1, encoder);
clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
clk_set_rate(hdmi->mod_clk, mode->crtc_clock * 1000);
clk_set_rate(hdmi->tmds_clk, mode->crtc_clock * 1000);
/* Set input sync enable */
writel(SUN4I_HDMI_UNKNOWN_INPUT_SYNC,
hdmi->base + SUN4I_HDMI_UNKNOWN_REG);
/* Setup timing registers */
writel(SUN4I_HDMI_VID_TIMING_X(mode->hdisplay) |
SUN4I_HDMI_VID_TIMING_Y(mode->vdisplay),
hdmi->base + SUN4I_HDMI_VID_TIMING_ACT_REG);
x = mode->htotal - mode->hsync_start;
y = mode->vtotal - mode->vsync_start;
writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
hdmi->base + SUN4I_HDMI_VID_TIMING_BP_REG);
x = mode->hsync_start - mode->hdisplay;
y = mode->vsync_start - mode->vdisplay;
writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
hdmi->base + SUN4I_HDMI_VID_TIMING_FP_REG);
x = mode->hsync_end - mode->hsync_start;
y = mode->vsync_end - mode->vsync_start;
writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
hdmi->base + SUN4I_HDMI_VID_TIMING_SPW_REG);
val = SUN4I_HDMI_VID_TIMING_POL_TX_CLK;
if (mode->flags & DRM_MODE_FLAG_PHSYNC)
val |= SUN4I_HDMI_VID_TIMING_POL_HSYNC;
if (mode->flags & DRM_MODE_FLAG_PVSYNC)
val |= SUN4I_HDMI_VID_TIMING_POL_VSYNC;
writel(val, hdmi->base + SUN4I_HDMI_VID_TIMING_POL_REG);
}
static const struct drm_encoder_helper_funcs sun4i_hdmi_helper_funcs = {
.atomic_check = sun4i_hdmi_atomic_check,
.disable = sun4i_hdmi_disable,
.enable = sun4i_hdmi_enable,
.mode_set = sun4i_hdmi_mode_set,
};
static const struct drm_encoder_funcs sun4i_hdmi_funcs = {
.destroy = drm_encoder_cleanup,
};
static int sun4i_hdmi_read_sub_block(struct sun4i_hdmi *hdmi,
unsigned int blk, unsigned int offset,
u8 *buf, unsigned int count)
{
unsigned long reg;
int i;
reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
writel(reg | SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ,
hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
writel(SUN4I_HDMI_DDC_ADDR_SEGMENT(offset >> 8) |
SUN4I_HDMI_DDC_ADDR_EDDC(DDC_SEGMENT_ADDR << 1) |
SUN4I_HDMI_DDC_ADDR_OFFSET(offset) |
SUN4I_HDMI_DDC_ADDR_SLAVE(DDC_ADDR),
hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
writel(reg | SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR,
hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
writel(count, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG);
writel(SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ,
hdmi->base + SUN4I_HDMI_DDC_CMD_REG);
reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
!(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
100, 100000))
return -EIO;
for (i = 0; i < count; i++)
buf[i] = readb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG);
return 0;
}
static int sun4i_hdmi_read_edid_block(void *data, u8 *buf, unsigned int blk,
size_t length)
{
struct sun4i_hdmi *hdmi = data;
int retry = 2, i;
do {
for (i = 0; i < length; i += SUN4I_HDMI_DDC_FIFO_SIZE) {
unsigned char offset = blk * EDID_LENGTH + i;
unsigned int count = min((unsigned int)SUN4I_HDMI_DDC_FIFO_SIZE,
length - i);
int ret;
ret = sun4i_hdmi_read_sub_block(hdmi, blk, offset,
buf + i, count);
if (ret)
return ret;
}
} while (!drm_edid_block_valid(buf, blk, true, NULL) && (retry--));
return 0;
}
static int sun4i_hdmi_get_modes(struct drm_connector *connector)
{
struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
unsigned long reg;
struct edid *edid;
int ret;
/* Reset i2c controller */
writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET,
hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
!(reg & SUN4I_HDMI_DDC_CTRL_RESET),
100, 2000))
return -EIO;
writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE |
SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE,
hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG);
clk_prepare_enable(hdmi->ddc_clk);
clk_set_rate(hdmi->ddc_clk, 100000);
edid = drm_do_get_edid(connector, sun4i_hdmi_read_edid_block, hdmi);
if (!edid)
return 0;
hdmi->hdmi_monitor = drm_detect_hdmi_monitor(edid);
DRM_DEBUG_DRIVER("Monitor is %s monitor\n",
hdmi->hdmi_monitor ? "an HDMI" : "a DVI");
drm_mode_connector_update_edid_property(connector, edid);
ret = drm_add_edid_modes(connector, edid);
kfree(edid);
clk_disable_unprepare(hdmi->ddc_clk);
return ret;
}
static const struct drm_connector_helper_funcs sun4i_hdmi_connector_helper_funcs = {
.get_modes = sun4i_hdmi_get_modes,
};
static enum drm_connector_status
sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force)
{
struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
unsigned long reg;
if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg,
reg & SUN4I_HDMI_HPD_HIGH,
0, 500000))
return connector_status_disconnected;
return connector_status_connected;
}
static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = {
.dpms = drm_atomic_helper_connector_dpms,
.detect = sun4i_hdmi_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static int sun4i_hdmi_bind(struct device *dev, struct device *master,
void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct drm_device *drm = data;
struct sun4i_drv *drv = drm->dev_private;
struct sun4i_hdmi *hdmi;
struct resource *res;
u32 reg;
int ret;
hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
if (!hdmi)
return -ENOMEM;
dev_set_drvdata(dev, hdmi);
hdmi->dev = dev;
hdmi->drv = drv;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
hdmi->base = devm_ioremap_resource(dev, res);
if (IS_ERR(hdmi->base)) {
dev_err(dev, "Couldn't map the HDMI encoder registers\n");
return PTR_ERR(hdmi->base);
}
hdmi->bus_clk = devm_clk_get(dev, "ahb");
if (IS_ERR(hdmi->bus_clk)) {
dev_err(dev, "Couldn't get the HDMI bus clock\n");
return PTR_ERR(hdmi->bus_clk);
}
clk_prepare_enable(hdmi->bus_clk);
hdmi->mod_clk = devm_clk_get(dev, "mod");
if (IS_ERR(hdmi->mod_clk)) {
dev_err(dev, "Couldn't get the HDMI mod clock\n");
return PTR_ERR(hdmi->mod_clk);
}
clk_prepare_enable(hdmi->mod_clk);
hdmi->pll0_clk = devm_clk_get(dev, "pll-0");
if (IS_ERR(hdmi->pll0_clk)) {
dev_err(dev, "Couldn't get the HDMI PLL 0 clock\n");
return PTR_ERR(hdmi->pll0_clk);
}
hdmi->pll1_clk = devm_clk_get(dev, "pll-1");
if (IS_ERR(hdmi->pll1_clk)) {
dev_err(dev, "Couldn't get the HDMI PLL 1 clock\n");
return PTR_ERR(hdmi->pll1_clk);
}
ret = sun4i_tmds_create(hdmi);
if (ret) {
dev_err(dev, "Couldn't create the TMDS clock\n");
return ret;
}
writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG);
writel(SUN4I_HDMI_PAD_CTRL0_TXEN | SUN4I_HDMI_PAD_CTRL0_CKEN |
SUN4I_HDMI_PAD_CTRL0_PWENG | SUN4I_HDMI_PAD_CTRL0_PWEND |
SUN4I_HDMI_PAD_CTRL0_PWENC | SUN4I_HDMI_PAD_CTRL0_LDODEN |
SUN4I_HDMI_PAD_CTRL0_LDOCEN | SUN4I_HDMI_PAD_CTRL0_BIASEN,
hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG);
/*
* We can't just initialize the register there, we need to
* protect the clock bits that have already been read out and
* cached by the clock framework.
*/
reg = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
reg &= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
reg |= SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) |
SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) |
SUN4I_HDMI_PAD_CTRL1_REG_DENCK |
SUN4I_HDMI_PAD_CTRL1_REG_DEN |
SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT |
SUN4I_HDMI_PAD_CTRL1_EMP_OPT |
SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT |
SUN4I_HDMI_PAD_CTRL1_AMP_OPT;
writel(reg, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
reg &= SUN4I_HDMI_PLL_CTRL_DIV_MASK;
reg |= SUN4I_HDMI_PLL_CTRL_VCO_S(8) | SUN4I_HDMI_PLL_CTRL_CS(7) |
SUN4I_HDMI_PLL_CTRL_CP_S(15) | SUN4I_HDMI_PLL_CTRL_S(7) |
SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | SUN4I_HDMI_PLL_CTRL_SDIV2 |
SUN4I_HDMI_PLL_CTRL_LDO2_EN | SUN4I_HDMI_PLL_CTRL_LDO1_EN |
SUN4I_HDMI_PLL_CTRL_HV_IS_33 | SUN4I_HDMI_PLL_CTRL_BWS |
SUN4I_HDMI_PLL_CTRL_PLL_EN;
writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk);
if (ret) {
dev_err(dev, "Couldn't create the DDC clock\n");
return ret;
}
drm_encoder_helper_add(&hdmi->encoder,
&sun4i_hdmi_helper_funcs);
ret = drm_encoder_init(drm,
&hdmi->encoder,
&sun4i_hdmi_funcs,
DRM_MODE_ENCODER_TMDS,
NULL);
if (ret) {
dev_err(dev, "Couldn't initialise the HDMI encoder\n");
return ret;
}
hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm,
dev->of_node);
if (!hdmi->encoder.possible_crtcs)
return -EPROBE_DEFER;
drm_connector_helper_add(&hdmi->connector,
&sun4i_hdmi_connector_helper_funcs);
ret = drm_connector_init(drm, &hdmi->connector,
&sun4i_hdmi_connector_funcs,
DRM_MODE_CONNECTOR_HDMIA);
if (ret) {
dev_err(dev,
"Couldn't initialise the HDMI connector\n");
goto err_cleanup_connector;
}
/* There is no HPD interrupt, so we need to poll the controller */
hdmi->connector.polled = DRM_CONNECTOR_POLL_CONNECT |
DRM_CONNECTOR_POLL_DISCONNECT;
drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder);
return 0;
err_cleanup_connector:
drm_encoder_cleanup(&hdmi->encoder);
return ret;
}
static void sun4i_hdmi_unbind(struct device *dev, struct device *master,
void *data)
{
struct sun4i_hdmi *hdmi = dev_get_drvdata(dev);
drm_connector_cleanup(&hdmi->connector);
drm_encoder_cleanup(&hdmi->encoder);
}
static const struct component_ops sun4i_hdmi_ops = {
.bind = sun4i_hdmi_bind,
.unbind = sun4i_hdmi_unbind,
};
static int sun4i_hdmi_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &sun4i_hdmi_ops);
}
static int sun4i_hdmi_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &sun4i_hdmi_ops);
return 0;
}
static const struct of_device_id sun4i_hdmi_of_table[] = {
{ .compatible = "allwinner,sun5i-a10s-hdmi" },
{ }
};
MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table);
static struct platform_driver sun4i_hdmi_driver = {
.probe = sun4i_hdmi_probe,
.remove = sun4i_hdmi_remove,
.driver = {
.name = "sun4i-hdmi",
.of_match_table = sun4i_hdmi_of_table,
},
};
module_platform_driver(sun4i_hdmi_driver);
MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
MODULE_DESCRIPTION("Allwinner A10 HDMI Driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,225 @@
/*
* Copyright (C) 2016 Free Electrons
* Copyright (C) 2016 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*/
#include <linux/clk-provider.h>
#include "sun4i_tcon.h"
#include "sun4i_hdmi.h"
struct sun4i_tmds {
struct clk_hw hw;
struct sun4i_hdmi *hdmi;
};
static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)
{
return container_of(hw, struct sun4i_tmds, hw);
}
static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
unsigned long parent_rate,
u8 *div,
bool *half)
{
unsigned long best_rate = 0;
u8 best_m = 0, m;
bool is_double;
for (m = 1; m < 16; m++) {
u8 d;
for (d = 1; d < 3; d++) {
unsigned long tmp_rate;
tmp_rate = parent_rate / m / d;
if (tmp_rate > rate)
continue;
if (!best_rate ||
(rate - tmp_rate) < (rate - best_rate)) {
best_rate = tmp_rate;
best_m = m;
is_double = d;
}
}
}
if (div && half) {
*div = best_m;
*half = is_double;
}
return best_rate;
}
static int sun4i_tmds_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct clk_hw *parent;
unsigned long best_parent = 0;
unsigned long rate = req->rate;
int best_div = 1, best_half = 1;
int i, j;
/*
* We only consider PLL3, since the TCON is very likely to be
* clocked from it, and to have the same rate than our HDMI
* clock, so we should not need to do anything.
*/
parent = clk_hw_get_parent_by_index(hw, 0);
if (!parent)
return -EINVAL;
for (i = 1; i < 3; i++) {
for (j = 1; j < 16; j++) {
unsigned long ideal = rate * i * j;
unsigned long rounded;
rounded = clk_hw_round_rate(parent, ideal);
if (rounded == ideal) {
best_parent = rounded;
best_half = i;
best_div = j;
goto out;
}
if (abs(rate - rounded / i) <
abs(rate - best_parent / best_div)) {
best_parent = rounded;
best_div = i;
}
}
}
out:
req->rate = best_parent / best_half / best_div;
req->best_parent_rate = best_parent;
req->best_parent_hw = parent;
return 0;
}
static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct sun4i_tmds *tmds = hw_to_tmds(hw);
u32 reg;
reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
if (reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK)
parent_rate /= 2;
reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
reg = (reg >> 4) & 0xf;
if (!reg)
reg = 1;
return parent_rate / reg;
}
static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct sun4i_tmds *tmds = hw_to_tmds(hw);
bool half;
u32 reg;
u8 div;
sun4i_tmds_calc_divider(rate, parent_rate, &div, &half);
reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
if (half)
reg |= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
writel(reg, tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK;
writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div),
tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
return 0;
}
static u8 sun4i_tmds_get_parent(struct clk_hw *hw)
{
struct sun4i_tmds *tmds = hw_to_tmds(hw);
u32 reg;
reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
return ((reg & SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) >>
SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT);
}
static int sun4i_tmds_set_parent(struct clk_hw *hw, u8 index)
{
struct sun4i_tmds *tmds = hw_to_tmds(hw);
u32 reg;
if (index > 1)
return -EINVAL;
reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
reg &= ~SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK;
writel(reg | SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(index),
tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
return 0;
}
static const struct clk_ops sun4i_tmds_ops = {
.determine_rate = sun4i_tmds_determine_rate,
.recalc_rate = sun4i_tmds_recalc_rate,
.set_rate = sun4i_tmds_set_rate,
.get_parent = sun4i_tmds_get_parent,
.set_parent = sun4i_tmds_set_parent,
};
int sun4i_tmds_create(struct sun4i_hdmi *hdmi)
{
struct clk_init_data init;
struct sun4i_tmds *tmds;
const char *parents[2];
parents[0] = __clk_get_name(hdmi->pll0_clk);
if (!parents[0])
return -ENODEV;
parents[1] = __clk_get_name(hdmi->pll1_clk);
if (!parents[1])
return -ENODEV;
tmds = devm_kzalloc(hdmi->dev, sizeof(*tmds), GFP_KERNEL);
if (!tmds)
return -ENOMEM;
init.name = "hdmi-tmds";
init.ops = &sun4i_tmds_ops;
init.parent_names = parents;
init.num_parents = 2;
init.flags = CLK_SET_RATE_PARENT;
tmds->hdmi = hdmi;
tmds->hw.init = &init;
hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw);
if (IS_ERR(hdmi->tmds_clk))
return PTR_ERR(hdmi->tmds_clk);
return 0;
}

View file

@ -11,12 +11,12 @@
*/
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_plane_helper.h>
#include <drm/drmP.h>
#include "sun4i_backend.h"
#include "sun4i_layer.h"
#include "sunxi_engine.h"
struct sun4i_plane_desc {
enum drm_plane_type type;
@ -128,15 +128,16 @@ static struct sun4i_layer *sun4i_layer_init_one(struct drm_device *drm,
return layer;
}
struct sun4i_layer **sun4i_layers_init(struct drm_device *drm,
struct sun4i_backend *backend)
struct drm_plane **sun4i_layers_init(struct drm_device *drm,
struct sunxi_engine *engine)
{
struct sun4i_layer **layers;
struct drm_plane **planes;
struct sun4i_backend *backend = engine_to_sun4i_backend(engine);
int i;
layers = devm_kcalloc(drm->dev, ARRAY_SIZE(sun4i_backend_planes) + 1,
sizeof(*layers), GFP_KERNEL);
if (!layers)
planes = devm_kcalloc(drm->dev, ARRAY_SIZE(sun4i_backend_planes) + 1,
sizeof(*planes), GFP_KERNEL);
if (!planes)
return ERR_PTR(-ENOMEM);
/*
@ -173,13 +174,13 @@ struct sun4i_layer **sun4i_layers_init(struct drm_device *drm,
DRM_DEBUG_DRIVER("Assigning %s plane to pipe %d\n",
i ? "overlay" : "primary", plane->pipe);
regmap_update_bits(backend->regs, SUN4I_BACKEND_ATTCTL_REG0(i),
regmap_update_bits(engine->regs, SUN4I_BACKEND_ATTCTL_REG0(i),
SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL_MASK,
SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL(plane->pipe));
layer->id = i;
layers[i] = layer;
planes[i] = &layer->plane;
};
return layers;
return planes;
}

View file

@ -13,6 +13,8 @@
#ifndef _SUN4I_LAYER_H_
#define _SUN4I_LAYER_H_
struct sunxi_engine;
struct sun4i_layer {
struct drm_plane plane;
struct sun4i_drv *drv;
@ -26,7 +28,7 @@ plane_to_sun4i_layer(struct drm_plane *plane)
return container_of(plane, struct sun4i_layer, plane);
}
struct sun4i_layer **sun4i_layers_init(struct drm_device *drm,
struct sun4i_backend *backend);
struct drm_plane **sun4i_layers_init(struct drm_device *drm,
struct sunxi_engine *engine);
#endif /* _SUN4I_LAYER_H_ */

View file

@ -175,8 +175,7 @@ static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder,
struct sun4i_tcon *tcon = rgb->tcon;
sun4i_tcon0_mode_set(tcon, mode);
clk_set_rate(tcon->dclk, mode->crtc_clock * 1000);
sun4i_tcon_set_mux(tcon, 0, encoder);
/* FIXME: This seems to be board specific */
clk_set_phase(tcon->dclk, 120);

View file

@ -30,6 +30,7 @@
#include "sun4i_drv.h"
#include "sun4i_rgb.h"
#include "sun4i_tcon.h"
#include "sunxi_engine.h"
void sun4i_tcon_disable(struct sun4i_tcon *tcon)
{
@ -54,6 +55,8 @@ EXPORT_SYMBOL(sun4i_tcon_enable);
void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel)
{
DRM_DEBUG_DRIVER("Disabling TCON channel %d\n", channel);
/* Disable the TCON's channel */
if (channel == 0) {
regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
@ -71,6 +74,8 @@ EXPORT_SYMBOL(sun4i_tcon_channel_disable);
void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel)
{
DRM_DEBUG_DRIVER("Enabling TCON channel %d\n", channel);
/* Enable the TCON's channel */
if (channel == 0) {
regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
@ -104,6 +109,29 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable)
}
EXPORT_SYMBOL(sun4i_tcon_enable_vblank);
void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
struct drm_encoder *encoder)
{
u32 val;
if (!tcon->quirks->has_unknown_mux)
return;
if (channel != 1)
return;
if (encoder->encoder_type == DRM_MODE_ENCODER_TVDAC)
val = 1;
else
val = 0;
/*
* FIXME: Undocumented bits
*/
regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, val);
}
EXPORT_SYMBOL(sun4i_tcon_set_mux);
static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode,
int channel)
{
@ -129,6 +157,9 @@ void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon,
u8 clk_delay;
u32 val = 0;
/* Configure the dot clock */
clk_set_rate(tcon->dclk, mode->crtc_clock * 1000);
/* Adjust clock delay */
clk_delay = sun4i_tcon_get_clk_delay(mode, 0);
regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
@ -163,7 +194,7 @@ void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon,
/* Set vertical display timings */
regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG,
SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal) |
SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal * 2) |
SUN4I_TCON0_BASIC2_V_BACKPORCH(bp));
/* Set Hsync and Vsync length */
@ -198,12 +229,15 @@ EXPORT_SYMBOL(sun4i_tcon0_mode_set);
void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
struct drm_display_mode *mode)
{
unsigned int bp, hsync, vsync;
unsigned int bp, hsync, vsync, vtotal;
u8 clk_delay;
u32 val;
WARN_ON(!tcon->quirks->has_channel_1);
/* Configure the dot clock */
clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
/* Adjust clock delay */
clk_delay = sun4i_tcon_get_clk_delay(mode, 1);
regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
@ -235,19 +269,37 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
SUN4I_TCON1_BASIC2_Y(mode->crtc_vdisplay));
/* Set horizontal display timings */
bp = mode->crtc_htotal - mode->crtc_hsync_end;
bp = mode->crtc_htotal - mode->crtc_hsync_start;
DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n",
mode->htotal, bp);
regmap_write(tcon->regs, SUN4I_TCON1_BASIC3_REG,
SUN4I_TCON1_BASIC3_H_TOTAL(mode->crtc_htotal) |
SUN4I_TCON1_BASIC3_H_BACKPORCH(bp));
/* Set vertical display timings */
bp = mode->crtc_vtotal - mode->crtc_vsync_end;
bp = mode->crtc_vtotal - mode->crtc_vsync_start;
DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n",
mode->vtotal, bp);
mode->crtc_vtotal, bp);
/*
* The vertical resolution needs to be doubled in all
* cases. We could use crtc_vtotal and always multiply by two,
* but that leads to a rounding error in interlace when vtotal
* is odd.
*
* This happens with TV's PAL for example, where vtotal will
* be 625, crtc_vtotal 312, and thus crtc_vtotal * 2 will be
* 624, which apparently confuses the hardware.
*
* To work around this, we will always use vtotal, and
* multiply by two only if we're not in interlace.
*/
vtotal = mode->vtotal;
if (!(mode->flags & DRM_MODE_FLAG_INTERLACE))
vtotal = vtotal * 2;
/* Set vertical display timings */
regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG,
SUN4I_TCON1_BASIC4_V_TOTAL(mode->vtotal) |
SUN4I_TCON1_BASIC4_V_TOTAL(vtotal) |
SUN4I_TCON1_BASIC4_V_BACKPORCH(bp));
/* Set Hsync and Vsync length */
@ -262,12 +314,6 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
SUN4I_TCON_GCTL_IOMAP_MASK,
SUN4I_TCON_GCTL_IOMAP_TCON1);
/*
* FIXME: Undocumented bits
*/
if (tcon->quirks->has_unknown_mux)
regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1);
}
EXPORT_SYMBOL(sun4i_tcon1_mode_set);
@ -402,21 +448,79 @@ static int sun4i_tcon_init_regmap(struct device *dev,
return 0;
}
/*
* On SoCs with the old display pipeline design (Display Engine 1.0),
* the TCON is always tied to just one backend. Hence we can traverse
* the of_graph upwards to find the backend our tcon is connected to,
* and take its ID as our own.
*
* We can either identify backends from their compatible strings, which
* means maintaining a large list of them. Or, since the backend is
* registered and binded before the TCON, we can just go through the
* list of registered backends and compare the device node.
*
* As the structures now store engines instead of backends, here this
* function in fact searches the corresponding engine, and the ID is
* requested via the get_id function of the engine.
*/
static struct sunxi_engine *sun4i_tcon_find_engine(struct sun4i_drv *drv,
struct device_node *node)
{
struct device_node *port, *ep, *remote;
struct sunxi_engine *engine;
port = of_graph_get_port_by_id(node, 0);
if (!port)
return ERR_PTR(-EINVAL);
for_each_available_child_of_node(port, ep) {
remote = of_graph_get_remote_port_parent(ep);
if (!remote)
continue;
/* does this node match any registered engines? */
list_for_each_entry(engine, &drv->engine_list, list) {
if (remote == engine->node) {
of_node_put(remote);
of_node_put(port);
return engine;
}
}
/* keep looking through upstream ports */
engine = sun4i_tcon_find_engine(drv, remote);
if (!IS_ERR(engine)) {
of_node_put(remote);
of_node_put(port);
return engine;
}
}
return ERR_PTR(-EINVAL);
}
static int sun4i_tcon_bind(struct device *dev, struct device *master,
void *data)
{
struct drm_device *drm = data;
struct sun4i_drv *drv = drm->dev_private;
struct sunxi_engine *engine;
struct sun4i_tcon *tcon;
int ret;
engine = sun4i_tcon_find_engine(drv, dev->of_node);
if (IS_ERR(engine)) {
dev_err(dev, "Couldn't find matching engine\n");
return -EPROBE_DEFER;
}
tcon = devm_kzalloc(dev, sizeof(*tcon), GFP_KERNEL);
if (!tcon)
return -ENOMEM;
dev_set_drvdata(dev, tcon);
drv->tcon = tcon;
tcon->drm = drm;
tcon->dev = dev;
tcon->id = engine->id;
tcon->quirks = of_device_get_match_data(dev);
tcon->lcd_rst = devm_reset_control_get(dev, "lcd");
@ -459,7 +563,7 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
goto err_free_dotclock;
}
tcon->crtc = sun4i_crtc_init(drm, drv->backend, tcon);
tcon->crtc = sun4i_crtc_init(drm, engine, tcon);
if (IS_ERR(tcon->crtc)) {
dev_err(dev, "Couldn't create our CRTC\n");
ret = PTR_ERR(tcon->crtc);
@ -470,6 +574,8 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
if (ret < 0)
goto err_free_clocks;
list_add_tail(&tcon->list, &drv->tcon_list);
return 0;
err_free_dotclock:
@ -486,6 +592,7 @@ static void sun4i_tcon_unbind(struct device *dev, struct device *master,
{
struct sun4i_tcon *tcon = dev_get_drvdata(dev);
list_del(&tcon->list);
sun4i_dclk_free(tcon);
sun4i_tcon_free_clocks(tcon);
}
@ -533,11 +640,16 @@ static const struct sun4i_tcon_quirks sun8i_a33_quirks = {
/* nothing is supported */
};
static const struct sun4i_tcon_quirks sun8i_v3s_quirks = {
/* nothing is supported */
};
static const struct of_device_id sun4i_tcon_of_table[] = {
{ .compatible = "allwinner,sun5i-a13-tcon", .data = &sun5i_a13_quirks },
{ .compatible = "allwinner,sun6i-a31-tcon", .data = &sun6i_a31_quirks },
{ .compatible = "allwinner,sun6i-a31s-tcon", .data = &sun6i_a31s_quirks },
{ .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks },
{ .compatible = "allwinner,sun8i-v3s-tcon", .data = &sun8i_v3s_quirks },
{ }
};
MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table);

View file

@ -17,6 +17,7 @@
#include <drm/drm_crtc.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/reset.h>
#define SUN4I_TCON_GCTL_REG 0x0
@ -51,7 +52,7 @@
#define SUN4I_TCON0_BASIC1_H_BACKPORCH(bp) (((bp) - 1) & 0xfff)
#define SUN4I_TCON0_BASIC2_REG 0x50
#define SUN4I_TCON0_BASIC2_V_TOTAL(total) ((((total) * 2) & 0x1fff) << 16)
#define SUN4I_TCON0_BASIC2_V_TOTAL(total) (((total) & 0x1fff) << 16)
#define SUN4I_TCON0_BASIC2_V_BACKPORCH(bp) (((bp) - 1) & 0xfff)
#define SUN4I_TCON0_BASIC3_REG 0x54
@ -172,6 +173,11 @@ struct sun4i_tcon {
/* Associated crtc */
struct sun4i_crtc *crtc;
int id;
/* TCON list management */
struct list_head list;
};
struct drm_bridge *sun4i_tcon_find_bridge(struct device_node *node);
@ -190,6 +196,8 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable);
/* Mode Related Controls */
void sun4i_tcon_switch_interlace(struct sun4i_tcon *tcon,
bool enable);
void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
struct drm_encoder *encoder);
void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon,
struct drm_display_mode *mode);
void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,

View file

@ -22,10 +22,10 @@
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include "sun4i_backend.h"
#include "sun4i_crtc.h"
#include "sun4i_drv.h"
#include "sun4i_tcon.h"
#include "sunxi_engine.h"
#define SUN4I_TVE_EN_REG 0x000
#define SUN4I_TVE_EN_DAC_MAP_MASK GENMASK(19, 4)
@ -353,7 +353,6 @@ static void sun4i_tv_disable(struct drm_encoder *encoder)
struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder);
struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
struct sun4i_tcon *tcon = crtc->tcon;
struct sun4i_backend *backend = crtc->backend;
DRM_DEBUG_DRIVER("Disabling the TV Output\n");
@ -362,7 +361,8 @@ static void sun4i_tv_disable(struct drm_encoder *encoder)
regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG,
SUN4I_TVE_EN_ENABLE,
0);
sun4i_backend_disable_color_correction(backend);
sunxi_engine_disable_color_correction(crtc->engine);
}
static void sun4i_tv_enable(struct drm_encoder *encoder)
@ -370,11 +370,10 @@ static void sun4i_tv_enable(struct drm_encoder *encoder)
struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder);
struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
struct sun4i_tcon *tcon = crtc->tcon;
struct sun4i_backend *backend = crtc->backend;
DRM_DEBUG_DRIVER("Enabling the TV Output\n");
sun4i_backend_apply_color_correction(backend);
sunxi_engine_apply_color_correction(crtc->engine);
regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG,
SUN4I_TVE_EN_ENABLE,
@ -393,6 +392,7 @@ static void sun4i_tv_mode_set(struct drm_encoder *encoder,
const struct tv_mode *tv_mode = sun4i_tv_find_tv_by_mode(mode);
sun4i_tcon1_mode_set(tcon, mode);
sun4i_tcon_set_mux(tcon, 1, encoder);
/* Enable and map the DAC to the output */
regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG,
@ -486,8 +486,6 @@ static void sun4i_tv_mode_set(struct drm_encoder *encoder,
SUN4I_TVE_RESYNC_FIELD : 0));
regmap_write(tv->regs, SUN4I_TVE_SLAVE_REG, 0);
clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
}
static struct drm_encoder_helper_funcs sun4i_tv_helper_funcs = {

View file

@ -0,0 +1,134 @@
/*
* Copyright (C) Icenowy Zheng <icenowy@aosc.io>
*
* Based on sun4i_layer.h, which is:
* Copyright (C) 2015 Free Electrons
* Copyright (C) 2015 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*/
#include <drm/drm_atomic_helper.h>
#include <drm/drm_plane_helper.h>
#include <drm/drmP.h>
#include "sun8i_layer.h"
#include "sun8i_mixer.h"
struct sun8i_plane_desc {
enum drm_plane_type type;
const uint32_t *formats;
uint32_t nformats;
};
static void sun8i_mixer_layer_atomic_disable(struct drm_plane *plane,
struct drm_plane_state *old_state)
{
struct sun8i_layer *layer = plane_to_sun8i_layer(plane);
struct sun8i_mixer *mixer = layer->mixer;
sun8i_mixer_layer_enable(mixer, layer->id, false);
}
static void sun8i_mixer_layer_atomic_update(struct drm_plane *plane,
struct drm_plane_state *old_state)
{
struct sun8i_layer *layer = plane_to_sun8i_layer(plane);
struct sun8i_mixer *mixer = layer->mixer;
sun8i_mixer_update_layer_coord(mixer, layer->id, plane);
sun8i_mixer_update_layer_formats(mixer, layer->id, plane);
sun8i_mixer_update_layer_buffer(mixer, layer->id, plane);
sun8i_mixer_layer_enable(mixer, layer->id, true);
}
static struct drm_plane_helper_funcs sun8i_mixer_layer_helper_funcs = {
.atomic_disable = sun8i_mixer_layer_atomic_disable,
.atomic_update = sun8i_mixer_layer_atomic_update,
};
static const struct drm_plane_funcs sun8i_mixer_layer_funcs = {
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.destroy = drm_plane_cleanup,
.disable_plane = drm_atomic_helper_disable_plane,
.reset = drm_atomic_helper_plane_reset,
.update_plane = drm_atomic_helper_update_plane,
};
static const uint32_t sun8i_mixer_layer_formats[] = {
DRM_FORMAT_RGB888,
DRM_FORMAT_ARGB8888,
DRM_FORMAT_XRGB8888,
};
static const struct sun8i_plane_desc sun8i_mixer_planes[] = {
{
.type = DRM_PLANE_TYPE_PRIMARY,
.formats = sun8i_mixer_layer_formats,
.nformats = ARRAY_SIZE(sun8i_mixer_layer_formats),
},
};
static struct sun8i_layer *sun8i_layer_init_one(struct drm_device *drm,
struct sun8i_mixer *mixer,
const struct sun8i_plane_desc *plane)
{
struct sun8i_layer *layer;
int ret;
layer = devm_kzalloc(drm->dev, sizeof(*layer), GFP_KERNEL);
if (!layer)
return ERR_PTR(-ENOMEM);
/* possible crtcs are set later */
ret = drm_universal_plane_init(drm, &layer->plane, 0,
&sun8i_mixer_layer_funcs,
plane->formats, plane->nformats,
plane->type, NULL);
if (ret) {
dev_err(drm->dev, "Couldn't initialize layer\n");
return ERR_PTR(ret);
}
drm_plane_helper_add(&layer->plane,
&sun8i_mixer_layer_helper_funcs);
layer->mixer = mixer;
return layer;
}
struct drm_plane **sun8i_layers_init(struct drm_device *drm,
struct sunxi_engine *engine)
{
struct drm_plane **planes;
struct sun8i_mixer *mixer = engine_to_sun8i_mixer(engine);
int i;
planes = devm_kcalloc(drm->dev, ARRAY_SIZE(sun8i_mixer_planes) + 1,
sizeof(*planes), GFP_KERNEL);
if (!planes)
return ERR_PTR(-ENOMEM);
for (i = 0; i < ARRAY_SIZE(sun8i_mixer_planes); i++) {
const struct sun8i_plane_desc *plane = &sun8i_mixer_planes[i];
struct sun8i_layer *layer;
layer = sun8i_layer_init_one(drm, mixer, plane);
if (IS_ERR(layer)) {
dev_err(drm->dev, "Couldn't initialize %s plane\n",
i ? "overlay" : "primary");
return ERR_CAST(layer);
};
layer->id = i;
planes[i] = &layer->plane;
};
return planes;
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) Icenowy Zheng <icenowy@aosc.io>
*
* Based on sun4i_layer.h, which is:
* Copyright (C) 2015 Free Electrons
* Copyright (C) 2015 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*/
#ifndef _SUN8I_LAYER_H_
#define _SUN8I_LAYER_H_
struct sunxi_engine;
struct sun8i_layer {
struct drm_plane plane;
struct sun4i_drv *drv;
struct sun8i_mixer *mixer;
int id;
};
static inline struct sun8i_layer *
plane_to_sun8i_layer(struct drm_plane *plane)
{
return container_of(plane, struct sun8i_layer, plane);
}
struct drm_plane **sun8i_layers_init(struct drm_device *drm,
struct sunxi_engine *engine);
#endif /* _SUN8I_LAYER_H_ */

View file

@ -0,0 +1,414 @@
/*
* Copyright (C) 2017 Icenowy Zheng <icenowy@aosc.io>
*
* Based on sun4i_backend.c, which is:
* Copyright (C) 2015 Free Electrons
* Copyright (C) 2015 NextThing Co
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*/
#include <drm/drmP.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_plane_helper.h>
#include <linux/component.h>
#include <linux/dma-mapping.h>
#include <linux/reset.h>
#include <linux/of_device.h>
#include "sun4i_drv.h"
#include "sun8i_mixer.h"
#include "sun8i_layer.h"
#include "sunxi_engine.h"
static void sun8i_mixer_commit(struct sunxi_engine *engine)
{
DRM_DEBUG_DRIVER("Committing changes\n");
regmap_write(engine->regs, SUN8I_MIXER_GLOBAL_DBUFF,
SUN8I_MIXER_GLOBAL_DBUFF_ENABLE);
}
void sun8i_mixer_layer_enable(struct sun8i_mixer *mixer,
int layer, bool enable)
{
u32 val;
/* Currently the first UI channel is used */
int chan = mixer->cfg->vi_num;
DRM_DEBUG_DRIVER("Enabling layer %d in channel %d\n", layer, chan);
if (enable)
val = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN;
else
val = 0;
regmap_update_bits(mixer->engine.regs,
SUN8I_MIXER_CHAN_UI_LAYER_ATTR(chan, layer),
SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN, val);
/* Set the alpha configuration */
regmap_update_bits(mixer->engine.regs,
SUN8I_MIXER_CHAN_UI_LAYER_ATTR(chan, layer),
SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_MASK,
SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_DEF);
regmap_update_bits(mixer->engine.regs,
SUN8I_MIXER_CHAN_UI_LAYER_ATTR(chan, layer),
SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MASK,
SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_DEF);
}
static int sun8i_mixer_drm_format_to_layer(struct drm_plane *plane,
u32 format, u32 *mode)
{
switch (format) {
case DRM_FORMAT_ARGB8888:
*mode = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_ARGB8888;
break;
case DRM_FORMAT_XRGB8888:
*mode = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_XRGB8888;
break;
case DRM_FORMAT_RGB888:
*mode = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_RGB888;
break;
default:
return -EINVAL;
}
return 0;
}
int sun8i_mixer_update_layer_coord(struct sun8i_mixer *mixer,
int layer, struct drm_plane *plane)
{
struct drm_plane_state *state = plane->state;
struct drm_framebuffer *fb = state->fb;
/* Currently the first UI channel is used */
int chan = mixer->cfg->vi_num;
DRM_DEBUG_DRIVER("Updating layer %d\n", layer);
if (plane->type == DRM_PLANE_TYPE_PRIMARY) {
DRM_DEBUG_DRIVER("Primary layer, updating global size W: %u H: %u\n",
state->crtc_w, state->crtc_h);
regmap_write(mixer->engine.regs, SUN8I_MIXER_GLOBAL_SIZE,
SUN8I_MIXER_SIZE(state->crtc_w,
state->crtc_h));
DRM_DEBUG_DRIVER("Updating blender size\n");
regmap_write(mixer->engine.regs,
SUN8I_MIXER_BLEND_ATTR_INSIZE(0),
SUN8I_MIXER_SIZE(state->crtc_w,
state->crtc_h));
regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_OUTSIZE,
SUN8I_MIXER_SIZE(state->crtc_w,
state->crtc_h));
DRM_DEBUG_DRIVER("Updating channel size\n");
regmap_write(mixer->engine.regs,
SUN8I_MIXER_CHAN_UI_OVL_SIZE(chan),
SUN8I_MIXER_SIZE(state->crtc_w,
state->crtc_h));
}
/* Set the line width */
DRM_DEBUG_DRIVER("Layer line width: %d bytes\n", fb->pitches[0]);
regmap_write(mixer->engine.regs,
SUN8I_MIXER_CHAN_UI_LAYER_PITCH(chan, layer),
fb->pitches[0]);
/* Set height and width */
DRM_DEBUG_DRIVER("Layer size W: %u H: %u\n",
state->crtc_w, state->crtc_h);
regmap_write(mixer->engine.regs,
SUN8I_MIXER_CHAN_UI_LAYER_SIZE(chan, layer),
SUN8I_MIXER_SIZE(state->crtc_w, state->crtc_h));
/* Set base coordinates */
DRM_DEBUG_DRIVER("Layer coordinates X: %d Y: %d\n",
state->crtc_x, state->crtc_y);
regmap_write(mixer->engine.regs,
SUN8I_MIXER_CHAN_UI_LAYER_COORD(chan, layer),
SUN8I_MIXER_COORD(state->crtc_x, state->crtc_y));
return 0;
}
int sun8i_mixer_update_layer_formats(struct sun8i_mixer *mixer,
int layer, struct drm_plane *plane)
{
struct drm_plane_state *state = plane->state;
struct drm_framebuffer *fb = state->fb;
bool interlaced = false;
u32 val;
/* Currently the first UI channel is used */
int chan = mixer->cfg->vi_num;
int ret;
if (plane->state->crtc)
interlaced = plane->state->crtc->state->adjusted_mode.flags
& DRM_MODE_FLAG_INTERLACE;
regmap_update_bits(mixer->engine.regs, SUN8I_MIXER_BLEND_OUTCTL,
SUN8I_MIXER_BLEND_OUTCTL_INTERLACED,
interlaced ?
SUN8I_MIXER_BLEND_OUTCTL_INTERLACED : 0);
DRM_DEBUG_DRIVER("Switching display mixer interlaced mode %s\n",
interlaced ? "on" : "off");
ret = sun8i_mixer_drm_format_to_layer(plane, fb->format->format,
&val);
if (ret) {
DRM_DEBUG_DRIVER("Invalid format\n");
return ret;
}
regmap_update_bits(mixer->engine.regs,
SUN8I_MIXER_CHAN_UI_LAYER_ATTR(chan, layer),
SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_MASK, val);
return 0;
}
int sun8i_mixer_update_layer_buffer(struct sun8i_mixer *mixer,
int layer, struct drm_plane *plane)
{
struct drm_plane_state *state = plane->state;
struct drm_framebuffer *fb = state->fb;
struct drm_gem_cma_object *gem;
dma_addr_t paddr;
/* Currently the first UI channel is used */
int chan = mixer->cfg->vi_num;
int bpp;
/* Get the physical address of the buffer in memory */
gem = drm_fb_cma_get_gem_obj(fb, 0);
DRM_DEBUG_DRIVER("Using GEM @ %pad\n", &gem->paddr);
/* Compute the start of the displayed memory */
bpp = fb->format->cpp[0];
paddr = gem->paddr + fb->offsets[0];
/* Fixup framebuffer address for src coordinates */
paddr += (state->src_x >> 16) * bpp;
paddr += (state->src_y >> 16) * fb->pitches[0];
/*
* The hardware cannot correctly deal with negative crtc
* coordinates, the display is cropped to the requested size,
* but the display content is not moved.
* Manually move the display content by fixup the framebuffer
* address when crtc_x or crtc_y is negative, like what we
* have did for src_x and src_y.
*/
if (state->crtc_x < 0)
paddr += -state->crtc_x * bpp;
if (state->crtc_y < 0)
paddr += -state->crtc_y * fb->pitches[0];
DRM_DEBUG_DRIVER("Setting buffer address to %pad\n", &paddr);
regmap_write(mixer->engine.regs,
SUN8I_MIXER_CHAN_UI_LAYER_TOP_LADDR(chan, layer),
lower_32_bits(paddr));
return 0;
}
static const struct sunxi_engine_ops sun8i_engine_ops = {
.commit = sun8i_mixer_commit,
.layers_init = sun8i_layers_init,
};
static struct regmap_config sun8i_mixer_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = 0xbfffc, /* guessed */
};
static int sun8i_mixer_bind(struct device *dev, struct device *master,
void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct drm_device *drm = data;
struct sun4i_drv *drv = drm->dev_private;
struct sun8i_mixer *mixer;
struct resource *res;
void __iomem *regs;
int i, ret;
/*
* The mixer uses single 32-bit register to store memory
* addresses, so that it cannot deal with 64-bit memory
* addresses.
* Restrict the DMA mask so that the mixer won't be
* allocated some memory that is too high.
*/
ret = dma_set_mask(dev, DMA_BIT_MASK(32));
if (ret) {
dev_err(dev, "Cannot do 32-bit DMA.\n");
return ret;
}
mixer = devm_kzalloc(dev, sizeof(*mixer), GFP_KERNEL);
if (!mixer)
return -ENOMEM;
dev_set_drvdata(dev, mixer);
mixer->engine.ops = &sun8i_engine_ops;
mixer->engine.node = dev->of_node;
/* The ID of the mixer currently doesn't matter */
mixer->engine.id = -1;
mixer->cfg = of_device_get_match_data(dev);
if (!mixer->cfg)
return -EINVAL;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
regs = devm_ioremap_resource(dev, res);
if (IS_ERR(regs))
return PTR_ERR(regs);
mixer->engine.regs = devm_regmap_init_mmio(dev, regs,
&sun8i_mixer_regmap_config);
if (IS_ERR(mixer->engine.regs)) {
dev_err(dev, "Couldn't create the mixer regmap\n");
return PTR_ERR(mixer->engine.regs);
}
mixer->reset = devm_reset_control_get(dev, NULL);
if (IS_ERR(mixer->reset)) {
dev_err(dev, "Couldn't get our reset line\n");
return PTR_ERR(mixer->reset);
}
ret = reset_control_deassert(mixer->reset);
if (ret) {
dev_err(dev, "Couldn't deassert our reset line\n");
return ret;
}
mixer->bus_clk = devm_clk_get(dev, "bus");
if (IS_ERR(mixer->bus_clk)) {
dev_err(dev, "Couldn't get the mixer bus clock\n");
ret = PTR_ERR(mixer->bus_clk);
goto err_assert_reset;
}
clk_prepare_enable(mixer->bus_clk);
mixer->mod_clk = devm_clk_get(dev, "mod");
if (IS_ERR(mixer->mod_clk)) {
dev_err(dev, "Couldn't get the mixer module clock\n");
ret = PTR_ERR(mixer->mod_clk);
goto err_disable_bus_clk;
}
clk_prepare_enable(mixer->mod_clk);
list_add_tail(&mixer->engine.list, &drv->engine_list);
/* Reset the registers */
for (i = 0x0; i < 0x20000; i += 4)
regmap_write(mixer->engine.regs, i, 0);
/* Enable the mixer */
regmap_write(mixer->engine.regs, SUN8I_MIXER_GLOBAL_CTL,
SUN8I_MIXER_GLOBAL_CTL_RT_EN);
/* Initialize blender */
regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_FCOLOR_CTL,
SUN8I_MIXER_BLEND_FCOLOR_CTL_DEF);
regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_PREMULTIPLY,
SUN8I_MIXER_BLEND_PREMULTIPLY_DEF);
regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_BKCOLOR,
SUN8I_MIXER_BLEND_BKCOLOR_DEF);
regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_MODE(0),
SUN8I_MIXER_BLEND_MODE_DEF);
regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_CK_CTL,
SUN8I_MIXER_BLEND_CK_CTL_DEF);
regmap_write(mixer->engine.regs,
SUN8I_MIXER_BLEND_ATTR_FCOLOR(0),
SUN8I_MIXER_BLEND_ATTR_FCOLOR_DEF);
/* Select the first UI channel */
DRM_DEBUG_DRIVER("Selecting channel %d (first UI channel)\n",
mixer->cfg->vi_num);
regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_ROUTE,
mixer->cfg->vi_num);
return 0;
err_disable_bus_clk:
clk_disable_unprepare(mixer->bus_clk);
err_assert_reset:
reset_control_assert(mixer->reset);
return ret;
}
static void sun8i_mixer_unbind(struct device *dev, struct device *master,
void *data)
{
struct sun8i_mixer *mixer = dev_get_drvdata(dev);
list_del(&mixer->engine.list);
clk_disable_unprepare(mixer->mod_clk);
clk_disable_unprepare(mixer->bus_clk);
reset_control_assert(mixer->reset);
}
static const struct component_ops sun8i_mixer_ops = {
.bind = sun8i_mixer_bind,
.unbind = sun8i_mixer_unbind,
};
static int sun8i_mixer_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &sun8i_mixer_ops);
}
static int sun8i_mixer_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &sun8i_mixer_ops);
return 0;
}
static const struct sun8i_mixer_cfg sun8i_v3s_mixer_cfg = {
.vi_num = 2,
.ui_num = 1,
};
static const struct of_device_id sun8i_mixer_of_table[] = {
{
.compatible = "allwinner,sun8i-v3s-de2-mixer",
.data = &sun8i_v3s_mixer_cfg,
},
{ }
};
MODULE_DEVICE_TABLE(of, sun8i_mixer_of_table);
static struct platform_driver sun8i_mixer_platform_driver = {
.probe = sun8i_mixer_probe,
.remove = sun8i_mixer_remove,
.driver = {
.name = "sun8i-mixer",
.of_match_table = sun8i_mixer_of_table,
},
};
module_platform_driver(sun8i_mixer_platform_driver);
MODULE_AUTHOR("Icenowy Zheng <icenowy@aosc.io>");
MODULE_DESCRIPTION("Allwinner DE2 Mixer driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,137 @@
/*
* Copyright (C) 2017 Icenowy Zheng <icenowy@aosc.io>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*/
#ifndef _SUN8I_MIXER_H_
#define _SUN8I_MIXER_H_
#include <linux/clk.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include "sunxi_engine.h"
#define SUN8I_MIXER_MAX_CHAN_COUNT 4
#define SUN8I_MIXER_SIZE(w, h) (((h) - 1) << 16 | ((w) - 1))
#define SUN8I_MIXER_COORD(x, y) ((y) << 16 | (x))
#define SUN8I_MIXER_GLOBAL_CTL 0x0
#define SUN8I_MIXER_GLOBAL_STATUS 0x4
#define SUN8I_MIXER_GLOBAL_DBUFF 0x8
#define SUN8I_MIXER_GLOBAL_SIZE 0xc
#define SUN8I_MIXER_GLOBAL_CTL_RT_EN 0x1
#define SUN8I_MIXER_GLOBAL_DBUFF_ENABLE 0x1
#define SUN8I_MIXER_BLEND_FCOLOR_CTL 0x1000
#define SUN8I_MIXER_BLEND_ATTR_FCOLOR(x) (0x1004 + 0x10 * (x) + 0x0)
#define SUN8I_MIXER_BLEND_ATTR_INSIZE(x) (0x1004 + 0x10 * (x) + 0x4)
#define SUN8I_MIXER_BLEND_ATTR_OFFSET(x) (0x1004 + 0x10 * (x) + 0x8)
#define SUN8I_MIXER_BLEND_ROUTE 0x1080
#define SUN8I_MIXER_BLEND_PREMULTIPLY 0x1084
#define SUN8I_MIXER_BLEND_BKCOLOR 0x1088
#define SUN8I_MIXER_BLEND_OUTSIZE 0x108c
#define SUN8I_MIXER_BLEND_MODE(x) (0x1090 + 0x04 * (x))
#define SUN8I_MIXER_BLEND_CK_CTL 0x10b0
#define SUN8I_MIXER_BLEND_CK_CFG 0x10b4
#define SUN8I_MIXER_BLEND_CK_MAX(x) (0x10c0 + 0x04 * (x))
#define SUN8I_MIXER_BLEND_CK_MIN(x) (0x10e0 + 0x04 * (x))
#define SUN8I_MIXER_BLEND_OUTCTL 0x10fc
/* The following numbers are some still unknown magic numbers */
#define SUN8I_MIXER_BLEND_ATTR_FCOLOR_DEF 0xff000000
#define SUN8I_MIXER_BLEND_FCOLOR_CTL_DEF 0x00000101
#define SUN8I_MIXER_BLEND_PREMULTIPLY_DEF 0x0
#define SUN8I_MIXER_BLEND_BKCOLOR_DEF 0xff000000
#define SUN8I_MIXER_BLEND_MODE_DEF 0x03010301
#define SUN8I_MIXER_BLEND_CK_CTL_DEF 0x0
#define SUN8I_MIXER_BLEND_OUTCTL_INTERLACED BIT(1)
/*
* VI channels are not used now, but the support of them may be introduced in
* the future.
*/
#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch, layer) \
(0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x0)
#define SUN8I_MIXER_CHAN_UI_LAYER_SIZE(ch, layer) \
(0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x4)
#define SUN8I_MIXER_CHAN_UI_LAYER_COORD(ch, layer) \
(0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x8)
#define SUN8I_MIXER_CHAN_UI_LAYER_PITCH(ch, layer) \
(0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0xc)
#define SUN8I_MIXER_CHAN_UI_LAYER_TOP_LADDR(ch, layer) \
(0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x10)
#define SUN8I_MIXER_CHAN_UI_LAYER_BOT_LADDR(ch, layer) \
(0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x14)
#define SUN8I_MIXER_CHAN_UI_LAYER_FCOLOR(ch, layer) \
(0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x18)
#define SUN8I_MIXER_CHAN_UI_TOP_HADDR(ch) (0x2000 + 0x1000 * (ch) + 0x80)
#define SUN8I_MIXER_CHAN_UI_BOT_HADDR(ch) (0x2000 + 0x1000 * (ch) + 0x84)
#define SUN8I_MIXER_CHAN_UI_OVL_SIZE(ch) (0x2000 + 0x1000 * (ch) + 0x88)
#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN BIT(0)
#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_MASK GENMASK(2, 1)
#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_MASK GENMASK(11, 8)
#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MASK GENMASK(31, 24)
#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_DEF (1 << 1)
#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_ARGB8888 (0 << 8)
#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_XRGB8888 (4 << 8)
#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_RGB888 (8 << 8)
#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_DEF (0xff << 24)
/*
* These sub-engines are still unknown now, the EN registers are here only to
* be used to disable these sub-engines.
*/
#define SUN8I_MIXER_VSU_EN 0x20000
#define SUN8I_MIXER_GSU1_EN 0x30000
#define SUN8I_MIXER_GSU2_EN 0x40000
#define SUN8I_MIXER_GSU3_EN 0x50000
#define SUN8I_MIXER_FCE_EN 0xa0000
#define SUN8I_MIXER_BWS_EN 0xa2000
#define SUN8I_MIXER_LTI_EN 0xa4000
#define SUN8I_MIXER_PEAK_EN 0xa6000
#define SUN8I_MIXER_ASE_EN 0xa8000
#define SUN8I_MIXER_FCC_EN 0xaa000
#define SUN8I_MIXER_DCSC_EN 0xb0000
struct sun8i_mixer_cfg {
int vi_num;
int ui_num;
};
struct sun8i_mixer {
struct sunxi_engine engine;
const struct sun8i_mixer_cfg *cfg;
struct reset_control *reset;
struct clk *bus_clk;
struct clk *mod_clk;
};
static inline struct sun8i_mixer *
engine_to_sun8i_mixer(struct sunxi_engine *engine)
{
return container_of(engine, struct sun8i_mixer, engine);
}
void sun8i_mixer_layer_enable(struct sun8i_mixer *mixer,
int layer, bool enable);
int sun8i_mixer_update_layer_coord(struct sun8i_mixer *mixer,
int layer, struct drm_plane *plane);
int sun8i_mixer_update_layer_formats(struct sun8i_mixer *mixer,
int layer, struct drm_plane *plane);
int sun8i_mixer_update_layer_buffer(struct sun8i_mixer *mixer,
int layer, struct drm_plane *plane);
#endif /* _SUN8I_MIXER_H_ */

View file

@ -0,0 +1,98 @@
/*
* Copyright (C) 2017 Icenowy Zheng <icenowy@aosc.io>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*/
#ifndef _SUNXI_ENGINE_H_
#define _SUNXI_ENGINE_H_
struct drm_plane;
struct drm_device;
struct sunxi_engine;
struct sunxi_engine_ops {
void (*commit)(struct sunxi_engine *engine);
struct drm_plane **(*layers_init)(struct drm_device *drm,
struct sunxi_engine *engine);
void (*apply_color_correction)(struct sunxi_engine *engine);
void (*disable_color_correction)(struct sunxi_engine *engine);
};
/**
* struct sunxi_engine - the common parts of an engine for sun4i-drm driver
* @ops: the operations of the engine
* @node: the of device node of the engine
* @regs: the regmap of the engine
* @id: the id of the engine (-1 if not used)
*/
struct sunxi_engine {
const struct sunxi_engine_ops *ops;
struct device_node *node;
struct regmap *regs;
int id;
/* Engine list management */
struct list_head list;
};
/**
* sunxi_engine_commit() - commit all changes of the engine
* @engine: pointer to the engine
*/
static inline void
sunxi_engine_commit(struct sunxi_engine *engine)
{
if (engine->ops && engine->ops->commit)
engine->ops->commit(engine);
}
/**
* sunxi_engine_layers_init() - Create planes (layers) for the engine
* @drm: pointer to the drm_device for which planes will be created
* @engine: pointer to the engine
*/
static inline struct drm_plane **
sunxi_engine_layers_init(struct drm_device *drm, struct sunxi_engine *engine)
{
if (engine->ops && engine->ops->layers_init)
return engine->ops->layers_init(drm, engine);
return ERR_PTR(-ENOSYS);
}
/**
* sunxi_engine_apply_color_correction - Apply the RGB2YUV color correction
* @engine: pointer to the engine
*
* This functionality is optional for an engine, however, if the engine is
* intended to be used with TV Encoder, the output will be incorrect
* without the color correction, due to TV Encoder expects the engine to
* output directly YUV signal.
*/
static inline void
sunxi_engine_apply_color_correction(struct sunxi_engine *engine)
{
if (engine->ops && engine->ops->apply_color_correction)
engine->ops->apply_color_correction(engine);
}
/**
* sunxi_engine_disable_color_correction - Disable the color space correction
* @engine: pointer to the engine
*
* This function is paired with apply_color_correction().
*/
static inline void
sunxi_engine_disable_color_correction(struct sunxi_engine *engine)
{
if (engine->ops && engine->ops->disable_color_correction)
engine->ops->disable_color_correction(engine);
}
#endif /* _SUNXI_ENGINE_H_ */