diff options
author | Dave Airlie <airlied@redhat.com> | 2017-06-15 20:02:35 -0400 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2017-06-15 20:02:35 -0400 |
commit | 7249e3d64ee512521087de802d3f3ad504258535 (patch) | |
tree | 4ca6053e519e6be3d7139d3f92a972009807eab5 | |
parent | 04d4fb5fa63876d8e7cf67f2788aecfafc6a28a7 (diff) | |
parent | 110d33dd428ea49b9482bcb780fb096dfb4dcd3e (diff) |
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
...
24 files changed, 2285 insertions, 105 deletions
diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt index 57a8d0610062..b83e6018041d 100644 --- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt +++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt | |||
@@ -4,6 +4,44 @@ Allwinner A10 Display Pipeline | |||
4 | The Allwinner A10 Display pipeline is composed of several components | 4 | The Allwinner A10 Display pipeline is composed of several components |
5 | that are going to be documented below: | 5 | that are going to be documented below: |
6 | 6 | ||
7 | For the input port of all components up to the TCON in the display | ||
8 | pipeline, if there are multiple components, the local endpoint IDs | ||
9 | must correspond to the index of the upstream block. For example, if | ||
10 | the remote endpoint is Frontend 1, then the local endpoint ID must | ||
11 | be 1. | ||
12 | |||
13 | Conversely, for the output ports of the same group, the remote endpoint | ||
14 | ID must be the index of the local hardware block. If the local backend | ||
15 | is backend 1, then the remote endpoint ID must be 1. | ||
16 | |||
17 | HDMI Encoder | ||
18 | ------------ | ||
19 | |||
20 | The HDMI Encoder supports the HDMI video and audio outputs, and does | ||
21 | CEC. It is one end of the pipeline. | ||
22 | |||
23 | Required properties: | ||
24 | - compatible: value must be one of: | ||
25 | * allwinner,sun5i-a10s-hdmi | ||
26 | - reg: base address and size of memory-mapped region | ||
27 | - interrupts: interrupt associated to this IP | ||
28 | - clocks: phandles to the clocks feeding the HDMI encoder | ||
29 | * ahb: the HDMI interface clock | ||
30 | * mod: the HDMI module clock | ||
31 | * pll-0: the first video PLL | ||
32 | * pll-1: the second video PLL | ||
33 | - clock-names: the clock names mentioned above | ||
34 | - dmas: phandles to the DMA channels used by the HDMI encoder | ||
35 | * ddc-tx: The channel for DDC transmission | ||
36 | * ddc-rx: The channel for DDC reception | ||
37 | * audio-tx: The channel used for audio transmission | ||
38 | - dma-names: the channel names mentioned above | ||
39 | |||
40 | - ports: A ports node with endpoint definitions as defined in | ||
41 | Documentation/devicetree/bindings/media/video-interfaces.txt. The | ||
42 | first port should be the input endpoint. The second should be the | ||
43 | output, usually to an HDMI connector. | ||
44 | |||
7 | TV Encoder | 45 | TV Encoder |
8 | ---------- | 46 | ---------- |
9 | 47 | ||
@@ -31,6 +69,7 @@ Required properties: | |||
31 | * allwinner,sun6i-a31-tcon | 69 | * allwinner,sun6i-a31-tcon |
32 | * allwinner,sun6i-a31s-tcon | 70 | * allwinner,sun6i-a31s-tcon |
33 | * allwinner,sun8i-a33-tcon | 71 | * allwinner,sun8i-a33-tcon |
72 | * allwinner,sun8i-v3s-tcon | ||
34 | - reg: base address and size of memory-mapped region | 73 | - reg: base address and size of memory-mapped region |
35 | - interrupts: interrupt associated to this IP | 74 | - interrupts: interrupt associated to this IP |
36 | - clocks: phandles to the clocks feeding the TCON. Three are needed: | 75 | - clocks: phandles to the clocks feeding the TCON. Three are needed: |
@@ -47,12 +86,15 @@ Required properties: | |||
47 | Documentation/devicetree/bindings/media/video-interfaces.txt. The | 86 | Documentation/devicetree/bindings/media/video-interfaces.txt. The |
48 | first port should be the input endpoint, the second one the output | 87 | first port should be the input endpoint, the second one the output |
49 | 88 | ||
50 | The output should have two endpoints. The first is the block | 89 | The output may have multiple endpoints. The TCON has two channels, |
51 | connected to the TCON channel 0 (usually a panel or a bridge), the | 90 | usually with the first channel being used for the panels interfaces |
52 | second the block connected to the TCON channel 1 (usually the TV | 91 | (RGB, LVDS, etc.), and the second being used for the outputs that |
53 | encoder) | 92 | require another controller (TV Encoder, HDMI, etc.). The endpoints |
93 | will take an extra property, allwinner,tcon-channel, to specify the | ||
94 | channel the endpoint is associated to. If that property is not | ||
95 | present, the endpoint number will be used as the channel number. | ||
54 | 96 | ||
55 | On SoCs other than the A33, there is one more clock required: | 97 | On SoCs other than the A33 and V3s, there is one more clock required: |
56 | - 'tcon-ch1': The clock driving the TCON channel 1 | 98 | - 'tcon-ch1': The clock driving the TCON channel 1 |
57 | 99 | ||
58 | DRC | 100 | DRC |
@@ -138,6 +180,26 @@ Required properties: | |||
138 | Documentation/devicetree/bindings/media/video-interfaces.txt. The | 180 | Documentation/devicetree/bindings/media/video-interfaces.txt. The |
139 | first port should be the input endpoints, the second one the outputs | 181 | first port should be the input endpoints, the second one the outputs |
140 | 182 | ||
183 | Display Engine 2.0 Mixer | ||
184 | ------------------------ | ||
185 | |||
186 | The DE2 mixer have many functionalities, currently only layer blending is | ||
187 | supported. | ||
188 | |||
189 | Required properties: | ||
190 | - compatible: value must be one of: | ||
191 | * allwinner,sun8i-v3s-de2-mixer | ||
192 | - reg: base address and size of the memory-mapped region. | ||
193 | - clocks: phandles to the clocks feeding the mixer | ||
194 | * bus: the mixer interface clock | ||
195 | * mod: the mixer module clock | ||
196 | - clock-names: the clock names mentioned above | ||
197 | - resets: phandles to the reset controllers driving the mixer | ||
198 | |||
199 | - ports: A ports node with endpoint definitions as defined in | ||
200 | Documentation/devicetree/bindings/media/video-interfaces.txt. The | ||
201 | first port should be the input endpoints, the second one the output | ||
202 | |||
141 | 203 | ||
142 | Display Engine Pipeline | 204 | Display Engine Pipeline |
143 | ----------------------- | 205 | ----------------------- |
@@ -148,13 +210,15 @@ extra node. | |||
148 | 210 | ||
149 | Required properties: | 211 | Required properties: |
150 | - compatible: value must be one of: | 212 | - compatible: value must be one of: |
213 | * allwinner,sun5i-a10s-display-engine | ||
151 | * allwinner,sun5i-a13-display-engine | 214 | * allwinner,sun5i-a13-display-engine |
152 | * allwinner,sun6i-a31-display-engine | 215 | * allwinner,sun6i-a31-display-engine |
153 | * allwinner,sun6i-a31s-display-engine | 216 | * allwinner,sun6i-a31s-display-engine |
154 | * allwinner,sun8i-a33-display-engine | 217 | * allwinner,sun8i-a33-display-engine |
218 | * allwinner,sun8i-v3s-display-engine | ||
155 | 219 | ||
156 | - allwinner,pipelines: list of phandle to the display engine | 220 | - allwinner,pipelines: list of phandle to the display engine |
157 | frontends available. | 221 | frontends (DE 1.0) or mixers (DE 2.0) available. |
158 | 222 | ||
159 | Example: | 223 | Example: |
160 | 224 | ||
@@ -173,6 +237,57 @@ panel: panel { | |||
173 | }; | 237 | }; |
174 | }; | 238 | }; |
175 | 239 | ||
240 | connector { | ||
241 | compatible = "hdmi-connector"; | ||
242 | type = "a"; | ||
243 | |||
244 | port { | ||
245 | hdmi_con_in: endpoint { | ||
246 | remote-endpoint = <&hdmi_out_con>; | ||
247 | }; | ||
248 | }; | ||
249 | }; | ||
250 | |||
251 | hdmi: hdmi@01c16000 { | ||
252 | compatible = "allwinner,sun5i-a10s-hdmi"; | ||
253 | reg = <0x01c16000 0x1000>; | ||
254 | interrupts = <58>; | ||
255 | clocks = <&ccu CLK_AHB_HDMI>, <&ccu CLK_HDMI>, | ||
256 | <&ccu CLK_PLL_VIDEO0_2X>, | ||
257 | <&ccu CLK_PLL_VIDEO1_2X>; | ||
258 | clock-names = "ahb", "mod", "pll-0", "pll-1"; | ||
259 | dmas = <&dma SUN4I_DMA_NORMAL 16>, | ||
260 | <&dma SUN4I_DMA_NORMAL 16>, | ||
261 | <&dma SUN4I_DMA_DEDICATED 24>; | ||
262 | dma-names = "ddc-tx", "ddc-rx", "audio-tx"; | ||
263 | status = "disabled"; | ||
264 | |||
265 | ports { | ||
266 | #address-cells = <1>; | ||
267 | #size-cells = <0>; | ||
268 | |||
269 | port@0 { | ||
270 | #address-cells = <1>; | ||
271 | #size-cells = <0>; | ||
272 | reg = <0>; | ||
273 | |||
274 | hdmi_in_tcon0: endpoint { | ||
275 | remote-endpoint = <&tcon0_out_hdmi>; | ||
276 | }; | ||
277 | }; | ||
278 | |||
279 | port@1 { | ||
280 | #address-cells = <1>; | ||
281 | #size-cells = <0>; | ||
282 | reg = <1>; | ||
283 | |||
284 | hdmi_out_con: endpoint { | ||
285 | remote-endpoint = <&hdmi_con_in>; | ||
286 | }; | ||
287 | }; | ||
288 | }; | ||
289 | }; | ||
290 | |||
176 | tve0: tv-encoder@01c0a000 { | 291 | tve0: tv-encoder@01c0a000 { |
177 | compatible = "allwinner,sun4i-a10-tv-encoder"; | 292 | compatible = "allwinner,sun4i-a10-tv-encoder"; |
178 | reg = <0x01c0a000 0x1000>; | 293 | reg = <0x01c0a000 0x1000>; |
diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig index a4b357db8856..5bcad8f5fb4f 100644 --- a/drivers/gpu/drm/sun4i/Kconfig +++ b/drivers/gpu/drm/sun4i/Kconfig | |||
@@ -12,3 +12,31 @@ config DRM_SUN4I | |||
12 | Choose this option if you have an Allwinner SoC with a | 12 | Choose this option if you have an Allwinner SoC with a |
13 | Display Engine. If M is selected the module will be called | 13 | Display Engine. If M is selected the module will be called |
14 | sun4i-drm. | 14 | sun4i-drm. |
15 | |||
16 | config DRM_SUN4I_HDMI | ||
17 | tristate "Allwinner A10 HDMI Controller Support" | ||
18 | depends on DRM_SUN4I | ||
19 | default DRM_SUN4I | ||
20 | help | ||
21 | Choose this option if you have an Allwinner SoC with an HDMI | ||
22 | controller. | ||
23 | |||
24 | config DRM_SUN4I_BACKEND | ||
25 | tristate "Support for Allwinner A10 Display Engine Backend" | ||
26 | depends on DRM_SUN4I | ||
27 | default DRM_SUN4I | ||
28 | help | ||
29 | Choose this option if you have an Allwinner SoC with the | ||
30 | original Allwinner Display Engine, which has a backend to | ||
31 | do some alpha blending and feed graphics to TCON. If M is | ||
32 | selected the module will be called sun4i-backend. | ||
33 | |||
34 | config DRM_SUN8I_MIXER | ||
35 | tristate "Support for Allwinner Display Engine 2.0 Mixer" | ||
36 | depends on DRM_SUN4I | ||
37 | default MACH_SUN8I | ||
38 | help | ||
39 | Choose this option if you have an Allwinner SoC with the | ||
40 | Allwinner Display Engine 2.0, which has a mixer to do some | ||
41 | graphics mixture and feed graphics to TCON, If M is | ||
42 | selected the module will be called sun8i-mixer. | ||
diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile index 59b757350a1f..e29fd3a2ba9c 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile | |||
@@ -1,13 +1,23 @@ | |||
1 | sun4i-drm-y += sun4i_drv.o | 1 | sun4i-drm-y += sun4i_drv.o |
2 | sun4i-drm-y += sun4i_framebuffer.o | 2 | sun4i-drm-y += sun4i_framebuffer.o |
3 | 3 | ||
4 | sun4i-drm-hdmi-y += sun4i_hdmi_enc.o | ||
5 | sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o | ||
6 | sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o | ||
7 | |||
4 | sun4i-tcon-y += sun4i_tcon.o | 8 | sun4i-tcon-y += sun4i_tcon.o |
5 | sun4i-tcon-y += sun4i_rgb.o | 9 | sun4i-tcon-y += sun4i_rgb.o |
6 | sun4i-tcon-y += sun4i_dotclock.o | 10 | sun4i-tcon-y += sun4i_dotclock.o |
7 | sun4i-tcon-y += sun4i_crtc.o | 11 | sun4i-tcon-y += sun4i_crtc.o |
8 | sun4i-tcon-y += sun4i_layer.o | 12 | |
13 | sun4i-backend-y += sun4i_backend.o sun4i_layer.o | ||
14 | |||
15 | sun8i-mixer-y += sun8i_mixer.o sun8i_layer.o | ||
9 | 16 | ||
10 | obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o sun4i-tcon.o | 17 | obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o sun4i-tcon.o |
11 | obj-$(CONFIG_DRM_SUN4I) += sun4i_backend.o | ||
12 | obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o | 18 | obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o |
13 | obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o | 19 | obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o |
20 | |||
21 | obj-$(CONFIG_DRM_SUN4I_BACKEND) += sun4i-backend.o | ||
22 | obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o | ||
23 | obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o | ||
diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.c b/drivers/gpu/drm/sun4i/sun4i_backend.c index d660741ba475..cf480218daa5 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.c +++ b/drivers/gpu/drm/sun4i/sun4i_backend.c | |||
@@ -19,10 +19,14 @@ | |||
19 | #include <drm/drm_plane_helper.h> | 19 | #include <drm/drm_plane_helper.h> |
20 | 20 | ||
21 | #include <linux/component.h> | 21 | #include <linux/component.h> |
22 | #include <linux/list.h> | ||
23 | #include <linux/of_graph.h> | ||
22 | #include <linux/reset.h> | 24 | #include <linux/reset.h> |
23 | 25 | ||
24 | #include "sun4i_backend.h" | 26 | #include "sun4i_backend.h" |
25 | #include "sun4i_drv.h" | 27 | #include "sun4i_drv.h" |
28 | #include "sun4i_layer.h" | ||
29 | #include "sunxi_engine.h" | ||
26 | 30 | ||
27 | static const u32 sunxi_rgb2yuv_coef[12] = { | 31 | static const u32 sunxi_rgb2yuv_coef[12] = { |
28 | 0x00000107, 0x00000204, 0x00000064, 0x00000108, | 32 | 0x00000107, 0x00000204, 0x00000064, 0x00000108, |
@@ -30,58 +34,55 @@ static const u32 sunxi_rgb2yuv_coef[12] = { | |||
30 | 0x000001c1, 0x00003e88, 0x00003fb8, 0x00000808 | 34 | 0x000001c1, 0x00003e88, 0x00003fb8, 0x00000808 |
31 | }; | 35 | }; |
32 | 36 | ||
33 | void sun4i_backend_apply_color_correction(struct sun4i_backend *backend) | 37 | static void sun4i_backend_apply_color_correction(struct sunxi_engine *engine) |
34 | { | 38 | { |
35 | int i; | 39 | int i; |
36 | 40 | ||
37 | DRM_DEBUG_DRIVER("Applying RGB to YUV color correction\n"); | 41 | DRM_DEBUG_DRIVER("Applying RGB to YUV color correction\n"); |
38 | 42 | ||
39 | /* Set color correction */ | 43 | /* Set color correction */ |
40 | regmap_write(backend->regs, SUN4I_BACKEND_OCCTL_REG, | 44 | regmap_write(engine->regs, SUN4I_BACKEND_OCCTL_REG, |
41 | SUN4I_BACKEND_OCCTL_ENABLE); | 45 | SUN4I_BACKEND_OCCTL_ENABLE); |
42 | 46 | ||
43 | for (i = 0; i < 12; i++) | 47 | for (i = 0; i < 12; i++) |
44 | regmap_write(backend->regs, SUN4I_BACKEND_OCRCOEF_REG(i), | 48 | regmap_write(engine->regs, SUN4I_BACKEND_OCRCOEF_REG(i), |
45 | sunxi_rgb2yuv_coef[i]); | 49 | sunxi_rgb2yuv_coef[i]); |
46 | } | 50 | } |
47 | EXPORT_SYMBOL(sun4i_backend_apply_color_correction); | ||
48 | 51 | ||
49 | void sun4i_backend_disable_color_correction(struct sun4i_backend *backend) | 52 | static void sun4i_backend_disable_color_correction(struct sunxi_engine *engine) |
50 | { | 53 | { |
51 | DRM_DEBUG_DRIVER("Disabling color correction\n"); | 54 | DRM_DEBUG_DRIVER("Disabling color correction\n"); |
52 | 55 | ||
53 | /* Disable color correction */ | 56 | /* Disable color correction */ |
54 | regmap_update_bits(backend->regs, SUN4I_BACKEND_OCCTL_REG, | 57 | regmap_update_bits(engine->regs, SUN4I_BACKEND_OCCTL_REG, |
55 | SUN4I_BACKEND_OCCTL_ENABLE, 0); | 58 | SUN4I_BACKEND_OCCTL_ENABLE, 0); |
56 | } | 59 | } |
57 | EXPORT_SYMBOL(sun4i_backend_disable_color_correction); | ||
58 | 60 | ||
59 | void sun4i_backend_commit(struct sun4i_backend *backend) | 61 | static void sun4i_backend_commit(struct sunxi_engine *engine) |
60 | { | 62 | { |
61 | DRM_DEBUG_DRIVER("Committing changes\n"); | 63 | DRM_DEBUG_DRIVER("Committing changes\n"); |
62 | 64 | ||
63 | regmap_write(backend->regs, SUN4I_BACKEND_REGBUFFCTL_REG, | 65 | regmap_write(engine->regs, SUN4I_BACKEND_REGBUFFCTL_REG, |
64 | SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS | | 66 | SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS | |
65 | SUN4I_BACKEND_REGBUFFCTL_LOADCTL); | 67 | SUN4I_BACKEND_REGBUFFCTL_LOADCTL); |
66 | } | 68 | } |
67 | EXPORT_SYMBOL(sun4i_backend_commit); | ||
68 | 69 | ||
69 | void sun4i_backend_layer_enable(struct sun4i_backend *backend, | 70 | void sun4i_backend_layer_enable(struct sun4i_backend *backend, |
70 | int layer, bool enable) | 71 | int layer, bool enable) |
71 | { | 72 | { |
72 | u32 val; | 73 | u32 val; |
73 | 74 | ||
74 | DRM_DEBUG_DRIVER("Enabling layer %d\n", layer); | 75 | DRM_DEBUG_DRIVER("%sabling layer %d\n", enable ? "En" : "Dis", |
76 | layer); | ||
75 | 77 | ||
76 | if (enable) | 78 | if (enable) |
77 | val = SUN4I_BACKEND_MODCTL_LAY_EN(layer); | 79 | val = SUN4I_BACKEND_MODCTL_LAY_EN(layer); |
78 | else | 80 | else |
79 | val = 0; | 81 | val = 0; |
80 | 82 | ||
81 | regmap_update_bits(backend->regs, SUN4I_BACKEND_MODCTL_REG, | 83 | regmap_update_bits(backend->engine.regs, SUN4I_BACKEND_MODCTL_REG, |
82 | SUN4I_BACKEND_MODCTL_LAY_EN(layer), val); | 84 | SUN4I_BACKEND_MODCTL_LAY_EN(layer), val); |
83 | } | 85 | } |
84 | EXPORT_SYMBOL(sun4i_backend_layer_enable); | ||
85 | 86 | ||
86 | static int sun4i_backend_drm_format_to_layer(struct drm_plane *plane, | 87 | static int sun4i_backend_drm_format_to_layer(struct drm_plane *plane, |
87 | u32 format, u32 *mode) | 88 | u32 format, u32 *mode) |
@@ -141,33 +142,33 @@ int sun4i_backend_update_layer_coord(struct sun4i_backend *backend, | |||
141 | if (plane->type == DRM_PLANE_TYPE_PRIMARY) { | 142 | if (plane->type == DRM_PLANE_TYPE_PRIMARY) { |
142 | DRM_DEBUG_DRIVER("Primary layer, updating global size W: %u H: %u\n", | 143 | DRM_DEBUG_DRIVER("Primary layer, updating global size W: %u H: %u\n", |
143 | state->crtc_w, state->crtc_h); | 144 | state->crtc_w, state->crtc_h); |
144 | regmap_write(backend->regs, SUN4I_BACKEND_DISSIZE_REG, | 145 | regmap_write(backend->engine.regs, SUN4I_BACKEND_DISSIZE_REG, |
145 | SUN4I_BACKEND_DISSIZE(state->crtc_w, | 146 | SUN4I_BACKEND_DISSIZE(state->crtc_w, |
146 | state->crtc_h)); | 147 | state->crtc_h)); |
147 | } | 148 | } |
148 | 149 | ||
149 | /* Set the line width */ | 150 | /* Set the line width */ |
150 | DRM_DEBUG_DRIVER("Layer line width: %d bits\n", fb->pitches[0] * 8); | 151 | DRM_DEBUG_DRIVER("Layer line width: %d bits\n", fb->pitches[0] * 8); |
151 | regmap_write(backend->regs, SUN4I_BACKEND_LAYLINEWIDTH_REG(layer), | 152 | regmap_write(backend->engine.regs, |
153 | SUN4I_BACKEND_LAYLINEWIDTH_REG(layer), | ||
152 | fb->pitches[0] * 8); | 154 | fb->pitches[0] * 8); |
153 | 155 | ||
154 | /* Set height and width */ | 156 | /* Set height and width */ |
155 | DRM_DEBUG_DRIVER("Layer size W: %u H: %u\n", | 157 | DRM_DEBUG_DRIVER("Layer size W: %u H: %u\n", |
156 | state->crtc_w, state->crtc_h); | 158 | state->crtc_w, state->crtc_h); |
157 | regmap_write(backend->regs, SUN4I_BACKEND_LAYSIZE_REG(layer), | 159 | regmap_write(backend->engine.regs, SUN4I_BACKEND_LAYSIZE_REG(layer), |
158 | SUN4I_BACKEND_LAYSIZE(state->crtc_w, | 160 | SUN4I_BACKEND_LAYSIZE(state->crtc_w, |
159 | state->crtc_h)); | 161 | state->crtc_h)); |
160 | 162 | ||
161 | /* Set base coordinates */ | 163 | /* Set base coordinates */ |
162 | DRM_DEBUG_DRIVER("Layer coordinates X: %d Y: %d\n", | 164 | DRM_DEBUG_DRIVER("Layer coordinates X: %d Y: %d\n", |
163 | state->crtc_x, state->crtc_y); | 165 | state->crtc_x, state->crtc_y); |
164 | regmap_write(backend->regs, SUN4I_BACKEND_LAYCOOR_REG(layer), | 166 | regmap_write(backend->engine.regs, SUN4I_BACKEND_LAYCOOR_REG(layer), |
165 | SUN4I_BACKEND_LAYCOOR(state->crtc_x, | 167 | SUN4I_BACKEND_LAYCOOR(state->crtc_x, |
166 | state->crtc_y)); | 168 | state->crtc_y)); |
167 | 169 | ||
168 | return 0; | 170 | return 0; |
169 | } | 171 | } |
170 | EXPORT_SYMBOL(sun4i_backend_update_layer_coord); | ||
171 | 172 | ||
172 | int sun4i_backend_update_layer_formats(struct sun4i_backend *backend, | 173 | int sun4i_backend_update_layer_formats(struct sun4i_backend *backend, |
173 | int layer, struct drm_plane *plane) | 174 | int layer, struct drm_plane *plane) |
@@ -182,7 +183,7 @@ int sun4i_backend_update_layer_formats(struct sun4i_backend *backend, | |||
182 | interlaced = plane->state->crtc->state->adjusted_mode.flags | 183 | interlaced = plane->state->crtc->state->adjusted_mode.flags |
183 | & DRM_MODE_FLAG_INTERLACE; | 184 | & DRM_MODE_FLAG_INTERLACE; |
184 | 185 | ||
185 | regmap_update_bits(backend->regs, SUN4I_BACKEND_MODCTL_REG, | 186 | regmap_update_bits(backend->engine.regs, SUN4I_BACKEND_MODCTL_REG, |
186 | SUN4I_BACKEND_MODCTL_ITLMOD_EN, | 187 | SUN4I_BACKEND_MODCTL_ITLMOD_EN, |
187 | interlaced ? SUN4I_BACKEND_MODCTL_ITLMOD_EN : 0); | 188 | interlaced ? SUN4I_BACKEND_MODCTL_ITLMOD_EN : 0); |
188 | 189 | ||
@@ -196,12 +197,12 @@ int sun4i_backend_update_layer_formats(struct sun4i_backend *backend, | |||
196 | return ret; | 197 | return ret; |
197 | } | 198 | } |
198 | 199 | ||
199 | regmap_update_bits(backend->regs, SUN4I_BACKEND_ATTCTL_REG1(layer), | 200 | regmap_update_bits(backend->engine.regs, |
201 | SUN4I_BACKEND_ATTCTL_REG1(layer), | ||
200 | SUN4I_BACKEND_ATTCTL_REG1_LAY_FBFMT, val); | 202 | SUN4I_BACKEND_ATTCTL_REG1_LAY_FBFMT, val); |
201 | 203 | ||
202 | return 0; | 204 | return 0; |
203 | } | 205 | } |
204 | EXPORT_SYMBOL(sun4i_backend_update_layer_formats); | ||
205 | 206 | ||
206 | int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, | 207 | int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, |
207 | int layer, struct drm_plane *plane) | 208 | int layer, struct drm_plane *plane) |
@@ -229,19 +230,19 @@ int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, | |||
229 | /* Write the 32 lower bits of the address (in bits) */ | 230 | /* Write the 32 lower bits of the address (in bits) */ |
230 | lo_paddr = paddr << 3; | 231 | lo_paddr = paddr << 3; |
231 | DRM_DEBUG_DRIVER("Setting address lower bits to 0x%x\n", lo_paddr); | 232 | DRM_DEBUG_DRIVER("Setting address lower bits to 0x%x\n", lo_paddr); |
232 | regmap_write(backend->regs, SUN4I_BACKEND_LAYFB_L32ADD_REG(layer), | 233 | regmap_write(backend->engine.regs, |
234 | SUN4I_BACKEND_LAYFB_L32ADD_REG(layer), | ||
233 | lo_paddr); | 235 | lo_paddr); |
234 | 236 | ||
235 | /* And the upper bits */ | 237 | /* And the upper bits */ |
236 | hi_paddr = paddr >> 29; | 238 | hi_paddr = paddr >> 29; |
237 | DRM_DEBUG_DRIVER("Setting address high bits to 0x%x\n", hi_paddr); | 239 | DRM_DEBUG_DRIVER("Setting address high bits to 0x%x\n", hi_paddr); |
238 | regmap_update_bits(backend->regs, SUN4I_BACKEND_LAYFB_H4ADD_REG, | 240 | regmap_update_bits(backend->engine.regs, SUN4I_BACKEND_LAYFB_H4ADD_REG, |
239 | SUN4I_BACKEND_LAYFB_H4ADD_MSK(layer), | 241 | SUN4I_BACKEND_LAYFB_H4ADD_MSK(layer), |
240 | SUN4I_BACKEND_LAYFB_H4ADD(layer, hi_paddr)); | 242 | SUN4I_BACKEND_LAYFB_H4ADD(layer, hi_paddr)); |
241 | 243 | ||
242 | return 0; | 244 | return 0; |
243 | } | 245 | } |
244 | EXPORT_SYMBOL(sun4i_backend_update_layer_buffer); | ||
245 | 246 | ||
246 | static int sun4i_backend_init_sat(struct device *dev) { | 247 | static int sun4i_backend_init_sat(struct device *dev) { |
247 | struct sun4i_backend *backend = dev_get_drvdata(dev); | 248 | struct sun4i_backend *backend = dev_get_drvdata(dev); |
@@ -288,6 +289,52 @@ static int sun4i_backend_free_sat(struct device *dev) { | |||
288 | return 0; | 289 | return 0; |
289 | } | 290 | } |
290 | 291 | ||
292 | /* | ||
293 | * The display backend can take video output from the display frontend, or | ||
294 | * the display enhancement unit on the A80, as input for one it its layers. | ||
295 | * This relationship within the display pipeline is encoded in the device | ||
296 | * tree with of_graph, and we use it here to figure out which backend, if | ||
297 | * there are 2 or more, we are currently probing. The number would be in | ||
298 | * the "reg" property of the upstream output port endpoint. | ||
299 | */ | ||
300 | static int sun4i_backend_of_get_id(struct device_node *node) | ||
301 | { | ||
302 | struct device_node *port, *ep; | ||
303 | int ret = -EINVAL; | ||
304 | |||
305 | /* input is port 0 */ | ||
306 | port = of_graph_get_port_by_id(node, 0); | ||
307 | if (!port) | ||
308 | return -EINVAL; | ||
309 | |||
310 | /* try finding an upstream endpoint */ | ||
311 | for_each_available_child_of_node(port, ep) { | ||
312 | struct device_node *remote; | ||
313 | u32 reg; | ||
314 | |||
315 | remote = of_parse_phandle(ep, "remote-endpoint", 0); | ||
316 | if (!remote) | ||
317 | continue; | ||
318 | |||
319 | ret = of_property_read_u32(remote, "reg", ®); | ||
320 | if (ret) | ||
321 | continue; | ||
322 | |||
323 | ret = reg; | ||
324 | } | ||
325 | |||
326 | of_node_put(port); | ||
327 | |||
328 | return ret; | ||
329 | } | ||
330 | |||
331 | static const struct sunxi_engine_ops sun4i_backend_engine_ops = { | ||
332 | .commit = sun4i_backend_commit, | ||
333 | .layers_init = sun4i_layers_init, | ||
334 | .apply_color_correction = sun4i_backend_apply_color_correction, | ||
335 | .disable_color_correction = sun4i_backend_disable_color_correction, | ||
336 | }; | ||
337 | |||
291 | static struct regmap_config sun4i_backend_regmap_config = { | 338 | static struct regmap_config sun4i_backend_regmap_config = { |
292 | .reg_bits = 32, | 339 | .reg_bits = 32, |
293 | .val_bits = 32, | 340 | .val_bits = 32, |
@@ -310,18 +357,23 @@ static int sun4i_backend_bind(struct device *dev, struct device *master, | |||
310 | if (!backend) | 357 | if (!backend) |
311 | return -ENOMEM; | 358 | return -ENOMEM; |
312 | dev_set_drvdata(dev, backend); | 359 | dev_set_drvdata(dev, backend); |
313 | drv->backend = backend; | 360 | |
361 | backend->engine.node = dev->of_node; | ||
362 | backend->engine.ops = &sun4i_backend_engine_ops; | ||
363 | backend->engine.id = sun4i_backend_of_get_id(dev->of_node); | ||
364 | if (backend->engine.id < 0) | ||
365 | return backend->engine.id; | ||
314 | 366 | ||
315 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 367 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
316 | regs = devm_ioremap_resource(dev, res); | 368 | regs = devm_ioremap_resource(dev, res); |
317 | if (IS_ERR(regs)) | 369 | if (IS_ERR(regs)) |
318 | return PTR_ERR(regs); | 370 | return PTR_ERR(regs); |
319 | 371 | ||
320 | backend->regs = devm_regmap_init_mmio(dev, regs, | 372 | backend->engine.regs = devm_regmap_init_mmio(dev, regs, |
321 | &sun4i_backend_regmap_config); | 373 | &sun4i_backend_regmap_config); |
322 | if (IS_ERR(backend->regs)) { | 374 | if (IS_ERR(backend->engine.regs)) { |
323 | dev_err(dev, "Couldn't create the backend0 regmap\n"); | 375 | dev_err(dev, "Couldn't create the backend regmap\n"); |
324 | return PTR_ERR(backend->regs); | 376 | return PTR_ERR(backend->engine.regs); |
325 | } | 377 | } |
326 | 378 | ||
327 | backend->reset = devm_reset_control_get(dev, NULL); | 379 | backend->reset = devm_reset_control_get(dev, NULL); |
@@ -369,16 +421,18 @@ static int sun4i_backend_bind(struct device *dev, struct device *master, | |||
369 | } | 421 | } |
370 | } | 422 | } |
371 | 423 | ||
424 | list_add_tail(&backend->engine.list, &drv->engine_list); | ||
425 | |||
372 | /* Reset the registers */ | 426 | /* Reset the registers */ |
373 | for (i = 0x800; i < 0x1000; i += 4) | 427 | for (i = 0x800; i < 0x1000; i += 4) |
374 | regmap_write(backend->regs, i, 0); | 428 | regmap_write(backend->engine.regs, i, 0); |
375 | 429 | ||
376 | /* Disable registers autoloading */ | 430 | /* Disable registers autoloading */ |
377 | regmap_write(backend->regs, SUN4I_BACKEND_REGBUFFCTL_REG, | 431 | regmap_write(backend->engine.regs, SUN4I_BACKEND_REGBUFFCTL_REG, |
378 | SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS); | 432 | SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS); |
379 | 433 | ||
380 | /* Enable the backend */ | 434 | /* Enable the backend */ |
381 | regmap_write(backend->regs, SUN4I_BACKEND_MODCTL_REG, | 435 | regmap_write(backend->engine.regs, SUN4I_BACKEND_MODCTL_REG, |
382 | SUN4I_BACKEND_MODCTL_DEBE_EN | | 436 | SUN4I_BACKEND_MODCTL_DEBE_EN | |
383 | SUN4I_BACKEND_MODCTL_START_CTL); | 437 | SUN4I_BACKEND_MODCTL_START_CTL); |
384 | 438 | ||
@@ -400,6 +454,8 @@ static void sun4i_backend_unbind(struct device *dev, struct device *master, | |||
400 | { | 454 | { |
401 | struct sun4i_backend *backend = dev_get_drvdata(dev); | 455 | struct sun4i_backend *backend = dev_get_drvdata(dev); |
402 | 456 | ||
457 | list_del(&backend->engine.list); | ||
458 | |||
403 | if (of_device_is_compatible(dev->of_node, | 459 | if (of_device_is_compatible(dev->of_node, |
404 | "allwinner,sun8i-a33-display-backend")) | 460 | "allwinner,sun8i-a33-display-backend")) |
405 | sun4i_backend_free_sat(dev); | 461 | sun4i_backend_free_sat(dev); |
diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.h b/drivers/gpu/drm/sun4i/sun4i_backend.h index 83e63cc702b4..21945af67a9d 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.h +++ b/drivers/gpu/drm/sun4i/sun4i_backend.h | |||
@@ -14,9 +14,13 @@ | |||
14 | #define _SUN4I_BACKEND_H_ | 14 | #define _SUN4I_BACKEND_H_ |
15 | 15 | ||
16 | #include <linux/clk.h> | 16 | #include <linux/clk.h> |
17 | #include <linux/list.h> | ||
18 | #include <linux/of.h> | ||
17 | #include <linux/regmap.h> | 19 | #include <linux/regmap.h> |
18 | #include <linux/reset.h> | 20 | #include <linux/reset.h> |
19 | 21 | ||
22 | #include "sunxi_engine.h" | ||
23 | |||
20 | #define SUN4I_BACKEND_MODCTL_REG 0x800 | 24 | #define SUN4I_BACKEND_MODCTL_REG 0x800 |
21 | #define SUN4I_BACKEND_MODCTL_LINE_SEL BIT(29) | 25 | #define SUN4I_BACKEND_MODCTL_LINE_SEL BIT(29) |
22 | #define SUN4I_BACKEND_MODCTL_ITLMOD_EN BIT(28) | 26 | #define SUN4I_BACKEND_MODCTL_ITLMOD_EN BIT(28) |
@@ -139,7 +143,7 @@ | |||
139 | #define SUN4I_BACKEND_PIPE_OFF(p) (0x5000 + (0x400 * (p))) | 143 | #define SUN4I_BACKEND_PIPE_OFF(p) (0x5000 + (0x400 * (p))) |
140 | 144 | ||
141 | struct sun4i_backend { | 145 | struct sun4i_backend { |
142 | struct regmap *regs; | 146 | struct sunxi_engine engine; |
143 | 147 | ||
144 | struct reset_control *reset; | 148 | struct reset_control *reset; |
145 | 149 | ||
@@ -151,10 +155,11 @@ struct sun4i_backend { | |||
151 | struct reset_control *sat_reset; | 155 | struct reset_control *sat_reset; |
152 | }; | 156 | }; |
153 | 157 | ||
154 | void sun4i_backend_apply_color_correction(struct sun4i_backend *backend); | 158 | static inline struct sun4i_backend * |
155 | void sun4i_backend_disable_color_correction(struct sun4i_backend *backend); | 159 | engine_to_sun4i_backend(struct sunxi_engine *engine) |
156 | 160 | { | |
157 | void sun4i_backend_commit(struct sun4i_backend *backend); | 161 | return container_of(engine, struct sun4i_backend, engine); |
162 | } | ||
158 | 163 | ||
159 | void sun4i_backend_layer_enable(struct sun4i_backend *backend, | 164 | void sun4i_backend_layer_enable(struct sun4i_backend *backend, |
160 | int layer, bool enable); | 165 | int layer, bool enable); |
diff --git a/drivers/gpu/drm/sun4i/sun4i_crtc.c b/drivers/gpu/drm/sun4i/sun4i_crtc.c index 3c876c3a356a..f8c70439d1e2 100644 --- a/drivers/gpu/drm/sun4i/sun4i_crtc.c +++ b/drivers/gpu/drm/sun4i/sun4i_crtc.c | |||
@@ -25,10 +25,9 @@ | |||
25 | 25 | ||
26 | #include <video/videomode.h> | 26 | #include <video/videomode.h> |
27 | 27 | ||
28 | #include "sun4i_backend.h" | ||
29 | #include "sun4i_crtc.h" | 28 | #include "sun4i_crtc.h" |
30 | #include "sun4i_drv.h" | 29 | #include "sun4i_drv.h" |
31 | #include "sun4i_layer.h" | 30 | #include "sunxi_engine.h" |
32 | #include "sun4i_tcon.h" | 31 | #include "sun4i_tcon.h" |
33 | 32 | ||
34 | static void sun4i_crtc_atomic_begin(struct drm_crtc *crtc, | 33 | static void sun4i_crtc_atomic_begin(struct drm_crtc *crtc, |
@@ -56,7 +55,7 @@ static void sun4i_crtc_atomic_flush(struct drm_crtc *crtc, | |||
56 | 55 | ||
57 | DRM_DEBUG_DRIVER("Committing plane changes\n"); | 56 | DRM_DEBUG_DRIVER("Committing plane changes\n"); |
58 | 57 | ||
59 | sun4i_backend_commit(scrtc->backend); | 58 | sunxi_engine_commit(scrtc->engine); |
60 | 59 | ||
61 | if (event) { | 60 | if (event) { |
62 | crtc->state->event = NULL; | 61 | crtc->state->event = NULL; |
@@ -135,36 +134,37 @@ static const struct drm_crtc_funcs sun4i_crtc_funcs = { | |||
135 | }; | 134 | }; |
136 | 135 | ||
137 | struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm, | 136 | struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm, |
138 | struct sun4i_backend *backend, | 137 | struct sunxi_engine *engine, |
139 | struct sun4i_tcon *tcon) | 138 | struct sun4i_tcon *tcon) |
140 | { | 139 | { |
141 | struct sun4i_crtc *scrtc; | 140 | struct sun4i_crtc *scrtc; |
141 | struct drm_plane **planes; | ||
142 | struct drm_plane *primary = NULL, *cursor = NULL; | 142 | struct drm_plane *primary = NULL, *cursor = NULL; |
143 | int ret, i; | 143 | int ret, i; |
144 | 144 | ||
145 | scrtc = devm_kzalloc(drm->dev, sizeof(*scrtc), GFP_KERNEL); | 145 | scrtc = devm_kzalloc(drm->dev, sizeof(*scrtc), GFP_KERNEL); |
146 | if (!scrtc) | 146 | if (!scrtc) |
147 | return ERR_PTR(-ENOMEM); | 147 | return ERR_PTR(-ENOMEM); |
148 | scrtc->backend = backend; | 148 | scrtc->engine = engine; |
149 | scrtc->tcon = tcon; | 149 | scrtc->tcon = tcon; |
150 | 150 | ||
151 | /* Create our layers */ | 151 | /* Create our layers */ |
152 | scrtc->layers = sun4i_layers_init(drm, scrtc->backend); | 152 | planes = sunxi_engine_layers_init(drm, engine); |
153 | if (IS_ERR(scrtc->layers)) { | 153 | if (IS_ERR(planes)) { |
154 | dev_err(drm->dev, "Couldn't create the planes\n"); | 154 | dev_err(drm->dev, "Couldn't create the planes\n"); |
155 | return NULL; | 155 | return NULL; |
156 | } | 156 | } |
157 | 157 | ||
158 | /* find primary and cursor planes for drm_crtc_init_with_planes */ | 158 | /* find primary and cursor planes for drm_crtc_init_with_planes */ |
159 | for (i = 0; scrtc->layers[i]; i++) { | 159 | for (i = 0; planes[i]; i++) { |
160 | struct sun4i_layer *layer = scrtc->layers[i]; | 160 | struct drm_plane *plane = planes[i]; |
161 | 161 | ||
162 | switch (layer->plane.type) { | 162 | switch (plane->type) { |
163 | case DRM_PLANE_TYPE_PRIMARY: | 163 | case DRM_PLANE_TYPE_PRIMARY: |
164 | primary = &layer->plane; | 164 | primary = plane; |
165 | break; | 165 | break; |
166 | case DRM_PLANE_TYPE_CURSOR: | 166 | case DRM_PLANE_TYPE_CURSOR: |
167 | cursor = &layer->plane; | 167 | cursor = plane; |
168 | break; | 168 | break; |
169 | default: | 169 | default: |
170 | break; | 170 | break; |
@@ -188,12 +188,12 @@ struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm, | |||
188 | 1); | 188 | 1); |
189 | 189 | ||
190 | /* Set possible_crtcs to this crtc for overlay planes */ | 190 | /* Set possible_crtcs to this crtc for overlay planes */ |
191 | for (i = 0; scrtc->layers[i]; i++) { | 191 | for (i = 0; planes[i]; i++) { |
192 | uint32_t possible_crtcs = BIT(drm_crtc_index(&scrtc->crtc)); | 192 | uint32_t possible_crtcs = BIT(drm_crtc_index(&scrtc->crtc)); |
193 | struct sun4i_layer *layer = scrtc->layers[i]; | 193 | struct drm_plane *plane = planes[i]; |
194 | 194 | ||
195 | if (layer->plane.type == DRM_PLANE_TYPE_OVERLAY) | 195 | if (plane->type == DRM_PLANE_TYPE_OVERLAY) |
196 | layer->plane.possible_crtcs = possible_crtcs; | 196 | plane->possible_crtcs = possible_crtcs; |
197 | } | 197 | } |
198 | 198 | ||
199 | return scrtc; | 199 | return scrtc; |
diff --git a/drivers/gpu/drm/sun4i/sun4i_crtc.h b/drivers/gpu/drm/sun4i/sun4i_crtc.h index 230cb8f0d601..bf0ce36eb518 100644 --- a/drivers/gpu/drm/sun4i/sun4i_crtc.h +++ b/drivers/gpu/drm/sun4i/sun4i_crtc.h | |||
@@ -17,9 +17,8 @@ struct sun4i_crtc { | |||
17 | struct drm_crtc crtc; | 17 | struct drm_crtc crtc; |
18 | struct drm_pending_vblank_event *event; | 18 | struct drm_pending_vblank_event *event; |
19 | 19 | ||
20 | struct sun4i_backend *backend; | 20 | struct sunxi_engine *engine; |
21 | struct sun4i_tcon *tcon; | 21 | struct sun4i_tcon *tcon; |
22 | struct sun4i_layer **layers; | ||
23 | }; | 22 | }; |
24 | 23 | ||
25 | static inline struct sun4i_crtc *drm_crtc_to_sun4i_crtc(struct drm_crtc *crtc) | 24 | 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) | |||
28 | } | 27 | } |
29 | 28 | ||
30 | struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm, | 29 | struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm, |
31 | struct sun4i_backend *backend, | 30 | struct sunxi_engine *engine, |
32 | struct sun4i_tcon *tcon); | 31 | struct sun4i_tcon *tcon); |
33 | 32 | ||
34 | #endif /* _SUN4I_CRTC_H_ */ | 33 | #endif /* _SUN4I_CRTC_H_ */ |
diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c index c26d5888f8e1..abc7d8fe06b4 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.c +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c | |||
@@ -91,6 +91,8 @@ static int sun4i_drv_bind(struct device *dev) | |||
91 | goto free_drm; | 91 | goto free_drm; |
92 | } | 92 | } |
93 | drm->dev_private = drv; | 93 | drm->dev_private = drv; |
94 | INIT_LIST_HEAD(&drv->engine_list); | ||
95 | INIT_LIST_HEAD(&drv->tcon_list); | ||
94 | 96 | ||
95 | ret = of_reserved_mem_device_init(dev); | 97 | ret = of_reserved_mem_device_init(dev); |
96 | if (ret && ret != -ENODEV) { | 98 | if (ret && ret != -ENODEV) { |
@@ -162,6 +164,11 @@ static const struct component_master_ops sun4i_drv_master_ops = { | |||
162 | .unbind = sun4i_drv_unbind, | 164 | .unbind = sun4i_drv_unbind, |
163 | }; | 165 | }; |
164 | 166 | ||
167 | static bool sun4i_drv_node_is_connector(struct device_node *node) | ||
168 | { | ||
169 | return of_device_is_compatible(node, "hdmi-connector"); | ||
170 | } | ||
171 | |||
165 | static bool sun4i_drv_node_is_frontend(struct device_node *node) | 172 | static bool sun4i_drv_node_is_frontend(struct device_node *node) |
166 | { | 173 | { |
167 | return of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") || | 174 | 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) | |||
174 | return of_device_is_compatible(node, "allwinner,sun5i-a13-tcon") || | 181 | return of_device_is_compatible(node, "allwinner,sun5i-a13-tcon") || |
175 | of_device_is_compatible(node, "allwinner,sun6i-a31-tcon") || | 182 | of_device_is_compatible(node, "allwinner,sun6i-a31-tcon") || |
176 | of_device_is_compatible(node, "allwinner,sun6i-a31s-tcon") || | 183 | of_device_is_compatible(node, "allwinner,sun6i-a31s-tcon") || |
177 | of_device_is_compatible(node, "allwinner,sun8i-a33-tcon"); | 184 | of_device_is_compatible(node, "allwinner,sun8i-a33-tcon") || |
185 | of_device_is_compatible(node, "allwinner,sun8i-v3s-tcon"); | ||
178 | } | 186 | } |
179 | 187 | ||
180 | static int compare_of(struct device *dev, void *data) | 188 | static int compare_of(struct device *dev, void *data) |
@@ -202,6 +210,13 @@ static int sun4i_drv_add_endpoints(struct device *dev, | |||
202 | !of_device_is_available(node)) | 210 | !of_device_is_available(node)) |
203 | return 0; | 211 | return 0; |
204 | 212 | ||
213 | /* | ||
214 | * The connectors will be the last nodes in our pipeline, we | ||
215 | * can just bail out. | ||
216 | */ | ||
217 | if (sun4i_drv_node_is_connector(node)) | ||
218 | return 0; | ||
219 | |||
205 | if (!sun4i_drv_node_is_frontend(node)) { | 220 | if (!sun4i_drv_node_is_frontend(node)) { |
206 | /* Add current component */ | 221 | /* Add current component */ |
207 | DRM_DEBUG_DRIVER("Adding component %s\n", | 222 | DRM_DEBUG_DRIVER("Adding component %s\n", |
@@ -288,10 +303,12 @@ static int sun4i_drv_remove(struct platform_device *pdev) | |||
288 | } | 303 | } |
289 | 304 | ||
290 | static const struct of_device_id sun4i_drv_of_table[] = { | 305 | static const struct of_device_id sun4i_drv_of_table[] = { |
306 | { .compatible = "allwinner,sun5i-a10s-display-engine" }, | ||
291 | { .compatible = "allwinner,sun5i-a13-display-engine" }, | 307 | { .compatible = "allwinner,sun5i-a13-display-engine" }, |
292 | { .compatible = "allwinner,sun6i-a31-display-engine" }, | 308 | { .compatible = "allwinner,sun6i-a31-display-engine" }, |
293 | { .compatible = "allwinner,sun6i-a31s-display-engine" }, | 309 | { .compatible = "allwinner,sun6i-a31s-display-engine" }, |
294 | { .compatible = "allwinner,sun8i-a33-display-engine" }, | 310 | { .compatible = "allwinner,sun8i-a33-display-engine" }, |
311 | { .compatible = "allwinner,sun8i-v3s-display-engine" }, | ||
295 | { } | 312 | { } |
296 | }; | 313 | }; |
297 | MODULE_DEVICE_TABLE(of, sun4i_drv_of_table); | 314 | MODULE_DEVICE_TABLE(of, sun4i_drv_of_table); |
diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.h b/drivers/gpu/drm/sun4i/sun4i_drv.h index 5df50126ff52..a960c89270cc 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.h +++ b/drivers/gpu/drm/sun4i/sun4i_drv.h | |||
@@ -14,11 +14,12 @@ | |||
14 | #define _SUN4I_DRV_H_ | 14 | #define _SUN4I_DRV_H_ |
15 | 15 | ||
16 | #include <linux/clk.h> | 16 | #include <linux/clk.h> |
17 | #include <linux/list.h> | ||
17 | #include <linux/regmap.h> | 18 | #include <linux/regmap.h> |
18 | 19 | ||
19 | struct sun4i_drv { | 20 | struct sun4i_drv { |
20 | struct sun4i_backend *backend; | 21 | struct list_head engine_list; |
21 | struct sun4i_tcon *tcon; | 22 | struct list_head tcon_list; |
22 | 23 | ||
23 | struct drm_fbdev_cma *fbdev; | 24 | struct drm_fbdev_cma *fbdev; |
24 | }; | 25 | }; |
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h new file mode 100644 index 000000000000..2f2f2ff1ea63 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h | |||
@@ -0,0 +1,157 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2016 Maxime Ripard | ||
3 | * | ||
4 | * Maxime Ripard <maxime.ripard@free-electrons.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or | ||
7 | * modify it under the terms of the GNU General Public License as | ||
8 | * published by the Free Software Foundation; either version 2 of | ||
9 | * the License, or (at your option) any later version. | ||
10 | */ | ||
11 | |||
12 | #ifndef _SUN4I_HDMI_H_ | ||
13 | #define _SUN4I_HDMI_H_ | ||
14 | |||
15 | #include <drm/drm_connector.h> | ||
16 | #include <drm/drm_encoder.h> | ||
17 | |||
18 | #define SUN4I_HDMI_CTRL_REG 0x004 | ||
19 | #define SUN4I_HDMI_CTRL_ENABLE BIT(31) | ||
20 | |||
21 | #define SUN4I_HDMI_IRQ_REG 0x008 | ||
22 | #define SUN4I_HDMI_IRQ_STA_MASK 0x73 | ||
23 | #define SUN4I_HDMI_IRQ_STA_FIFO_OF BIT(1) | ||
24 | #define SUN4I_HDMI_IRQ_STA_FIFO_UF BIT(0) | ||
25 | |||
26 | #define SUN4I_HDMI_HPD_REG 0x00c | ||
27 | #define SUN4I_HDMI_HPD_HIGH BIT(0) | ||
28 | |||
29 | #define SUN4I_HDMI_VID_CTRL_REG 0x010 | ||
30 | #define SUN4I_HDMI_VID_CTRL_ENABLE BIT(31) | ||
31 | #define SUN4I_HDMI_VID_CTRL_HDMI_MODE BIT(30) | ||
32 | |||
33 | #define SUN4I_HDMI_VID_TIMING_ACT_REG 0x014 | ||
34 | #define SUN4I_HDMI_VID_TIMING_BP_REG 0x018 | ||
35 | #define SUN4I_HDMI_VID_TIMING_FP_REG 0x01c | ||
36 | #define SUN4I_HDMI_VID_TIMING_SPW_REG 0x020 | ||
37 | |||
38 | #define SUN4I_HDMI_VID_TIMING_X(x) ((((x) - 1) & GENMASK(11, 0))) | ||
39 | #define SUN4I_HDMI_VID_TIMING_Y(y) ((((y) - 1) & GENMASK(11, 0)) << 16) | ||
40 | |||
41 | #define SUN4I_HDMI_VID_TIMING_POL_REG 0x024 | ||
42 | #define SUN4I_HDMI_VID_TIMING_POL_TX_CLK (0x3e0 << 16) | ||
43 | #define SUN4I_HDMI_VID_TIMING_POL_VSYNC BIT(1) | ||
44 | #define SUN4I_HDMI_VID_TIMING_POL_HSYNC BIT(0) | ||
45 | |||
46 | #define SUN4I_HDMI_AVI_INFOFRAME_REG(n) (0x080 + (n)) | ||
47 | |||
48 | #define SUN4I_HDMI_PAD_CTRL0_REG 0x200 | ||
49 | #define SUN4I_HDMI_PAD_CTRL0_BIASEN BIT(31) | ||
50 | #define SUN4I_HDMI_PAD_CTRL0_LDOCEN BIT(30) | ||
51 | #define SUN4I_HDMI_PAD_CTRL0_LDODEN BIT(29) | ||
52 | #define SUN4I_HDMI_PAD_CTRL0_PWENC BIT(28) | ||
53 | #define SUN4I_HDMI_PAD_CTRL0_PWEND BIT(27) | ||
54 | #define SUN4I_HDMI_PAD_CTRL0_PWENG BIT(26) | ||
55 | #define SUN4I_HDMI_PAD_CTRL0_CKEN BIT(25) | ||
56 | #define SUN4I_HDMI_PAD_CTRL0_TXEN BIT(23) | ||
57 | |||
58 | #define SUN4I_HDMI_PAD_CTRL1_REG 0x204 | ||
59 | #define SUN4I_HDMI_PAD_CTRL1_AMP_OPT BIT(23) | ||
60 | #define SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT BIT(22) | ||
61 | #define SUN4I_HDMI_PAD_CTRL1_EMP_OPT BIT(20) | ||
62 | #define SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT BIT(19) | ||
63 | #define SUN4I_HDMI_PAD_CTRL1_REG_DEN BIT(15) | ||
64 | #define SUN4I_HDMI_PAD_CTRL1_REG_DENCK BIT(14) | ||
65 | #define SUN4I_HDMI_PAD_CTRL1_REG_EMP(n) (((n) & 7) << 10) | ||
66 | #define SUN4I_HDMI_PAD_CTRL1_HALVE_CLK BIT(6) | ||
67 | #define SUN4I_HDMI_PAD_CTRL1_REG_AMP(n) (((n) & 7) << 3) | ||
68 | |||
69 | #define SUN4I_HDMI_PLL_CTRL_REG 0x208 | ||
70 | #define SUN4I_HDMI_PLL_CTRL_PLL_EN BIT(31) | ||
71 | #define SUN4I_HDMI_PLL_CTRL_BWS BIT(30) | ||
72 | #define SUN4I_HDMI_PLL_CTRL_HV_IS_33 BIT(29) | ||
73 | #define SUN4I_HDMI_PLL_CTRL_LDO1_EN BIT(28) | ||
74 | #define SUN4I_HDMI_PLL_CTRL_LDO2_EN BIT(27) | ||
75 | #define SUN4I_HDMI_PLL_CTRL_SDIV2 BIT(25) | ||
76 | #define SUN4I_HDMI_PLL_CTRL_VCO_GAIN(n) (((n) & 7) << 20) | ||
77 | #define SUN4I_HDMI_PLL_CTRL_S(n) (((n) & 7) << 17) | ||
78 | #define SUN4I_HDMI_PLL_CTRL_CP_S(n) (((n) & 0x1f) << 12) | ||
79 | #define SUN4I_HDMI_PLL_CTRL_CS(n) (((n) & 0xf) << 8) | ||
80 | #define SUN4I_HDMI_PLL_CTRL_DIV(n) (((n) & 0xf) << 4) | ||
81 | #define SUN4I_HDMI_PLL_CTRL_DIV_MASK GENMASK(7, 4) | ||
82 | #define SUN4I_HDMI_PLL_CTRL_VCO_S(n) ((n) & 0xf) | ||
83 | |||
84 | #define SUN4I_HDMI_PLL_DBG0_REG 0x20c | ||
85 | #define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(n) (((n) & 1) << 21) | ||
86 | #define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK BIT(21) | ||
87 | #define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT 21 | ||
88 | |||
89 | #define SUN4I_HDMI_PKT_CTRL_REG(n) (0x2f0 + (4 * (n))) | ||
90 | #define SUN4I_HDMI_PKT_CTRL_TYPE(n, t) ((t) << (((n) % 4) * 4)) | ||
91 | |||
92 | #define SUN4I_HDMI_UNKNOWN_REG 0x300 | ||
93 | #define SUN4I_HDMI_UNKNOWN_INPUT_SYNC BIT(27) | ||
94 | |||
95 | #define SUN4I_HDMI_DDC_CTRL_REG 0x500 | ||
96 | #define SUN4I_HDMI_DDC_CTRL_ENABLE BIT(31) | ||
97 | #define SUN4I_HDMI_DDC_CTRL_START_CMD BIT(30) | ||
98 | #define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK BIT(8) | ||
99 | #define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ (0 << 8) | ||
100 | #define SUN4I_HDMI_DDC_CTRL_RESET BIT(0) | ||
101 | |||
102 | #define SUN4I_HDMI_DDC_ADDR_REG 0x504 | ||
103 | #define SUN4I_HDMI_DDC_ADDR_SEGMENT(seg) (((seg) & 0xff) << 24) | ||
104 | #define SUN4I_HDMI_DDC_ADDR_EDDC(addr) (((addr) & 0xff) << 16) | ||
105 | #define SUN4I_HDMI_DDC_ADDR_OFFSET(off) (((off) & 0xff) << 8) | ||
106 | #define SUN4I_HDMI_DDC_ADDR_SLAVE(addr) ((addr) & 0xff) | ||
107 | |||
108 | #define SUN4I_HDMI_DDC_FIFO_CTRL_REG 0x510 | ||
109 | #define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(31) | ||
110 | |||
111 | #define SUN4I_HDMI_DDC_FIFO_DATA_REG 0x518 | ||
112 | #define SUN4I_HDMI_DDC_BYTE_COUNT_REG 0x51c | ||
113 | |||
114 | #define SUN4I_HDMI_DDC_CMD_REG 0x520 | ||
115 | #define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ 6 | ||
116 | |||
117 | #define SUN4I_HDMI_DDC_CLK_REG 0x528 | ||
118 | #define SUN4I_HDMI_DDC_CLK_M(m) (((m) & 0x7) << 3) | ||
119 | #define SUN4I_HDMI_DDC_CLK_N(n) ((n) & 0x7) | ||
120 | |||
121 | #define SUN4I_HDMI_DDC_LINE_CTRL_REG 0x540 | ||
122 | #define SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE BIT(9) | ||
123 | #define SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE BIT(8) | ||
124 | |||
125 | #define SUN4I_HDMI_DDC_FIFO_SIZE 16 | ||
126 | |||
127 | enum sun4i_hdmi_pkt_type { | ||
128 | SUN4I_HDMI_PKT_AVI = 2, | ||
129 | SUN4I_HDMI_PKT_END = 15, | ||
130 | }; | ||
131 | |||
132 | struct sun4i_hdmi { | ||
133 | struct drm_connector connector; | ||
134 | struct drm_encoder encoder; | ||
135 | struct device *dev; | ||
136 | |||
137 | void __iomem *base; | ||
138 | |||
139 | /* Parent clocks */ | ||
140 | struct clk *bus_clk; | ||
141 | struct clk *mod_clk; | ||
142 | struct clk *pll0_clk; | ||
143 | struct clk *pll1_clk; | ||
144 | |||
145 | /* And the clocks we create */ | ||
146 | struct clk *ddc_clk; | ||
147 | struct clk *tmds_clk; | ||
148 | |||
149 | struct sun4i_drv *drv; | ||
150 | |||
151 | bool hdmi_monitor; | ||
152 | }; | ||
153 | |||
154 | int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk); | ||
155 | int sun4i_tmds_create(struct sun4i_hdmi *hdmi); | ||
156 | |||
157 | #endif /* _SUN4I_HDMI_H_ */ | ||
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c new file mode 100644 index 000000000000..4692e8c345ed --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c | |||
@@ -0,0 +1,127 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2016 Free Electrons | ||
3 | * Copyright (C) 2016 NextThing Co | ||
4 | * | ||
5 | * Maxime Ripard <maxime.ripard@free-electrons.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU General Public License as | ||
9 | * published by the Free Software Foundation; either version 2 of | ||
10 | * the License, or (at your option) any later version. | ||
11 | */ | ||
12 | |||
13 | #include <linux/clk-provider.h> | ||
14 | |||
15 | #include "sun4i_tcon.h" | ||
16 | #include "sun4i_hdmi.h" | ||
17 | |||
18 | struct sun4i_ddc { | ||
19 | struct clk_hw hw; | ||
20 | struct sun4i_hdmi *hdmi; | ||
21 | }; | ||
22 | |||
23 | static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw) | ||
24 | { | ||
25 | return container_of(hw, struct sun4i_ddc, hw); | ||
26 | } | ||
27 | |||
28 | static unsigned long sun4i_ddc_calc_divider(unsigned long rate, | ||
29 | unsigned long parent_rate, | ||
30 | u8 *m, u8 *n) | ||
31 | { | ||
32 | unsigned long best_rate = 0; | ||
33 | u8 best_m = 0, best_n = 0, _m, _n; | ||
34 | |||
35 | for (_m = 0; _m < 8; _m++) { | ||
36 | for (_n = 0; _n < 8; _n++) { | ||
37 | unsigned long tmp_rate; | ||
38 | |||
39 | tmp_rate = (((parent_rate / 2) / 10) >> _n) / (_m + 1); | ||
40 | |||
41 | if (tmp_rate > rate) | ||
42 | continue; | ||
43 | |||
44 | if (abs(rate - tmp_rate) < abs(rate - best_rate)) { | ||
45 | best_rate = tmp_rate; | ||
46 | best_m = _m; | ||
47 | best_n = _n; | ||
48 | } | ||
49 | } | ||
50 | } | ||
51 | |||
52 | if (m && n) { | ||
53 | *m = best_m; | ||
54 | *n = best_n; | ||
55 | } | ||
56 | |||
57 | return best_rate; | ||
58 | } | ||
59 | |||
60 | static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate, | ||
61 | unsigned long *prate) | ||
62 | { | ||
63 | return sun4i_ddc_calc_divider(rate, *prate, NULL, NULL); | ||
64 | } | ||
65 | |||
66 | static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw, | ||
67 | unsigned long parent_rate) | ||
68 | { | ||
69 | struct sun4i_ddc *ddc = hw_to_ddc(hw); | ||
70 | u32 reg; | ||
71 | u8 m, n; | ||
72 | |||
73 | reg = readl(ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG); | ||
74 | m = (reg >> 3) & 0x7; | ||
75 | n = reg & 0x7; | ||
76 | |||
77 | return (((parent_rate / 2) / 10) >> n) / (m + 1); | ||
78 | } | ||
79 | |||
80 | static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate, | ||
81 | unsigned long parent_rate) | ||
82 | { | ||
83 | struct sun4i_ddc *ddc = hw_to_ddc(hw); | ||
84 | u8 div_m, div_n; | ||
85 | |||
86 | sun4i_ddc_calc_divider(rate, parent_rate, &div_m, &div_n); | ||
87 | |||
88 | writel(SUN4I_HDMI_DDC_CLK_M(div_m) | SUN4I_HDMI_DDC_CLK_N(div_n), | ||
89 | ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG); | ||
90 | |||
91 | return 0; | ||
92 | } | ||
93 | |||
94 | static const struct clk_ops sun4i_ddc_ops = { | ||
95 | .recalc_rate = sun4i_ddc_recalc_rate, | ||
96 | .round_rate = sun4i_ddc_round_rate, | ||
97 | .set_rate = sun4i_ddc_set_rate, | ||
98 | }; | ||
99 | |||
100 | int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent) | ||
101 | { | ||
102 | struct clk_init_data init; | ||
103 | struct sun4i_ddc *ddc; | ||
104 | const char *parent_name; | ||
105 | |||
106 | parent_name = __clk_get_name(parent); | ||
107 | if (!parent_name) | ||
108 | return -ENODEV; | ||
109 | |||
110 | ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL); | ||
111 | if (!ddc) | ||
112 | return -ENOMEM; | ||
113 | |||
114 | init.name = "hdmi-ddc"; | ||
115 | init.ops = &sun4i_ddc_ops; | ||
116 | init.parent_names = &parent_name; | ||
117 | init.num_parents = 1; | ||
118 | |||
119 | ddc->hdmi = hdmi; | ||
120 | ddc->hw.init = &init; | ||
121 | |||
122 | hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw); | ||
123 | if (IS_ERR(hdmi->ddc_clk)) | ||
124 | return PTR_ERR(hdmi->ddc_clk); | ||
125 | |||
126 | return 0; | ||
127 | } | ||
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c new file mode 100644 index 000000000000..d3398f6250ef --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | |||
@@ -0,0 +1,501 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2016 Maxime Ripard | ||
3 | * | ||
4 | * Maxime Ripard <maxime.ripard@free-electrons.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or | ||
7 | * modify it under the terms of the GNU General Public License as | ||
8 | * published by the Free Software Foundation; either version 2 of | ||
9 | * the License, or (at your option) any later version. | ||
10 | */ | ||
11 | |||
12 | #include <drm/drmP.h> | ||
13 | #include <drm/drm_atomic_helper.h> | ||
14 | #include <drm/drm_crtc_helper.h> | ||
15 | #include <drm/drm_edid.h> | ||
16 | #include <drm/drm_encoder.h> | ||
17 | #include <drm/drm_of.h> | ||
18 | #include <drm/drm_panel.h> | ||
19 | |||
20 | #include <linux/clk.h> | ||
21 | #include <linux/component.h> | ||
22 | #include <linux/iopoll.h> | ||
23 | #include <linux/platform_device.h> | ||
24 | #include <linux/pm_runtime.h> | ||
25 | |||
26 | #include "sun4i_backend.h" | ||
27 | #include "sun4i_crtc.h" | ||
28 | #include "sun4i_drv.h" | ||
29 | #include "sun4i_hdmi.h" | ||
30 | #include "sun4i_tcon.h" | ||
31 | |||
32 | #define DDC_SEGMENT_ADDR 0x30 | ||
33 | |||
34 | static inline struct sun4i_hdmi * | ||
35 | drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder) | ||
36 | { | ||
37 | return container_of(encoder, struct sun4i_hdmi, | ||
38 | encoder); | ||
39 | } | ||
40 | |||
41 | static inline struct sun4i_hdmi * | ||
42 | drm_connector_to_sun4i_hdmi(struct drm_connector *connector) | ||
43 | { | ||
44 | return container_of(connector, struct sun4i_hdmi, | ||
45 | connector); | ||
46 | } | ||
47 | |||
48 | static int sun4i_hdmi_setup_avi_infoframes(struct sun4i_hdmi *hdmi, | ||
49 | struct drm_display_mode *mode) | ||
50 | { | ||
51 | struct hdmi_avi_infoframe frame; | ||
52 | u8 buffer[17]; | ||
53 | int i, ret; | ||
54 | |||
55 | ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode); | ||
56 | if (ret < 0) { | ||
57 | DRM_ERROR("Failed to get infoframes from mode\n"); | ||
58 | return ret; | ||
59 | } | ||
60 | |||
61 | ret = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer)); | ||
62 | if (ret < 0) { | ||
63 | DRM_ERROR("Failed to pack infoframes\n"); | ||
64 | return ret; | ||
65 | } | ||
66 | |||
67 | for (i = 0; i < sizeof(buffer); i++) | ||
68 | writeb(buffer[i], hdmi->base + SUN4I_HDMI_AVI_INFOFRAME_REG(i)); | ||
69 | |||
70 | return 0; | ||
71 | } | ||
72 | |||
73 | static int sun4i_hdmi_atomic_check(struct drm_encoder *encoder, | ||
74 | struct drm_crtc_state *crtc_state, | ||
75 | struct drm_connector_state *conn_state) | ||
76 | { | ||
77 | struct drm_display_mode *mode = &crtc_state->mode; | ||
78 | |||
79 | if (mode->flags & DRM_MODE_FLAG_DBLCLK) | ||
80 | return -EINVAL; | ||
81 | |||
82 | return 0; | ||
83 | } | ||
84 | |||
85 | static void sun4i_hdmi_disable(struct drm_encoder *encoder) | ||
86 | { | ||
87 | struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder); | ||
88 | struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); | ||
89 | struct sun4i_tcon *tcon = crtc->tcon; | ||
90 | u32 val; | ||
91 | |||
92 | DRM_DEBUG_DRIVER("Disabling the HDMI Output\n"); | ||
93 | |||
94 | val = readl(hdmi->base + SUN4I_HDMI_VID_CTRL_REG); | ||
95 | val &= ~SUN4I_HDMI_VID_CTRL_ENABLE; | ||
96 | writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG); | ||
97 | |||
98 | sun4i_tcon_channel_disable(tcon, 1); | ||
99 | } | ||
100 | |||
101 | static void sun4i_hdmi_enable(struct drm_encoder *encoder) | ||
102 | { | ||
103 | struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode; | ||
104 | struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder); | ||
105 | struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); | ||
106 | struct sun4i_tcon *tcon = crtc->tcon; | ||
107 | u32 val = 0; | ||
108 | |||
109 | DRM_DEBUG_DRIVER("Enabling the HDMI Output\n"); | ||
110 | |||
111 | sun4i_tcon_channel_enable(tcon, 1); | ||
112 | |||
113 | sun4i_hdmi_setup_avi_infoframes(hdmi, mode); | ||
114 | val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI); | ||
115 | val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END); | ||
116 | writel(val, hdmi->base + SUN4I_HDMI_PKT_CTRL_REG(0)); | ||
117 | |||
118 | val = SUN4I_HDMI_VID_CTRL_ENABLE; | ||
119 | if (hdmi->hdmi_monitor) | ||
120 | val |= SUN4I_HDMI_VID_CTRL_HDMI_MODE; | ||
121 | |||
122 | writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG); | ||
123 | } | ||
124 | |||
125 | static void sun4i_hdmi_mode_set(struct drm_encoder *encoder, | ||
126 | struct drm_display_mode *mode, | ||
127 | struct drm_display_mode *adjusted_mode) | ||
128 | { | ||
129 | struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder); | ||
130 | struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); | ||
131 | struct sun4i_tcon *tcon = crtc->tcon; | ||
132 | unsigned int x, y; | ||
133 | u32 val; | ||
134 | |||
135 | sun4i_tcon1_mode_set(tcon, mode); | ||
136 | sun4i_tcon_set_mux(tcon, 1, encoder); | ||
137 | |||
138 | clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000); | ||
139 | clk_set_rate(hdmi->mod_clk, mode->crtc_clock * 1000); | ||
140 | clk_set_rate(hdmi->tmds_clk, mode->crtc_clock * 1000); | ||
141 | |||
142 | /* Set input sync enable */ | ||
143 | writel(SUN4I_HDMI_UNKNOWN_INPUT_SYNC, | ||
144 | hdmi->base + SUN4I_HDMI_UNKNOWN_REG); | ||
145 | |||
146 | /* Setup timing registers */ | ||
147 | writel(SUN4I_HDMI_VID_TIMING_X(mode->hdisplay) | | ||
148 | SUN4I_HDMI_VID_TIMING_Y(mode->vdisplay), | ||
149 | hdmi->base + SUN4I_HDMI_VID_TIMING_ACT_REG); | ||
150 | |||
151 | x = mode->htotal - mode->hsync_start; | ||
152 | y = mode->vtotal - mode->vsync_start; | ||
153 | writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y), | ||
154 | hdmi->base + SUN4I_HDMI_VID_TIMING_BP_REG); | ||
155 | |||
156 | x = mode->hsync_start - mode->hdisplay; | ||
157 | y = mode->vsync_start - mode->vdisplay; | ||
158 | writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y), | ||
159 | hdmi->base + SUN4I_HDMI_VID_TIMING_FP_REG); | ||
160 | |||
161 | x = mode->hsync_end - mode->hsync_start; | ||
162 | y = mode->vsync_end - mode->vsync_start; | ||
163 | writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y), | ||
164 | hdmi->base + SUN4I_HDMI_VID_TIMING_SPW_REG); | ||
165 | |||
166 | val = SUN4I_HDMI_VID_TIMING_POL_TX_CLK; | ||
167 | if (mode->flags & DRM_MODE_FLAG_PHSYNC) | ||
168 | val |= SUN4I_HDMI_VID_TIMING_POL_HSYNC; | ||
169 | |||
170 | if (mode->flags & DRM_MODE_FLAG_PVSYNC) | ||
171 | val |= SUN4I_HDMI_VID_TIMING_POL_VSYNC; | ||
172 | |||
173 | writel(val, hdmi->base + SUN4I_HDMI_VID_TIMING_POL_REG); | ||
174 | } | ||
175 | |||
176 | static const struct drm_encoder_helper_funcs sun4i_hdmi_helper_funcs = { | ||
177 | .atomic_check = sun4i_hdmi_atomic_check, | ||
178 | .disable = sun4i_hdmi_disable, | ||
179 | .enable = sun4i_hdmi_enable, | ||
180 | .mode_set = sun4i_hdmi_mode_set, | ||
181 | }; | ||
182 | |||
183 | static const struct drm_encoder_funcs sun4i_hdmi_funcs = { | ||
184 | .destroy = drm_encoder_cleanup, | ||
185 | }; | ||
186 | |||
187 | static int sun4i_hdmi_read_sub_block(struct sun4i_hdmi *hdmi, | ||
188 | unsigned int blk, unsigned int offset, | ||
189 | u8 *buf, unsigned int count) | ||
190 | { | ||
191 | unsigned long reg; | ||
192 | int i; | ||
193 | |||
194 | reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); | ||
195 | reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK; | ||
196 | writel(reg | SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ, | ||
197 | hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); | ||
198 | |||
199 | writel(SUN4I_HDMI_DDC_ADDR_SEGMENT(offset >> 8) | | ||
200 | SUN4I_HDMI_DDC_ADDR_EDDC(DDC_SEGMENT_ADDR << 1) | | ||
201 | SUN4I_HDMI_DDC_ADDR_OFFSET(offset) | | ||
202 | SUN4I_HDMI_DDC_ADDR_SLAVE(DDC_ADDR), | ||
203 | hdmi->base + SUN4I_HDMI_DDC_ADDR_REG); | ||
204 | |||
205 | reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG); | ||
206 | writel(reg | SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR, | ||
207 | hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG); | ||
208 | |||
209 | writel(count, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG); | ||
210 | writel(SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ, | ||
211 | hdmi->base + SUN4I_HDMI_DDC_CMD_REG); | ||
212 | |||
213 | reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); | ||
214 | writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD, | ||
215 | hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); | ||
216 | |||
217 | if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg, | ||
218 | !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD), | ||
219 | 100, 100000)) | ||
220 | return -EIO; | ||
221 | |||
222 | for (i = 0; i < count; i++) | ||
223 | buf[i] = readb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG); | ||
224 | |||
225 | return 0; | ||
226 | } | ||
227 | |||
228 | static int sun4i_hdmi_read_edid_block(void *data, u8 *buf, unsigned int blk, | ||
229 | size_t length) | ||
230 | { | ||
231 | struct sun4i_hdmi *hdmi = data; | ||
232 | int retry = 2, i; | ||
233 | |||
234 | do { | ||
235 | for (i = 0; i < length; i += SUN4I_HDMI_DDC_FIFO_SIZE) { | ||
236 | unsigned char offset = blk * EDID_LENGTH + i; | ||
237 | unsigned int count = min((unsigned int)SUN4I_HDMI_DDC_FIFO_SIZE, | ||
238 | length - i); | ||
239 | int ret; | ||
240 | |||
241 | ret = sun4i_hdmi_read_sub_block(hdmi, blk, offset, | ||
242 | buf + i, count); | ||
243 | if (ret) | ||
244 | return ret; | ||
245 | } | ||
246 | } while (!drm_edid_block_valid(buf, blk, true, NULL) && (retry--)); | ||
247 | |||
248 | return 0; | ||
249 | } | ||
250 | |||
251 | static int sun4i_hdmi_get_modes(struct drm_connector *connector) | ||
252 | { | ||
253 | struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector); | ||
254 | unsigned long reg; | ||
255 | struct edid *edid; | ||
256 | int ret; | ||
257 | |||
258 | /* Reset i2c controller */ | ||
259 | writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET, | ||
260 | hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); | ||
261 | if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg, | ||
262 | !(reg & SUN4I_HDMI_DDC_CTRL_RESET), | ||
263 | 100, 2000)) | ||
264 | return -EIO; | ||
265 | |||
266 | writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE | | ||
267 | SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE, | ||
268 | hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG); | ||
269 | |||
270 | clk_prepare_enable(hdmi->ddc_clk); | ||
271 | clk_set_rate(hdmi->ddc_clk, 100000); | ||
272 | |||
273 | edid = drm_do_get_edid(connector, sun4i_hdmi_read_edid_block, hdmi); | ||
274 | if (!edid) | ||
275 | return 0; | ||
276 | |||
277 | hdmi->hdmi_monitor = drm_detect_hdmi_monitor(edid); | ||
278 | DRM_DEBUG_DRIVER("Monitor is %s monitor\n", | ||
279 | hdmi->hdmi_monitor ? "an HDMI" : "a DVI"); | ||
280 | |||
281 | drm_mode_connector_update_edid_property(connector, edid); | ||
282 | ret = drm_add_edid_modes(connector, edid); | ||
283 | kfree(edid); | ||
284 | |||
285 | clk_disable_unprepare(hdmi->ddc_clk); | ||
286 | |||
287 | return ret; | ||
288 | } | ||
289 | |||
290 | static const struct drm_connector_helper_funcs sun4i_hdmi_connector_helper_funcs = { | ||
291 | .get_modes = sun4i_hdmi_get_modes, | ||
292 | }; | ||
293 | |||
294 | static enum drm_connector_status | ||
295 | sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force) | ||
296 | { | ||
297 | struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector); | ||
298 | unsigned long reg; | ||
299 | |||
300 | if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg, | ||
301 | reg & SUN4I_HDMI_HPD_HIGH, | ||
302 | 0, 500000)) | ||
303 | return connector_status_disconnected; | ||
304 | |||
305 | return connector_status_connected; | ||
306 | } | ||
307 | |||
308 | static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = { | ||
309 | .dpms = drm_atomic_helper_connector_dpms, | ||
310 | .detect = sun4i_hdmi_connector_detect, | ||
311 | .fill_modes = drm_helper_probe_single_connector_modes, | ||
312 | .destroy = drm_connector_cleanup, | ||
313 | .reset = drm_atomic_helper_connector_reset, | ||
314 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, | ||
315 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, | ||
316 | }; | ||
317 | |||
318 | static int sun4i_hdmi_bind(struct device *dev, struct device *master, | ||
319 | void *data) | ||
320 | { | ||
321 | struct platform_device *pdev = to_platform_device(dev); | ||
322 | struct drm_device *drm = data; | ||
323 | struct sun4i_drv *drv = drm->dev_private; | ||
324 | struct sun4i_hdmi *hdmi; | ||
325 | struct resource *res; | ||
326 | u32 reg; | ||
327 | int ret; | ||
328 | |||
329 | hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); | ||
330 | if (!hdmi) | ||
331 | return -ENOMEM; | ||
332 | dev_set_drvdata(dev, hdmi); | ||
333 | hdmi->dev = dev; | ||
334 | hdmi->drv = drv; | ||
335 | |||
336 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
337 | hdmi->base = devm_ioremap_resource(dev, res); | ||
338 | if (IS_ERR(hdmi->base)) { | ||
339 | dev_err(dev, "Couldn't map the HDMI encoder registers\n"); | ||
340 | return PTR_ERR(hdmi->base); | ||
341 | } | ||
342 | |||
343 | hdmi->bus_clk = devm_clk_get(dev, "ahb"); | ||
344 | if (IS_ERR(hdmi->bus_clk)) { | ||
345 | dev_err(dev, "Couldn't get the HDMI bus clock\n"); | ||
346 | return PTR_ERR(hdmi->bus_clk); | ||
347 | } | ||
348 | clk_prepare_enable(hdmi->bus_clk); | ||
349 | |||
350 | hdmi->mod_clk = devm_clk_get(dev, "mod"); | ||
351 | if (IS_ERR(hdmi->mod_clk)) { | ||
352 | dev_err(dev, "Couldn't get the HDMI mod clock\n"); | ||
353 | return PTR_ERR(hdmi->mod_clk); | ||
354 | } | ||
355 | clk_prepare_enable(hdmi->mod_clk); | ||
356 | |||
357 | hdmi->pll0_clk = devm_clk_get(dev, "pll-0"); | ||
358 | if (IS_ERR(hdmi->pll0_clk)) { | ||
359 | dev_err(dev, "Couldn't get the HDMI PLL 0 clock\n"); | ||
360 | return PTR_ERR(hdmi->pll0_clk); | ||
361 | } | ||
362 | |||
363 | hdmi->pll1_clk = devm_clk_get(dev, "pll-1"); | ||
364 | if (IS_ERR(hdmi->pll1_clk)) { | ||
365 | dev_err(dev, "Couldn't get the HDMI PLL 1 clock\n"); | ||
366 | return PTR_ERR(hdmi->pll1_clk); | ||
367 | } | ||
368 | |||
369 | ret = sun4i_tmds_create(hdmi); | ||
370 | if (ret) { | ||
371 | dev_err(dev, "Couldn't create the TMDS clock\n"); | ||
372 | return ret; | ||
373 | } | ||
374 | |||
375 | writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG); | ||
376 | |||
377 | writel(SUN4I_HDMI_PAD_CTRL0_TXEN | SUN4I_HDMI_PAD_CTRL0_CKEN | | ||
378 | SUN4I_HDMI_PAD_CTRL0_PWENG | SUN4I_HDMI_PAD_CTRL0_PWEND | | ||
379 | SUN4I_HDMI_PAD_CTRL0_PWENC | SUN4I_HDMI_PAD_CTRL0_LDODEN | | ||
380 | SUN4I_HDMI_PAD_CTRL0_LDOCEN | SUN4I_HDMI_PAD_CTRL0_BIASEN, | ||
381 | hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG); | ||
382 | |||
383 | /* | ||
384 | * We can't just initialize the register there, we need to | ||
385 | * protect the clock bits that have already been read out and | ||
386 | * cached by the clock framework. | ||
387 | */ | ||
388 | reg = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); | ||
389 | reg &= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK; | ||
390 | reg |= SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) | | ||
391 | SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) | | ||
392 | SUN4I_HDMI_PAD_CTRL1_REG_DENCK | | ||
393 | SUN4I_HDMI_PAD_CTRL1_REG_DEN | | ||
394 | SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT | | ||
395 | SUN4I_HDMI_PAD_CTRL1_EMP_OPT | | ||
396 | SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT | | ||
397 | SUN4I_HDMI_PAD_CTRL1_AMP_OPT; | ||
398 | writel(reg, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); | ||
399 | |||
400 | reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); | ||
401 | reg &= SUN4I_HDMI_PLL_CTRL_DIV_MASK; | ||
402 | reg |= SUN4I_HDMI_PLL_CTRL_VCO_S(8) | SUN4I_HDMI_PLL_CTRL_CS(7) | | ||
403 | SUN4I_HDMI_PLL_CTRL_CP_S(15) | SUN4I_HDMI_PLL_CTRL_S(7) | | ||
404 | SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | SUN4I_HDMI_PLL_CTRL_SDIV2 | | ||
405 | SUN4I_HDMI_PLL_CTRL_LDO2_EN | SUN4I_HDMI_PLL_CTRL_LDO1_EN | | ||
406 | SUN4I_HDMI_PLL_CTRL_HV_IS_33 | SUN4I_HDMI_PLL_CTRL_BWS | | ||
407 | SUN4I_HDMI_PLL_CTRL_PLL_EN; | ||
408 | writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); | ||
409 | |||
410 | ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk); | ||
411 | if (ret) { | ||
412 | dev_err(dev, "Couldn't create the DDC clock\n"); | ||
413 | return ret; | ||
414 | } | ||
415 | |||
416 | drm_encoder_helper_add(&hdmi->encoder, | ||
417 | &sun4i_hdmi_helper_funcs); | ||
418 | ret = drm_encoder_init(drm, | ||
419 | &hdmi->encoder, | ||
420 | &sun4i_hdmi_funcs, | ||
421 | DRM_MODE_ENCODER_TMDS, | ||
422 | NULL); | ||
423 | if (ret) { | ||
424 | dev_err(dev, "Couldn't initialise the HDMI encoder\n"); | ||
425 | return ret; | ||
426 | } | ||
427 | |||
428 | hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm, | ||
429 | dev->of_node); | ||
430 | if (!hdmi->encoder.possible_crtcs) | ||
431 | return -EPROBE_DEFER; | ||
432 | |||
433 | drm_connector_helper_add(&hdmi->connector, | ||
434 | &sun4i_hdmi_connector_helper_funcs); | ||
435 | ret = drm_connector_init(drm, &hdmi->connector, | ||
436 | &sun4i_hdmi_connector_funcs, | ||
437 | DRM_MODE_CONNECTOR_HDMIA); | ||
438 | if (ret) { | ||
439 | dev_err(dev, | ||
440 | "Couldn't initialise the HDMI connector\n"); | ||
441 | goto err_cleanup_connector; | ||
442 | } | ||
443 | |||
444 | /* There is no HPD interrupt, so we need to poll the controller */ | ||
445 | hdmi->connector.polled = DRM_CONNECTOR_POLL_CONNECT | | ||
446 | DRM_CONNECTOR_POLL_DISCONNECT; | ||
447 | |||
448 | drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder); | ||
449 | |||
450 | return 0; | ||
451 | |||
452 | err_cleanup_connector: | ||
453 | drm_encoder_cleanup(&hdmi->encoder); | ||
454 | return ret; | ||
455 | } | ||
456 | |||
457 | static void sun4i_hdmi_unbind(struct device *dev, struct device *master, | ||
458 | void *data) | ||
459 | { | ||
460 | struct sun4i_hdmi *hdmi = dev_get_drvdata(dev); | ||
461 | |||
462 | drm_connector_cleanup(&hdmi->connector); | ||
463 | drm_encoder_cleanup(&hdmi->encoder); | ||
464 | } | ||
465 | |||
466 | static const struct component_ops sun4i_hdmi_ops = { | ||
467 | .bind = sun4i_hdmi_bind, | ||
468 | .unbind = sun4i_hdmi_unbind, | ||
469 | }; | ||
470 | |||
471 | static int sun4i_hdmi_probe(struct platform_device *pdev) | ||
472 | { | ||
473 | return component_add(&pdev->dev, &sun4i_hdmi_ops); | ||
474 | } | ||
475 | |||
476 | static int sun4i_hdmi_remove(struct platform_device *pdev) | ||
477 | { | ||
478 | component_del(&pdev->dev, &sun4i_hdmi_ops); | ||
479 | |||
480 | return 0; | ||
481 | } | ||
482 | |||
483 | static const struct of_device_id sun4i_hdmi_of_table[] = { | ||
484 | { .compatible = "allwinner,sun5i-a10s-hdmi" }, | ||
485 | { } | ||
486 | }; | ||
487 | MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table); | ||
488 | |||
489 | static struct platform_driver sun4i_hdmi_driver = { | ||
490 | .probe = sun4i_hdmi_probe, | ||
491 | .remove = sun4i_hdmi_remove, | ||
492 | .driver = { | ||
493 | .name = "sun4i-hdmi", | ||
494 | .of_match_table = sun4i_hdmi_of_table, | ||
495 | }, | ||
496 | }; | ||
497 | module_platform_driver(sun4i_hdmi_driver); | ||
498 | |||
499 | MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); | ||
500 | MODULE_DESCRIPTION("Allwinner A10 HDMI Driver"); | ||
501 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c new file mode 100644 index 000000000000..5cf2527bffc8 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c | |||
@@ -0,0 +1,225 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2016 Free Electrons | ||
3 | * Copyright (C) 2016 NextThing Co | ||
4 | * | ||
5 | * Maxime Ripard <maxime.ripard@free-electrons.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU General Public License as | ||
9 | * published by the Free Software Foundation; either version 2 of | ||
10 | * the License, or (at your option) any later version. | ||
11 | */ | ||
12 | |||
13 | #include <linux/clk-provider.h> | ||
14 | |||
15 | #include "sun4i_tcon.h" | ||
16 | #include "sun4i_hdmi.h" | ||
17 | |||
18 | struct sun4i_tmds { | ||
19 | struct clk_hw hw; | ||
20 | struct sun4i_hdmi *hdmi; | ||
21 | }; | ||
22 | |||
23 | static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw) | ||
24 | { | ||
25 | return container_of(hw, struct sun4i_tmds, hw); | ||
26 | } | ||
27 | |||
28 | |||
29 | static unsigned long sun4i_tmds_calc_divider(unsigned long rate, | ||
30 | unsigned long parent_rate, | ||
31 | u8 *div, | ||
32 | bool *half) | ||
33 | { | ||
34 | unsigned long best_rate = 0; | ||
35 | u8 best_m = 0, m; | ||
36 | bool is_double; | ||
37 | |||
38 | for (m = 1; m < 16; m++) { | ||
39 | u8 d; | ||
40 | |||
41 | for (d = 1; d < 3; d++) { | ||
42 | unsigned long tmp_rate; | ||
43 | |||
44 | tmp_rate = parent_rate / m / d; | ||
45 | |||
46 | if (tmp_rate > rate) | ||
47 | continue; | ||
48 | |||
49 | if (!best_rate || | ||
50 | (rate - tmp_rate) < (rate - best_rate)) { | ||
51 | best_rate = tmp_rate; | ||
52 | best_m = m; | ||
53 | is_double = d; | ||
54 | } | ||
55 | } | ||
56 | } | ||
57 | |||
58 | if (div && half) { | ||
59 | *div = best_m; | ||
60 | *half = is_double; | ||
61 | } | ||
62 | |||
63 | return best_rate; | ||
64 | } | ||
65 | |||
66 | |||
67 | static int sun4i_tmds_determine_rate(struct clk_hw *hw, | ||
68 | struct clk_rate_request *req) | ||
69 | { | ||
70 | struct clk_hw *parent; | ||
71 | unsigned long best_parent = 0; | ||
72 | unsigned long rate = req->rate; | ||
73 | int best_div = 1, best_half = 1; | ||
74 | int i, j; | ||
75 | |||
76 | /* | ||
77 | * We only consider PLL3, since the TCON is very likely to be | ||
78 | * clocked from it, and to have the same rate than our HDMI | ||
79 | * clock, so we should not need to do anything. | ||
80 | */ | ||
81 | |||
82 | parent = clk_hw_get_parent_by_index(hw, 0); | ||
83 | if (!parent) | ||
84 | return -EINVAL; | ||
85 | |||
86 | for (i = 1; i < 3; i++) { | ||
87 | for (j = 1; j < 16; j++) { | ||
88 | unsigned long ideal = rate * i * j; | ||
89 | unsigned long rounded; | ||
90 | |||
91 | rounded = clk_hw_round_rate(parent, ideal); | ||
92 | |||
93 | if (rounded == ideal) { | ||
94 | best_parent = rounded; | ||
95 | best_half = i; | ||
96 | best_div = j; | ||
97 | goto out; | ||
98 | } | ||
99 | |||
100 | if (abs(rate - rounded / i) < | ||
101 | abs(rate - best_parent / best_div)) { | ||
102 | best_parent = rounded; | ||
103 | best_div = i; | ||
104 | } | ||
105 | } | ||
106 | } | ||
107 | |||
108 | out: | ||
109 | req->rate = best_parent / best_half / best_div; | ||
110 | req->best_parent_rate = best_parent; | ||
111 | req->best_parent_hw = parent; | ||
112 | |||
113 | return 0; | ||
114 | } | ||
115 | |||
116 | static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw, | ||
117 | unsigned long parent_rate) | ||
118 | { | ||
119 | struct sun4i_tmds *tmds = hw_to_tmds(hw); | ||
120 | u32 reg; | ||
121 | |||
122 | reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); | ||
123 | if (reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK) | ||
124 | parent_rate /= 2; | ||
125 | |||
126 | reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); | ||
127 | reg = (reg >> 4) & 0xf; | ||
128 | if (!reg) | ||
129 | reg = 1; | ||
130 | |||
131 | return parent_rate / reg; | ||
132 | } | ||
133 | |||
134 | static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate, | ||
135 | unsigned long parent_rate) | ||
136 | { | ||
137 | struct sun4i_tmds *tmds = hw_to_tmds(hw); | ||
138 | bool half; | ||
139 | u32 reg; | ||
140 | u8 div; | ||
141 | |||
142 | sun4i_tmds_calc_divider(rate, parent_rate, &div, &half); | ||
143 | |||
144 | reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); | ||
145 | reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK; | ||
146 | if (half) | ||
147 | reg |= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK; | ||
148 | writel(reg, tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); | ||
149 | |||
150 | reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); | ||
151 | reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK; | ||
152 | writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div), | ||
153 | tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); | ||
154 | |||
155 | return 0; | ||
156 | } | ||
157 | |||
158 | static u8 sun4i_tmds_get_parent(struct clk_hw *hw) | ||
159 | { | ||
160 | struct sun4i_tmds *tmds = hw_to_tmds(hw); | ||
161 | u32 reg; | ||
162 | |||
163 | reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG); | ||
164 | return ((reg & SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) >> | ||
165 | SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT); | ||
166 | } | ||
167 | |||
168 | static int sun4i_tmds_set_parent(struct clk_hw *hw, u8 index) | ||
169 | { | ||
170 | struct sun4i_tmds *tmds = hw_to_tmds(hw); | ||
171 | u32 reg; | ||
172 | |||
173 | if (index > 1) | ||
174 | return -EINVAL; | ||
175 | |||
176 | reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG); | ||
177 | reg &= ~SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK; | ||
178 | writel(reg | SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(index), | ||
179 | tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG); | ||
180 | |||
181 | return 0; | ||
182 | } | ||
183 | |||
184 | static const struct clk_ops sun4i_tmds_ops = { | ||
185 | .determine_rate = sun4i_tmds_determine_rate, | ||
186 | .recalc_rate = sun4i_tmds_recalc_rate, | ||
187 | .set_rate = sun4i_tmds_set_rate, | ||
188 | |||
189 | .get_parent = sun4i_tmds_get_parent, | ||
190 | .set_parent = sun4i_tmds_set_parent, | ||
191 | }; | ||
192 | |||
193 | int sun4i_tmds_create(struct sun4i_hdmi *hdmi) | ||
194 | { | ||
195 | struct clk_init_data init; | ||
196 | struct sun4i_tmds *tmds; | ||
197 | const char *parents[2]; | ||
198 | |||
199 | parents[0] = __clk_get_name(hdmi->pll0_clk); | ||
200 | if (!parents[0]) | ||
201 | return -ENODEV; | ||
202 | |||
203 | parents[1] = __clk_get_name(hdmi->pll1_clk); | ||
204 | if (!parents[1]) | ||
205 | return -ENODEV; | ||
206 | |||
207 | tmds = devm_kzalloc(hdmi->dev, sizeof(*tmds), GFP_KERNEL); | ||
208 | if (!tmds) | ||
209 | return -ENOMEM; | ||
210 | |||
211 | init.name = "hdmi-tmds"; | ||
212 | init.ops = &sun4i_tmds_ops; | ||
213 | init.parent_names = parents; | ||
214 | init.num_parents = 2; | ||
215 | init.flags = CLK_SET_RATE_PARENT; | ||
216 | |||
217 | tmds->hdmi = hdmi; | ||
218 | tmds->hw.init = &init; | ||
219 | |||
220 | hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw); | ||
221 | if (IS_ERR(hdmi->tmds_clk)) | ||
222 | return PTR_ERR(hdmi->tmds_clk); | ||
223 | |||
224 | return 0; | ||
225 | } | ||
diff --git a/drivers/gpu/drm/sun4i/sun4i_layer.c b/drivers/gpu/drm/sun4i/sun4i_layer.c index f26bde5b9117..ead4f9d4c1ee 100644 --- a/drivers/gpu/drm/sun4i/sun4i_layer.c +++ b/drivers/gpu/drm/sun4i/sun4i_layer.c | |||
@@ -11,12 +11,12 @@ | |||
11 | */ | 11 | */ |
12 | 12 | ||
13 | #include <drm/drm_atomic_helper.h> | 13 | #include <drm/drm_atomic_helper.h> |
14 | #include <drm/drm_crtc.h> | ||
15 | #include <drm/drm_plane_helper.h> | 14 | #include <drm/drm_plane_helper.h> |
16 | #include <drm/drmP.h> | 15 | #include <drm/drmP.h> |
17 | 16 | ||
18 | #include "sun4i_backend.h" | 17 | #include "sun4i_backend.h" |
19 | #include "sun4i_layer.h" | 18 | #include "sun4i_layer.h" |
19 | #include "sunxi_engine.h" | ||
20 | 20 | ||
21 | struct sun4i_plane_desc { | 21 | struct sun4i_plane_desc { |
22 | enum drm_plane_type type; | 22 | enum drm_plane_type type; |
@@ -128,15 +128,16 @@ static struct sun4i_layer *sun4i_layer_init_one(struct drm_device *drm, | |||
128 | return layer; | 128 | return layer; |
129 | } | 129 | } |
130 | 130 | ||
131 | struct sun4i_layer **sun4i_layers_init(struct drm_device *drm, | 131 | struct drm_plane **sun4i_layers_init(struct drm_device *drm, |
132 | struct sun4i_backend *backend) | 132 | struct sunxi_engine *engine) |
133 | { | 133 | { |
134 | struct sun4i_layer **layers; | 134 | struct drm_plane **planes; |
135 | struct sun4i_backend *backend = engine_to_sun4i_backend(engine); | ||
135 | int i; | 136 | int i; |
136 | 137 | ||
137 | layers = devm_kcalloc(drm->dev, ARRAY_SIZE(sun4i_backend_planes) + 1, | 138 | planes = devm_kcalloc(drm->dev, ARRAY_SIZE(sun4i_backend_planes) + 1, |
138 | sizeof(*layers), GFP_KERNEL); | 139 | sizeof(*planes), GFP_KERNEL); |
139 | if (!layers) | 140 | if (!planes) |
140 | return ERR_PTR(-ENOMEM); | 141 | return ERR_PTR(-ENOMEM); |
141 | 142 | ||
142 | /* | 143 | /* |
@@ -173,13 +174,13 @@ struct sun4i_layer **sun4i_layers_init(struct drm_device *drm, | |||
173 | 174 | ||
174 | DRM_DEBUG_DRIVER("Assigning %s plane to pipe %d\n", | 175 | DRM_DEBUG_DRIVER("Assigning %s plane to pipe %d\n", |
175 | i ? "overlay" : "primary", plane->pipe); | 176 | i ? "overlay" : "primary", plane->pipe); |
176 | regmap_update_bits(backend->regs, SUN4I_BACKEND_ATTCTL_REG0(i), | 177 | regmap_update_bits(engine->regs, SUN4I_BACKEND_ATTCTL_REG0(i), |
177 | SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL_MASK, | 178 | SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL_MASK, |
178 | SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL(plane->pipe)); | 179 | SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL(plane->pipe)); |
179 | 180 | ||
180 | layer->id = i; | 181 | layer->id = i; |
181 | layers[i] = layer; | 182 | planes[i] = &layer->plane; |
182 | }; | 183 | }; |
183 | 184 | ||
184 | return layers; | 185 | return planes; |
185 | } | 186 | } |
diff --git a/drivers/gpu/drm/sun4i/sun4i_layer.h b/drivers/gpu/drm/sun4i/sun4i_layer.h index 4be1f0919df2..4e84f438b346 100644 --- a/drivers/gpu/drm/sun4i/sun4i_layer.h +++ b/drivers/gpu/drm/sun4i/sun4i_layer.h | |||
@@ -13,6 +13,8 @@ | |||
13 | #ifndef _SUN4I_LAYER_H_ | 13 | #ifndef _SUN4I_LAYER_H_ |
14 | #define _SUN4I_LAYER_H_ | 14 | #define _SUN4I_LAYER_H_ |
15 | 15 | ||
16 | struct sunxi_engine; | ||
17 | |||
16 | struct sun4i_layer { | 18 | struct sun4i_layer { |
17 | struct drm_plane plane; | 19 | struct drm_plane plane; |
18 | struct sun4i_drv *drv; | 20 | struct sun4i_drv *drv; |
@@ -26,7 +28,7 @@ plane_to_sun4i_layer(struct drm_plane *plane) | |||
26 | return container_of(plane, struct sun4i_layer, plane); | 28 | return container_of(plane, struct sun4i_layer, plane); |
27 | } | 29 | } |
28 | 30 | ||
29 | struct sun4i_layer **sun4i_layers_init(struct drm_device *drm, | 31 | struct drm_plane **sun4i_layers_init(struct drm_device *drm, |
30 | struct sun4i_backend *backend); | 32 | struct sunxi_engine *engine); |
31 | 33 | ||
32 | #endif /* _SUN4I_LAYER_H_ */ | 34 | #endif /* _SUN4I_LAYER_H_ */ |
diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c index 67f0b91a99de..422b191faa77 100644 --- a/drivers/gpu/drm/sun4i/sun4i_rgb.c +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c | |||
@@ -175,8 +175,7 @@ static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder, | |||
175 | struct sun4i_tcon *tcon = rgb->tcon; | 175 | struct sun4i_tcon *tcon = rgb->tcon; |
176 | 176 | ||
177 | sun4i_tcon0_mode_set(tcon, mode); | 177 | sun4i_tcon0_mode_set(tcon, mode); |
178 | 178 | sun4i_tcon_set_mux(tcon, 0, encoder); | |
179 | clk_set_rate(tcon->dclk, mode->crtc_clock * 1000); | ||
180 | 179 | ||
181 | /* FIXME: This seems to be board specific */ | 180 | /* FIXME: This seems to be board specific */ |
182 | clk_set_phase(tcon->dclk, 120); | 181 | clk_set_phase(tcon->dclk, 120); |
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 9a83a85529ac..d9791292553e 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c | |||
@@ -30,6 +30,7 @@ | |||
30 | #include "sun4i_drv.h" | 30 | #include "sun4i_drv.h" |
31 | #include "sun4i_rgb.h" | 31 | #include "sun4i_rgb.h" |
32 | #include "sun4i_tcon.h" | 32 | #include "sun4i_tcon.h" |
33 | #include "sunxi_engine.h" | ||
33 | 34 | ||
34 | void sun4i_tcon_disable(struct sun4i_tcon *tcon) | 35 | void sun4i_tcon_disable(struct sun4i_tcon *tcon) |
35 | { | 36 | { |
@@ -54,6 +55,8 @@ EXPORT_SYMBOL(sun4i_tcon_enable); | |||
54 | 55 | ||
55 | void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel) | 56 | void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel) |
56 | { | 57 | { |
58 | DRM_DEBUG_DRIVER("Disabling TCON channel %d\n", channel); | ||
59 | |||
57 | /* Disable the TCON's channel */ | 60 | /* Disable the TCON's channel */ |
58 | if (channel == 0) { | 61 | if (channel == 0) { |
59 | regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, | 62 | regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, |
@@ -71,6 +74,8 @@ EXPORT_SYMBOL(sun4i_tcon_channel_disable); | |||
71 | 74 | ||
72 | void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel) | 75 | void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel) |
73 | { | 76 | { |
77 | DRM_DEBUG_DRIVER("Enabling TCON channel %d\n", channel); | ||
78 | |||
74 | /* Enable the TCON's channel */ | 79 | /* Enable the TCON's channel */ |
75 | if (channel == 0) { | 80 | if (channel == 0) { |
76 | regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, | 81 | regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, |
@@ -104,6 +109,29 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable) | |||
104 | } | 109 | } |
105 | EXPORT_SYMBOL(sun4i_tcon_enable_vblank); | 110 | EXPORT_SYMBOL(sun4i_tcon_enable_vblank); |
106 | 111 | ||
112 | void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, | ||
113 | struct drm_encoder *encoder) | ||
114 | { | ||
115 | u32 val; | ||
116 | |||
117 | if (!tcon->quirks->has_unknown_mux) | ||
118 | return; | ||
119 | |||
120 | if (channel != 1) | ||
121 | return; | ||
122 | |||
123 | if (encoder->encoder_type == DRM_MODE_ENCODER_TVDAC) | ||
124 | val = 1; | ||
125 | else | ||
126 | val = 0; | ||
127 | |||
128 | /* | ||
129 | * FIXME: Undocumented bits | ||
130 | */ | ||
131 | regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, val); | ||
132 | } | ||
133 | EXPORT_SYMBOL(sun4i_tcon_set_mux); | ||
134 | |||
107 | static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode, | 135 | static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode, |
108 | int channel) | 136 | int channel) |
109 | { | 137 | { |
@@ -129,6 +157,9 @@ void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, | |||
129 | u8 clk_delay; | 157 | u8 clk_delay; |
130 | u32 val = 0; | 158 | u32 val = 0; |
131 | 159 | ||
160 | /* Configure the dot clock */ | ||
161 | clk_set_rate(tcon->dclk, mode->crtc_clock * 1000); | ||
162 | |||
132 | /* Adjust clock delay */ | 163 | /* Adjust clock delay */ |
133 | clk_delay = sun4i_tcon_get_clk_delay(mode, 0); | 164 | clk_delay = sun4i_tcon_get_clk_delay(mode, 0); |
134 | regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, | 165 | regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, |
@@ -163,7 +194,7 @@ void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, | |||
163 | 194 | ||
164 | /* Set vertical display timings */ | 195 | /* Set vertical display timings */ |
165 | regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG, | 196 | regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG, |
166 | SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal) | | 197 | SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal * 2) | |
167 | SUN4I_TCON0_BASIC2_V_BACKPORCH(bp)); | 198 | SUN4I_TCON0_BASIC2_V_BACKPORCH(bp)); |
168 | 199 | ||
169 | /* Set Hsync and Vsync length */ | 200 | /* Set Hsync and Vsync length */ |
@@ -198,12 +229,15 @@ EXPORT_SYMBOL(sun4i_tcon0_mode_set); | |||
198 | void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, | 229 | void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, |
199 | struct drm_display_mode *mode) | 230 | struct drm_display_mode *mode) |
200 | { | 231 | { |
201 | unsigned int bp, hsync, vsync; | 232 | unsigned int bp, hsync, vsync, vtotal; |
202 | u8 clk_delay; | 233 | u8 clk_delay; |
203 | u32 val; | 234 | u32 val; |
204 | 235 | ||
205 | WARN_ON(!tcon->quirks->has_channel_1); | 236 | WARN_ON(!tcon->quirks->has_channel_1); |
206 | 237 | ||
238 | /* Configure the dot clock */ | ||
239 | clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000); | ||
240 | |||
207 | /* Adjust clock delay */ | 241 | /* Adjust clock delay */ |
208 | clk_delay = sun4i_tcon_get_clk_delay(mode, 1); | 242 | clk_delay = sun4i_tcon_get_clk_delay(mode, 1); |
209 | regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, | 243 | regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, |
@@ -235,19 +269,37 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, | |||
235 | SUN4I_TCON1_BASIC2_Y(mode->crtc_vdisplay)); | 269 | SUN4I_TCON1_BASIC2_Y(mode->crtc_vdisplay)); |
236 | 270 | ||
237 | /* Set horizontal display timings */ | 271 | /* Set horizontal display timings */ |
238 | bp = mode->crtc_htotal - mode->crtc_hsync_end; | 272 | bp = mode->crtc_htotal - mode->crtc_hsync_start; |
239 | DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n", | 273 | DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n", |
240 | mode->htotal, bp); | 274 | mode->htotal, bp); |
241 | regmap_write(tcon->regs, SUN4I_TCON1_BASIC3_REG, | 275 | regmap_write(tcon->regs, SUN4I_TCON1_BASIC3_REG, |
242 | SUN4I_TCON1_BASIC3_H_TOTAL(mode->crtc_htotal) | | 276 | SUN4I_TCON1_BASIC3_H_TOTAL(mode->crtc_htotal) | |
243 | SUN4I_TCON1_BASIC3_H_BACKPORCH(bp)); | 277 | SUN4I_TCON1_BASIC3_H_BACKPORCH(bp)); |
244 | 278 | ||
245 | /* Set vertical display timings */ | 279 | bp = mode->crtc_vtotal - mode->crtc_vsync_start; |
246 | bp = mode->crtc_vtotal - mode->crtc_vsync_end; | ||
247 | DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n", | 280 | DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n", |
248 | mode->vtotal, bp); | 281 | mode->crtc_vtotal, bp); |
282 | |||
283 | /* | ||
284 | * The vertical resolution needs to be doubled in all | ||
285 | * cases. We could use crtc_vtotal and always multiply by two, | ||
286 | * but that leads to a rounding error in interlace when vtotal | ||
287 | * is odd. | ||
288 | * | ||
289 | * This happens with TV's PAL for example, where vtotal will | ||
290 | * be 625, crtc_vtotal 312, and thus crtc_vtotal * 2 will be | ||
291 | * 624, which apparently confuses the hardware. | ||
292 | * | ||
293 | * To work around this, we will always use vtotal, and | ||
294 | * multiply by two only if we're not in interlace. | ||
295 | */ | ||
296 | vtotal = mode->vtotal; | ||
297 | if (!(mode->flags & DRM_MODE_FLAG_INTERLACE)) | ||
298 | vtotal = vtotal * 2; | ||
299 | |||
300 | /* Set vertical display timings */ | ||
249 | regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG, | 301 | regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG, |
250 | SUN4I_TCON1_BASIC4_V_TOTAL(mode->vtotal) | | 302 | SUN4I_TCON1_BASIC4_V_TOTAL(vtotal) | |
251 | SUN4I_TCON1_BASIC4_V_BACKPORCH(bp)); | 303 | SUN4I_TCON1_BASIC4_V_BACKPORCH(bp)); |
252 | 304 | ||
253 | /* Set Hsync and Vsync length */ | 305 | /* Set Hsync and Vsync length */ |
@@ -262,12 +314,6 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, | |||
262 | regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, | 314 | regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, |
263 | SUN4I_TCON_GCTL_IOMAP_MASK, | 315 | SUN4I_TCON_GCTL_IOMAP_MASK, |
264 | SUN4I_TCON_GCTL_IOMAP_TCON1); | 316 | SUN4I_TCON_GCTL_IOMAP_TCON1); |
265 | |||
266 | /* | ||
267 | * FIXME: Undocumented bits | ||
268 | */ | ||
269 | if (tcon->quirks->has_unknown_mux) | ||
270 | regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1); | ||
271 | } | 317 | } |
272 | EXPORT_SYMBOL(sun4i_tcon1_mode_set); | 318 | EXPORT_SYMBOL(sun4i_tcon1_mode_set); |
273 | 319 | ||
@@ -402,21 +448,79 @@ static int sun4i_tcon_init_regmap(struct device *dev, | |||
402 | return 0; | 448 | return 0; |
403 | } | 449 | } |
404 | 450 | ||
451 | /* | ||
452 | * On SoCs with the old display pipeline design (Display Engine 1.0), | ||
453 | * the TCON is always tied to just one backend. Hence we can traverse | ||
454 | * the of_graph upwards to find the backend our tcon is connected to, | ||
455 | * and take its ID as our own. | ||
456 | * | ||
457 | * We can either identify backends from their compatible strings, which | ||
458 | * means maintaining a large list of them. Or, since the backend is | ||
459 | * registered and binded before the TCON, we can just go through the | ||
460 | * list of registered backends and compare the device node. | ||
461 | * | ||
462 | * As the structures now store engines instead of backends, here this | ||
463 | * function in fact searches the corresponding engine, and the ID is | ||
464 | * requested via the get_id function of the engine. | ||
465 | */ | ||
466 | static struct sunxi_engine *sun4i_tcon_find_engine(struct sun4i_drv *drv, | ||
467 | struct device_node *node) | ||
468 | { | ||
469 | struct device_node *port, *ep, *remote; | ||
470 | struct sunxi_engine *engine; | ||
471 | |||
472 | port = of_graph_get_port_by_id(node, 0); | ||
473 | if (!port) | ||
474 | return ERR_PTR(-EINVAL); | ||
475 | |||
476 | for_each_available_child_of_node(port, ep) { | ||
477 | remote = of_graph_get_remote_port_parent(ep); | ||
478 | if (!remote) | ||
479 | continue; | ||
480 | |||
481 | /* does this node match any registered engines? */ | ||
482 | list_for_each_entry(engine, &drv->engine_list, list) { | ||
483 | if (remote == engine->node) { | ||
484 | of_node_put(remote); | ||
485 | of_node_put(port); | ||
486 | return engine; | ||
487 | } | ||
488 | } | ||
489 | |||
490 | /* keep looking through upstream ports */ | ||
491 | engine = sun4i_tcon_find_engine(drv, remote); | ||
492 | if (!IS_ERR(engine)) { | ||
493 | of_node_put(remote); | ||
494 | of_node_put(port); | ||
495 | return engine; | ||
496 | } | ||
497 | } | ||
498 | |||
499 | return ERR_PTR(-EINVAL); | ||
500 | } | ||
501 | |||
405 | static int sun4i_tcon_bind(struct device *dev, struct device *master, | 502 | static int sun4i_tcon_bind(struct device *dev, struct device *master, |
406 | void *data) | 503 | void *data) |
407 | { | 504 | { |
408 | struct drm_device *drm = data; | 505 | struct drm_device *drm = data; |
409 | struct sun4i_drv *drv = drm->dev_private; | 506 | struct sun4i_drv *drv = drm->dev_private; |
507 | struct sunxi_engine *engine; | ||
410 | struct sun4i_tcon *tcon; | 508 | struct sun4i_tcon *tcon; |
411 | int ret; | 509 | int ret; |
412 | 510 | ||
511 | engine = sun4i_tcon_find_engine(drv, dev->of_node); | ||
512 | if (IS_ERR(engine)) { | ||
513 | dev_err(dev, "Couldn't find matching engine\n"); | ||
514 | return -EPROBE_DEFER; | ||
515 | } | ||
516 | |||
413 | tcon = devm_kzalloc(dev, sizeof(*tcon), GFP_KERNEL); | 517 | tcon = devm_kzalloc(dev, sizeof(*tcon), GFP_KERNEL); |
414 | if (!tcon) | 518 | if (!tcon) |
415 | return -ENOMEM; | 519 | return -ENOMEM; |
416 | dev_set_drvdata(dev, tcon); | 520 | dev_set_drvdata(dev, tcon); |
417 | drv->tcon = tcon; | ||
418 | tcon->drm = drm; | 521 | tcon->drm = drm; |
419 | tcon->dev = dev; | 522 | tcon->dev = dev; |
523 | tcon->id = engine->id; | ||
420 | tcon->quirks = of_device_get_match_data(dev); | 524 | tcon->quirks = of_device_get_match_data(dev); |
421 | 525 | ||
422 | tcon->lcd_rst = devm_reset_control_get(dev, "lcd"); | 526 | tcon->lcd_rst = devm_reset_control_get(dev, "lcd"); |
@@ -459,7 +563,7 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, | |||
459 | goto err_free_dotclock; | 563 | goto err_free_dotclock; |
460 | } | 564 | } |
461 | 565 | ||
462 | tcon->crtc = sun4i_crtc_init(drm, drv->backend, tcon); | 566 | tcon->crtc = sun4i_crtc_init(drm, engine, tcon); |
463 | if (IS_ERR(tcon->crtc)) { | 567 | if (IS_ERR(tcon->crtc)) { |
464 | dev_err(dev, "Couldn't create our CRTC\n"); | 568 | dev_err(dev, "Couldn't create our CRTC\n"); |
465 | ret = PTR_ERR(tcon->crtc); | 569 | ret = PTR_ERR(tcon->crtc); |
@@ -470,6 +574,8 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, | |||
470 | if (ret < 0) | 574 | if (ret < 0) |
471 | goto err_free_clocks; | 575 | goto err_free_clocks; |
472 | 576 | ||
577 | list_add_tail(&tcon->list, &drv->tcon_list); | ||
578 | |||
473 | return 0; | 579 | return 0; |
474 | 580 | ||
475 | err_free_dotclock: | 581 | err_free_dotclock: |
@@ -486,6 +592,7 @@ static void sun4i_tcon_unbind(struct device *dev, struct device *master, | |||
486 | { | 592 | { |
487 | struct sun4i_tcon *tcon = dev_get_drvdata(dev); | 593 | struct sun4i_tcon *tcon = dev_get_drvdata(dev); |
488 | 594 | ||
595 | list_del(&tcon->list); | ||
489 | sun4i_dclk_free(tcon); | 596 | sun4i_dclk_free(tcon); |
490 | sun4i_tcon_free_clocks(tcon); | 597 | sun4i_tcon_free_clocks(tcon); |
491 | } | 598 | } |
@@ -533,11 +640,16 @@ static const struct sun4i_tcon_quirks sun8i_a33_quirks = { | |||
533 | /* nothing is supported */ | 640 | /* nothing is supported */ |
534 | }; | 641 | }; |
535 | 642 | ||
643 | static const struct sun4i_tcon_quirks sun8i_v3s_quirks = { | ||
644 | /* nothing is supported */ | ||
645 | }; | ||
646 | |||
536 | static const struct of_device_id sun4i_tcon_of_table[] = { | 647 | static const struct of_device_id sun4i_tcon_of_table[] = { |
537 | { .compatible = "allwinner,sun5i-a13-tcon", .data = &sun5i_a13_quirks }, | 648 | { .compatible = "allwinner,sun5i-a13-tcon", .data = &sun5i_a13_quirks }, |
538 | { .compatible = "allwinner,sun6i-a31-tcon", .data = &sun6i_a31_quirks }, | 649 | { .compatible = "allwinner,sun6i-a31-tcon", .data = &sun6i_a31_quirks }, |
539 | { .compatible = "allwinner,sun6i-a31s-tcon", .data = &sun6i_a31s_quirks }, | 650 | { .compatible = "allwinner,sun6i-a31s-tcon", .data = &sun6i_a31s_quirks }, |
540 | { .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks }, | 651 | { .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks }, |
652 | { .compatible = "allwinner,sun8i-v3s-tcon", .data = &sun8i_v3s_quirks }, | ||
541 | { } | 653 | { } |
542 | }; | 654 | }; |
543 | MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table); | 655 | MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table); |
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h index f636343a935d..e3c50ecdcd04 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h | |||
@@ -17,6 +17,7 @@ | |||
17 | #include <drm/drm_crtc.h> | 17 | #include <drm/drm_crtc.h> |
18 | 18 | ||
19 | #include <linux/kernel.h> | 19 | #include <linux/kernel.h> |
20 | #include <linux/list.h> | ||
20 | #include <linux/reset.h> | 21 | #include <linux/reset.h> |
21 | 22 | ||
22 | #define SUN4I_TCON_GCTL_REG 0x0 | 23 | #define SUN4I_TCON_GCTL_REG 0x0 |
@@ -51,7 +52,7 @@ | |||
51 | #define SUN4I_TCON0_BASIC1_H_BACKPORCH(bp) (((bp) - 1) & 0xfff) | 52 | #define SUN4I_TCON0_BASIC1_H_BACKPORCH(bp) (((bp) - 1) & 0xfff) |
52 | 53 | ||
53 | #define SUN4I_TCON0_BASIC2_REG 0x50 | 54 | #define SUN4I_TCON0_BASIC2_REG 0x50 |
54 | #define SUN4I_TCON0_BASIC2_V_TOTAL(total) ((((total) * 2) & 0x1fff) << 16) | 55 | #define SUN4I_TCON0_BASIC2_V_TOTAL(total) (((total) & 0x1fff) << 16) |
55 | #define SUN4I_TCON0_BASIC2_V_BACKPORCH(bp) (((bp) - 1) & 0xfff) | 56 | #define SUN4I_TCON0_BASIC2_V_BACKPORCH(bp) (((bp) - 1) & 0xfff) |
56 | 57 | ||
57 | #define SUN4I_TCON0_BASIC3_REG 0x54 | 58 | #define SUN4I_TCON0_BASIC3_REG 0x54 |
@@ -172,6 +173,11 @@ struct sun4i_tcon { | |||
172 | 173 | ||
173 | /* Associated crtc */ | 174 | /* Associated crtc */ |
174 | struct sun4i_crtc *crtc; | 175 | struct sun4i_crtc *crtc; |
176 | |||
177 | int id; | ||
178 | |||
179 | /* TCON list management */ | ||
180 | struct list_head list; | ||
175 | }; | 181 | }; |
176 | 182 | ||
177 | struct drm_bridge *sun4i_tcon_find_bridge(struct device_node *node); | 183 | 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); | |||
190 | /* Mode Related Controls */ | 196 | /* Mode Related Controls */ |
191 | void sun4i_tcon_switch_interlace(struct sun4i_tcon *tcon, | 197 | void sun4i_tcon_switch_interlace(struct sun4i_tcon *tcon, |
192 | bool enable); | 198 | bool enable); |
199 | void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, | ||
200 | struct drm_encoder *encoder); | ||
193 | void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, | 201 | void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, |
194 | struct drm_display_mode *mode); | 202 | struct drm_display_mode *mode); |
195 | void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, | 203 | void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, |
diff --git a/drivers/gpu/drm/sun4i/sun4i_tv.c b/drivers/gpu/drm/sun4i/sun4i_tv.c index 49c49431a053..338b9e5bb2a3 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tv.c +++ b/drivers/gpu/drm/sun4i/sun4i_tv.c | |||
@@ -22,10 +22,10 @@ | |||
22 | #include <drm/drm_of.h> | 22 | #include <drm/drm_of.h> |
23 | #include <drm/drm_panel.h> | 23 | #include <drm/drm_panel.h> |
24 | 24 | ||
25 | #include "sun4i_backend.h" | ||
26 | #include "sun4i_crtc.h" | 25 | #include "sun4i_crtc.h" |
27 | #include "sun4i_drv.h" | 26 | #include "sun4i_drv.h" |
28 | #include "sun4i_tcon.h" | 27 | #include "sun4i_tcon.h" |
28 | #include "sunxi_engine.h" | ||
29 | 29 | ||
30 | #define SUN4I_TVE_EN_REG 0x000 | 30 | #define SUN4I_TVE_EN_REG 0x000 |
31 | #define SUN4I_TVE_EN_DAC_MAP_MASK GENMASK(19, 4) | 31 | #define SUN4I_TVE_EN_DAC_MAP_MASK GENMASK(19, 4) |
@@ -353,7 +353,6 @@ static void sun4i_tv_disable(struct drm_encoder *encoder) | |||
353 | struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder); | 353 | struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder); |
354 | struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); | 354 | struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); |
355 | struct sun4i_tcon *tcon = crtc->tcon; | 355 | struct sun4i_tcon *tcon = crtc->tcon; |
356 | struct sun4i_backend *backend = crtc->backend; | ||
357 | 356 | ||
358 | DRM_DEBUG_DRIVER("Disabling the TV Output\n"); | 357 | DRM_DEBUG_DRIVER("Disabling the TV Output\n"); |
359 | 358 | ||
@@ -362,7 +361,8 @@ static void sun4i_tv_disable(struct drm_encoder *encoder) | |||
362 | regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, | 361 | regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, |
363 | SUN4I_TVE_EN_ENABLE, | 362 | SUN4I_TVE_EN_ENABLE, |
364 | 0); | 363 | 0); |
365 | sun4i_backend_disable_color_correction(backend); | 364 | |
365 | sunxi_engine_disable_color_correction(crtc->engine); | ||
366 | } | 366 | } |
367 | 367 | ||
368 | static void sun4i_tv_enable(struct drm_encoder *encoder) | 368 | static void sun4i_tv_enable(struct drm_encoder *encoder) |
@@ -370,11 +370,10 @@ static void sun4i_tv_enable(struct drm_encoder *encoder) | |||
370 | struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder); | 370 | struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder); |
371 | struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); | 371 | struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); |
372 | struct sun4i_tcon *tcon = crtc->tcon; | 372 | struct sun4i_tcon *tcon = crtc->tcon; |
373 | struct sun4i_backend *backend = crtc->backend; | ||
374 | 373 | ||
375 | DRM_DEBUG_DRIVER("Enabling the TV Output\n"); | 374 | DRM_DEBUG_DRIVER("Enabling the TV Output\n"); |
376 | 375 | ||
377 | sun4i_backend_apply_color_correction(backend); | 376 | sunxi_engine_apply_color_correction(crtc->engine); |
378 | 377 | ||
379 | regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, | 378 | regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, |
380 | SUN4I_TVE_EN_ENABLE, | 379 | SUN4I_TVE_EN_ENABLE, |
@@ -393,6 +392,7 @@ static void sun4i_tv_mode_set(struct drm_encoder *encoder, | |||
393 | const struct tv_mode *tv_mode = sun4i_tv_find_tv_by_mode(mode); | 392 | const struct tv_mode *tv_mode = sun4i_tv_find_tv_by_mode(mode); |
394 | 393 | ||
395 | sun4i_tcon1_mode_set(tcon, mode); | 394 | sun4i_tcon1_mode_set(tcon, mode); |
395 | sun4i_tcon_set_mux(tcon, 1, encoder); | ||
396 | 396 | ||
397 | /* Enable and map the DAC to the output */ | 397 | /* Enable and map the DAC to the output */ |
398 | regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, | 398 | regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, |
@@ -486,8 +486,6 @@ static void sun4i_tv_mode_set(struct drm_encoder *encoder, | |||
486 | SUN4I_TVE_RESYNC_FIELD : 0)); | 486 | SUN4I_TVE_RESYNC_FIELD : 0)); |
487 | 487 | ||
488 | regmap_write(tv->regs, SUN4I_TVE_SLAVE_REG, 0); | 488 | regmap_write(tv->regs, SUN4I_TVE_SLAVE_REG, 0); |
489 | |||
490 | clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000); | ||
491 | } | 489 | } |
492 | 490 | ||
493 | static struct drm_encoder_helper_funcs sun4i_tv_helper_funcs = { | 491 | static struct drm_encoder_helper_funcs sun4i_tv_helper_funcs = { |
diff --git a/drivers/gpu/drm/sun4i/sun8i_layer.c b/drivers/gpu/drm/sun4i/sun8i_layer.c new file mode 100644 index 000000000000..e627eeece658 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_layer.c | |||
@@ -0,0 +1,134 @@ | |||
1 | /* | ||
2 | * Copyright (C) Icenowy Zheng <icenowy@aosc.io> | ||
3 | * | ||
4 | * Based on sun4i_layer.h, which is: | ||
5 | * Copyright (C) 2015 Free Electrons | ||
6 | * Copyright (C) 2015 NextThing Co | ||
7 | * | ||
8 | * Maxime Ripard <maxime.ripard@free-electrons.com> | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or | ||
11 | * modify it under the terms of the GNU General Public License as | ||
12 | * published by the Free Software Foundation; either version 2 of | ||
13 | * the License, or (at your option) any later version. | ||
14 | */ | ||
15 | |||
16 | #include <drm/drm_atomic_helper.h> | ||
17 | #include <drm/drm_plane_helper.h> | ||
18 | #include <drm/drmP.h> | ||
19 | |||
20 | #include "sun8i_layer.h" | ||
21 | #include "sun8i_mixer.h" | ||
22 | |||
23 | struct sun8i_plane_desc { | ||
24 | enum drm_plane_type type; | ||
25 | const uint32_t *formats; | ||
26 | uint32_t nformats; | ||
27 | }; | ||
28 | |||
29 | static void sun8i_mixer_layer_atomic_disable(struct drm_plane *plane, | ||
30 | struct drm_plane_state *old_state) | ||
31 | { | ||
32 | struct sun8i_layer *layer = plane_to_sun8i_layer(plane); | ||
33 | struct sun8i_mixer *mixer = layer->mixer; | ||
34 | |||
35 | sun8i_mixer_layer_enable(mixer, layer->id, false); | ||
36 | } | ||
37 | |||
38 | static void sun8i_mixer_layer_atomic_update(struct drm_plane *plane, | ||
39 | struct drm_plane_state *old_state) | ||
40 | { | ||
41 | struct sun8i_layer *layer = plane_to_sun8i_layer(plane); | ||
42 | struct sun8i_mixer *mixer = layer->mixer; | ||
43 | |||
44 | sun8i_mixer_update_layer_coord(mixer, layer->id, plane); | ||
45 | sun8i_mixer_update_layer_formats(mixer, layer->id, plane); | ||
46 | sun8i_mixer_update_layer_buffer(mixer, layer->id, plane); | ||
47 | sun8i_mixer_layer_enable(mixer, layer->id, true); | ||
48 | } | ||
49 | |||
50 | static struct drm_plane_helper_funcs sun8i_mixer_layer_helper_funcs = { | ||
51 | .atomic_disable = sun8i_mixer_layer_atomic_disable, | ||
52 | .atomic_update = sun8i_mixer_layer_atomic_update, | ||
53 | }; | ||
54 | |||
55 | static const struct drm_plane_funcs sun8i_mixer_layer_funcs = { | ||
56 | .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, | ||
57 | .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, | ||
58 | .destroy = drm_plane_cleanup, | ||
59 | .disable_plane = drm_atomic_helper_disable_plane, | ||
60 | .reset = drm_atomic_helper_plane_reset, | ||
61 | .update_plane = drm_atomic_helper_update_plane, | ||
62 | }; | ||
63 | |||
64 | static const uint32_t sun8i_mixer_layer_formats[] = { | ||
65 | DRM_FORMAT_RGB888, | ||
66 | DRM_FORMAT_ARGB8888, | ||
67 | DRM_FORMAT_XRGB8888, | ||
68 | }; | ||
69 | |||
70 | static const struct sun8i_plane_desc sun8i_mixer_planes[] = { | ||
71 | { | ||
72 | .type = DRM_PLANE_TYPE_PRIMARY, | ||
73 | .formats = sun8i_mixer_layer_formats, | ||
74 | .nformats = ARRAY_SIZE(sun8i_mixer_layer_formats), | ||
75 | }, | ||
76 | }; | ||
77 | |||
78 | static struct sun8i_layer *sun8i_layer_init_one(struct drm_device *drm, | ||
79 | struct sun8i_mixer *mixer, | ||
80 | const struct sun8i_plane_desc *plane) | ||
81 | { | ||
82 | struct sun8i_layer *layer; | ||
83 | int ret; | ||
84 | |||
85 | layer = devm_kzalloc(drm->dev, sizeof(*layer), GFP_KERNEL); | ||
86 | if (!layer) | ||
87 | return ERR_PTR(-ENOMEM); | ||
88 | |||
89 | /* possible crtcs are set later */ | ||
90 | ret = drm_universal_plane_init(drm, &layer->plane, 0, | ||
91 | &sun8i_mixer_layer_funcs, | ||
92 | plane->formats, plane->nformats, | ||
93 | plane->type, NULL); | ||
94 | if (ret) { | ||
95 | dev_err(drm->dev, "Couldn't initialize layer\n"); | ||
96 | return ERR_PTR(ret); | ||
97 | } | ||
98 | |||
99 | drm_plane_helper_add(&layer->plane, | ||
100 | &sun8i_mixer_layer_helper_funcs); | ||
101 | layer->mixer = mixer; | ||
102 | |||
103 | return layer; | ||
104 | } | ||
105 | |||
106 | struct drm_plane **sun8i_layers_init(struct drm_device *drm, | ||
107 | struct sunxi_engine *engine) | ||
108 | { | ||
109 | struct drm_plane **planes; | ||
110 | struct sun8i_mixer *mixer = engine_to_sun8i_mixer(engine); | ||
111 | int i; | ||
112 | |||
113 | planes = devm_kcalloc(drm->dev, ARRAY_SIZE(sun8i_mixer_planes) + 1, | ||
114 | sizeof(*planes), GFP_KERNEL); | ||
115 | if (!planes) | ||
116 | return ERR_PTR(-ENOMEM); | ||
117 | |||
118 | for (i = 0; i < ARRAY_SIZE(sun8i_mixer_planes); i++) { | ||
119 | const struct sun8i_plane_desc *plane = &sun8i_mixer_planes[i]; | ||
120 | struct sun8i_layer *layer; | ||
121 | |||
122 | layer = sun8i_layer_init_one(drm, mixer, plane); | ||
123 | if (IS_ERR(layer)) { | ||
124 | dev_err(drm->dev, "Couldn't initialize %s plane\n", | ||
125 | i ? "overlay" : "primary"); | ||
126 | return ERR_CAST(layer); | ||
127 | }; | ||
128 | |||
129 | layer->id = i; | ||
130 | planes[i] = &layer->plane; | ||
131 | }; | ||
132 | |||
133 | return planes; | ||
134 | } | ||
diff --git a/drivers/gpu/drm/sun4i/sun8i_layer.h b/drivers/gpu/drm/sun4i/sun8i_layer.h new file mode 100644 index 000000000000..e5eccd27cff0 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_layer.h | |||
@@ -0,0 +1,36 @@ | |||
1 | /* | ||
2 | * Copyright (C) Icenowy Zheng <icenowy@aosc.io> | ||
3 | * | ||
4 | * Based on sun4i_layer.h, which is: | ||
5 | * Copyright (C) 2015 Free Electrons | ||
6 | * Copyright (C) 2015 NextThing Co | ||
7 | * | ||
8 | * Maxime Ripard <maxime.ripard@free-electrons.com> | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or | ||
11 | * modify it under the terms of the GNU General Public License as | ||
12 | * published by the Free Software Foundation; either version 2 of | ||
13 | * the License, or (at your option) any later version. | ||
14 | */ | ||
15 | |||
16 | #ifndef _SUN8I_LAYER_H_ | ||
17 | #define _SUN8I_LAYER_H_ | ||
18 | |||
19 | struct sunxi_engine; | ||
20 | |||
21 | struct sun8i_layer { | ||
22 | struct drm_plane plane; | ||
23 | struct sun4i_drv *drv; | ||
24 | struct sun8i_mixer *mixer; | ||
25 | int id; | ||
26 | }; | ||
27 | |||
28 | static inline struct sun8i_layer * | ||
29 | plane_to_sun8i_layer(struct drm_plane *plane) | ||
30 | { | ||
31 | return container_of(plane, struct sun8i_layer, plane); | ||
32 | } | ||
33 | |||
34 | struct drm_plane **sun8i_layers_init(struct drm_device *drm, | ||
35 | struct sunxi_engine *engine); | ||
36 | #endif /* _SUN8I_LAYER_H_ */ | ||
diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c new file mode 100644 index 000000000000..cb193c5f1686 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c | |||
@@ -0,0 +1,414 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2017 Icenowy Zheng <icenowy@aosc.io> | ||
3 | * | ||
4 | * Based on sun4i_backend.c, which is: | ||
5 | * Copyright (C) 2015 Free Electrons | ||
6 | * Copyright (C) 2015 NextThing Co | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or | ||
9 | * modify it under the terms of the GNU General Public License as | ||
10 | * published by the Free Software Foundation; either version 2 of | ||
11 | * the License, or (at your option) any later version. | ||
12 | */ | ||
13 | |||
14 | #include <drm/drmP.h> | ||
15 | #include <drm/drm_atomic_helper.h> | ||
16 | #include <drm/drm_crtc.h> | ||
17 | #include <drm/drm_crtc_helper.h> | ||
18 | #include <drm/drm_fb_cma_helper.h> | ||
19 | #include <drm/drm_gem_cma_helper.h> | ||
20 | #include <drm/drm_plane_helper.h> | ||
21 | |||
22 | #include <linux/component.h> | ||
23 | #include <linux/dma-mapping.h> | ||
24 | #include <linux/reset.h> | ||
25 | #include <linux/of_device.h> | ||
26 | |||
27 | #include "sun4i_drv.h" | ||
28 | #include "sun8i_mixer.h" | ||
29 | #include "sun8i_layer.h" | ||
30 | #include "sunxi_engine.h" | ||
31 | |||
32 | static void sun8i_mixer_commit(struct sunxi_engine *engine) | ||
33 | { | ||
34 | DRM_DEBUG_DRIVER("Committing changes\n"); | ||
35 | |||
36 | regmap_write(engine->regs, SUN8I_MIXER_GLOBAL_DBUFF, | ||
37 | SUN8I_MIXER_GLOBAL_DBUFF_ENABLE); | ||
38 | } | ||
39 | |||
40 | void sun8i_mixer_layer_enable(struct sun8i_mixer *mixer, | ||
41 | int layer, bool enable) | ||
42 | { | ||
43 | u32 val; | ||
44 | /* Currently the first UI channel is used */ | ||
45 | int chan = mixer->cfg->vi_num; | ||
46 | |||
47 | DRM_DEBUG_DRIVER("Enabling layer %d in channel %d\n", layer, chan); | ||
48 | |||
49 | if (enable) | ||
50 | val = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN; | ||
51 | else | ||
52 | val = 0; | ||
53 | |||
54 | regmap_update_bits(mixer->engine.regs, | ||
55 | SUN8I_MIXER_CHAN_UI_LAYER_ATTR(chan, layer), | ||
56 | SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN, val); | ||
57 | |||
58 | /* Set the alpha configuration */ | ||
59 | regmap_update_bits(mixer->engine.regs, | ||
60 | SUN8I_MIXER_CHAN_UI_LAYER_ATTR(chan, layer), | ||
61 | SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_MASK, | ||
62 | SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_DEF); | ||
63 | regmap_update_bits(mixer->engine.regs, | ||
64 | SUN8I_MIXER_CHAN_UI_LAYER_ATTR(chan, layer), | ||
65 | SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MASK, | ||
66 | SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_DEF); | ||
67 | } | ||
68 | |||
69 | static int sun8i_mixer_drm_format_to_layer(struct drm_plane *plane, | ||
70 | u32 format, u32 *mode) | ||
71 | { | ||
72 | switch (format) { | ||
73 | case DRM_FORMAT_ARGB8888: | ||
74 | *mode = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_ARGB8888; | ||
75 | break; | ||
76 | |||
77 | case DRM_FORMAT_XRGB8888: | ||
78 | *mode = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_XRGB8888; | ||
79 | break; | ||
80 | |||
81 | case DRM_FORMAT_RGB888: | ||
82 | *mode = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_RGB888; | ||
83 | break; | ||
84 | |||
85 | default: | ||
86 | return -EINVAL; | ||
87 | } | ||
88 | |||
89 | return 0; | ||
90 | } | ||
91 | |||
92 | int sun8i_mixer_update_layer_coord(struct sun8i_mixer *mixer, | ||
93 | int layer, struct drm_plane *plane) | ||
94 | { | ||
95 | struct drm_plane_state *state = plane->state; | ||
96 | struct drm_framebuffer *fb = state->fb; | ||
97 | /* Currently the first UI channel is used */ | ||
98 | int chan = mixer->cfg->vi_num; | ||
99 | |||
100 | DRM_DEBUG_DRIVER("Updating layer %d\n", layer); | ||
101 | |||
102 | if (plane->type == DRM_PLANE_TYPE_PRIMARY) { | ||
103 | DRM_DEBUG_DRIVER("Primary layer, updating global size W: %u H: %u\n", | ||
104 | state->crtc_w, state->crtc_h); | ||
105 | regmap_write(mixer->engine.regs, SUN8I_MIXER_GLOBAL_SIZE, | ||
106 | SUN8I_MIXER_SIZE(state->crtc_w, | ||
107 | state->crtc_h)); | ||
108 | DRM_DEBUG_DRIVER("Updating blender size\n"); | ||
109 | regmap_write(mixer->engine.regs, | ||
110 | SUN8I_MIXER_BLEND_ATTR_INSIZE(0), | ||
111 | SUN8I_MIXER_SIZE(state->crtc_w, | ||
112 | state->crtc_h)); | ||
113 | regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_OUTSIZE, | ||
114 | SUN8I_MIXER_SIZE(state->crtc_w, | ||
115 | state->crtc_h)); | ||
116 | DRM_DEBUG_DRIVER("Updating channel size\n"); | ||
117 | regmap_write(mixer->engine.regs, | ||
118 | SUN8I_MIXER_CHAN_UI_OVL_SIZE(chan), | ||
119 | SUN8I_MIXER_SIZE(state->crtc_w, | ||
120 | state->crtc_h)); | ||
121 | } | ||
122 | |||
123 | /* Set the line width */ | ||
124 | DRM_DEBUG_DRIVER("Layer line width: %d bytes\n", fb->pitches[0]); | ||
125 | regmap_write(mixer->engine.regs, | ||
126 | SUN8I_MIXER_CHAN_UI_LAYER_PITCH(chan, layer), | ||
127 | fb->pitches[0]); | ||
128 | |||
129 | /* Set height and width */ | ||
130 | DRM_DEBUG_DRIVER("Layer size W: %u H: %u\n", | ||
131 | state->crtc_w, state->crtc_h); | ||
132 | regmap_write(mixer->engine.regs, | ||
133 | SUN8I_MIXER_CHAN_UI_LAYER_SIZE(chan, layer), | ||
134 | SUN8I_MIXER_SIZE(state->crtc_w, state->crtc_h)); | ||
135 | |||
136 | /* Set base coordinates */ | ||
137 | DRM_DEBUG_DRIVER("Layer coordinates X: %d Y: %d\n", | ||
138 | state->crtc_x, state->crtc_y); | ||
139 | regmap_write(mixer->engine.regs, | ||
140 | SUN8I_MIXER_CHAN_UI_LAYER_COORD(chan, layer), | ||
141 | SUN8I_MIXER_COORD(state->crtc_x, state->crtc_y)); | ||
142 | |||
143 | return 0; | ||
144 | } | ||
145 | |||
146 | int sun8i_mixer_update_layer_formats(struct sun8i_mixer *mixer, | ||
147 | int layer, struct drm_plane *plane) | ||
148 | { | ||
149 | struct drm_plane_state *state = plane->state; | ||
150 | struct drm_framebuffer *fb = state->fb; | ||
151 | bool interlaced = false; | ||
152 | u32 val; | ||
153 | /* Currently the first UI channel is used */ | ||
154 | int chan = mixer->cfg->vi_num; | ||
155 | int ret; | ||
156 | |||
157 | if (plane->state->crtc) | ||
158 | interlaced = plane->state->crtc->state->adjusted_mode.flags | ||
159 | & DRM_MODE_FLAG_INTERLACE; | ||
160 | |||
161 | regmap_update_bits(mixer->engine.regs, SUN8I_MIXER_BLEND_OUTCTL, | ||
162 | SUN8I_MIXER_BLEND_OUTCTL_INTERLACED, | ||
163 | interlaced ? | ||
164 | SUN8I_MIXER_BLEND_OUTCTL_INTERLACED : 0); | ||
165 | |||
166 | DRM_DEBUG_DRIVER("Switching display mixer interlaced mode %s\n", | ||
167 | interlaced ? "on" : "off"); | ||
168 | |||
169 | ret = sun8i_mixer_drm_format_to_layer(plane, fb->format->format, | ||
170 | &val); | ||
171 | if (ret) { | ||
172 | DRM_DEBUG_DRIVER("Invalid format\n"); | ||
173 | return ret; | ||
174 | } | ||
175 | |||
176 | regmap_update_bits(mixer->engine.regs, | ||
177 | SUN8I_MIXER_CHAN_UI_LAYER_ATTR(chan, layer), | ||
178 | SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_MASK, val); | ||
179 | |||
180 | return 0; | ||
181 | } | ||
182 | |||
183 | int sun8i_mixer_update_layer_buffer(struct sun8i_mixer *mixer, | ||
184 | int layer, struct drm_plane *plane) | ||
185 | { | ||
186 | struct drm_plane_state *state = plane->state; | ||
187 | struct drm_framebuffer *fb = state->fb; | ||
188 | struct drm_gem_cma_object *gem; | ||
189 | dma_addr_t paddr; | ||
190 | /* Currently the first UI channel is used */ | ||
191 | int chan = mixer->cfg->vi_num; | ||
192 | int bpp; | ||
193 | |||
194 | /* Get the physical address of the buffer in memory */ | ||
195 | gem = drm_fb_cma_get_gem_obj(fb, 0); | ||
196 | |||
197 | DRM_DEBUG_DRIVER("Using GEM @ %pad\n", &gem->paddr); | ||
198 | |||
199 | /* Compute the start of the displayed memory */ | ||
200 | bpp = fb->format->cpp[0]; | ||
201 | paddr = gem->paddr + fb->offsets[0]; | ||
202 | |||
203 | /* Fixup framebuffer address for src coordinates */ | ||
204 | paddr += (state->src_x >> 16) * bpp; | ||
205 | paddr += (state->src_y >> 16) * fb->pitches[0]; | ||
206 | |||
207 | /* | ||
208 | * The hardware cannot correctly deal with negative crtc | ||
209 | * coordinates, the display is cropped to the requested size, | ||
210 | * but the display content is not moved. | ||
211 | * Manually move the display content by fixup the framebuffer | ||
212 | * address when crtc_x or crtc_y is negative, like what we | ||
213 | * have did for src_x and src_y. | ||
214 | */ | ||
215 | if (state->crtc_x < 0) | ||
216 | paddr += -state->crtc_x * bpp; | ||
217 | if (state->crtc_y < 0) | ||
218 | paddr += -state->crtc_y * fb->pitches[0]; | ||
219 | |||
220 | DRM_DEBUG_DRIVER("Setting buffer address to %pad\n", &paddr); | ||
221 | |||
222 | regmap_write(mixer->engine.regs, | ||
223 | SUN8I_MIXER_CHAN_UI_LAYER_TOP_LADDR(chan, layer), | ||
224 | lower_32_bits(paddr)); | ||
225 | |||
226 | return 0; | ||
227 | } | ||
228 | |||
229 | static const struct sunxi_engine_ops sun8i_engine_ops = { | ||
230 | .commit = sun8i_mixer_commit, | ||
231 | .layers_init = sun8i_layers_init, | ||
232 | }; | ||
233 | |||
234 | static struct regmap_config sun8i_mixer_regmap_config = { | ||
235 | .reg_bits = 32, | ||
236 | .val_bits = 32, | ||
237 | .reg_stride = 4, | ||
238 | .max_register = 0xbfffc, /* guessed */ | ||
239 | }; | ||
240 | |||
241 | static int sun8i_mixer_bind(struct device *dev, struct device *master, | ||
242 | void *data) | ||
243 | { | ||
244 | struct platform_device *pdev = to_platform_device(dev); | ||
245 | struct drm_device *drm = data; | ||
246 | struct sun4i_drv *drv = drm->dev_private; | ||
247 | struct sun8i_mixer *mixer; | ||
248 | struct resource *res; | ||
249 | void __iomem *regs; | ||
250 | int i, ret; | ||
251 | |||
252 | /* | ||
253 | * The mixer uses single 32-bit register to store memory | ||
254 | * addresses, so that it cannot deal with 64-bit memory | ||
255 | * addresses. | ||
256 | * Restrict the DMA mask so that the mixer won't be | ||
257 | * allocated some memory that is too high. | ||
258 | */ | ||
259 | ret = dma_set_mask(dev, DMA_BIT_MASK(32)); | ||
260 | if (ret) { | ||
261 | dev_err(dev, "Cannot do 32-bit DMA.\n"); | ||
262 | return ret; | ||
263 | } | ||
264 | |||
265 | mixer = devm_kzalloc(dev, sizeof(*mixer), GFP_KERNEL); | ||
266 | if (!mixer) | ||
267 | return -ENOMEM; | ||
268 | dev_set_drvdata(dev, mixer); | ||
269 | mixer->engine.ops = &sun8i_engine_ops; | ||
270 | mixer->engine.node = dev->of_node; | ||
271 | /* The ID of the mixer currently doesn't matter */ | ||
272 | mixer->engine.id = -1; | ||
273 | |||
274 | mixer->cfg = of_device_get_match_data(dev); | ||
275 | if (!mixer->cfg) | ||
276 | return -EINVAL; | ||
277 | |||
278 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
279 | regs = devm_ioremap_resource(dev, res); | ||
280 | if (IS_ERR(regs)) | ||
281 | return PTR_ERR(regs); | ||
282 | |||
283 | mixer->engine.regs = devm_regmap_init_mmio(dev, regs, | ||
284 | &sun8i_mixer_regmap_config); | ||
285 | if (IS_ERR(mixer->engine.regs)) { | ||
286 | dev_err(dev, "Couldn't create the mixer regmap\n"); | ||
287 | return PTR_ERR(mixer->engine.regs); | ||
288 | } | ||
289 | |||
290 | mixer->reset = devm_reset_control_get(dev, NULL); | ||
291 | if (IS_ERR(mixer->reset)) { | ||
292 | dev_err(dev, "Couldn't get our reset line\n"); | ||
293 | return PTR_ERR(mixer->reset); | ||
294 | } | ||
295 | |||
296 | ret = reset_control_deassert(mixer->reset); | ||
297 | if (ret) { | ||
298 | dev_err(dev, "Couldn't deassert our reset line\n"); | ||
299 | return ret; | ||
300 | } | ||
301 | |||
302 | mixer->bus_clk = devm_clk_get(dev, "bus"); | ||
303 | if (IS_ERR(mixer->bus_clk)) { | ||
304 | dev_err(dev, "Couldn't get the mixer bus clock\n"); | ||
305 | ret = PTR_ERR(mixer->bus_clk); | ||
306 | goto err_assert_reset; | ||
307 | } | ||
308 | clk_prepare_enable(mixer->bus_clk); | ||
309 | |||
310 | mixer->mod_clk = devm_clk_get(dev, "mod"); | ||
311 | if (IS_ERR(mixer->mod_clk)) { | ||
312 | dev_err(dev, "Couldn't get the mixer module clock\n"); | ||
313 | ret = PTR_ERR(mixer->mod_clk); | ||
314 | goto err_disable_bus_clk; | ||
315 | } | ||
316 | clk_prepare_enable(mixer->mod_clk); | ||
317 | |||
318 | list_add_tail(&mixer->engine.list, &drv->engine_list); | ||
319 | |||
320 | /* Reset the registers */ | ||
321 | for (i = 0x0; i < 0x20000; i += 4) | ||
322 | regmap_write(mixer->engine.regs, i, 0); | ||
323 | |||
324 | /* Enable the mixer */ | ||
325 | regmap_write(mixer->engine.regs, SUN8I_MIXER_GLOBAL_CTL, | ||
326 | SUN8I_MIXER_GLOBAL_CTL_RT_EN); | ||
327 | |||
328 | /* Initialize blender */ | ||
329 | regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_FCOLOR_CTL, | ||
330 | SUN8I_MIXER_BLEND_FCOLOR_CTL_DEF); | ||
331 | regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_PREMULTIPLY, | ||
332 | SUN8I_MIXER_BLEND_PREMULTIPLY_DEF); | ||
333 | regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_BKCOLOR, | ||
334 | SUN8I_MIXER_BLEND_BKCOLOR_DEF); | ||
335 | regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_MODE(0), | ||
336 | SUN8I_MIXER_BLEND_MODE_DEF); | ||
337 | regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_CK_CTL, | ||
338 | SUN8I_MIXER_BLEND_CK_CTL_DEF); | ||
339 | |||
340 | regmap_write(mixer->engine.regs, | ||
341 | SUN8I_MIXER_BLEND_ATTR_FCOLOR(0), | ||
342 | SUN8I_MIXER_BLEND_ATTR_FCOLOR_DEF); | ||
343 | |||
344 | /* Select the first UI channel */ | ||
345 | DRM_DEBUG_DRIVER("Selecting channel %d (first UI channel)\n", | ||
346 | mixer->cfg->vi_num); | ||
347 | regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_ROUTE, | ||
348 | mixer->cfg->vi_num); | ||
349 | |||
350 | return 0; | ||
351 | |||
352 | err_disable_bus_clk: | ||
353 | clk_disable_unprepare(mixer->bus_clk); | ||
354 | err_assert_reset: | ||
355 | reset_control_assert(mixer->reset); | ||
356 | return ret; | ||
357 | } | ||
358 | |||
359 | static void sun8i_mixer_unbind(struct device *dev, struct device *master, | ||
360 | void *data) | ||
361 | { | ||
362 | struct sun8i_mixer *mixer = dev_get_drvdata(dev); | ||
363 | |||
364 | list_del(&mixer->engine.list); | ||
365 | |||
366 | clk_disable_unprepare(mixer->mod_clk); | ||
367 | clk_disable_unprepare(mixer->bus_clk); | ||
368 | reset_control_assert(mixer->reset); | ||
369 | } | ||
370 | |||
371 | static const struct component_ops sun8i_mixer_ops = { | ||
372 | .bind = sun8i_mixer_bind, | ||
373 | .unbind = sun8i_mixer_unbind, | ||
374 | }; | ||
375 | |||
376 | static int sun8i_mixer_probe(struct platform_device *pdev) | ||
377 | { | ||
378 | return component_add(&pdev->dev, &sun8i_mixer_ops); | ||
379 | } | ||
380 | |||
381 | static int sun8i_mixer_remove(struct platform_device *pdev) | ||
382 | { | ||
383 | component_del(&pdev->dev, &sun8i_mixer_ops); | ||
384 | |||
385 | return 0; | ||
386 | } | ||
387 | |||
388 | static const struct sun8i_mixer_cfg sun8i_v3s_mixer_cfg = { | ||
389 | .vi_num = 2, | ||
390 | .ui_num = 1, | ||
391 | }; | ||
392 | |||
393 | static const struct of_device_id sun8i_mixer_of_table[] = { | ||
394 | { | ||
395 | .compatible = "allwinner,sun8i-v3s-de2-mixer", | ||
396 | .data = &sun8i_v3s_mixer_cfg, | ||
397 | }, | ||
398 | { } | ||
399 | }; | ||
400 | MODULE_DEVICE_TABLE(of, sun8i_mixer_of_table); | ||
401 | |||
402 | static struct platform_driver sun8i_mixer_platform_driver = { | ||
403 | .probe = sun8i_mixer_probe, | ||
404 | .remove = sun8i_mixer_remove, | ||
405 | .driver = { | ||
406 | .name = "sun8i-mixer", | ||
407 | .of_match_table = sun8i_mixer_of_table, | ||
408 | }, | ||
409 | }; | ||
410 | module_platform_driver(sun8i_mixer_platform_driver); | ||
411 | |||
412 | MODULE_AUTHOR("Icenowy Zheng <icenowy@aosc.io>"); | ||
413 | MODULE_DESCRIPTION("Allwinner DE2 Mixer driver"); | ||
414 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.h b/drivers/gpu/drm/sun4i/sun8i_mixer.h new file mode 100644 index 000000000000..4785ac090b8c --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.h | |||
@@ -0,0 +1,137 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2017 Icenowy Zheng <icenowy@aosc.io> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU General Public License as | ||
6 | * published by the Free Software Foundation; either version 2 of | ||
7 | * the License, or (at your option) any later version. | ||
8 | */ | ||
9 | |||
10 | #ifndef _SUN8I_MIXER_H_ | ||
11 | #define _SUN8I_MIXER_H_ | ||
12 | |||
13 | #include <linux/clk.h> | ||
14 | #include <linux/regmap.h> | ||
15 | #include <linux/reset.h> | ||
16 | |||
17 | #include "sunxi_engine.h" | ||
18 | |||
19 | #define SUN8I_MIXER_MAX_CHAN_COUNT 4 | ||
20 | |||
21 | #define SUN8I_MIXER_SIZE(w, h) (((h) - 1) << 16 | ((w) - 1)) | ||
22 | #define SUN8I_MIXER_COORD(x, y) ((y) << 16 | (x)) | ||
23 | |||
24 | #define SUN8I_MIXER_GLOBAL_CTL 0x0 | ||
25 | #define SUN8I_MIXER_GLOBAL_STATUS 0x4 | ||
26 | #define SUN8I_MIXER_GLOBAL_DBUFF 0x8 | ||
27 | #define SUN8I_MIXER_GLOBAL_SIZE 0xc | ||
28 | |||
29 | #define SUN8I_MIXER_GLOBAL_CTL_RT_EN 0x1 | ||
30 | |||
31 | #define SUN8I_MIXER_GLOBAL_DBUFF_ENABLE 0x1 | ||
32 | |||
33 | #define SUN8I_MIXER_BLEND_FCOLOR_CTL 0x1000 | ||
34 | #define SUN8I_MIXER_BLEND_ATTR_FCOLOR(x) (0x1004 + 0x10 * (x) + 0x0) | ||
35 | #define SUN8I_MIXER_BLEND_ATTR_INSIZE(x) (0x1004 + 0x10 * (x) + 0x4) | ||
36 | #define SUN8I_MIXER_BLEND_ATTR_OFFSET(x) (0x1004 + 0x10 * (x) + 0x8) | ||
37 | #define SUN8I_MIXER_BLEND_ROUTE 0x1080 | ||
38 | #define SUN8I_MIXER_BLEND_PREMULTIPLY 0x1084 | ||
39 | #define SUN8I_MIXER_BLEND_BKCOLOR 0x1088 | ||
40 | #define SUN8I_MIXER_BLEND_OUTSIZE 0x108c | ||
41 | #define SUN8I_MIXER_BLEND_MODE(x) (0x1090 + 0x04 * (x)) | ||
42 | #define SUN8I_MIXER_BLEND_CK_CTL 0x10b0 | ||
43 | #define SUN8I_MIXER_BLEND_CK_CFG 0x10b4 | ||
44 | #define SUN8I_MIXER_BLEND_CK_MAX(x) (0x10c0 + 0x04 * (x)) | ||
45 | #define SUN8I_MIXER_BLEND_CK_MIN(x) (0x10e0 + 0x04 * (x)) | ||
46 | #define SUN8I_MIXER_BLEND_OUTCTL 0x10fc | ||
47 | |||
48 | /* The following numbers are some still unknown magic numbers */ | ||
49 | #define SUN8I_MIXER_BLEND_ATTR_FCOLOR_DEF 0xff000000 | ||
50 | #define SUN8I_MIXER_BLEND_FCOLOR_CTL_DEF 0x00000101 | ||
51 | #define SUN8I_MIXER_BLEND_PREMULTIPLY_DEF 0x0 | ||
52 | #define SUN8I_MIXER_BLEND_BKCOLOR_DEF 0xff000000 | ||
53 | #define SUN8I_MIXER_BLEND_MODE_DEF 0x03010301 | ||
54 | #define SUN8I_MIXER_BLEND_CK_CTL_DEF 0x0 | ||
55 | |||
56 | #define SUN8I_MIXER_BLEND_OUTCTL_INTERLACED BIT(1) | ||
57 | |||
58 | /* | ||
59 | * VI channels are not used now, but the support of them may be introduced in | ||
60 | * the future. | ||
61 | */ | ||
62 | |||
63 | #define SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch, layer) \ | ||
64 | (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x0) | ||
65 | #define SUN8I_MIXER_CHAN_UI_LAYER_SIZE(ch, layer) \ | ||
66 | (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x4) | ||
67 | #define SUN8I_MIXER_CHAN_UI_LAYER_COORD(ch, layer) \ | ||
68 | (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x8) | ||
69 | #define SUN8I_MIXER_CHAN_UI_LAYER_PITCH(ch, layer) \ | ||
70 | (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0xc) | ||
71 | #define SUN8I_MIXER_CHAN_UI_LAYER_TOP_LADDR(ch, layer) \ | ||
72 | (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x10) | ||
73 | #define SUN8I_MIXER_CHAN_UI_LAYER_BOT_LADDR(ch, layer) \ | ||
74 | (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x14) | ||
75 | #define SUN8I_MIXER_CHAN_UI_LAYER_FCOLOR(ch, layer) \ | ||
76 | (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x18) | ||
77 | #define SUN8I_MIXER_CHAN_UI_TOP_HADDR(ch) (0x2000 + 0x1000 * (ch) + 0x80) | ||
78 | #define SUN8I_MIXER_CHAN_UI_BOT_HADDR(ch) (0x2000 + 0x1000 * (ch) + 0x84) | ||
79 | #define SUN8I_MIXER_CHAN_UI_OVL_SIZE(ch) (0x2000 + 0x1000 * (ch) + 0x88) | ||
80 | |||
81 | #define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN BIT(0) | ||
82 | #define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_MASK GENMASK(2, 1) | ||
83 | #define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_MASK GENMASK(11, 8) | ||
84 | #define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MASK GENMASK(31, 24) | ||
85 | #define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_DEF (1 << 1) | ||
86 | #define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_ARGB8888 (0 << 8) | ||
87 | #define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_XRGB8888 (4 << 8) | ||
88 | #define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_RGB888 (8 << 8) | ||
89 | #define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_DEF (0xff << 24) | ||
90 | |||
91 | /* | ||
92 | * These sub-engines are still unknown now, the EN registers are here only to | ||
93 | * be used to disable these sub-engines. | ||
94 | */ | ||
95 | #define SUN8I_MIXER_VSU_EN 0x20000 | ||
96 | #define SUN8I_MIXER_GSU1_EN 0x30000 | ||
97 | #define SUN8I_MIXER_GSU2_EN 0x40000 | ||
98 | #define SUN8I_MIXER_GSU3_EN 0x50000 | ||
99 | #define SUN8I_MIXER_FCE_EN 0xa0000 | ||
100 | #define SUN8I_MIXER_BWS_EN 0xa2000 | ||
101 | #define SUN8I_MIXER_LTI_EN 0xa4000 | ||
102 | #define SUN8I_MIXER_PEAK_EN 0xa6000 | ||
103 | #define SUN8I_MIXER_ASE_EN 0xa8000 | ||
104 | #define SUN8I_MIXER_FCC_EN 0xaa000 | ||
105 | #define SUN8I_MIXER_DCSC_EN 0xb0000 | ||
106 | |||
107 | struct sun8i_mixer_cfg { | ||
108 | int vi_num; | ||
109 | int ui_num; | ||
110 | }; | ||
111 | |||
112 | struct sun8i_mixer { | ||
113 | struct sunxi_engine engine; | ||
114 | |||
115 | const struct sun8i_mixer_cfg *cfg; | ||
116 | |||
117 | struct reset_control *reset; | ||
118 | |||
119 | struct clk *bus_clk; | ||
120 | struct clk *mod_clk; | ||
121 | }; | ||
122 | |||
123 | static inline struct sun8i_mixer * | ||
124 | engine_to_sun8i_mixer(struct sunxi_engine *engine) | ||
125 | { | ||
126 | return container_of(engine, struct sun8i_mixer, engine); | ||
127 | } | ||
128 | |||
129 | void sun8i_mixer_layer_enable(struct sun8i_mixer *mixer, | ||
130 | int layer, bool enable); | ||
131 | int sun8i_mixer_update_layer_coord(struct sun8i_mixer *mixer, | ||
132 | int layer, struct drm_plane *plane); | ||
133 | int sun8i_mixer_update_layer_formats(struct sun8i_mixer *mixer, | ||
134 | int layer, struct drm_plane *plane); | ||
135 | int sun8i_mixer_update_layer_buffer(struct sun8i_mixer *mixer, | ||
136 | int layer, struct drm_plane *plane); | ||
137 | #endif /* _SUN8I_MIXER_H_ */ | ||
diff --git a/drivers/gpu/drm/sun4i/sunxi_engine.h b/drivers/gpu/drm/sun4i/sunxi_engine.h new file mode 100644 index 000000000000..4cb70ae65c79 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sunxi_engine.h | |||
@@ -0,0 +1,98 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2017 Icenowy Zheng <icenowy@aosc.io> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU General Public License as | ||
6 | * published by the Free Software Foundation; either version 2 of | ||
7 | * the License, or (at your option) any later version. | ||
8 | */ | ||
9 | |||
10 | #ifndef _SUNXI_ENGINE_H_ | ||
11 | #define _SUNXI_ENGINE_H_ | ||
12 | |||
13 | struct drm_plane; | ||
14 | struct drm_device; | ||
15 | |||
16 | struct sunxi_engine; | ||
17 | |||
18 | struct sunxi_engine_ops { | ||
19 | void (*commit)(struct sunxi_engine *engine); | ||
20 | struct drm_plane **(*layers_init)(struct drm_device *drm, | ||
21 | struct sunxi_engine *engine); | ||
22 | |||
23 | void (*apply_color_correction)(struct sunxi_engine *engine); | ||
24 | void (*disable_color_correction)(struct sunxi_engine *engine); | ||
25 | }; | ||
26 | |||
27 | /** | ||
28 | * struct sunxi_engine - the common parts of an engine for sun4i-drm driver | ||
29 | * @ops: the operations of the engine | ||
30 | * @node: the of device node of the engine | ||
31 | * @regs: the regmap of the engine | ||
32 | * @id: the id of the engine (-1 if not used) | ||
33 | */ | ||
34 | struct sunxi_engine { | ||
35 | const struct sunxi_engine_ops *ops; | ||
36 | |||
37 | struct device_node *node; | ||
38 | struct regmap *regs; | ||
39 | |||
40 | int id; | ||
41 | |||
42 | /* Engine list management */ | ||
43 | struct list_head list; | ||
44 | }; | ||
45 | |||
46 | /** | ||
47 | * sunxi_engine_commit() - commit all changes of the engine | ||
48 | * @engine: pointer to the engine | ||
49 | */ | ||
50 | static inline void | ||
51 | sunxi_engine_commit(struct sunxi_engine *engine) | ||
52 | { | ||
53 | if (engine->ops && engine->ops->commit) | ||
54 | engine->ops->commit(engine); | ||
55 | } | ||
56 | |||
57 | /** | ||
58 | * sunxi_engine_layers_init() - Create planes (layers) for the engine | ||
59 | * @drm: pointer to the drm_device for which planes will be created | ||
60 | * @engine: pointer to the engine | ||
61 | */ | ||
62 | static inline struct drm_plane ** | ||
63 | sunxi_engine_layers_init(struct drm_device *drm, struct sunxi_engine *engine) | ||
64 | { | ||
65 | if (engine->ops && engine->ops->layers_init) | ||
66 | return engine->ops->layers_init(drm, engine); | ||
67 | return ERR_PTR(-ENOSYS); | ||
68 | } | ||
69 | |||
70 | /** | ||
71 | * sunxi_engine_apply_color_correction - Apply the RGB2YUV color correction | ||
72 | * @engine: pointer to the engine | ||
73 | * | ||
74 | * This functionality is optional for an engine, however, if the engine is | ||
75 | * intended to be used with TV Encoder, the output will be incorrect | ||
76 | * without the color correction, due to TV Encoder expects the engine to | ||
77 | * output directly YUV signal. | ||
78 | */ | ||
79 | static inline void | ||
80 | sunxi_engine_apply_color_correction(struct sunxi_engine *engine) | ||
81 | { | ||
82 | if (engine->ops && engine->ops->apply_color_correction) | ||
83 | engine->ops->apply_color_correction(engine); | ||
84 | } | ||
85 | |||
86 | /** | ||
87 | * sunxi_engine_disable_color_correction - Disable the color space correction | ||
88 | * @engine: pointer to the engine | ||
89 | * | ||
90 | * This function is paired with apply_color_correction(). | ||
91 | */ | ||
92 | static inline void | ||
93 | sunxi_engine_disable_color_correction(struct sunxi_engine *engine) | ||
94 | { | ||
95 | if (engine->ops && engine->ops->disable_color_correction) | ||
96 | engine->ops->disable_color_correction(engine); | ||
97 | } | ||
98 | #endif /* _SUNXI_ENGINE_H_ */ | ||