diff options
author | Dave Airlie <airlied@redhat.com> | 2016-05-04 05:53:15 -0400 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2016-05-04 05:53:15 -0400 |
commit | 4946dd2e14d252cd04e188ed6a4b794541d1c3ce (patch) | |
tree | f60cae0676808538becd116090ab6f5f19520edf | |
parent | 090e1a7f5ce139ab4246fcaafaac0415008b7dfa (diff) | |
parent | c84ffde963e227bf68efb12315bd39c75e00ff05 (diff) |
Merge tag 'drm-hisilicon-next-2016-04-29' of github.com:xin3liang/linux into drm-next
drm-hisilicon-next for 4.7
Add new hisilicon kirin drm driver:
- Add maintainer for hisilicon DRM driver
- Add support for external bridge
- Add designware dsi host driver
- Add designware dsi encoder driver
- Add cma fbdev and hotplug
- Add vblank driver for ADE
- Add plane driver for ADE
- Add crtc driver for ADE
- Add hisilicon kirin drm master driver
- Add device tree binding for hi6220 display subsystem
* tag 'drm-hisilicon-next-2016-04-29' of github.com:xin3liang/linux:
MAINTAINERS: Add maintainer for hisilicon DRM driver
drm/hisilicon: Add support for external bridge
drm/hisilicon: Add designware dsi host driver
drm/hisilicon: Add designware dsi encoder driver
drm/hisilicon: Add cma fbdev and hotplug
drm/hisilicon: Add vblank driver for ADE
drm/hisilicon: Add plane driver for ADE
drm/hisilicon: Add crtc driver for ADE
drm/hisilicon: Add hisilicon kirin drm master driver
drm/hisilicon: Add device tree binding for hi6220 display subsystem
-rw-r--r-- | Documentation/devicetree/bindings/display/hisilicon/dw-dsi.txt | 72 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/display/hisilicon/hisi-ade.txt | 64 | ||||
-rw-r--r-- | MAINTAINERS | 10 | ||||
-rw-r--r-- | drivers/gpu/drm/Kconfig | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/hisilicon/Kconfig | 5 | ||||
-rw-r--r-- | drivers/gpu/drm/hisilicon/Makefile | 5 | ||||
-rw-r--r-- | drivers/gpu/drm/hisilicon/kirin/Kconfig | 18 | ||||
-rw-r--r-- | drivers/gpu/drm/hisilicon/kirin/Makefile | 6 | ||||
-rw-r--r-- | drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c | 857 | ||||
-rw-r--r-- | drivers/gpu/drm/hisilicon/kirin/dw_dsi_reg.h | 103 | ||||
-rw-r--r-- | drivers/gpu/drm/hisilicon/kirin/kirin_ade_reg.h | 230 | ||||
-rw-r--r-- | drivers/gpu/drm/hisilicon/kirin/kirin_drm_ade.c | 1057 | ||||
-rw-r--r-- | drivers/gpu/drm/hisilicon/kirin/kirin_drm_drv.c | 367 | ||||
-rw-r--r-- | drivers/gpu/drm/hisilicon/kirin/kirin_drm_drv.h | 31 |
15 files changed, 2828 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/display/hisilicon/dw-dsi.txt b/Documentation/devicetree/bindings/display/hisilicon/dw-dsi.txt new file mode 100644 index 000000000000..d270bfe4e4e0 --- /dev/null +++ b/Documentation/devicetree/bindings/display/hisilicon/dw-dsi.txt | |||
@@ -0,0 +1,72 @@ | |||
1 | Device-Tree bindings for DesignWare DSI Host Controller v1.20a driver | ||
2 | |||
3 | A DSI Host Controller resides in the middle of display controller and external | ||
4 | HDMI converter or panel. | ||
5 | |||
6 | Required properties: | ||
7 | - compatible: value should be "hisilicon,hi6220-dsi". | ||
8 | - reg: physical base address and length of dsi controller's registers. | ||
9 | - clocks: contains APB clock phandle + clock-specifier pair. | ||
10 | - clock-names: should be "pclk". | ||
11 | - ports: contains DSI controller input and output sub port. | ||
12 | The input port connects to ADE output port with the reg value "0". | ||
13 | The output port with the reg value "1", it could connect to panel or | ||
14 | any other bridge endpoints. | ||
15 | See Documentation/devicetree/bindings/graph.txt for more device graph info. | ||
16 | |||
17 | A example of HiKey board hi6220 SoC and board specific DT entry: | ||
18 | Example: | ||
19 | |||
20 | SoC specific: | ||
21 | dsi: dsi@f4107800 { | ||
22 | compatible = "hisilicon,hi6220-dsi"; | ||
23 | reg = <0x0 0xf4107800 0x0 0x100>; | ||
24 | clocks = <&media_ctrl HI6220_DSI_PCLK>; | ||
25 | clock-names = "pclk"; | ||
26 | status = "disabled"; | ||
27 | |||
28 | ports { | ||
29 | #address-cells = <1>; | ||
30 | #size-cells = <0>; | ||
31 | |||
32 | /* 0 for input port */ | ||
33 | port@0 { | ||
34 | reg = <0>; | ||
35 | dsi_in: endpoint { | ||
36 | remote-endpoint = <&ade_out>; | ||
37 | }; | ||
38 | }; | ||
39 | }; | ||
40 | }; | ||
41 | |||
42 | |||
43 | Board specific: | ||
44 | &dsi { | ||
45 | status = "ok"; | ||
46 | |||
47 | ports { | ||
48 | /* 1 for output port */ | ||
49 | port@1 { | ||
50 | reg = <1>; | ||
51 | |||
52 | dsi_out0: endpoint@0 { | ||
53 | remote-endpoint = <&adv7533_in>; | ||
54 | }; | ||
55 | }; | ||
56 | }; | ||
57 | }; | ||
58 | |||
59 | &i2c2 { | ||
60 | ... | ||
61 | |||
62 | adv7533: adv7533@39 { | ||
63 | ... | ||
64 | |||
65 | port { | ||
66 | adv7533_in: endpoint { | ||
67 | remote-endpoint = <&dsi_out0>; | ||
68 | }; | ||
69 | }; | ||
70 | }; | ||
71 | }; | ||
72 | |||
diff --git a/Documentation/devicetree/bindings/display/hisilicon/hisi-ade.txt b/Documentation/devicetree/bindings/display/hisilicon/hisi-ade.txt new file mode 100644 index 000000000000..38dc9d60eef8 --- /dev/null +++ b/Documentation/devicetree/bindings/display/hisilicon/hisi-ade.txt | |||
@@ -0,0 +1,64 @@ | |||
1 | Device-Tree bindings for hisilicon ADE display controller driver | ||
2 | |||
3 | ADE (Advanced Display Engine) is the display controller which grab image | ||
4 | data from memory, do composition, do post image processing, generate RGB | ||
5 | timing stream and transfer to DSI. | ||
6 | |||
7 | Required properties: | ||
8 | - compatible: value should be "hisilicon,hi6220-ade". | ||
9 | - reg: physical base address and length of the ADE controller's registers. | ||
10 | - hisilicon,noc-syscon: ADE NOC QoS syscon. | ||
11 | - resets: The ADE reset controller node. | ||
12 | - interrupt: the ldi vblank interrupt number used. | ||
13 | - clocks: a list of phandle + clock-specifier pairs, one for each entry | ||
14 | in clock-names. | ||
15 | - clock-names: should contain: | ||
16 | "clk_ade_core" for the ADE core clock. | ||
17 | "clk_codec_jpeg" for the media NOC QoS clock, which use the same clock with | ||
18 | jpeg codec. | ||
19 | "clk_ade_pix" for the ADE pixel clok. | ||
20 | - assigned-clocks: Should contain "clk_ade_core" and "clk_codec_jpeg" clocks' | ||
21 | phandle + clock-specifier pairs. | ||
22 | - assigned-clock-rates: clock rates, one for each entry in assigned-clocks. | ||
23 | The rate of "clk_ade_core" could be "360000000" or "180000000"; | ||
24 | The rate of "clk_codec_jpeg" could be or less than "1440000000". | ||
25 | These rate values could be configured according to performance and power | ||
26 | consumption. | ||
27 | - port: the output port. This contains one endpoint subnode, with its | ||
28 | remote-endpoint set to the phandle of the connected DSI input endpoint. | ||
29 | See Documentation/devicetree/bindings/graph.txt for more device graph info. | ||
30 | |||
31 | Optional properties: | ||
32 | - dma-coherent: Present if dma operations are coherent. | ||
33 | |||
34 | |||
35 | A example of HiKey board hi6220 SoC specific DT entry: | ||
36 | Example: | ||
37 | |||
38 | ade: ade@f4100000 { | ||
39 | compatible = "hisilicon,hi6220-ade"; | ||
40 | reg = <0x0 0xf4100000 0x0 0x7800>; | ||
41 | reg-names = "ade_base"; | ||
42 | hisilicon,noc-syscon = <&medianoc_ade>; | ||
43 | resets = <&media_ctrl MEDIA_ADE>; | ||
44 | interrupts = <0 115 4>; /* ldi interrupt */ | ||
45 | |||
46 | clocks = <&media_ctrl HI6220_ADE_CORE>, | ||
47 | <&media_ctrl HI6220_CODEC_JPEG>, | ||
48 | <&media_ctrl HI6220_ADE_PIX_SRC>; | ||
49 | /*clock name*/ | ||
50 | clock-names = "clk_ade_core", | ||
51 | "clk_codec_jpeg", | ||
52 | "clk_ade_pix"; | ||
53 | |||
54 | assigned-clocks = <&media_ctrl HI6220_ADE_CORE>, | ||
55 | <&media_ctrl HI6220_CODEC_JPEG>; | ||
56 | assigned-clock-rates = <360000000>, <288000000>; | ||
57 | dma-coherent; | ||
58 | |||
59 | port { | ||
60 | ade_out: endpoint { | ||
61 | remote-endpoint = <&dsi_in>; | ||
62 | }; | ||
63 | }; | ||
64 | }; | ||
diff --git a/MAINTAINERS b/MAINTAINERS index d1d6498af02f..f262128644e6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS | |||
@@ -3853,6 +3853,16 @@ T: git git://github.com/patjak/drm-gma500 | |||
3853 | S: Maintained | 3853 | S: Maintained |
3854 | F: drivers/gpu/drm/gma500/ | 3854 | F: drivers/gpu/drm/gma500/ |
3855 | 3855 | ||
3856 | DRM DRIVERS FOR HISILICON | ||
3857 | M: Xinliang Liu <z.liuxinliang@hisilicon.com> | ||
3858 | R: Xinwei Kong <kong.kongxinwei@hisilicon.com> | ||
3859 | R: Chen Feng <puck.chen@hisilicon.com> | ||
3860 | L: dri-devel@lists.freedesktop.org | ||
3861 | T: git git://github.com/xin3liang/linux.git | ||
3862 | S: Maintained | ||
3863 | F: drivers/gpu/drm/hisilicon/ | ||
3864 | F: Documentation/devicetree/bindings/display/hisilicon/ | ||
3865 | |||
3856 | DRM DRIVERS FOR NVIDIA TEGRA | 3866 | DRM DRIVERS FOR NVIDIA TEGRA |
3857 | M: Thierry Reding <thierry.reding@gmail.com> | 3867 | M: Thierry Reding <thierry.reding@gmail.com> |
3858 | M: Terje Bergström <tbergstrom@nvidia.com> | 3868 | M: Terje Bergström <tbergstrom@nvidia.com> |
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index cd515029bb49..87f9ab68e8d1 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig | |||
@@ -285,3 +285,5 @@ source "drivers/gpu/drm/vc4/Kconfig" | |||
285 | source "drivers/gpu/drm/etnaviv/Kconfig" | 285 | source "drivers/gpu/drm/etnaviv/Kconfig" |
286 | 286 | ||
287 | source "drivers/gpu/drm/arc/Kconfig" | 287 | source "drivers/gpu/drm/arc/Kconfig" |
288 | |||
289 | source "drivers/gpu/drm/hisilicon/Kconfig" | ||
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 1a26b4eb1ce0..43c2abf425ee 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile | |||
@@ -80,3 +80,4 @@ obj-y += bridge/ | |||
80 | obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/ | 80 | obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/ |
81 | obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/ | 81 | obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/ |
82 | obj-$(CONFIG_DRM_ARCPGU)+= arc/ | 82 | obj-$(CONFIG_DRM_ARCPGU)+= arc/ |
83 | obj-y += hisilicon/ | ||
diff --git a/drivers/gpu/drm/hisilicon/Kconfig b/drivers/gpu/drm/hisilicon/Kconfig new file mode 100644 index 000000000000..558c61b1b8e8 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/Kconfig | |||
@@ -0,0 +1,5 @@ | |||
1 | # | ||
2 | # hisilicon drm device configuration. | ||
3 | # Please keep this list sorted alphabetically | ||
4 | |||
5 | source "drivers/gpu/drm/hisilicon/kirin/Kconfig" | ||
diff --git a/drivers/gpu/drm/hisilicon/Makefile b/drivers/gpu/drm/hisilicon/Makefile new file mode 100644 index 000000000000..e3f6d493c996 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/Makefile | |||
@@ -0,0 +1,5 @@ | |||
1 | # | ||
2 | # Makefile for hisilicon drm drivers. | ||
3 | # Please keep this list sorted alphabetically | ||
4 | |||
5 | obj-$(CONFIG_DRM_HISI_KIRIN) += kirin/ | ||
diff --git a/drivers/gpu/drm/hisilicon/kirin/Kconfig b/drivers/gpu/drm/hisilicon/kirin/Kconfig new file mode 100644 index 000000000000..ea0df6115f7e --- /dev/null +++ b/drivers/gpu/drm/hisilicon/kirin/Kconfig | |||
@@ -0,0 +1,18 @@ | |||
1 | config DRM_HISI_KIRIN | ||
2 | tristate "DRM Support for Hisilicon Kirin series SoCs Platform" | ||
3 | depends on DRM && OF && ARM64 | ||
4 | select DRM_KMS_HELPER | ||
5 | select DRM_GEM_CMA_HELPER | ||
6 | select DRM_KMS_CMA_HELPER | ||
7 | help | ||
8 | Choose this option if you have a hisilicon Kirin chipsets(hi6220). | ||
9 | If M is selected the module will be called kirin-drm. | ||
10 | |||
11 | config HISI_KIRIN_DW_DSI | ||
12 | tristate "HiSilicon Kirin specific extensions for Synopsys DW MIPI DSI" | ||
13 | depends on DRM_HISI_KIRIN | ||
14 | select DRM_MIPI_DSI | ||
15 | help | ||
16 | This selects support for HiSilicon Kirin SoC specific extensions for | ||
17 | the Synopsys DesignWare DSI driver. If you want to enable MIPI DSI on | ||
18 | hi6220 based SoC, you should selet this option. | ||
diff --git a/drivers/gpu/drm/hisilicon/kirin/Makefile b/drivers/gpu/drm/hisilicon/kirin/Makefile new file mode 100644 index 000000000000..cdf61589485c --- /dev/null +++ b/drivers/gpu/drm/hisilicon/kirin/Makefile | |||
@@ -0,0 +1,6 @@ | |||
1 | kirin-drm-y := kirin_drm_drv.o \ | ||
2 | kirin_drm_ade.o | ||
3 | |||
4 | obj-$(CONFIG_DRM_HISI_KIRIN) += kirin-drm.o | ||
5 | |||
6 | obj-$(CONFIG_HISI_KIRIN_DW_DSI) += dw_drm_dsi.o | ||
diff --git a/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c b/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c new file mode 100644 index 000000000000..bfbc2159250d --- /dev/null +++ b/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c | |||
@@ -0,0 +1,857 @@ | |||
1 | /* | ||
2 | * DesignWare MIPI DSI Host Controller v1.02 driver | ||
3 | * | ||
4 | * Copyright (c) 2016 Linaro Limited. | ||
5 | * Copyright (c) 2014-2016 Hisilicon Limited. | ||
6 | * | ||
7 | * Author: | ||
8 | * Xinliang Liu <z.liuxinliang@hisilicon.com> | ||
9 | * Xinliang Liu <xinliang.liu@linaro.org> | ||
10 | * Xinwei Kong <kong.kongxinwei@hisilicon.com> | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or modify | ||
13 | * it under the terms of the GNU General Public License version 2 as | ||
14 | * published by the Free Software Foundation. | ||
15 | * | ||
16 | */ | ||
17 | |||
18 | #include <linux/clk.h> | ||
19 | #include <linux/component.h> | ||
20 | #include <linux/of_graph.h> | ||
21 | |||
22 | #include <drm/drm_of.h> | ||
23 | #include <drm/drm_crtc_helper.h> | ||
24 | #include <drm/drm_mipi_dsi.h> | ||
25 | #include <drm/drm_encoder_slave.h> | ||
26 | #include <drm/drm_atomic_helper.h> | ||
27 | |||
28 | #include "dw_dsi_reg.h" | ||
29 | |||
30 | #define MAX_TX_ESC_CLK 10 | ||
31 | #define ROUND(x, y) ((x) / (y) + \ | ||
32 | ((x) % (y) * 10 / (y) >= 5 ? 1 : 0)) | ||
33 | #define PHY_REF_CLK_RATE 19200000 | ||
34 | #define PHY_REF_CLK_PERIOD_PS (1000000000 / (PHY_REF_CLK_RATE / 1000)) | ||
35 | |||
36 | #define encoder_to_dsi(encoder) \ | ||
37 | container_of(encoder, struct dw_dsi, encoder) | ||
38 | #define host_to_dsi(host) \ | ||
39 | container_of(host, struct dw_dsi, host) | ||
40 | |||
41 | struct mipi_phy_params { | ||
42 | u32 clk_t_lpx; | ||
43 | u32 clk_t_hs_prepare; | ||
44 | u32 clk_t_hs_zero; | ||
45 | u32 clk_t_hs_trial; | ||
46 | u32 clk_t_wakeup; | ||
47 | u32 data_t_lpx; | ||
48 | u32 data_t_hs_prepare; | ||
49 | u32 data_t_hs_zero; | ||
50 | u32 data_t_hs_trial; | ||
51 | u32 data_t_ta_go; | ||
52 | u32 data_t_ta_get; | ||
53 | u32 data_t_wakeup; | ||
54 | u32 hstx_ckg_sel; | ||
55 | u32 pll_fbd_div5f; | ||
56 | u32 pll_fbd_div1f; | ||
57 | u32 pll_fbd_2p; | ||
58 | u32 pll_enbwt; | ||
59 | u32 pll_fbd_p; | ||
60 | u32 pll_fbd_s; | ||
61 | u32 pll_pre_div1p; | ||
62 | u32 pll_pre_p; | ||
63 | u32 pll_vco_750M; | ||
64 | u32 pll_lpf_rs; | ||
65 | u32 pll_lpf_cs; | ||
66 | u32 clklp2hs_time; | ||
67 | u32 clkhs2lp_time; | ||
68 | u32 lp2hs_time; | ||
69 | u32 hs2lp_time; | ||
70 | u32 clk_to_data_delay; | ||
71 | u32 data_to_clk_delay; | ||
72 | u32 lane_byte_clk_kHz; | ||
73 | u32 clk_division; | ||
74 | }; | ||
75 | |||
76 | struct dsi_hw_ctx { | ||
77 | void __iomem *base; | ||
78 | struct clk *pclk; | ||
79 | }; | ||
80 | |||
81 | struct dw_dsi { | ||
82 | struct drm_encoder encoder; | ||
83 | struct drm_bridge *bridge; | ||
84 | struct mipi_dsi_host host; | ||
85 | struct drm_display_mode cur_mode; | ||
86 | struct dsi_hw_ctx *ctx; | ||
87 | struct mipi_phy_params phy; | ||
88 | |||
89 | u32 lanes; | ||
90 | enum mipi_dsi_pixel_format format; | ||
91 | unsigned long mode_flags; | ||
92 | bool enable; | ||
93 | }; | ||
94 | |||
95 | struct dsi_data { | ||
96 | struct dw_dsi dsi; | ||
97 | struct dsi_hw_ctx ctx; | ||
98 | }; | ||
99 | |||
100 | struct dsi_phy_range { | ||
101 | u32 min_range_kHz; | ||
102 | u32 max_range_kHz; | ||
103 | u32 pll_vco_750M; | ||
104 | u32 hstx_ckg_sel; | ||
105 | }; | ||
106 | |||
107 | static const struct dsi_phy_range dphy_range_info[] = { | ||
108 | { 46875, 62500, 1, 7 }, | ||
109 | { 62500, 93750, 0, 7 }, | ||
110 | { 93750, 125000, 1, 6 }, | ||
111 | { 125000, 187500, 0, 6 }, | ||
112 | { 187500, 250000, 1, 5 }, | ||
113 | { 250000, 375000, 0, 5 }, | ||
114 | { 375000, 500000, 1, 4 }, | ||
115 | { 500000, 750000, 0, 4 }, | ||
116 | { 750000, 1000000, 1, 0 }, | ||
117 | { 1000000, 1500000, 0, 0 } | ||
118 | }; | ||
119 | |||
120 | static u32 dsi_calc_phy_rate(u32 req_kHz, struct mipi_phy_params *phy) | ||
121 | { | ||
122 | u32 ref_clk_ps = PHY_REF_CLK_PERIOD_PS; | ||
123 | u32 tmp_kHz = req_kHz; | ||
124 | u32 i = 0; | ||
125 | u32 q_pll = 1; | ||
126 | u32 m_pll = 0; | ||
127 | u32 n_pll = 0; | ||
128 | u32 r_pll = 1; | ||
129 | u32 m_n = 0; | ||
130 | u32 m_n_int = 0; | ||
131 | u32 f_kHz = 0; | ||
132 | u64 temp; | ||
133 | |||
134 | /* | ||
135 | * Find a rate >= req_kHz. | ||
136 | */ | ||
137 | do { | ||
138 | f_kHz = tmp_kHz; | ||
139 | |||
140 | for (i = 0; i < ARRAY_SIZE(dphy_range_info); i++) | ||
141 | if (f_kHz >= dphy_range_info[i].min_range_kHz && | ||
142 | f_kHz <= dphy_range_info[i].max_range_kHz) | ||
143 | break; | ||
144 | |||
145 | if (i == ARRAY_SIZE(dphy_range_info)) { | ||
146 | DRM_ERROR("%dkHz out of range\n", f_kHz); | ||
147 | return 0; | ||
148 | } | ||
149 | |||
150 | phy->pll_vco_750M = dphy_range_info[i].pll_vco_750M; | ||
151 | phy->hstx_ckg_sel = dphy_range_info[i].hstx_ckg_sel; | ||
152 | |||
153 | if (phy->hstx_ckg_sel <= 7 && | ||
154 | phy->hstx_ckg_sel >= 4) | ||
155 | q_pll = 0x10 >> (7 - phy->hstx_ckg_sel); | ||
156 | |||
157 | temp = f_kHz * (u64)q_pll * (u64)ref_clk_ps; | ||
158 | m_n_int = temp / (u64)1000000000; | ||
159 | m_n = (temp % (u64)1000000000) / (u64)100000000; | ||
160 | |||
161 | if (m_n_int % 2 == 0) { | ||
162 | if (m_n * 6 >= 50) { | ||
163 | n_pll = 2; | ||
164 | m_pll = (m_n_int + 1) * n_pll; | ||
165 | } else if (m_n * 6 >= 30) { | ||
166 | n_pll = 3; | ||
167 | m_pll = m_n_int * n_pll + 2; | ||
168 | } else { | ||
169 | n_pll = 1; | ||
170 | m_pll = m_n_int * n_pll; | ||
171 | } | ||
172 | } else { | ||
173 | if (m_n * 6 >= 50) { | ||
174 | n_pll = 1; | ||
175 | m_pll = (m_n_int + 1) * n_pll; | ||
176 | } else if (m_n * 6 >= 30) { | ||
177 | n_pll = 1; | ||
178 | m_pll = (m_n_int + 1) * n_pll; | ||
179 | } else if (m_n * 6 >= 10) { | ||
180 | n_pll = 3; | ||
181 | m_pll = m_n_int * n_pll + 1; | ||
182 | } else { | ||
183 | n_pll = 2; | ||
184 | m_pll = m_n_int * n_pll; | ||
185 | } | ||
186 | } | ||
187 | |||
188 | if (n_pll == 1) { | ||
189 | phy->pll_fbd_p = 0; | ||
190 | phy->pll_pre_div1p = 1; | ||
191 | } else { | ||
192 | phy->pll_fbd_p = n_pll; | ||
193 | phy->pll_pre_div1p = 0; | ||
194 | } | ||
195 | |||
196 | if (phy->pll_fbd_2p <= 7 && phy->pll_fbd_2p >= 4) | ||
197 | r_pll = 0x10 >> (7 - phy->pll_fbd_2p); | ||
198 | |||
199 | if (m_pll == 2) { | ||
200 | phy->pll_pre_p = 0; | ||
201 | phy->pll_fbd_s = 0; | ||
202 | phy->pll_fbd_div1f = 0; | ||
203 | phy->pll_fbd_div5f = 1; | ||
204 | } else if (m_pll >= 2 * 2 * r_pll && m_pll <= 2 * 4 * r_pll) { | ||
205 | phy->pll_pre_p = m_pll / (2 * r_pll); | ||
206 | phy->pll_fbd_s = 0; | ||
207 | phy->pll_fbd_div1f = 1; | ||
208 | phy->pll_fbd_div5f = 0; | ||
209 | } else if (m_pll >= 2 * 5 * r_pll && m_pll <= 2 * 150 * r_pll) { | ||
210 | if (((m_pll / (2 * r_pll)) % 2) == 0) { | ||
211 | phy->pll_pre_p = | ||
212 | (m_pll / (2 * r_pll)) / 2 - 1; | ||
213 | phy->pll_fbd_s = | ||
214 | (m_pll / (2 * r_pll)) % 2 + 2; | ||
215 | } else { | ||
216 | phy->pll_pre_p = | ||
217 | (m_pll / (2 * r_pll)) / 2; | ||
218 | phy->pll_fbd_s = | ||
219 | (m_pll / (2 * r_pll)) % 2; | ||
220 | } | ||
221 | phy->pll_fbd_div1f = 0; | ||
222 | phy->pll_fbd_div5f = 0; | ||
223 | } else { | ||
224 | phy->pll_pre_p = 0; | ||
225 | phy->pll_fbd_s = 0; | ||
226 | phy->pll_fbd_div1f = 0; | ||
227 | phy->pll_fbd_div5f = 1; | ||
228 | } | ||
229 | |||
230 | f_kHz = (u64)1000000000 * (u64)m_pll / | ||
231 | ((u64)ref_clk_ps * (u64)n_pll * (u64)q_pll); | ||
232 | |||
233 | if (f_kHz >= req_kHz) | ||
234 | break; | ||
235 | |||
236 | tmp_kHz += 10; | ||
237 | |||
238 | } while (true); | ||
239 | |||
240 | return f_kHz; | ||
241 | } | ||
242 | |||
243 | static void dsi_get_phy_params(u32 phy_req_kHz, | ||
244 | struct mipi_phy_params *phy) | ||
245 | { | ||
246 | u32 ref_clk_ps = PHY_REF_CLK_PERIOD_PS; | ||
247 | u32 phy_rate_kHz; | ||
248 | u32 ui; | ||
249 | |||
250 | memset(phy, 0, sizeof(*phy)); | ||
251 | |||
252 | phy_rate_kHz = dsi_calc_phy_rate(phy_req_kHz, phy); | ||
253 | if (!phy_rate_kHz) | ||
254 | return; | ||
255 | |||
256 | ui = 1000000 / phy_rate_kHz; | ||
257 | |||
258 | phy->clk_t_lpx = ROUND(50, 8 * ui); | ||
259 | phy->clk_t_hs_prepare = ROUND(133, 16 * ui) - 1; | ||
260 | |||
261 | phy->clk_t_hs_zero = ROUND(262, 8 * ui); | ||
262 | phy->clk_t_hs_trial = 2 * (ROUND(60, 8 * ui) - 1); | ||
263 | phy->clk_t_wakeup = ROUND(1000000, (ref_clk_ps / 1000) - 1); | ||
264 | if (phy->clk_t_wakeup > 0xff) | ||
265 | phy->clk_t_wakeup = 0xff; | ||
266 | phy->data_t_wakeup = phy->clk_t_wakeup; | ||
267 | phy->data_t_lpx = phy->clk_t_lpx; | ||
268 | phy->data_t_hs_prepare = ROUND(125 + 10 * ui, 16 * ui) - 1; | ||
269 | phy->data_t_hs_zero = ROUND(105 + 6 * ui, 8 * ui); | ||
270 | phy->data_t_hs_trial = 2 * (ROUND(60 + 4 * ui, 8 * ui) - 1); | ||
271 | phy->data_t_ta_go = 3; | ||
272 | phy->data_t_ta_get = 4; | ||
273 | |||
274 | phy->pll_enbwt = 1; | ||
275 | phy->clklp2hs_time = ROUND(407, 8 * ui) + 12; | ||
276 | phy->clkhs2lp_time = ROUND(105 + 12 * ui, 8 * ui); | ||
277 | phy->lp2hs_time = ROUND(240 + 12 * ui, 8 * ui) + 1; | ||
278 | phy->hs2lp_time = phy->clkhs2lp_time; | ||
279 | phy->clk_to_data_delay = 1 + phy->clklp2hs_time; | ||
280 | phy->data_to_clk_delay = ROUND(60 + 52 * ui, 8 * ui) + | ||
281 | phy->clkhs2lp_time; | ||
282 | |||
283 | phy->lane_byte_clk_kHz = phy_rate_kHz / 8; | ||
284 | phy->clk_division = | ||
285 | DIV_ROUND_UP(phy->lane_byte_clk_kHz, MAX_TX_ESC_CLK); | ||
286 | } | ||
287 | |||
288 | static u32 dsi_get_dpi_color_coding(enum mipi_dsi_pixel_format format) | ||
289 | { | ||
290 | u32 val; | ||
291 | |||
292 | /* | ||
293 | * TODO: only support RGB888 now, to support more | ||
294 | */ | ||
295 | switch (format) { | ||
296 | case MIPI_DSI_FMT_RGB888: | ||
297 | val = DSI_24BITS_1; | ||
298 | break; | ||
299 | default: | ||
300 | val = DSI_24BITS_1; | ||
301 | break; | ||
302 | } | ||
303 | |||
304 | return val; | ||
305 | } | ||
306 | |||
307 | /* | ||
308 | * dsi phy reg write function | ||
309 | */ | ||
310 | static void dsi_phy_tst_set(void __iomem *base, u32 reg, u32 val) | ||
311 | { | ||
312 | u32 reg_write = 0x10000 + reg; | ||
313 | |||
314 | /* | ||
315 | * latch reg first | ||
316 | */ | ||
317 | writel(reg_write, base + PHY_TST_CTRL1); | ||
318 | writel(0x02, base + PHY_TST_CTRL0); | ||
319 | writel(0x00, base + PHY_TST_CTRL0); | ||
320 | |||
321 | /* | ||
322 | * then latch value | ||
323 | */ | ||
324 | writel(val, base + PHY_TST_CTRL1); | ||
325 | writel(0x02, base + PHY_TST_CTRL0); | ||
326 | writel(0x00, base + PHY_TST_CTRL0); | ||
327 | } | ||
328 | |||
329 | static void dsi_set_phy_timer(void __iomem *base, | ||
330 | struct mipi_phy_params *phy, | ||
331 | u32 lanes) | ||
332 | { | ||
333 | u32 val; | ||
334 | |||
335 | /* | ||
336 | * Set lane value and phy stop wait time. | ||
337 | */ | ||
338 | val = (lanes - 1) | (PHY_STOP_WAIT_TIME << 8); | ||
339 | writel(val, base + PHY_IF_CFG); | ||
340 | |||
341 | /* | ||
342 | * Set phy clk division. | ||
343 | */ | ||
344 | val = readl(base + CLKMGR_CFG) | phy->clk_division; | ||
345 | writel(val, base + CLKMGR_CFG); | ||
346 | |||
347 | /* | ||
348 | * Set lp and hs switching params. | ||
349 | */ | ||
350 | dw_update_bits(base + PHY_TMR_CFG, 24, MASK(8), phy->hs2lp_time); | ||
351 | dw_update_bits(base + PHY_TMR_CFG, 16, MASK(8), phy->lp2hs_time); | ||
352 | dw_update_bits(base + PHY_TMR_LPCLK_CFG, 16, MASK(10), | ||
353 | phy->clkhs2lp_time); | ||
354 | dw_update_bits(base + PHY_TMR_LPCLK_CFG, 0, MASK(10), | ||
355 | phy->clklp2hs_time); | ||
356 | dw_update_bits(base + CLK_DATA_TMR_CFG, 8, MASK(8), | ||
357 | phy->data_to_clk_delay); | ||
358 | dw_update_bits(base + CLK_DATA_TMR_CFG, 0, MASK(8), | ||
359 | phy->clk_to_data_delay); | ||
360 | } | ||
361 | |||
362 | static void dsi_set_mipi_phy(void __iomem *base, | ||
363 | struct mipi_phy_params *phy, | ||
364 | u32 lanes) | ||
365 | { | ||
366 | u32 delay_count; | ||
367 | u32 val; | ||
368 | u32 i; | ||
369 | |||
370 | /* phy timer setting */ | ||
371 | dsi_set_phy_timer(base, phy, lanes); | ||
372 | |||
373 | /* | ||
374 | * Reset to clean up phy tst params. | ||
375 | */ | ||
376 | writel(0, base + PHY_RSTZ); | ||
377 | writel(0, base + PHY_TST_CTRL0); | ||
378 | writel(1, base + PHY_TST_CTRL0); | ||
379 | writel(0, base + PHY_TST_CTRL0); | ||
380 | |||
381 | /* | ||
382 | * Clock lane timing control setting: TLPX, THS-PREPARE, | ||
383 | * THS-ZERO, THS-TRAIL, TWAKEUP. | ||
384 | */ | ||
385 | dsi_phy_tst_set(base, CLK_TLPX, phy->clk_t_lpx); | ||
386 | dsi_phy_tst_set(base, CLK_THS_PREPARE, phy->clk_t_hs_prepare); | ||
387 | dsi_phy_tst_set(base, CLK_THS_ZERO, phy->clk_t_hs_zero); | ||
388 | dsi_phy_tst_set(base, CLK_THS_TRAIL, phy->clk_t_hs_trial); | ||
389 | dsi_phy_tst_set(base, CLK_TWAKEUP, phy->clk_t_wakeup); | ||
390 | |||
391 | /* | ||
392 | * Data lane timing control setting: TLPX, THS-PREPARE, | ||
393 | * THS-ZERO, THS-TRAIL, TTA-GO, TTA-GET, TWAKEUP. | ||
394 | */ | ||
395 | for (i = 0; i < lanes; i++) { | ||
396 | dsi_phy_tst_set(base, DATA_TLPX(i), phy->data_t_lpx); | ||
397 | dsi_phy_tst_set(base, DATA_THS_PREPARE(i), | ||
398 | phy->data_t_hs_prepare); | ||
399 | dsi_phy_tst_set(base, DATA_THS_ZERO(i), phy->data_t_hs_zero); | ||
400 | dsi_phy_tst_set(base, DATA_THS_TRAIL(i), phy->data_t_hs_trial); | ||
401 | dsi_phy_tst_set(base, DATA_TTA_GO(i), phy->data_t_ta_go); | ||
402 | dsi_phy_tst_set(base, DATA_TTA_GET(i), phy->data_t_ta_get); | ||
403 | dsi_phy_tst_set(base, DATA_TWAKEUP(i), phy->data_t_wakeup); | ||
404 | } | ||
405 | |||
406 | /* | ||
407 | * physical configuration: I, pll I, pll II, pll III, | ||
408 | * pll IV, pll V. | ||
409 | */ | ||
410 | dsi_phy_tst_set(base, PHY_CFG_I, phy->hstx_ckg_sel); | ||
411 | val = (phy->pll_fbd_div5f << 5) + (phy->pll_fbd_div1f << 4) + | ||
412 | (phy->pll_fbd_2p << 1) + phy->pll_enbwt; | ||
413 | dsi_phy_tst_set(base, PHY_CFG_PLL_I, val); | ||
414 | dsi_phy_tst_set(base, PHY_CFG_PLL_II, phy->pll_fbd_p); | ||
415 | dsi_phy_tst_set(base, PHY_CFG_PLL_III, phy->pll_fbd_s); | ||
416 | val = (phy->pll_pre_div1p << 7) + phy->pll_pre_p; | ||
417 | dsi_phy_tst_set(base, PHY_CFG_PLL_IV, val); | ||
418 | val = (5 << 5) + (phy->pll_vco_750M << 4) + (phy->pll_lpf_rs << 2) + | ||
419 | phy->pll_lpf_cs; | ||
420 | dsi_phy_tst_set(base, PHY_CFG_PLL_V, val); | ||
421 | |||
422 | writel(PHY_ENABLECLK, base + PHY_RSTZ); | ||
423 | udelay(1); | ||
424 | writel(PHY_ENABLECLK | PHY_UNSHUTDOWNZ, base + PHY_RSTZ); | ||
425 | udelay(1); | ||
426 | writel(PHY_ENABLECLK | PHY_UNRSTZ | PHY_UNSHUTDOWNZ, base + PHY_RSTZ); | ||
427 | usleep_range(1000, 1500); | ||
428 | |||
429 | /* | ||
430 | * wait for phy's clock ready | ||
431 | */ | ||
432 | delay_count = 100; | ||
433 | while (delay_count--) { | ||
434 | val = readl(base + PHY_STATUS); | ||
435 | if ((BIT(0) | BIT(2)) & val) | ||
436 | break; | ||
437 | |||
438 | udelay(1); | ||
439 | } | ||
440 | |||
441 | if (!delay_count) | ||
442 | DRM_INFO("phylock and phystopstateclklane is not ready.\n"); | ||
443 | } | ||
444 | |||
445 | static void dsi_set_mode_timing(void __iomem *base, | ||
446 | u32 lane_byte_clk_kHz, | ||
447 | struct drm_display_mode *mode, | ||
448 | enum mipi_dsi_pixel_format format) | ||
449 | { | ||
450 | u32 hfp, hbp, hsw, vfp, vbp, vsw; | ||
451 | u32 hline_time; | ||
452 | u32 hsa_time; | ||
453 | u32 hbp_time; | ||
454 | u32 pixel_clk_kHz; | ||
455 | int htot, vtot; | ||
456 | u32 val; | ||
457 | u64 tmp; | ||
458 | |||
459 | val = dsi_get_dpi_color_coding(format); | ||
460 | writel(val, base + DPI_COLOR_CODING); | ||
461 | |||
462 | val = (mode->flags & DRM_MODE_FLAG_NHSYNC ? 1 : 0) << 2; | ||
463 | val |= (mode->flags & DRM_MODE_FLAG_NVSYNC ? 1 : 0) << 1; | ||
464 | writel(val, base + DPI_CFG_POL); | ||
465 | |||
466 | /* | ||
467 | * The DSI IP accepts vertical timing using lines as normal, | ||
468 | * but horizontal timing is a mixture of pixel-clocks for the | ||
469 | * active region and byte-lane clocks for the blanking-related | ||
470 | * timings. hfp is specified as the total hline_time in byte- | ||
471 | * lane clocks minus hsa, hbp and active. | ||
472 | */ | ||
473 | pixel_clk_kHz = mode->clock; | ||
474 | htot = mode->htotal; | ||
475 | vtot = mode->vtotal; | ||
476 | hfp = mode->hsync_start - mode->hdisplay; | ||
477 | hbp = mode->htotal - mode->hsync_end; | ||
478 | hsw = mode->hsync_end - mode->hsync_start; | ||
479 | vfp = mode->vsync_start - mode->vdisplay; | ||
480 | vbp = mode->vtotal - mode->vsync_end; | ||
481 | vsw = mode->vsync_end - mode->vsync_start; | ||
482 | if (vsw > 15) { | ||
483 | DRM_DEBUG_DRIVER("vsw exceeded 15\n"); | ||
484 | vsw = 15; | ||
485 | } | ||
486 | |||
487 | hsa_time = (hsw * lane_byte_clk_kHz) / pixel_clk_kHz; | ||
488 | hbp_time = (hbp * lane_byte_clk_kHz) / pixel_clk_kHz; | ||
489 | tmp = (u64)htot * (u64)lane_byte_clk_kHz; | ||
490 | hline_time = DIV_ROUND_UP(tmp, pixel_clk_kHz); | ||
491 | |||
492 | /* all specified in byte-lane clocks */ | ||
493 | writel(hsa_time, base + VID_HSA_TIME); | ||
494 | writel(hbp_time, base + VID_HBP_TIME); | ||
495 | writel(hline_time, base + VID_HLINE_TIME); | ||
496 | |||
497 | writel(vsw, base + VID_VSA_LINES); | ||
498 | writel(vbp, base + VID_VBP_LINES); | ||
499 | writel(vfp, base + VID_VFP_LINES); | ||
500 | writel(mode->vdisplay, base + VID_VACTIVE_LINES); | ||
501 | writel(mode->hdisplay, base + VID_PKT_SIZE); | ||
502 | |||
503 | DRM_DEBUG_DRIVER("htot=%d, hfp=%d, hbp=%d, hsw=%d\n", | ||
504 | htot, hfp, hbp, hsw); | ||
505 | DRM_DEBUG_DRIVER("vtol=%d, vfp=%d, vbp=%d, vsw=%d\n", | ||
506 | vtot, vfp, vbp, vsw); | ||
507 | DRM_DEBUG_DRIVER("hsa_time=%d, hbp_time=%d, hline_time=%d\n", | ||
508 | hsa_time, hbp_time, hline_time); | ||
509 | } | ||
510 | |||
511 | static void dsi_set_video_mode(void __iomem *base, unsigned long flags) | ||
512 | { | ||
513 | u32 val; | ||
514 | u32 mode_mask = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | | ||
515 | MIPI_DSI_MODE_VIDEO_SYNC_PULSE; | ||
516 | u32 non_burst_sync_pulse = MIPI_DSI_MODE_VIDEO | | ||
517 | MIPI_DSI_MODE_VIDEO_SYNC_PULSE; | ||
518 | u32 non_burst_sync_event = MIPI_DSI_MODE_VIDEO; | ||
519 | |||
520 | /* | ||
521 | * choose video mode type | ||
522 | */ | ||
523 | if ((flags & mode_mask) == non_burst_sync_pulse) | ||
524 | val = DSI_NON_BURST_SYNC_PULSES; | ||
525 | else if ((flags & mode_mask) == non_burst_sync_event) | ||
526 | val = DSI_NON_BURST_SYNC_EVENTS; | ||
527 | else | ||
528 | val = DSI_BURST_SYNC_PULSES_1; | ||
529 | writel(val, base + VID_MODE_CFG); | ||
530 | |||
531 | writel(PHY_TXREQUESTCLKHS, base + LPCLK_CTRL); | ||
532 | writel(DSI_VIDEO_MODE, base + MODE_CFG); | ||
533 | } | ||
534 | |||
535 | static void dsi_mipi_init(struct dw_dsi *dsi) | ||
536 | { | ||
537 | struct dsi_hw_ctx *ctx = dsi->ctx; | ||
538 | struct mipi_phy_params *phy = &dsi->phy; | ||
539 | struct drm_display_mode *mode = &dsi->cur_mode; | ||
540 | u32 bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); | ||
541 | void __iomem *base = ctx->base; | ||
542 | u32 dphy_req_kHz; | ||
543 | |||
544 | /* | ||
545 | * count phy params | ||
546 | */ | ||
547 | dphy_req_kHz = mode->clock * bpp / dsi->lanes; | ||
548 | dsi_get_phy_params(dphy_req_kHz, phy); | ||
549 | |||
550 | /* reset Core */ | ||
551 | writel(RESET, base + PWR_UP); | ||
552 | |||
553 | /* set dsi phy params */ | ||
554 | dsi_set_mipi_phy(base, phy, dsi->lanes); | ||
555 | |||
556 | /* set dsi mode timing */ | ||
557 | dsi_set_mode_timing(base, phy->lane_byte_clk_kHz, mode, dsi->format); | ||
558 | |||
559 | /* set dsi video mode */ | ||
560 | dsi_set_video_mode(base, dsi->mode_flags); | ||
561 | |||
562 | /* dsi wake up */ | ||
563 | writel(POWERUP, base + PWR_UP); | ||
564 | |||
565 | DRM_DEBUG_DRIVER("lanes=%d, pixel_clk=%d kHz, bytes_freq=%d kHz\n", | ||
566 | dsi->lanes, mode->clock, phy->lane_byte_clk_kHz); | ||
567 | } | ||
568 | |||
569 | static void dsi_encoder_disable(struct drm_encoder *encoder) | ||
570 | { | ||
571 | struct dw_dsi *dsi = encoder_to_dsi(encoder); | ||
572 | struct dsi_hw_ctx *ctx = dsi->ctx; | ||
573 | void __iomem *base = ctx->base; | ||
574 | |||
575 | if (!dsi->enable) | ||
576 | return; | ||
577 | |||
578 | writel(0, base + PWR_UP); | ||
579 | writel(0, base + LPCLK_CTRL); | ||
580 | writel(0, base + PHY_RSTZ); | ||
581 | clk_disable_unprepare(ctx->pclk); | ||
582 | |||
583 | dsi->enable = false; | ||
584 | } | ||
585 | |||
586 | static void dsi_encoder_enable(struct drm_encoder *encoder) | ||
587 | { | ||
588 | struct dw_dsi *dsi = encoder_to_dsi(encoder); | ||
589 | struct dsi_hw_ctx *ctx = dsi->ctx; | ||
590 | int ret; | ||
591 | |||
592 | if (dsi->enable) | ||
593 | return; | ||
594 | |||
595 | ret = clk_prepare_enable(ctx->pclk); | ||
596 | if (ret) { | ||
597 | DRM_ERROR("fail to enable pclk: %d\n", ret); | ||
598 | return; | ||
599 | } | ||
600 | |||
601 | dsi_mipi_init(dsi); | ||
602 | |||
603 | dsi->enable = true; | ||
604 | } | ||
605 | |||
606 | static void dsi_encoder_mode_set(struct drm_encoder *encoder, | ||
607 | struct drm_display_mode *mode, | ||
608 | struct drm_display_mode *adj_mode) | ||
609 | { | ||
610 | struct dw_dsi *dsi = encoder_to_dsi(encoder); | ||
611 | |||
612 | drm_mode_copy(&dsi->cur_mode, adj_mode); | ||
613 | } | ||
614 | |||
615 | static int dsi_encoder_atomic_check(struct drm_encoder *encoder, | ||
616 | struct drm_crtc_state *crtc_state, | ||
617 | struct drm_connector_state *conn_state) | ||
618 | { | ||
619 | /* do nothing */ | ||
620 | return 0; | ||
621 | } | ||
622 | |||
623 | static const struct drm_encoder_helper_funcs dw_encoder_helper_funcs = { | ||
624 | .atomic_check = dsi_encoder_atomic_check, | ||
625 | .mode_set = dsi_encoder_mode_set, | ||
626 | .enable = dsi_encoder_enable, | ||
627 | .disable = dsi_encoder_disable | ||
628 | }; | ||
629 | |||
630 | static const struct drm_encoder_funcs dw_encoder_funcs = { | ||
631 | .destroy = drm_encoder_cleanup, | ||
632 | }; | ||
633 | |||
634 | static int dw_drm_encoder_init(struct device *dev, | ||
635 | struct drm_device *drm_dev, | ||
636 | struct drm_encoder *encoder) | ||
637 | { | ||
638 | int ret; | ||
639 | u32 crtc_mask = drm_of_find_possible_crtcs(drm_dev, dev->of_node); | ||
640 | |||
641 | if (!crtc_mask) { | ||
642 | DRM_ERROR("failed to find crtc mask\n"); | ||
643 | return -EINVAL; | ||
644 | } | ||
645 | |||
646 | encoder->possible_crtcs = crtc_mask; | ||
647 | ret = drm_encoder_init(drm_dev, encoder, &dw_encoder_funcs, | ||
648 | DRM_MODE_ENCODER_DSI, NULL); | ||
649 | if (ret) { | ||
650 | DRM_ERROR("failed to init dsi encoder\n"); | ||
651 | return ret; | ||
652 | } | ||
653 | |||
654 | drm_encoder_helper_add(encoder, &dw_encoder_helper_funcs); | ||
655 | |||
656 | return 0; | ||
657 | } | ||
658 | |||
659 | static int dsi_host_attach(struct mipi_dsi_host *host, | ||
660 | struct mipi_dsi_device *mdsi) | ||
661 | { | ||
662 | struct dw_dsi *dsi = host_to_dsi(host); | ||
663 | |||
664 | if (mdsi->lanes < 1 || mdsi->lanes > 4) { | ||
665 | DRM_ERROR("dsi device params invalid\n"); | ||
666 | return -EINVAL; | ||
667 | } | ||
668 | |||
669 | dsi->lanes = mdsi->lanes; | ||
670 | dsi->format = mdsi->format; | ||
671 | dsi->mode_flags = mdsi->mode_flags; | ||
672 | |||
673 | return 0; | ||
674 | } | ||
675 | |||
676 | static int dsi_host_detach(struct mipi_dsi_host *host, | ||
677 | struct mipi_dsi_device *mdsi) | ||
678 | { | ||
679 | /* do nothing */ | ||
680 | return 0; | ||
681 | } | ||
682 | |||
683 | static const struct mipi_dsi_host_ops dsi_host_ops = { | ||
684 | .attach = dsi_host_attach, | ||
685 | .detach = dsi_host_detach, | ||
686 | }; | ||
687 | |||
688 | static int dsi_host_init(struct device *dev, struct dw_dsi *dsi) | ||
689 | { | ||
690 | struct mipi_dsi_host *host = &dsi->host; | ||
691 | int ret; | ||
692 | |||
693 | host->dev = dev; | ||
694 | host->ops = &dsi_host_ops; | ||
695 | ret = mipi_dsi_host_register(host); | ||
696 | if (ret) { | ||
697 | DRM_ERROR("failed to register dsi host\n"); | ||
698 | return ret; | ||
699 | } | ||
700 | |||
701 | return 0; | ||
702 | } | ||
703 | |||
704 | static int dsi_bridge_init(struct drm_device *dev, struct dw_dsi *dsi) | ||
705 | { | ||
706 | struct drm_encoder *encoder = &dsi->encoder; | ||
707 | struct drm_bridge *bridge = dsi->bridge; | ||
708 | int ret; | ||
709 | |||
710 | /* associate the bridge to dsi encoder */ | ||
711 | encoder->bridge = bridge; | ||
712 | bridge->encoder = encoder; | ||
713 | |||
714 | ret = drm_bridge_attach(dev, bridge); | ||
715 | if (ret) { | ||
716 | DRM_ERROR("failed to attach external bridge\n"); | ||
717 | return ret; | ||
718 | } | ||
719 | |||
720 | return 0; | ||
721 | } | ||
722 | |||
723 | static int dsi_bind(struct device *dev, struct device *master, void *data) | ||
724 | { | ||
725 | struct dsi_data *ddata = dev_get_drvdata(dev); | ||
726 | struct dw_dsi *dsi = &ddata->dsi; | ||
727 | struct drm_device *drm_dev = data; | ||
728 | int ret; | ||
729 | |||
730 | ret = dw_drm_encoder_init(dev, drm_dev, &dsi->encoder); | ||
731 | if (ret) | ||
732 | return ret; | ||
733 | |||
734 | ret = dsi_host_init(dev, dsi); | ||
735 | if (ret) | ||
736 | return ret; | ||
737 | |||
738 | ret = dsi_bridge_init(drm_dev, dsi); | ||
739 | if (ret) | ||
740 | return ret; | ||
741 | |||
742 | return 0; | ||
743 | } | ||
744 | |||
745 | static void dsi_unbind(struct device *dev, struct device *master, void *data) | ||
746 | { | ||
747 | /* do nothing */ | ||
748 | } | ||
749 | |||
750 | static const struct component_ops dsi_ops = { | ||
751 | .bind = dsi_bind, | ||
752 | .unbind = dsi_unbind, | ||
753 | }; | ||
754 | |||
755 | static int dsi_parse_dt(struct platform_device *pdev, struct dw_dsi *dsi) | ||
756 | { | ||
757 | struct dsi_hw_ctx *ctx = dsi->ctx; | ||
758 | struct device_node *np = pdev->dev.of_node; | ||
759 | struct device_node *endpoint, *bridge_node; | ||
760 | struct drm_bridge *bridge; | ||
761 | struct resource *res; | ||
762 | |||
763 | /* | ||
764 | * Get the endpoint node. In our case, dsi has one output port1 | ||
765 | * to which the external HDMI bridge is connected. | ||
766 | */ | ||
767 | endpoint = of_graph_get_endpoint_by_regs(np, 1, -1); | ||
768 | if (!endpoint) { | ||
769 | DRM_ERROR("no valid endpoint node\n"); | ||
770 | return -ENODEV; | ||
771 | } | ||
772 | of_node_put(endpoint); | ||
773 | |||
774 | bridge_node = of_graph_get_remote_port_parent(endpoint); | ||
775 | if (!bridge_node) { | ||
776 | DRM_ERROR("no valid bridge node\n"); | ||
777 | return -ENODEV; | ||
778 | } | ||
779 | of_node_put(bridge_node); | ||
780 | |||
781 | bridge = of_drm_find_bridge(bridge_node); | ||
782 | if (!bridge) { | ||
783 | DRM_INFO("wait for external HDMI bridge driver.\n"); | ||
784 | return -EPROBE_DEFER; | ||
785 | } | ||
786 | dsi->bridge = bridge; | ||
787 | |||
788 | ctx->pclk = devm_clk_get(&pdev->dev, "pclk"); | ||
789 | if (IS_ERR(ctx->pclk)) { | ||
790 | DRM_ERROR("failed to get pclk clock\n"); | ||
791 | return PTR_ERR(ctx->pclk); | ||
792 | } | ||
793 | |||
794 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
795 | ctx->base = devm_ioremap_resource(&pdev->dev, res); | ||
796 | if (IS_ERR(ctx->base)) { | ||
797 | DRM_ERROR("failed to remap dsi io region\n"); | ||
798 | return PTR_ERR(ctx->base); | ||
799 | } | ||
800 | |||
801 | return 0; | ||
802 | } | ||
803 | |||
804 | static int dsi_probe(struct platform_device *pdev) | ||
805 | { | ||
806 | struct dsi_data *data; | ||
807 | struct dw_dsi *dsi; | ||
808 | struct dsi_hw_ctx *ctx; | ||
809 | int ret; | ||
810 | |||
811 | data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); | ||
812 | if (!data) { | ||
813 | DRM_ERROR("failed to allocate dsi data.\n"); | ||
814 | return -ENOMEM; | ||
815 | } | ||
816 | dsi = &data->dsi; | ||
817 | ctx = &data->ctx; | ||
818 | dsi->ctx = ctx; | ||
819 | |||
820 | ret = dsi_parse_dt(pdev, dsi); | ||
821 | if (ret) | ||
822 | return ret; | ||
823 | |||
824 | platform_set_drvdata(pdev, data); | ||
825 | |||
826 | return component_add(&pdev->dev, &dsi_ops); | ||
827 | } | ||
828 | |||
829 | static int dsi_remove(struct platform_device *pdev) | ||
830 | { | ||
831 | component_del(&pdev->dev, &dsi_ops); | ||
832 | |||
833 | return 0; | ||
834 | } | ||
835 | |||
836 | static const struct of_device_id dsi_of_match[] = { | ||
837 | {.compatible = "hisilicon,hi6220-dsi"}, | ||
838 | { } | ||
839 | }; | ||
840 | MODULE_DEVICE_TABLE(of, dsi_of_match); | ||
841 | |||
842 | static struct platform_driver dsi_driver = { | ||
843 | .probe = dsi_probe, | ||
844 | .remove = dsi_remove, | ||
845 | .driver = { | ||
846 | .name = "dw-dsi", | ||
847 | .of_match_table = dsi_of_match, | ||
848 | }, | ||
849 | }; | ||
850 | |||
851 | module_platform_driver(dsi_driver); | ||
852 | |||
853 | MODULE_AUTHOR("Xinliang Liu <xinliang.liu@linaro.org>"); | ||
854 | MODULE_AUTHOR("Xinliang Liu <z.liuxinliang@hisilicon.com>"); | ||
855 | MODULE_AUTHOR("Xinwei Kong <kong.kongxinwei@hisilicon.com>"); | ||
856 | MODULE_DESCRIPTION("DesignWare MIPI DSI Host Controller v1.02 driver"); | ||
857 | MODULE_LICENSE("GPL v2"); | ||
diff --git a/drivers/gpu/drm/hisilicon/kirin/dw_dsi_reg.h b/drivers/gpu/drm/hisilicon/kirin/dw_dsi_reg.h new file mode 100644 index 000000000000..18808fc9f362 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/kirin/dw_dsi_reg.h | |||
@@ -0,0 +1,103 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2016 Linaro Limited. | ||
3 | * Copyright (c) 2014-2016 Hisilicon Limited. | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License version 2 as | ||
7 | * published by the Free Software Foundation. | ||
8 | * | ||
9 | */ | ||
10 | |||
11 | #ifndef __DW_DSI_REG_H__ | ||
12 | #define __DW_DSI_REG_H__ | ||
13 | |||
14 | #define MASK(x) (BIT(x) - 1) | ||
15 | |||
16 | /* | ||
17 | * regs | ||
18 | */ | ||
19 | #define PWR_UP 0x04 /* Core power-up */ | ||
20 | #define RESET 0 | ||
21 | #define POWERUP BIT(0) | ||
22 | #define PHY_IF_CFG 0xA4 /* D-PHY interface configuration */ | ||
23 | #define CLKMGR_CFG 0x08 /* the internal clock dividers */ | ||
24 | #define PHY_RSTZ 0xA0 /* D-PHY reset control */ | ||
25 | #define PHY_ENABLECLK BIT(2) | ||
26 | #define PHY_UNRSTZ BIT(1) | ||
27 | #define PHY_UNSHUTDOWNZ BIT(0) | ||
28 | #define PHY_TST_CTRL0 0xB4 /* D-PHY test interface control 0 */ | ||
29 | #define PHY_TST_CTRL1 0xB8 /* D-PHY test interface control 1 */ | ||
30 | #define CLK_TLPX 0x10 | ||
31 | #define CLK_THS_PREPARE 0x11 | ||
32 | #define CLK_THS_ZERO 0x12 | ||
33 | #define CLK_THS_TRAIL 0x13 | ||
34 | #define CLK_TWAKEUP 0x14 | ||
35 | #define DATA_TLPX(x) (0x20 + ((x) << 4)) | ||
36 | #define DATA_THS_PREPARE(x) (0x21 + ((x) << 4)) | ||
37 | #define DATA_THS_ZERO(x) (0x22 + ((x) << 4)) | ||
38 | #define DATA_THS_TRAIL(x) (0x23 + ((x) << 4)) | ||
39 | #define DATA_TTA_GO(x) (0x24 + ((x) << 4)) | ||
40 | #define DATA_TTA_GET(x) (0x25 + ((x) << 4)) | ||
41 | #define DATA_TWAKEUP(x) (0x26 + ((x) << 4)) | ||
42 | #define PHY_CFG_I 0x60 | ||
43 | #define PHY_CFG_PLL_I 0x63 | ||
44 | #define PHY_CFG_PLL_II 0x64 | ||
45 | #define PHY_CFG_PLL_III 0x65 | ||
46 | #define PHY_CFG_PLL_IV 0x66 | ||
47 | #define PHY_CFG_PLL_V 0x67 | ||
48 | #define DPI_COLOR_CODING 0x10 /* DPI color coding */ | ||
49 | #define DPI_CFG_POL 0x14 /* DPI polarity configuration */ | ||
50 | #define VID_HSA_TIME 0x48 /* Horizontal Sync Active time */ | ||
51 | #define VID_HBP_TIME 0x4C /* Horizontal Back Porch time */ | ||
52 | #define VID_HLINE_TIME 0x50 /* Line time */ | ||
53 | #define VID_VSA_LINES 0x54 /* Vertical Sync Active period */ | ||
54 | #define VID_VBP_LINES 0x58 /* Vertical Back Porch period */ | ||
55 | #define VID_VFP_LINES 0x5C /* Vertical Front Porch period */ | ||
56 | #define VID_VACTIVE_LINES 0x60 /* Vertical resolution */ | ||
57 | #define VID_PKT_SIZE 0x3C /* Video packet size */ | ||
58 | #define VID_MODE_CFG 0x38 /* Video mode configuration */ | ||
59 | #define PHY_TMR_CFG 0x9C /* Data lanes timing configuration */ | ||
60 | #define BTA_TO_CNT 0x8C /* Response timeout definition */ | ||
61 | #define PHY_TMR_LPCLK_CFG 0x98 /* clock lane timing configuration */ | ||
62 | #define CLK_DATA_TMR_CFG 0xCC | ||
63 | #define LPCLK_CTRL 0x94 /* Low-power in clock lane */ | ||
64 | #define PHY_TXREQUESTCLKHS BIT(0) | ||
65 | #define MODE_CFG 0x34 /* Video or Command mode selection */ | ||
66 | #define PHY_STATUS 0xB0 /* D-PHY PPI status interface */ | ||
67 | |||
68 | #define PHY_STOP_WAIT_TIME 0x30 | ||
69 | |||
70 | /* | ||
71 | * regs relevant enum | ||
72 | */ | ||
73 | enum dpi_color_coding { | ||
74 | DSI_24BITS_1 = 5, | ||
75 | }; | ||
76 | |||
77 | enum dsi_video_mode_type { | ||
78 | DSI_NON_BURST_SYNC_PULSES = 0, | ||
79 | DSI_NON_BURST_SYNC_EVENTS, | ||
80 | DSI_BURST_SYNC_PULSES_1, | ||
81 | DSI_BURST_SYNC_PULSES_2 | ||
82 | }; | ||
83 | |||
84 | enum dsi_work_mode { | ||
85 | DSI_VIDEO_MODE = 0, | ||
86 | DSI_COMMAND_MODE | ||
87 | }; | ||
88 | |||
89 | /* | ||
90 | * Register Write/Read Helper functions | ||
91 | */ | ||
92 | static inline void dw_update_bits(void __iomem *addr, u32 bit_start, | ||
93 | u32 mask, u32 val) | ||
94 | { | ||
95 | u32 tmp, orig; | ||
96 | |||
97 | orig = readl(addr); | ||
98 | tmp = orig & ~(mask << bit_start); | ||
99 | tmp |= (val & mask) << bit_start; | ||
100 | writel(tmp, addr); | ||
101 | } | ||
102 | |||
103 | #endif /* __DW_DRM_DSI_H__ */ | ||
diff --git a/drivers/gpu/drm/hisilicon/kirin/kirin_ade_reg.h b/drivers/gpu/drm/hisilicon/kirin/kirin_ade_reg.h new file mode 100644 index 000000000000..4cf281b7ed63 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/kirin/kirin_ade_reg.h | |||
@@ -0,0 +1,230 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2016 Linaro Limited. | ||
3 | * Copyright (c) 2014-2016 Hisilicon Limited. | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License version 2 as | ||
7 | * published by the Free Software Foundation. | ||
8 | * | ||
9 | */ | ||
10 | |||
11 | #ifndef __KIRIN_ADE_REG_H__ | ||
12 | #define __KIRIN_ADE_REG_H__ | ||
13 | |||
14 | /* | ||
15 | * ADE Registers | ||
16 | */ | ||
17 | #define MASK(x) (BIT(x) - 1) | ||
18 | |||
19 | #define ADE_CTRL 0x0004 | ||
20 | #define FRM_END_START_OFST 0 | ||
21 | #define FRM_END_START_MASK MASK(2) | ||
22 | #define AUTO_CLK_GATE_EN_OFST 0 | ||
23 | #define AUTO_CLK_GATE_EN BIT(0) | ||
24 | #define ADE_DISP_SRC_CFG 0x0018 | ||
25 | #define ADE_CTRL1 0x008C | ||
26 | #define ADE_EN 0x0100 | ||
27 | #define ADE_DISABLE 0 | ||
28 | #define ADE_ENABLE 1 | ||
29 | /* reset and reload regs */ | ||
30 | #define ADE_SOFT_RST_SEL(x) (0x0078 + (x) * 0x4) | ||
31 | #define ADE_RELOAD_DIS(x) (0x00AC + (x) * 0x4) | ||
32 | #define RDMA_OFST 0 | ||
33 | #define CLIP_OFST 15 | ||
34 | #define SCL_OFST 21 | ||
35 | #define CTRAN_OFST 24 | ||
36 | #define OVLY_OFST 37 /* 32+5 */ | ||
37 | /* channel regs */ | ||
38 | #define RD_CH_CTRL(x) (0x1004 + (x) * 0x80) | ||
39 | #define RD_CH_ADDR(x) (0x1008 + (x) * 0x80) | ||
40 | #define RD_CH_SIZE(x) (0x100C + (x) * 0x80) | ||
41 | #define RD_CH_STRIDE(x) (0x1010 + (x) * 0x80) | ||
42 | #define RD_CH_SPACE(x) (0x1014 + (x) * 0x80) | ||
43 | #define RD_CH_EN(x) (0x1020 + (x) * 0x80) | ||
44 | /* overlay regs */ | ||
45 | #define ADE_OVLY1_TRANS_CFG 0x002C | ||
46 | #define ADE_OVLY_CTL 0x0098 | ||
47 | #define ADE_OVLY_CH_XY0(x) (0x2004 + (x) * 4) | ||
48 | #define ADE_OVLY_CH_XY1(x) (0x2024 + (x) * 4) | ||
49 | #define ADE_OVLY_CH_CTL(x) (0x204C + (x) * 4) | ||
50 | #define ADE_OVLY_OUTPUT_SIZE(x) (0x2070 + (x) * 8) | ||
51 | #define OUTPUT_XSIZE_OFST 16 | ||
52 | #define ADE_OVLYX_CTL(x) (0x209C + (x) * 4) | ||
53 | #define CH_OVLY_SEL_OFST(x) ((x) * 4) | ||
54 | #define CH_OVLY_SEL_MASK MASK(2) | ||
55 | #define CH_OVLY_SEL_VAL(x) ((x) + 1) | ||
56 | #define CH_ALP_MODE_OFST 0 | ||
57 | #define CH_ALP_SEL_OFST 2 | ||
58 | #define CH_UNDER_ALP_SEL_OFST 4 | ||
59 | #define CH_EN_OFST 6 | ||
60 | #define CH_ALP_GBL_OFST 15 | ||
61 | #define CH_SEL_OFST 28 | ||
62 | /* ctran regs */ | ||
63 | #define ADE_CTRAN_DIS(x) (0x5004 + (x) * 0x100) | ||
64 | #define CTRAN_BYPASS_ON 1 | ||
65 | #define CTRAN_BYPASS_OFF 0 | ||
66 | #define ADE_CTRAN_IMAGE_SIZE(x) (0x503C + (x) * 0x100) | ||
67 | /* clip regs */ | ||
68 | #define ADE_CLIP_DISABLE(x) (0x6800 + (x) * 0x100) | ||
69 | #define ADE_CLIP_SIZE0(x) (0x6804 + (x) * 0x100) | ||
70 | #define ADE_CLIP_SIZE1(x) (0x6808 + (x) * 0x100) | ||
71 | |||
72 | /* | ||
73 | * LDI Registers | ||
74 | */ | ||
75 | #define LDI_HRZ_CTRL0 0x7400 | ||
76 | #define HBP_OFST 20 | ||
77 | #define LDI_HRZ_CTRL1 0x7404 | ||
78 | #define LDI_VRT_CTRL0 0x7408 | ||
79 | #define VBP_OFST 20 | ||
80 | #define LDI_VRT_CTRL1 0x740C | ||
81 | #define LDI_PLR_CTRL 0x7410 | ||
82 | #define FLAG_NVSYNC BIT(0) | ||
83 | #define FLAG_NHSYNC BIT(1) | ||
84 | #define FLAG_NPIXCLK BIT(2) | ||
85 | #define FLAG_NDE BIT(3) | ||
86 | #define LDI_DSP_SIZE 0x7414 | ||
87 | #define VSIZE_OFST 20 | ||
88 | #define LDI_INT_EN 0x741C | ||
89 | #define FRAME_END_INT_EN_OFST 1 | ||
90 | #define LDI_CTRL 0x7420 | ||
91 | #define BPP_OFST 3 | ||
92 | #define DATA_GATE_EN BIT(2) | ||
93 | #define LDI_EN BIT(0) | ||
94 | #define LDI_MSK_INT 0x7428 | ||
95 | #define LDI_INT_CLR 0x742C | ||
96 | #define LDI_WORK_MODE 0x7430 | ||
97 | #define LDI_HDMI_DSI_GT 0x7434 | ||
98 | |||
99 | /* | ||
100 | * ADE media bus service regs | ||
101 | */ | ||
102 | #define ADE0_QOSGENERATOR_MODE 0x010C | ||
103 | #define QOSGENERATOR_MODE_MASK MASK(2) | ||
104 | #define ADE0_QOSGENERATOR_EXTCONTROL 0x0118 | ||
105 | #define SOCKET_QOS_EN BIT(0) | ||
106 | #define ADE1_QOSGENERATOR_MODE 0x020C | ||
107 | #define ADE1_QOSGENERATOR_EXTCONTROL 0x0218 | ||
108 | |||
109 | /* | ||
110 | * ADE regs relevant enums | ||
111 | */ | ||
112 | enum frame_end_start { | ||
113 | /* regs take effect in every vsync */ | ||
114 | REG_EFFECTIVE_IN_VSYNC = 0, | ||
115 | /* regs take effect in fist ade en and every frame end */ | ||
116 | REG_EFFECTIVE_IN_ADEEN_FRMEND, | ||
117 | /* regs take effect in ade en immediately */ | ||
118 | REG_EFFECTIVE_IN_ADEEN, | ||
119 | /* regs take effect in first vsync and every frame end */ | ||
120 | REG_EFFECTIVE_IN_VSYNC_FRMEND | ||
121 | }; | ||
122 | |||
123 | enum ade_fb_format { | ||
124 | ADE_RGB_565 = 0, | ||
125 | ADE_BGR_565, | ||
126 | ADE_XRGB_8888, | ||
127 | ADE_XBGR_8888, | ||
128 | ADE_ARGB_8888, | ||
129 | ADE_ABGR_8888, | ||
130 | ADE_RGBA_8888, | ||
131 | ADE_BGRA_8888, | ||
132 | ADE_RGB_888, | ||
133 | ADE_BGR_888 = 9, | ||
134 | ADE_FORMAT_UNSUPPORT = 800 | ||
135 | }; | ||
136 | |||
137 | enum ade_channel { | ||
138 | ADE_CH1 = 0, /* channel 1 for primary plane */ | ||
139 | ADE_CH_NUM | ||
140 | }; | ||
141 | |||
142 | enum ade_scale { | ||
143 | ADE_SCL1 = 0, | ||
144 | ADE_SCL2, | ||
145 | ADE_SCL3, | ||
146 | ADE_SCL_NUM | ||
147 | }; | ||
148 | |||
149 | enum ade_ctran { | ||
150 | ADE_CTRAN1 = 0, | ||
151 | ADE_CTRAN2, | ||
152 | ADE_CTRAN3, | ||
153 | ADE_CTRAN4, | ||
154 | ADE_CTRAN5, | ||
155 | ADE_CTRAN6, | ||
156 | ADE_CTRAN_NUM | ||
157 | }; | ||
158 | |||
159 | enum ade_overlay { | ||
160 | ADE_OVLY1 = 0, | ||
161 | ADE_OVLY2, | ||
162 | ADE_OVLY3, | ||
163 | ADE_OVLY_NUM | ||
164 | }; | ||
165 | |||
166 | enum ade_alpha_mode { | ||
167 | ADE_ALP_GLOBAL = 0, | ||
168 | ADE_ALP_PIXEL, | ||
169 | ADE_ALP_PIXEL_AND_GLB | ||
170 | }; | ||
171 | |||
172 | enum ade_alpha_blending_mode { | ||
173 | ADE_ALP_MUL_COEFF_0 = 0, /* alpha */ | ||
174 | ADE_ALP_MUL_COEFF_1, /* 1-alpha */ | ||
175 | ADE_ALP_MUL_COEFF_2, /* 0 */ | ||
176 | ADE_ALP_MUL_COEFF_3 /* 1 */ | ||
177 | }; | ||
178 | |||
179 | /* | ||
180 | * LDI regs relevant enums | ||
181 | */ | ||
182 | enum dsi_pclk_en { | ||
183 | DSI_PCLK_ON = 0, | ||
184 | DSI_PCLK_OFF | ||
185 | }; | ||
186 | |||
187 | enum ldi_output_format { | ||
188 | LDI_OUT_RGB_565 = 0, | ||
189 | LDI_OUT_RGB_666, | ||
190 | LDI_OUT_RGB_888 | ||
191 | }; | ||
192 | |||
193 | enum ldi_work_mode { | ||
194 | TEST_MODE = 0, | ||
195 | NORMAL_MODE | ||
196 | }; | ||
197 | |||
198 | enum ldi_input_source { | ||
199 | DISP_SRC_NONE = 0, | ||
200 | DISP_SRC_OVLY2, | ||
201 | DISP_SRC_DISP, | ||
202 | DISP_SRC_ROT, | ||
203 | DISP_SRC_SCL2 | ||
204 | }; | ||
205 | |||
206 | /* | ||
207 | * ADE media bus service relevant enums | ||
208 | */ | ||
209 | enum qos_generator_mode { | ||
210 | FIXED_MODE = 0, | ||
211 | LIMITER_MODE, | ||
212 | BYPASS_MODE, | ||
213 | REGULATOR_MODE | ||
214 | }; | ||
215 | |||
216 | /* | ||
217 | * Register Write/Read Helper functions | ||
218 | */ | ||
219 | static inline void ade_update_bits(void __iomem *addr, u32 bit_start, | ||
220 | u32 mask, u32 val) | ||
221 | { | ||
222 | u32 tmp, orig; | ||
223 | |||
224 | orig = readl(addr); | ||
225 | tmp = orig & ~(mask << bit_start); | ||
226 | tmp |= (val & mask) << bit_start; | ||
227 | writel(tmp, addr); | ||
228 | } | ||
229 | |||
230 | #endif | ||
diff --git a/drivers/gpu/drm/hisilicon/kirin/kirin_drm_ade.c b/drivers/gpu/drm/hisilicon/kirin/kirin_drm_ade.c new file mode 100644 index 000000000000..fba6372d060e --- /dev/null +++ b/drivers/gpu/drm/hisilicon/kirin/kirin_drm_ade.c | |||
@@ -0,0 +1,1057 @@ | |||
1 | /* | ||
2 | * Hisilicon Hi6220 SoC ADE(Advanced Display Engine)'s crtc&plane driver | ||
3 | * | ||
4 | * Copyright (c) 2016 Linaro Limited. | ||
5 | * Copyright (c) 2014-2016 Hisilicon Limited. | ||
6 | * | ||
7 | * Author: | ||
8 | * Xinliang Liu <z.liuxinliang@hisilicon.com> | ||
9 | * Xinliang Liu <xinliang.liu@linaro.org> | ||
10 | * Xinwei Kong <kong.kongxinwei@hisilicon.com> | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or modify | ||
13 | * it under the terms of the GNU General Public License version 2 as | ||
14 | * published by the Free Software Foundation. | ||
15 | * | ||
16 | */ | ||
17 | |||
18 | #include <linux/bitops.h> | ||
19 | #include <linux/clk.h> | ||
20 | #include <video/display_timing.h> | ||
21 | #include <linux/mfd/syscon.h> | ||
22 | #include <linux/regmap.h> | ||
23 | #include <linux/reset.h> | ||
24 | |||
25 | #include <drm/drmP.h> | ||
26 | #include <drm/drm_crtc.h> | ||
27 | #include <drm/drm_crtc_helper.h> | ||
28 | #include <drm/drm_atomic.h> | ||
29 | #include <drm/drm_atomic_helper.h> | ||
30 | #include <drm/drm_plane_helper.h> | ||
31 | #include <drm/drm_gem_cma_helper.h> | ||
32 | #include <drm/drm_fb_cma_helper.h> | ||
33 | |||
34 | #include "kirin_drm_drv.h" | ||
35 | #include "kirin_ade_reg.h" | ||
36 | |||
37 | #define PRIMARY_CH ADE_CH1 /* primary plane */ | ||
38 | #define OUT_OVLY ADE_OVLY2 /* output overlay compositor */ | ||
39 | #define ADE_DEBUG 1 | ||
40 | |||
41 | #define to_ade_crtc(crtc) \ | ||
42 | container_of(crtc, struct ade_crtc, base) | ||
43 | |||
44 | #define to_ade_plane(plane) \ | ||
45 | container_of(plane, struct ade_plane, base) | ||
46 | |||
47 | struct ade_hw_ctx { | ||
48 | void __iomem *base; | ||
49 | struct regmap *noc_regmap; | ||
50 | struct clk *ade_core_clk; | ||
51 | struct clk *media_noc_clk; | ||
52 | struct clk *ade_pix_clk; | ||
53 | struct reset_control *reset; | ||
54 | bool power_on; | ||
55 | int irq; | ||
56 | }; | ||
57 | |||
58 | struct ade_crtc { | ||
59 | struct drm_crtc base; | ||
60 | struct ade_hw_ctx *ctx; | ||
61 | bool enable; | ||
62 | u32 out_format; | ||
63 | }; | ||
64 | |||
65 | struct ade_plane { | ||
66 | struct drm_plane base; | ||
67 | void *ctx; | ||
68 | u8 ch; /* channel */ | ||
69 | }; | ||
70 | |||
71 | struct ade_data { | ||
72 | struct ade_crtc acrtc; | ||
73 | struct ade_plane aplane[ADE_CH_NUM]; | ||
74 | struct ade_hw_ctx ctx; | ||
75 | }; | ||
76 | |||
77 | /* ade-format info: */ | ||
78 | struct ade_format { | ||
79 | u32 pixel_format; | ||
80 | enum ade_fb_format ade_format; | ||
81 | }; | ||
82 | |||
83 | static const struct ade_format ade_formats[] = { | ||
84 | /* 16bpp RGB: */ | ||
85 | { DRM_FORMAT_RGB565, ADE_RGB_565 }, | ||
86 | { DRM_FORMAT_BGR565, ADE_BGR_565 }, | ||
87 | /* 24bpp RGB: */ | ||
88 | { DRM_FORMAT_RGB888, ADE_RGB_888 }, | ||
89 | { DRM_FORMAT_BGR888, ADE_BGR_888 }, | ||
90 | /* 32bpp [A]RGB: */ | ||
91 | { DRM_FORMAT_XRGB8888, ADE_XRGB_8888 }, | ||
92 | { DRM_FORMAT_XBGR8888, ADE_XBGR_8888 }, | ||
93 | { DRM_FORMAT_RGBA8888, ADE_RGBA_8888 }, | ||
94 | { DRM_FORMAT_BGRA8888, ADE_BGRA_8888 }, | ||
95 | { DRM_FORMAT_ARGB8888, ADE_ARGB_8888 }, | ||
96 | { DRM_FORMAT_ABGR8888, ADE_ABGR_8888 }, | ||
97 | }; | ||
98 | |||
99 | static const u32 channel_formats1[] = { | ||
100 | /* channel 1,2,3,4 */ | ||
101 | DRM_FORMAT_RGB565, DRM_FORMAT_BGR565, DRM_FORMAT_RGB888, | ||
102 | DRM_FORMAT_BGR888, DRM_FORMAT_XRGB8888, DRM_FORMAT_XBGR8888, | ||
103 | DRM_FORMAT_RGBA8888, DRM_FORMAT_BGRA8888, DRM_FORMAT_ARGB8888, | ||
104 | DRM_FORMAT_ABGR8888 | ||
105 | }; | ||
106 | |||
107 | u32 ade_get_channel_formats(u8 ch, const u32 **formats) | ||
108 | { | ||
109 | switch (ch) { | ||
110 | case ADE_CH1: | ||
111 | *formats = channel_formats1; | ||
112 | return ARRAY_SIZE(channel_formats1); | ||
113 | default: | ||
114 | DRM_ERROR("no this channel %d\n", ch); | ||
115 | *formats = NULL; | ||
116 | return 0; | ||
117 | } | ||
118 | } | ||
119 | |||
120 | /* convert from fourcc format to ade format */ | ||
121 | static u32 ade_get_format(u32 pixel_format) | ||
122 | { | ||
123 | int i; | ||
124 | |||
125 | for (i = 0; i < ARRAY_SIZE(ade_formats); i++) | ||
126 | if (ade_formats[i].pixel_format == pixel_format) | ||
127 | return ade_formats[i].ade_format; | ||
128 | |||
129 | /* not found */ | ||
130 | DRM_ERROR("Not found pixel format!!fourcc_format= %d\n", | ||
131 | pixel_format); | ||
132 | return ADE_FORMAT_UNSUPPORT; | ||
133 | } | ||
134 | |||
135 | static void ade_update_reload_bit(void __iomem *base, u32 bit_num, u32 val) | ||
136 | { | ||
137 | u32 bit_ofst, reg_num; | ||
138 | |||
139 | bit_ofst = bit_num % 32; | ||
140 | reg_num = bit_num / 32; | ||
141 | |||
142 | ade_update_bits(base + ADE_RELOAD_DIS(reg_num), bit_ofst, | ||
143 | MASK(1), !!val); | ||
144 | } | ||
145 | |||
146 | static u32 ade_read_reload_bit(void __iomem *base, u32 bit_num) | ||
147 | { | ||
148 | u32 tmp, bit_ofst, reg_num; | ||
149 | |||
150 | bit_ofst = bit_num % 32; | ||
151 | reg_num = bit_num / 32; | ||
152 | |||
153 | tmp = readl(base + ADE_RELOAD_DIS(reg_num)); | ||
154 | return !!(BIT(bit_ofst) & tmp); | ||
155 | } | ||
156 | |||
157 | static void ade_init(struct ade_hw_ctx *ctx) | ||
158 | { | ||
159 | void __iomem *base = ctx->base; | ||
160 | |||
161 | /* enable clk gate */ | ||
162 | ade_update_bits(base + ADE_CTRL1, AUTO_CLK_GATE_EN_OFST, | ||
163 | AUTO_CLK_GATE_EN, ADE_ENABLE); | ||
164 | /* clear overlay */ | ||
165 | writel(0, base + ADE_OVLY1_TRANS_CFG); | ||
166 | writel(0, base + ADE_OVLY_CTL); | ||
167 | writel(0, base + ADE_OVLYX_CTL(OUT_OVLY)); | ||
168 | /* clear reset and reload regs */ | ||
169 | writel(MASK(32), base + ADE_SOFT_RST_SEL(0)); | ||
170 | writel(MASK(32), base + ADE_SOFT_RST_SEL(1)); | ||
171 | writel(MASK(32), base + ADE_RELOAD_DIS(0)); | ||
172 | writel(MASK(32), base + ADE_RELOAD_DIS(1)); | ||
173 | /* | ||
174 | * for video mode, all the ade registers should | ||
175 | * become effective at frame end. | ||
176 | */ | ||
177 | ade_update_bits(base + ADE_CTRL, FRM_END_START_OFST, | ||
178 | FRM_END_START_MASK, REG_EFFECTIVE_IN_ADEEN_FRMEND); | ||
179 | } | ||
180 | |||
181 | static void ade_set_pix_clk(struct ade_hw_ctx *ctx, | ||
182 | struct drm_display_mode *mode, | ||
183 | struct drm_display_mode *adj_mode) | ||
184 | { | ||
185 | u32 clk_Hz = mode->clock * 1000; | ||
186 | int ret; | ||
187 | |||
188 | /* | ||
189 | * Success should be guaranteed in mode_valid call back, | ||
190 | * so failure shouldn't happen here | ||
191 | */ | ||
192 | ret = clk_set_rate(ctx->ade_pix_clk, clk_Hz); | ||
193 | if (ret) | ||
194 | DRM_ERROR("failed to set pixel clk %dHz (%d)\n", clk_Hz, ret); | ||
195 | adj_mode->clock = clk_get_rate(ctx->ade_pix_clk) / 1000; | ||
196 | } | ||
197 | |||
198 | static void ade_ldi_set_mode(struct ade_crtc *acrtc, | ||
199 | struct drm_display_mode *mode, | ||
200 | struct drm_display_mode *adj_mode) | ||
201 | { | ||
202 | struct ade_hw_ctx *ctx = acrtc->ctx; | ||
203 | void __iomem *base = ctx->base; | ||
204 | u32 width = mode->hdisplay; | ||
205 | u32 height = mode->vdisplay; | ||
206 | u32 hfp, hbp, hsw, vfp, vbp, vsw; | ||
207 | u32 plr_flags; | ||
208 | |||
209 | plr_flags = (mode->flags & DRM_MODE_FLAG_NVSYNC) ? FLAG_NVSYNC : 0; | ||
210 | plr_flags |= (mode->flags & DRM_MODE_FLAG_NHSYNC) ? FLAG_NHSYNC : 0; | ||
211 | hfp = mode->hsync_start - mode->hdisplay; | ||
212 | hbp = mode->htotal - mode->hsync_end; | ||
213 | hsw = mode->hsync_end - mode->hsync_start; | ||
214 | vfp = mode->vsync_start - mode->vdisplay; | ||
215 | vbp = mode->vtotal - mode->vsync_end; | ||
216 | vsw = mode->vsync_end - mode->vsync_start; | ||
217 | if (vsw > 15) { | ||
218 | DRM_DEBUG_DRIVER("vsw exceeded 15\n"); | ||
219 | vsw = 15; | ||
220 | } | ||
221 | |||
222 | writel((hbp << HBP_OFST) | hfp, base + LDI_HRZ_CTRL0); | ||
223 | /* the configured value is actual value - 1 */ | ||
224 | writel(hsw - 1, base + LDI_HRZ_CTRL1); | ||
225 | writel((vbp << VBP_OFST) | vfp, base + LDI_VRT_CTRL0); | ||
226 | /* the configured value is actual value - 1 */ | ||
227 | writel(vsw - 1, base + LDI_VRT_CTRL1); | ||
228 | /* the configured value is actual value - 1 */ | ||
229 | writel(((height - 1) << VSIZE_OFST) | (width - 1), | ||
230 | base + LDI_DSP_SIZE); | ||
231 | writel(plr_flags, base + LDI_PLR_CTRL); | ||
232 | |||
233 | /* set overlay compositor output size */ | ||
234 | writel(((width - 1) << OUTPUT_XSIZE_OFST) | (height - 1), | ||
235 | base + ADE_OVLY_OUTPUT_SIZE(OUT_OVLY)); | ||
236 | |||
237 | /* ctran6 setting */ | ||
238 | writel(CTRAN_BYPASS_ON, base + ADE_CTRAN_DIS(ADE_CTRAN6)); | ||
239 | /* the configured value is actual value - 1 */ | ||
240 | writel(width * height - 1, base + ADE_CTRAN_IMAGE_SIZE(ADE_CTRAN6)); | ||
241 | ade_update_reload_bit(base, CTRAN_OFST + ADE_CTRAN6, 0); | ||
242 | |||
243 | ade_set_pix_clk(ctx, mode, adj_mode); | ||
244 | |||
245 | DRM_DEBUG_DRIVER("set mode: %dx%d\n", width, height); | ||
246 | } | ||
247 | |||
248 | static int ade_power_up(struct ade_hw_ctx *ctx) | ||
249 | { | ||
250 | int ret; | ||
251 | |||
252 | ret = clk_prepare_enable(ctx->media_noc_clk); | ||
253 | if (ret) { | ||
254 | DRM_ERROR("failed to enable media_noc_clk (%d)\n", ret); | ||
255 | return ret; | ||
256 | } | ||
257 | |||
258 | ret = reset_control_deassert(ctx->reset); | ||
259 | if (ret) { | ||
260 | DRM_ERROR("failed to deassert reset\n"); | ||
261 | return ret; | ||
262 | } | ||
263 | |||
264 | ret = clk_prepare_enable(ctx->ade_core_clk); | ||
265 | if (ret) { | ||
266 | DRM_ERROR("failed to enable ade_core_clk (%d)\n", ret); | ||
267 | return ret; | ||
268 | } | ||
269 | |||
270 | ade_init(ctx); | ||
271 | ctx->power_on = true; | ||
272 | return 0; | ||
273 | } | ||
274 | |||
275 | static void ade_power_down(struct ade_hw_ctx *ctx) | ||
276 | { | ||
277 | void __iomem *base = ctx->base; | ||
278 | |||
279 | writel(ADE_DISABLE, base + LDI_CTRL); | ||
280 | /* dsi pixel off */ | ||
281 | writel(DSI_PCLK_OFF, base + LDI_HDMI_DSI_GT); | ||
282 | |||
283 | clk_disable_unprepare(ctx->ade_core_clk); | ||
284 | reset_control_assert(ctx->reset); | ||
285 | clk_disable_unprepare(ctx->media_noc_clk); | ||
286 | ctx->power_on = false; | ||
287 | } | ||
288 | |||
289 | static void ade_set_medianoc_qos(struct ade_crtc *acrtc) | ||
290 | { | ||
291 | struct ade_hw_ctx *ctx = acrtc->ctx; | ||
292 | struct regmap *map = ctx->noc_regmap; | ||
293 | |||
294 | regmap_update_bits(map, ADE0_QOSGENERATOR_MODE, | ||
295 | QOSGENERATOR_MODE_MASK, BYPASS_MODE); | ||
296 | regmap_update_bits(map, ADE0_QOSGENERATOR_EXTCONTROL, | ||
297 | SOCKET_QOS_EN, SOCKET_QOS_EN); | ||
298 | |||
299 | regmap_update_bits(map, ADE1_QOSGENERATOR_MODE, | ||
300 | QOSGENERATOR_MODE_MASK, BYPASS_MODE); | ||
301 | regmap_update_bits(map, ADE1_QOSGENERATOR_EXTCONTROL, | ||
302 | SOCKET_QOS_EN, SOCKET_QOS_EN); | ||
303 | } | ||
304 | |||
305 | static int ade_enable_vblank(struct drm_device *dev, unsigned int pipe) | ||
306 | { | ||
307 | struct kirin_drm_private *priv = dev->dev_private; | ||
308 | struct ade_crtc *acrtc = to_ade_crtc(priv->crtc[pipe]); | ||
309 | struct ade_hw_ctx *ctx = acrtc->ctx; | ||
310 | void __iomem *base = ctx->base; | ||
311 | |||
312 | if (!ctx->power_on) | ||
313 | (void)ade_power_up(ctx); | ||
314 | |||
315 | ade_update_bits(base + LDI_INT_EN, FRAME_END_INT_EN_OFST, | ||
316 | MASK(1), 1); | ||
317 | |||
318 | return 0; | ||
319 | } | ||
320 | |||
321 | static void ade_disable_vblank(struct drm_device *dev, unsigned int pipe) | ||
322 | { | ||
323 | struct kirin_drm_private *priv = dev->dev_private; | ||
324 | struct ade_crtc *acrtc = to_ade_crtc(priv->crtc[pipe]); | ||
325 | struct ade_hw_ctx *ctx = acrtc->ctx; | ||
326 | void __iomem *base = ctx->base; | ||
327 | |||
328 | if (!ctx->power_on) { | ||
329 | DRM_ERROR("power is down! vblank disable fail\n"); | ||
330 | return; | ||
331 | } | ||
332 | |||
333 | ade_update_bits(base + LDI_INT_EN, FRAME_END_INT_EN_OFST, | ||
334 | MASK(1), 0); | ||
335 | } | ||
336 | |||
337 | static irqreturn_t ade_irq_handler(int irq, void *data) | ||
338 | { | ||
339 | struct ade_crtc *acrtc = data; | ||
340 | struct ade_hw_ctx *ctx = acrtc->ctx; | ||
341 | struct drm_crtc *crtc = &acrtc->base; | ||
342 | void __iomem *base = ctx->base; | ||
343 | u32 status; | ||
344 | |||
345 | status = readl(base + LDI_MSK_INT); | ||
346 | DRM_DEBUG_VBL("LDI IRQ: status=0x%X\n", status); | ||
347 | |||
348 | /* vblank irq */ | ||
349 | if (status & BIT(FRAME_END_INT_EN_OFST)) { | ||
350 | ade_update_bits(base + LDI_INT_CLR, FRAME_END_INT_EN_OFST, | ||
351 | MASK(1), 1); | ||
352 | drm_crtc_handle_vblank(crtc); | ||
353 | } | ||
354 | |||
355 | return IRQ_HANDLED; | ||
356 | } | ||
357 | |||
358 | static void ade_display_enable(struct ade_crtc *acrtc) | ||
359 | { | ||
360 | struct ade_hw_ctx *ctx = acrtc->ctx; | ||
361 | void __iomem *base = ctx->base; | ||
362 | u32 out_fmt = acrtc->out_format; | ||
363 | |||
364 | /* enable output overlay compositor */ | ||
365 | writel(ADE_ENABLE, base + ADE_OVLYX_CTL(OUT_OVLY)); | ||
366 | ade_update_reload_bit(base, OVLY_OFST + OUT_OVLY, 0); | ||
367 | |||
368 | /* display source setting */ | ||
369 | writel(DISP_SRC_OVLY2, base + ADE_DISP_SRC_CFG); | ||
370 | |||
371 | /* enable ade */ | ||
372 | writel(ADE_ENABLE, base + ADE_EN); | ||
373 | /* enable ldi */ | ||
374 | writel(NORMAL_MODE, base + LDI_WORK_MODE); | ||
375 | writel((out_fmt << BPP_OFST) | DATA_GATE_EN | LDI_EN, | ||
376 | base + LDI_CTRL); | ||
377 | /* dsi pixel on */ | ||
378 | writel(DSI_PCLK_ON, base + LDI_HDMI_DSI_GT); | ||
379 | } | ||
380 | |||
381 | #if ADE_DEBUG | ||
382 | static void ade_rdma_dump_regs(void __iomem *base, u32 ch) | ||
383 | { | ||
384 | u32 reg_ctrl, reg_addr, reg_size, reg_stride, reg_space, reg_en; | ||
385 | u32 val; | ||
386 | |||
387 | reg_ctrl = RD_CH_CTRL(ch); | ||
388 | reg_addr = RD_CH_ADDR(ch); | ||
389 | reg_size = RD_CH_SIZE(ch); | ||
390 | reg_stride = RD_CH_STRIDE(ch); | ||
391 | reg_space = RD_CH_SPACE(ch); | ||
392 | reg_en = RD_CH_EN(ch); | ||
393 | |||
394 | val = ade_read_reload_bit(base, RDMA_OFST + ch); | ||
395 | DRM_DEBUG_DRIVER("[rdma%d]: reload(%d)\n", ch + 1, val); | ||
396 | val = readl(base + reg_ctrl); | ||
397 | DRM_DEBUG_DRIVER("[rdma%d]: reg_ctrl(0x%08x)\n", ch + 1, val); | ||
398 | val = readl(base + reg_addr); | ||
399 | DRM_DEBUG_DRIVER("[rdma%d]: reg_addr(0x%08x)\n", ch + 1, val); | ||
400 | val = readl(base + reg_size); | ||
401 | DRM_DEBUG_DRIVER("[rdma%d]: reg_size(0x%08x)\n", ch + 1, val); | ||
402 | val = readl(base + reg_stride); | ||
403 | DRM_DEBUG_DRIVER("[rdma%d]: reg_stride(0x%08x)\n", ch + 1, val); | ||
404 | val = readl(base + reg_space); | ||
405 | DRM_DEBUG_DRIVER("[rdma%d]: reg_space(0x%08x)\n", ch + 1, val); | ||
406 | val = readl(base + reg_en); | ||
407 | DRM_DEBUG_DRIVER("[rdma%d]: reg_en(0x%08x)\n", ch + 1, val); | ||
408 | } | ||
409 | |||
410 | static void ade_clip_dump_regs(void __iomem *base, u32 ch) | ||
411 | { | ||
412 | u32 val; | ||
413 | |||
414 | val = ade_read_reload_bit(base, CLIP_OFST + ch); | ||
415 | DRM_DEBUG_DRIVER("[clip%d]: reload(%d)\n", ch + 1, val); | ||
416 | val = readl(base + ADE_CLIP_DISABLE(ch)); | ||
417 | DRM_DEBUG_DRIVER("[clip%d]: reg_clip_disable(0x%08x)\n", ch + 1, val); | ||
418 | val = readl(base + ADE_CLIP_SIZE0(ch)); | ||
419 | DRM_DEBUG_DRIVER("[clip%d]: reg_clip_size0(0x%08x)\n", ch + 1, val); | ||
420 | val = readl(base + ADE_CLIP_SIZE1(ch)); | ||
421 | DRM_DEBUG_DRIVER("[clip%d]: reg_clip_size1(0x%08x)\n", ch + 1, val); | ||
422 | } | ||
423 | |||
424 | static void ade_compositor_routing_dump_regs(void __iomem *base, u32 ch) | ||
425 | { | ||
426 | u8 ovly_ch = 0; /* TODO: Only primary plane now */ | ||
427 | u32 val; | ||
428 | |||
429 | val = readl(base + ADE_OVLY_CH_XY0(ovly_ch)); | ||
430 | DRM_DEBUG_DRIVER("[overlay ch%d]: reg_ch_xy0(0x%08x)\n", ovly_ch, val); | ||
431 | val = readl(base + ADE_OVLY_CH_XY1(ovly_ch)); | ||
432 | DRM_DEBUG_DRIVER("[overlay ch%d]: reg_ch_xy1(0x%08x)\n", ovly_ch, val); | ||
433 | val = readl(base + ADE_OVLY_CH_CTL(ovly_ch)); | ||
434 | DRM_DEBUG_DRIVER("[overlay ch%d]: reg_ch_ctl(0x%08x)\n", ovly_ch, val); | ||
435 | } | ||
436 | |||
437 | static void ade_dump_overlay_compositor_regs(void __iomem *base, u32 comp) | ||
438 | { | ||
439 | u32 val; | ||
440 | |||
441 | val = ade_read_reload_bit(base, OVLY_OFST + comp); | ||
442 | DRM_DEBUG_DRIVER("[overlay%d]: reload(%d)\n", comp + 1, val); | ||
443 | writel(ADE_ENABLE, base + ADE_OVLYX_CTL(comp)); | ||
444 | DRM_DEBUG_DRIVER("[overlay%d]: reg_ctl(0x%08x)\n", comp + 1, val); | ||
445 | val = readl(base + ADE_OVLY_CTL); | ||
446 | DRM_DEBUG_DRIVER("ovly_ctl(0x%08x)\n", val); | ||
447 | } | ||
448 | |||
449 | static void ade_dump_regs(void __iomem *base) | ||
450 | { | ||
451 | u32 i; | ||
452 | |||
453 | /* dump channel regs */ | ||
454 | for (i = 0; i < ADE_CH_NUM; i++) { | ||
455 | /* dump rdma regs */ | ||
456 | ade_rdma_dump_regs(base, i); | ||
457 | |||
458 | /* dump clip regs */ | ||
459 | ade_clip_dump_regs(base, i); | ||
460 | |||
461 | /* dump compositor routing regs */ | ||
462 | ade_compositor_routing_dump_regs(base, i); | ||
463 | } | ||
464 | |||
465 | /* dump overlay compositor regs */ | ||
466 | ade_dump_overlay_compositor_regs(base, OUT_OVLY); | ||
467 | } | ||
468 | #else | ||
469 | static void ade_dump_regs(void __iomem *base) { } | ||
470 | #endif | ||
471 | |||
472 | static void ade_crtc_enable(struct drm_crtc *crtc) | ||
473 | { | ||
474 | struct ade_crtc *acrtc = to_ade_crtc(crtc); | ||
475 | struct ade_hw_ctx *ctx = acrtc->ctx; | ||
476 | int ret; | ||
477 | |||
478 | if (acrtc->enable) | ||
479 | return; | ||
480 | |||
481 | if (!ctx->power_on) { | ||
482 | ret = ade_power_up(ctx); | ||
483 | if (ret) | ||
484 | return; | ||
485 | } | ||
486 | |||
487 | ade_set_medianoc_qos(acrtc); | ||
488 | ade_display_enable(acrtc); | ||
489 | ade_dump_regs(ctx->base); | ||
490 | acrtc->enable = true; | ||
491 | } | ||
492 | |||
493 | static void ade_crtc_disable(struct drm_crtc *crtc) | ||
494 | { | ||
495 | struct ade_crtc *acrtc = to_ade_crtc(crtc); | ||
496 | struct ade_hw_ctx *ctx = acrtc->ctx; | ||
497 | |||
498 | if (!acrtc->enable) | ||
499 | return; | ||
500 | |||
501 | ade_power_down(ctx); | ||
502 | acrtc->enable = false; | ||
503 | } | ||
504 | |||
505 | static int ade_crtc_atomic_check(struct drm_crtc *crtc, | ||
506 | struct drm_crtc_state *state) | ||
507 | { | ||
508 | /* do nothing */ | ||
509 | return 0; | ||
510 | } | ||
511 | |||
512 | static void ade_crtc_mode_set_nofb(struct drm_crtc *crtc) | ||
513 | { | ||
514 | struct ade_crtc *acrtc = to_ade_crtc(crtc); | ||
515 | struct ade_hw_ctx *ctx = acrtc->ctx; | ||
516 | struct drm_display_mode *mode = &crtc->state->mode; | ||
517 | struct drm_display_mode *adj_mode = &crtc->state->adjusted_mode; | ||
518 | |||
519 | if (!ctx->power_on) | ||
520 | (void)ade_power_up(ctx); | ||
521 | ade_ldi_set_mode(acrtc, mode, adj_mode); | ||
522 | } | ||
523 | |||
524 | static void ade_crtc_atomic_begin(struct drm_crtc *crtc, | ||
525 | struct drm_crtc_state *old_state) | ||
526 | { | ||
527 | struct ade_crtc *acrtc = to_ade_crtc(crtc); | ||
528 | struct ade_hw_ctx *ctx = acrtc->ctx; | ||
529 | |||
530 | if (!ctx->power_on) | ||
531 | (void)ade_power_up(ctx); | ||
532 | } | ||
533 | |||
534 | static void ade_crtc_atomic_flush(struct drm_crtc *crtc, | ||
535 | struct drm_crtc_state *old_state) | ||
536 | |||
537 | { | ||
538 | struct ade_crtc *acrtc = to_ade_crtc(crtc); | ||
539 | struct ade_hw_ctx *ctx = acrtc->ctx; | ||
540 | void __iomem *base = ctx->base; | ||
541 | |||
542 | /* only crtc is enabled regs take effect */ | ||
543 | if (acrtc->enable) { | ||
544 | ade_dump_regs(base); | ||
545 | /* flush ade registers */ | ||
546 | writel(ADE_ENABLE, base + ADE_EN); | ||
547 | } | ||
548 | } | ||
549 | |||
550 | static const struct drm_crtc_helper_funcs ade_crtc_helper_funcs = { | ||
551 | .enable = ade_crtc_enable, | ||
552 | .disable = ade_crtc_disable, | ||
553 | .atomic_check = ade_crtc_atomic_check, | ||
554 | .mode_set_nofb = ade_crtc_mode_set_nofb, | ||
555 | .atomic_begin = ade_crtc_atomic_begin, | ||
556 | .atomic_flush = ade_crtc_atomic_flush, | ||
557 | }; | ||
558 | |||
559 | static const struct drm_crtc_funcs ade_crtc_funcs = { | ||
560 | .destroy = drm_crtc_cleanup, | ||
561 | .set_config = drm_atomic_helper_set_config, | ||
562 | .page_flip = drm_atomic_helper_page_flip, | ||
563 | .reset = drm_atomic_helper_crtc_reset, | ||
564 | .set_property = drm_atomic_helper_crtc_set_property, | ||
565 | .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, | ||
566 | .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, | ||
567 | }; | ||
568 | |||
569 | static int ade_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, | ||
570 | struct drm_plane *plane) | ||
571 | { | ||
572 | struct kirin_drm_private *priv = dev->dev_private; | ||
573 | struct device_node *port; | ||
574 | int ret; | ||
575 | |||
576 | /* set crtc port so that | ||
577 | * drm_of_find_possible_crtcs call works | ||
578 | */ | ||
579 | port = of_get_child_by_name(dev->dev->of_node, "port"); | ||
580 | if (!port) { | ||
581 | DRM_ERROR("no port node found in %s\n", | ||
582 | dev->dev->of_node->full_name); | ||
583 | return -EINVAL; | ||
584 | } | ||
585 | of_node_put(port); | ||
586 | crtc->port = port; | ||
587 | |||
588 | ret = drm_crtc_init_with_planes(dev, crtc, plane, NULL, | ||
589 | &ade_crtc_funcs, NULL); | ||
590 | if (ret) { | ||
591 | DRM_ERROR("failed to init crtc.\n"); | ||
592 | return ret; | ||
593 | } | ||
594 | |||
595 | drm_crtc_helper_add(crtc, &ade_crtc_helper_funcs); | ||
596 | priv->crtc[drm_crtc_index(crtc)] = crtc; | ||
597 | |||
598 | return 0; | ||
599 | } | ||
600 | |||
601 | static void ade_rdma_set(void __iomem *base, struct drm_framebuffer *fb, | ||
602 | u32 ch, u32 y, u32 in_h, u32 fmt) | ||
603 | { | ||
604 | struct drm_gem_cma_object *obj = drm_fb_cma_get_gem_obj(fb, 0); | ||
605 | u32 reg_ctrl, reg_addr, reg_size, reg_stride, reg_space, reg_en; | ||
606 | u32 stride = fb->pitches[0]; | ||
607 | u32 addr = (u32)obj->paddr + y * stride; | ||
608 | |||
609 | DRM_DEBUG_DRIVER("rdma%d: (y=%d, height=%d), stride=%d, paddr=0x%x\n", | ||
610 | ch + 1, y, in_h, stride, (u32)obj->paddr); | ||
611 | DRM_DEBUG_DRIVER("addr=0x%x, fb:%dx%d, pixel_format=%d(%s)\n", | ||
612 | addr, fb->width, fb->height, fmt, | ||
613 | drm_get_format_name(fb->pixel_format)); | ||
614 | |||
615 | /* get reg offset */ | ||
616 | reg_ctrl = RD_CH_CTRL(ch); | ||
617 | reg_addr = RD_CH_ADDR(ch); | ||
618 | reg_size = RD_CH_SIZE(ch); | ||
619 | reg_stride = RD_CH_STRIDE(ch); | ||
620 | reg_space = RD_CH_SPACE(ch); | ||
621 | reg_en = RD_CH_EN(ch); | ||
622 | |||
623 | /* | ||
624 | * TODO: set rotation | ||
625 | */ | ||
626 | writel((fmt << 16) & 0x1f0000, base + reg_ctrl); | ||
627 | writel(addr, base + reg_addr); | ||
628 | writel((in_h << 16) | stride, base + reg_size); | ||
629 | writel(stride, base + reg_stride); | ||
630 | writel(in_h * stride, base + reg_space); | ||
631 | writel(ADE_ENABLE, base + reg_en); | ||
632 | ade_update_reload_bit(base, RDMA_OFST + ch, 0); | ||
633 | } | ||
634 | |||
635 | static void ade_rdma_disable(void __iomem *base, u32 ch) | ||
636 | { | ||
637 | u32 reg_en; | ||
638 | |||
639 | /* get reg offset */ | ||
640 | reg_en = RD_CH_EN(ch); | ||
641 | writel(0, base + reg_en); | ||
642 | ade_update_reload_bit(base, RDMA_OFST + ch, 1); | ||
643 | } | ||
644 | |||
645 | static void ade_clip_set(void __iomem *base, u32 ch, u32 fb_w, u32 x, | ||
646 | u32 in_w, u32 in_h) | ||
647 | { | ||
648 | u32 disable_val; | ||
649 | u32 clip_left; | ||
650 | u32 clip_right; | ||
651 | |||
652 | /* | ||
653 | * clip width, no need to clip height | ||
654 | */ | ||
655 | if (fb_w == in_w) { /* bypass */ | ||
656 | disable_val = 1; | ||
657 | clip_left = 0; | ||
658 | clip_right = 0; | ||
659 | } else { | ||
660 | disable_val = 0; | ||
661 | clip_left = x; | ||
662 | clip_right = fb_w - (x + in_w) - 1; | ||
663 | } | ||
664 | |||
665 | DRM_DEBUG_DRIVER("clip%d: clip_left=%d, clip_right=%d\n", | ||
666 | ch + 1, clip_left, clip_right); | ||
667 | |||
668 | writel(disable_val, base + ADE_CLIP_DISABLE(ch)); | ||
669 | writel((fb_w - 1) << 16 | (in_h - 1), base + ADE_CLIP_SIZE0(ch)); | ||
670 | writel(clip_left << 16 | clip_right, base + ADE_CLIP_SIZE1(ch)); | ||
671 | ade_update_reload_bit(base, CLIP_OFST + ch, 0); | ||
672 | } | ||
673 | |||
674 | static void ade_clip_disable(void __iomem *base, u32 ch) | ||
675 | { | ||
676 | writel(1, base + ADE_CLIP_DISABLE(ch)); | ||
677 | ade_update_reload_bit(base, CLIP_OFST + ch, 1); | ||
678 | } | ||
679 | |||
680 | static bool has_Alpha_channel(int format) | ||
681 | { | ||
682 | switch (format) { | ||
683 | case ADE_ARGB_8888: | ||
684 | case ADE_ABGR_8888: | ||
685 | case ADE_RGBA_8888: | ||
686 | case ADE_BGRA_8888: | ||
687 | return true; | ||
688 | default: | ||
689 | return false; | ||
690 | } | ||
691 | } | ||
692 | |||
693 | static void ade_get_blending_params(u32 fmt, u8 glb_alpha, u8 *alp_mode, | ||
694 | u8 *alp_sel, u8 *under_alp_sel) | ||
695 | { | ||
696 | bool has_alpha = has_Alpha_channel(fmt); | ||
697 | |||
698 | /* | ||
699 | * get alp_mode | ||
700 | */ | ||
701 | if (has_alpha && glb_alpha < 255) | ||
702 | *alp_mode = ADE_ALP_PIXEL_AND_GLB; | ||
703 | else if (has_alpha) | ||
704 | *alp_mode = ADE_ALP_PIXEL; | ||
705 | else | ||
706 | *alp_mode = ADE_ALP_GLOBAL; | ||
707 | |||
708 | /* | ||
709 | * get alp sel | ||
710 | */ | ||
711 | *alp_sel = ADE_ALP_MUL_COEFF_3; /* 1 */ | ||
712 | *under_alp_sel = ADE_ALP_MUL_COEFF_2; /* 0 */ | ||
713 | } | ||
714 | |||
715 | static void ade_compositor_routing_set(void __iomem *base, u8 ch, | ||
716 | u32 x0, u32 y0, | ||
717 | u32 in_w, u32 in_h, u32 fmt) | ||
718 | { | ||
719 | u8 ovly_ch = 0; /* TODO: This is the zpos, only one plane now */ | ||
720 | u8 glb_alpha = 255; | ||
721 | u32 x1 = x0 + in_w - 1; | ||
722 | u32 y1 = y0 + in_h - 1; | ||
723 | u32 val; | ||
724 | u8 alp_sel; | ||
725 | u8 under_alp_sel; | ||
726 | u8 alp_mode; | ||
727 | |||
728 | ade_get_blending_params(fmt, glb_alpha, &alp_mode, &alp_sel, | ||
729 | &under_alp_sel); | ||
730 | |||
731 | /* overlay routing setting | ||
732 | */ | ||
733 | writel(x0 << 16 | y0, base + ADE_OVLY_CH_XY0(ovly_ch)); | ||
734 | writel(x1 << 16 | y1, base + ADE_OVLY_CH_XY1(ovly_ch)); | ||
735 | val = (ch + 1) << CH_SEL_OFST | BIT(CH_EN_OFST) | | ||
736 | alp_sel << CH_ALP_SEL_OFST | | ||
737 | under_alp_sel << CH_UNDER_ALP_SEL_OFST | | ||
738 | glb_alpha << CH_ALP_GBL_OFST | | ||
739 | alp_mode << CH_ALP_MODE_OFST; | ||
740 | writel(val, base + ADE_OVLY_CH_CTL(ovly_ch)); | ||
741 | /* connect this plane/channel to overlay2 compositor */ | ||
742 | ade_update_bits(base + ADE_OVLY_CTL, CH_OVLY_SEL_OFST(ovly_ch), | ||
743 | CH_OVLY_SEL_MASK, CH_OVLY_SEL_VAL(OUT_OVLY)); | ||
744 | } | ||
745 | |||
746 | static void ade_compositor_routing_disable(void __iomem *base, u32 ch) | ||
747 | { | ||
748 | u8 ovly_ch = 0; /* TODO: Only primary plane now */ | ||
749 | |||
750 | /* disable this plane/channel */ | ||
751 | ade_update_bits(base + ADE_OVLY_CH_CTL(ovly_ch), CH_EN_OFST, | ||
752 | MASK(1), 0); | ||
753 | /* dis-connect this plane/channel of overlay2 compositor */ | ||
754 | ade_update_bits(base + ADE_OVLY_CTL, CH_OVLY_SEL_OFST(ovly_ch), | ||
755 | CH_OVLY_SEL_MASK, 0); | ||
756 | } | ||
757 | |||
758 | /* | ||
759 | * Typicaly, a channel looks like: DMA-->clip-->scale-->ctrans-->compositor | ||
760 | */ | ||
761 | static void ade_update_channel(struct ade_plane *aplane, | ||
762 | struct drm_framebuffer *fb, int crtc_x, | ||
763 | int crtc_y, unsigned int crtc_w, | ||
764 | unsigned int crtc_h, u32 src_x, | ||
765 | u32 src_y, u32 src_w, u32 src_h) | ||
766 | { | ||
767 | struct ade_hw_ctx *ctx = aplane->ctx; | ||
768 | void __iomem *base = ctx->base; | ||
769 | u32 fmt = ade_get_format(fb->pixel_format); | ||
770 | u32 ch = aplane->ch; | ||
771 | u32 in_w; | ||
772 | u32 in_h; | ||
773 | |||
774 | DRM_DEBUG_DRIVER("channel%d: src:(%d, %d)-%dx%d, crtc:(%d, %d)-%dx%d", | ||
775 | ch + 1, src_x, src_y, src_w, src_h, | ||
776 | crtc_x, crtc_y, crtc_w, crtc_h); | ||
777 | |||
778 | /* 1) DMA setting */ | ||
779 | in_w = src_w; | ||
780 | in_h = src_h; | ||
781 | ade_rdma_set(base, fb, ch, src_y, in_h, fmt); | ||
782 | |||
783 | /* 2) clip setting */ | ||
784 | ade_clip_set(base, ch, fb->width, src_x, in_w, in_h); | ||
785 | |||
786 | /* 3) TODO: scale setting for overlay planes */ | ||
787 | |||
788 | /* 4) TODO: ctran/csc setting for overlay planes */ | ||
789 | |||
790 | /* 5) compositor routing setting */ | ||
791 | ade_compositor_routing_set(base, ch, crtc_x, crtc_y, in_w, in_h, fmt); | ||
792 | } | ||
793 | |||
794 | static void ade_disable_channel(struct ade_plane *aplane) | ||
795 | { | ||
796 | struct ade_hw_ctx *ctx = aplane->ctx; | ||
797 | void __iomem *base = ctx->base; | ||
798 | u32 ch = aplane->ch; | ||
799 | |||
800 | DRM_DEBUG_DRIVER("disable channel%d\n", ch + 1); | ||
801 | |||
802 | /* disable read DMA */ | ||
803 | ade_rdma_disable(base, ch); | ||
804 | |||
805 | /* disable clip */ | ||
806 | ade_clip_disable(base, ch); | ||
807 | |||
808 | /* disable compositor routing */ | ||
809 | ade_compositor_routing_disable(base, ch); | ||
810 | } | ||
811 | |||
812 | static int ade_plane_prepare_fb(struct drm_plane *plane, | ||
813 | const struct drm_plane_state *new_state) | ||
814 | { | ||
815 | /* do nothing */ | ||
816 | return 0; | ||
817 | } | ||
818 | |||
819 | static void ade_plane_cleanup_fb(struct drm_plane *plane, | ||
820 | const struct drm_plane_state *old_state) | ||
821 | { | ||
822 | /* do nothing */ | ||
823 | } | ||
824 | |||
825 | static int ade_plane_atomic_check(struct drm_plane *plane, | ||
826 | struct drm_plane_state *state) | ||
827 | { | ||
828 | struct drm_framebuffer *fb = state->fb; | ||
829 | struct drm_crtc *crtc = state->crtc; | ||
830 | struct drm_crtc_state *crtc_state; | ||
831 | u32 src_x = state->src_x >> 16; | ||
832 | u32 src_y = state->src_y >> 16; | ||
833 | u32 src_w = state->src_w >> 16; | ||
834 | u32 src_h = state->src_h >> 16; | ||
835 | int crtc_x = state->crtc_x; | ||
836 | int crtc_y = state->crtc_y; | ||
837 | u32 crtc_w = state->crtc_w; | ||
838 | u32 crtc_h = state->crtc_h; | ||
839 | u32 fmt; | ||
840 | |||
841 | if (!crtc || !fb) | ||
842 | return 0; | ||
843 | |||
844 | fmt = ade_get_format(fb->pixel_format); | ||
845 | if (fmt == ADE_FORMAT_UNSUPPORT) | ||
846 | return -EINVAL; | ||
847 | |||
848 | crtc_state = drm_atomic_get_crtc_state(state->state, crtc); | ||
849 | if (IS_ERR(crtc_state)) | ||
850 | return PTR_ERR(crtc_state); | ||
851 | |||
852 | if (src_w != crtc_w || src_h != crtc_h) { | ||
853 | DRM_ERROR("Scale not support!!!\n"); | ||
854 | return -EINVAL; | ||
855 | } | ||
856 | |||
857 | if (src_x + src_w > fb->width || | ||
858 | src_y + src_h > fb->height) | ||
859 | return -EINVAL; | ||
860 | |||
861 | if (crtc_x < 0 || crtc_y < 0) | ||
862 | return -EINVAL; | ||
863 | |||
864 | if (crtc_x + crtc_w > crtc_state->adjusted_mode.hdisplay || | ||
865 | crtc_y + crtc_h > crtc_state->adjusted_mode.vdisplay) | ||
866 | return -EINVAL; | ||
867 | |||
868 | return 0; | ||
869 | } | ||
870 | |||
871 | static void ade_plane_atomic_update(struct drm_plane *plane, | ||
872 | struct drm_plane_state *old_state) | ||
873 | { | ||
874 | struct drm_plane_state *state = plane->state; | ||
875 | struct ade_plane *aplane = to_ade_plane(plane); | ||
876 | |||
877 | ade_update_channel(aplane, state->fb, state->crtc_x, state->crtc_y, | ||
878 | state->crtc_w, state->crtc_h, | ||
879 | state->src_x >> 16, state->src_y >> 16, | ||
880 | state->src_w >> 16, state->src_h >> 16); | ||
881 | } | ||
882 | |||
883 | static void ade_plane_atomic_disable(struct drm_plane *plane, | ||
884 | struct drm_plane_state *old_state) | ||
885 | { | ||
886 | struct ade_plane *aplane = to_ade_plane(plane); | ||
887 | |||
888 | ade_disable_channel(aplane); | ||
889 | } | ||
890 | |||
891 | static const struct drm_plane_helper_funcs ade_plane_helper_funcs = { | ||
892 | .prepare_fb = ade_plane_prepare_fb, | ||
893 | .cleanup_fb = ade_plane_cleanup_fb, | ||
894 | .atomic_check = ade_plane_atomic_check, | ||
895 | .atomic_update = ade_plane_atomic_update, | ||
896 | .atomic_disable = ade_plane_atomic_disable, | ||
897 | }; | ||
898 | |||
899 | static struct drm_plane_funcs ade_plane_funcs = { | ||
900 | .update_plane = drm_atomic_helper_update_plane, | ||
901 | .disable_plane = drm_atomic_helper_disable_plane, | ||
902 | .set_property = drm_atomic_helper_plane_set_property, | ||
903 | .destroy = drm_plane_cleanup, | ||
904 | .reset = drm_atomic_helper_plane_reset, | ||
905 | .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, | ||
906 | .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, | ||
907 | }; | ||
908 | |||
909 | static int ade_plane_init(struct drm_device *dev, struct ade_plane *aplane, | ||
910 | enum drm_plane_type type) | ||
911 | { | ||
912 | const u32 *fmts; | ||
913 | u32 fmts_cnt; | ||
914 | int ret = 0; | ||
915 | |||
916 | /* get properties */ | ||
917 | fmts_cnt = ade_get_channel_formats(aplane->ch, &fmts); | ||
918 | if (ret) | ||
919 | return ret; | ||
920 | |||
921 | ret = drm_universal_plane_init(dev, &aplane->base, 1, &ade_plane_funcs, | ||
922 | fmts, fmts_cnt, type, NULL); | ||
923 | if (ret) { | ||
924 | DRM_ERROR("fail to init plane, ch=%d\n", aplane->ch); | ||
925 | return ret; | ||
926 | } | ||
927 | |||
928 | drm_plane_helper_add(&aplane->base, &ade_plane_helper_funcs); | ||
929 | |||
930 | return 0; | ||
931 | } | ||
932 | |||
933 | static int ade_dts_parse(struct platform_device *pdev, struct ade_hw_ctx *ctx) | ||
934 | { | ||
935 | struct resource *res; | ||
936 | struct device *dev = &pdev->dev; | ||
937 | struct device_node *np = pdev->dev.of_node; | ||
938 | |||
939 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
940 | ctx->base = devm_ioremap_resource(dev, res); | ||
941 | if (IS_ERR(ctx->base)) { | ||
942 | DRM_ERROR("failed to remap ade io base\n"); | ||
943 | return PTR_ERR(ctx->base); | ||
944 | } | ||
945 | |||
946 | ctx->reset = devm_reset_control_get(dev, NULL); | ||
947 | if (IS_ERR(ctx->reset)) | ||
948 | return PTR_ERR(ctx->reset); | ||
949 | |||
950 | ctx->noc_regmap = | ||
951 | syscon_regmap_lookup_by_phandle(np, "hisilicon,noc-syscon"); | ||
952 | if (IS_ERR(ctx->noc_regmap)) { | ||
953 | DRM_ERROR("failed to get noc regmap\n"); | ||
954 | return PTR_ERR(ctx->noc_regmap); | ||
955 | } | ||
956 | |||
957 | ctx->irq = platform_get_irq(pdev, 0); | ||
958 | if (ctx->irq < 0) { | ||
959 | DRM_ERROR("failed to get irq\n"); | ||
960 | return -ENODEV; | ||
961 | } | ||
962 | |||
963 | ctx->ade_core_clk = devm_clk_get(dev, "clk_ade_core"); | ||
964 | if (!ctx->ade_core_clk) { | ||
965 | DRM_ERROR("failed to parse clk ADE_CORE\n"); | ||
966 | return -ENODEV; | ||
967 | } | ||
968 | |||
969 | ctx->media_noc_clk = devm_clk_get(dev, "clk_codec_jpeg"); | ||
970 | if (!ctx->media_noc_clk) { | ||
971 | DRM_ERROR("failed to parse clk CODEC_JPEG\n"); | ||
972 | return -ENODEV; | ||
973 | } | ||
974 | |||
975 | ctx->ade_pix_clk = devm_clk_get(dev, "clk_ade_pix"); | ||
976 | if (!ctx->ade_pix_clk) { | ||
977 | DRM_ERROR("failed to parse clk ADE_PIX\n"); | ||
978 | return -ENODEV; | ||
979 | } | ||
980 | |||
981 | return 0; | ||
982 | } | ||
983 | |||
984 | static int ade_drm_init(struct drm_device *dev) | ||
985 | { | ||
986 | struct platform_device *pdev = dev->platformdev; | ||
987 | struct ade_data *ade; | ||
988 | struct ade_hw_ctx *ctx; | ||
989 | struct ade_crtc *acrtc; | ||
990 | struct ade_plane *aplane; | ||
991 | enum drm_plane_type type; | ||
992 | int ret; | ||
993 | int i; | ||
994 | |||
995 | ade = devm_kzalloc(dev->dev, sizeof(*ade), GFP_KERNEL); | ||
996 | if (!ade) { | ||
997 | DRM_ERROR("failed to alloc ade_data\n"); | ||
998 | return -ENOMEM; | ||
999 | } | ||
1000 | platform_set_drvdata(pdev, ade); | ||
1001 | |||
1002 | ctx = &ade->ctx; | ||
1003 | acrtc = &ade->acrtc; | ||
1004 | acrtc->ctx = ctx; | ||
1005 | acrtc->out_format = LDI_OUT_RGB_888; | ||
1006 | |||
1007 | ret = ade_dts_parse(pdev, ctx); | ||
1008 | if (ret) | ||
1009 | return ret; | ||
1010 | |||
1011 | /* | ||
1012 | * plane init | ||
1013 | * TODO: Now only support primary plane, overlay planes | ||
1014 | * need to do. | ||
1015 | */ | ||
1016 | for (i = 0; i < ADE_CH_NUM; i++) { | ||
1017 | aplane = &ade->aplane[i]; | ||
1018 | aplane->ch = i; | ||
1019 | aplane->ctx = ctx; | ||
1020 | type = i == PRIMARY_CH ? DRM_PLANE_TYPE_PRIMARY : | ||
1021 | DRM_PLANE_TYPE_OVERLAY; | ||
1022 | |||
1023 | ret = ade_plane_init(dev, aplane, type); | ||
1024 | if (ret) | ||
1025 | return ret; | ||
1026 | } | ||
1027 | |||
1028 | /* crtc init */ | ||
1029 | ret = ade_crtc_init(dev, &acrtc->base, &ade->aplane[PRIMARY_CH].base); | ||
1030 | if (ret) | ||
1031 | return ret; | ||
1032 | |||
1033 | /* vblank irq init */ | ||
1034 | ret = devm_request_irq(dev->dev, ctx->irq, ade_irq_handler, | ||
1035 | IRQF_SHARED, dev->driver->name, acrtc); | ||
1036 | if (ret) | ||
1037 | return ret; | ||
1038 | dev->driver->get_vblank_counter = drm_vblank_no_hw_counter; | ||
1039 | dev->driver->enable_vblank = ade_enable_vblank; | ||
1040 | dev->driver->disable_vblank = ade_disable_vblank; | ||
1041 | |||
1042 | return 0; | ||
1043 | } | ||
1044 | |||
1045 | static void ade_drm_cleanup(struct drm_device *dev) | ||
1046 | { | ||
1047 | struct platform_device *pdev = dev->platformdev; | ||
1048 | struct ade_data *ade = platform_get_drvdata(pdev); | ||
1049 | struct drm_crtc *crtc = &ade->acrtc.base; | ||
1050 | |||
1051 | drm_crtc_cleanup(crtc); | ||
1052 | } | ||
1053 | |||
1054 | const struct kirin_dc_ops ade_dc_ops = { | ||
1055 | .init = ade_drm_init, | ||
1056 | .cleanup = ade_drm_cleanup | ||
1057 | }; | ||
diff --git a/drivers/gpu/drm/hisilicon/kirin/kirin_drm_drv.c b/drivers/gpu/drm/hisilicon/kirin/kirin_drm_drv.c new file mode 100644 index 000000000000..e102c9e1e7b2 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/kirin/kirin_drm_drv.c | |||
@@ -0,0 +1,367 @@ | |||
1 | /* | ||
2 | * Hisilicon Kirin SoCs drm master driver | ||
3 | * | ||
4 | * Copyright (c) 2016 Linaro Limited. | ||
5 | * Copyright (c) 2014-2016 Hisilicon Limited. | ||
6 | * | ||
7 | * Author: | ||
8 | * Xinliang Liu <z.liuxinliang@hisilicon.com> | ||
9 | * Xinliang Liu <xinliang.liu@linaro.org> | ||
10 | * Xinwei Kong <kong.kongxinwei@hisilicon.com> | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or modify | ||
13 | * it under the terms of the GNU General Public License version 2 as | ||
14 | * published by the Free Software Foundation. | ||
15 | * | ||
16 | */ | ||
17 | |||
18 | #include <linux/of_platform.h> | ||
19 | #include <linux/component.h> | ||
20 | #include <linux/of_graph.h> | ||
21 | |||
22 | #include <drm/drmP.h> | ||
23 | #include <drm/drm_gem_cma_helper.h> | ||
24 | #include <drm/drm_fb_cma_helper.h> | ||
25 | #include <drm/drm_atomic_helper.h> | ||
26 | #include <drm/drm_crtc_helper.h> | ||
27 | |||
28 | #include "kirin_drm_drv.h" | ||
29 | |||
30 | static struct kirin_dc_ops *dc_ops; | ||
31 | |||
32 | static int kirin_drm_kms_cleanup(struct drm_device *dev) | ||
33 | { | ||
34 | struct kirin_drm_private *priv = dev->dev_private; | ||
35 | |||
36 | #ifdef CONFIG_DRM_FBDEV_EMULATION | ||
37 | if (priv->fbdev) { | ||
38 | drm_fbdev_cma_fini(priv->fbdev); | ||
39 | priv->fbdev = NULL; | ||
40 | } | ||
41 | #endif | ||
42 | drm_kms_helper_poll_fini(dev); | ||
43 | drm_vblank_cleanup(dev); | ||
44 | dc_ops->cleanup(dev); | ||
45 | drm_mode_config_cleanup(dev); | ||
46 | devm_kfree(dev->dev, priv); | ||
47 | dev->dev_private = NULL; | ||
48 | |||
49 | return 0; | ||
50 | } | ||
51 | |||
52 | #ifdef CONFIG_DRM_FBDEV_EMULATION | ||
53 | static void kirin_fbdev_output_poll_changed(struct drm_device *dev) | ||
54 | { | ||
55 | struct kirin_drm_private *priv = dev->dev_private; | ||
56 | |||
57 | if (priv->fbdev) { | ||
58 | drm_fbdev_cma_hotplug_event(priv->fbdev); | ||
59 | } else { | ||
60 | priv->fbdev = drm_fbdev_cma_init(dev, 32, | ||
61 | dev->mode_config.num_crtc, | ||
62 | dev->mode_config.num_connector); | ||
63 | if (IS_ERR(priv->fbdev)) | ||
64 | priv->fbdev = NULL; | ||
65 | } | ||
66 | } | ||
67 | #endif | ||
68 | |||
69 | static const struct drm_mode_config_funcs kirin_drm_mode_config_funcs = { | ||
70 | .fb_create = drm_fb_cma_create, | ||
71 | #ifdef CONFIG_DRM_FBDEV_EMULATION | ||
72 | .output_poll_changed = kirin_fbdev_output_poll_changed, | ||
73 | #endif | ||
74 | .atomic_check = drm_atomic_helper_check, | ||
75 | .atomic_commit = drm_atomic_helper_commit, | ||
76 | }; | ||
77 | |||
78 | static void kirin_drm_mode_config_init(struct drm_device *dev) | ||
79 | { | ||
80 | dev->mode_config.min_width = 0; | ||
81 | dev->mode_config.min_height = 0; | ||
82 | |||
83 | dev->mode_config.max_width = 2048; | ||
84 | dev->mode_config.max_height = 2048; | ||
85 | |||
86 | dev->mode_config.funcs = &kirin_drm_mode_config_funcs; | ||
87 | } | ||
88 | |||
89 | static int kirin_drm_kms_init(struct drm_device *dev) | ||
90 | { | ||
91 | struct kirin_drm_private *priv; | ||
92 | int ret; | ||
93 | |||
94 | priv = devm_kzalloc(dev->dev, sizeof(*priv), GFP_KERNEL); | ||
95 | if (!priv) | ||
96 | return -ENOMEM; | ||
97 | |||
98 | dev->dev_private = priv; | ||
99 | dev_set_drvdata(dev->dev, dev); | ||
100 | |||
101 | /* dev->mode_config initialization */ | ||
102 | drm_mode_config_init(dev); | ||
103 | kirin_drm_mode_config_init(dev); | ||
104 | |||
105 | /* display controller init */ | ||
106 | ret = dc_ops->init(dev); | ||
107 | if (ret) | ||
108 | goto err_mode_config_cleanup; | ||
109 | |||
110 | /* bind and init sub drivers */ | ||
111 | ret = component_bind_all(dev->dev, dev); | ||
112 | if (ret) { | ||
113 | DRM_ERROR("failed to bind all component.\n"); | ||
114 | goto err_dc_cleanup; | ||
115 | } | ||
116 | |||
117 | /* vblank init */ | ||
118 | ret = drm_vblank_init(dev, dev->mode_config.num_crtc); | ||
119 | if (ret) { | ||
120 | DRM_ERROR("failed to initialize vblank.\n"); | ||
121 | goto err_unbind_all; | ||
122 | } | ||
123 | /* with irq_enabled = true, we can use the vblank feature. */ | ||
124 | dev->irq_enabled = true; | ||
125 | |||
126 | /* reset all the states of crtc/plane/encoder/connector */ | ||
127 | drm_mode_config_reset(dev); | ||
128 | |||
129 | /* init kms poll for handling hpd */ | ||
130 | drm_kms_helper_poll_init(dev); | ||
131 | |||
132 | /* force detection after connectors init */ | ||
133 | (void)drm_helper_hpd_irq_event(dev); | ||
134 | |||
135 | return 0; | ||
136 | |||
137 | err_unbind_all: | ||
138 | component_unbind_all(dev->dev, dev); | ||
139 | err_dc_cleanup: | ||
140 | dc_ops->cleanup(dev); | ||
141 | err_mode_config_cleanup: | ||
142 | drm_mode_config_cleanup(dev); | ||
143 | devm_kfree(dev->dev, priv); | ||
144 | dev->dev_private = NULL; | ||
145 | |||
146 | return ret; | ||
147 | } | ||
148 | |||
149 | static const struct file_operations kirin_drm_fops = { | ||
150 | .owner = THIS_MODULE, | ||
151 | .open = drm_open, | ||
152 | .release = drm_release, | ||
153 | .unlocked_ioctl = drm_ioctl, | ||
154 | #ifdef CONFIG_COMPAT | ||
155 | .compat_ioctl = drm_compat_ioctl, | ||
156 | #endif | ||
157 | .poll = drm_poll, | ||
158 | .read = drm_read, | ||
159 | .llseek = no_llseek, | ||
160 | .mmap = drm_gem_cma_mmap, | ||
161 | }; | ||
162 | |||
163 | static int kirin_gem_cma_dumb_create(struct drm_file *file, | ||
164 | struct drm_device *dev, | ||
165 | struct drm_mode_create_dumb *args) | ||
166 | { | ||
167 | return drm_gem_cma_dumb_create_internal(file, dev, args); | ||
168 | } | ||
169 | |||
170 | static struct drm_driver kirin_drm_driver = { | ||
171 | .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | | ||
172 | DRIVER_ATOMIC | DRIVER_HAVE_IRQ, | ||
173 | .fops = &kirin_drm_fops, | ||
174 | .set_busid = drm_platform_set_busid, | ||
175 | |||
176 | .gem_free_object = drm_gem_cma_free_object, | ||
177 | .gem_vm_ops = &drm_gem_cma_vm_ops, | ||
178 | .dumb_create = kirin_gem_cma_dumb_create, | ||
179 | .dumb_map_offset = drm_gem_cma_dumb_map_offset, | ||
180 | .dumb_destroy = drm_gem_dumb_destroy, | ||
181 | |||
182 | .prime_handle_to_fd = drm_gem_prime_handle_to_fd, | ||
183 | .prime_fd_to_handle = drm_gem_prime_fd_to_handle, | ||
184 | .gem_prime_export = drm_gem_prime_export, | ||
185 | .gem_prime_import = drm_gem_prime_import, | ||
186 | .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, | ||
187 | .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, | ||
188 | .gem_prime_vmap = drm_gem_cma_prime_vmap, | ||
189 | .gem_prime_vunmap = drm_gem_cma_prime_vunmap, | ||
190 | .gem_prime_mmap = drm_gem_cma_prime_mmap, | ||
191 | |||
192 | .name = "kirin", | ||
193 | .desc = "Hisilicon Kirin SoCs' DRM Driver", | ||
194 | .date = "20150718", | ||
195 | .major = 1, | ||
196 | .minor = 0, | ||
197 | }; | ||
198 | |||
199 | static int compare_of(struct device *dev, void *data) | ||
200 | { | ||
201 | return dev->of_node == data; | ||
202 | } | ||
203 | |||
204 | static int kirin_drm_connectors_register(struct drm_device *dev) | ||
205 | { | ||
206 | struct drm_connector *connector; | ||
207 | struct drm_connector *failed_connector; | ||
208 | int ret; | ||
209 | |||
210 | mutex_lock(&dev->mode_config.mutex); | ||
211 | drm_for_each_connector(connector, dev) { | ||
212 | ret = drm_connector_register(connector); | ||
213 | if (ret) { | ||
214 | failed_connector = connector; | ||
215 | goto err; | ||
216 | } | ||
217 | } | ||
218 | mutex_unlock(&dev->mode_config.mutex); | ||
219 | |||
220 | return 0; | ||
221 | |||
222 | err: | ||
223 | drm_for_each_connector(connector, dev) { | ||
224 | if (failed_connector == connector) | ||
225 | break; | ||
226 | drm_connector_unregister(connector); | ||
227 | } | ||
228 | mutex_unlock(&dev->mode_config.mutex); | ||
229 | |||
230 | return ret; | ||
231 | } | ||
232 | |||
233 | static int kirin_drm_bind(struct device *dev) | ||
234 | { | ||
235 | struct drm_driver *driver = &kirin_drm_driver; | ||
236 | struct drm_device *drm_dev; | ||
237 | int ret; | ||
238 | |||
239 | drm_dev = drm_dev_alloc(driver, dev); | ||
240 | if (!drm_dev) | ||
241 | return -ENOMEM; | ||
242 | |||
243 | drm_dev->platformdev = to_platform_device(dev); | ||
244 | |||
245 | ret = kirin_drm_kms_init(drm_dev); | ||
246 | if (ret) | ||
247 | goto err_drm_dev_unref; | ||
248 | |||
249 | ret = drm_dev_register(drm_dev, 0); | ||
250 | if (ret) | ||
251 | goto err_kms_cleanup; | ||
252 | |||
253 | /* connectors should be registered after drm device register */ | ||
254 | ret = kirin_drm_connectors_register(drm_dev); | ||
255 | if (ret) | ||
256 | goto err_drm_dev_unregister; | ||
257 | |||
258 | DRM_INFO("Initialized %s %d.%d.%d %s on minor %d\n", | ||
259 | driver->name, driver->major, driver->minor, driver->patchlevel, | ||
260 | driver->date, drm_dev->primary->index); | ||
261 | |||
262 | return 0; | ||
263 | |||
264 | err_drm_dev_unregister: | ||
265 | drm_dev_unregister(drm_dev); | ||
266 | err_kms_cleanup: | ||
267 | kirin_drm_kms_cleanup(drm_dev); | ||
268 | err_drm_dev_unref: | ||
269 | drm_dev_unref(drm_dev); | ||
270 | |||
271 | return ret; | ||
272 | } | ||
273 | |||
274 | static void kirin_drm_unbind(struct device *dev) | ||
275 | { | ||
276 | drm_put_dev(dev_get_drvdata(dev)); | ||
277 | } | ||
278 | |||
279 | static const struct component_master_ops kirin_drm_ops = { | ||
280 | .bind = kirin_drm_bind, | ||
281 | .unbind = kirin_drm_unbind, | ||
282 | }; | ||
283 | |||
284 | static struct device_node *kirin_get_remote_node(struct device_node *np) | ||
285 | { | ||
286 | struct device_node *endpoint, *remote; | ||
287 | |||
288 | /* get the first endpoint, in our case only one remote node | ||
289 | * is connected to display controller. | ||
290 | */ | ||
291 | endpoint = of_graph_get_next_endpoint(np, NULL); | ||
292 | if (!endpoint) { | ||
293 | DRM_ERROR("no valid endpoint node\n"); | ||
294 | return ERR_PTR(-ENODEV); | ||
295 | } | ||
296 | of_node_put(endpoint); | ||
297 | |||
298 | remote = of_graph_get_remote_port_parent(endpoint); | ||
299 | if (!remote) { | ||
300 | DRM_ERROR("no valid remote node\n"); | ||
301 | return ERR_PTR(-ENODEV); | ||
302 | } | ||
303 | of_node_put(remote); | ||
304 | |||
305 | if (!of_device_is_available(remote)) { | ||
306 | DRM_ERROR("not available for remote node\n"); | ||
307 | return ERR_PTR(-ENODEV); | ||
308 | } | ||
309 | |||
310 | return remote; | ||
311 | } | ||
312 | |||
313 | static int kirin_drm_platform_probe(struct platform_device *pdev) | ||
314 | { | ||
315 | struct device *dev = &pdev->dev; | ||
316 | struct device_node *np = dev->of_node; | ||
317 | struct component_match *match = NULL; | ||
318 | struct device_node *remote; | ||
319 | |||
320 | dc_ops = (struct kirin_dc_ops *)of_device_get_match_data(dev); | ||
321 | if (!dc_ops) { | ||
322 | DRM_ERROR("failed to get dt id data\n"); | ||
323 | return -EINVAL; | ||
324 | } | ||
325 | |||
326 | remote = kirin_get_remote_node(np); | ||
327 | if (IS_ERR(remote)) | ||
328 | return PTR_ERR(remote); | ||
329 | |||
330 | component_match_add(dev, &match, compare_of, remote); | ||
331 | |||
332 | return component_master_add_with_match(dev, &kirin_drm_ops, match); | ||
333 | |||
334 | return 0; | ||
335 | } | ||
336 | |||
337 | static int kirin_drm_platform_remove(struct platform_device *pdev) | ||
338 | { | ||
339 | component_master_del(&pdev->dev, &kirin_drm_ops); | ||
340 | dc_ops = NULL; | ||
341 | return 0; | ||
342 | } | ||
343 | |||
344 | static const struct of_device_id kirin_drm_dt_ids[] = { | ||
345 | { .compatible = "hisilicon,hi6220-ade", | ||
346 | .data = &ade_dc_ops, | ||
347 | }, | ||
348 | { /* end node */ }, | ||
349 | }; | ||
350 | MODULE_DEVICE_TABLE(of, kirin_drm_dt_ids); | ||
351 | |||
352 | static struct platform_driver kirin_drm_platform_driver = { | ||
353 | .probe = kirin_drm_platform_probe, | ||
354 | .remove = kirin_drm_platform_remove, | ||
355 | .driver = { | ||
356 | .name = "kirin-drm", | ||
357 | .of_match_table = kirin_drm_dt_ids, | ||
358 | }, | ||
359 | }; | ||
360 | |||
361 | module_platform_driver(kirin_drm_platform_driver); | ||
362 | |||
363 | MODULE_AUTHOR("Xinliang Liu <xinliang.liu@linaro.org>"); | ||
364 | MODULE_AUTHOR("Xinliang Liu <z.liuxinliang@hisilicon.com>"); | ||
365 | MODULE_AUTHOR("Xinwei Kong <kong.kongxinwei@hisilicon.com>"); | ||
366 | MODULE_DESCRIPTION("hisilicon Kirin SoCs' DRM master driver"); | ||
367 | MODULE_LICENSE("GPL v2"); | ||
diff --git a/drivers/gpu/drm/hisilicon/kirin/kirin_drm_drv.h b/drivers/gpu/drm/hisilicon/kirin/kirin_drm_drv.h new file mode 100644 index 000000000000..1a07caf8e7f4 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/kirin/kirin_drm_drv.h | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2016 Linaro Limited. | ||
3 | * Copyright (c) 2014-2016 Hisilicon Limited. | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License version 2 as | ||
7 | * published by the Free Software Foundation. | ||
8 | * | ||
9 | */ | ||
10 | |||
11 | #ifndef __KIRIN_DRM_DRV_H__ | ||
12 | #define __KIRIN_DRM_DRV_H__ | ||
13 | |||
14 | #define MAX_CRTC 2 | ||
15 | |||
16 | /* display controller init/cleanup ops */ | ||
17 | struct kirin_dc_ops { | ||
18 | int (*init)(struct drm_device *dev); | ||
19 | void (*cleanup)(struct drm_device *dev); | ||
20 | }; | ||
21 | |||
22 | struct kirin_drm_private { | ||
23 | struct drm_crtc *crtc[MAX_CRTC]; | ||
24 | #ifdef CONFIG_DRM_FBDEV_EMULATION | ||
25 | struct drm_fbdev_cma *fbdev; | ||
26 | #endif | ||
27 | }; | ||
28 | |||
29 | extern const struct kirin_dc_ops ade_dc_ops; | ||
30 | |||
31 | #endif /* __KIRIN_DRM_DRV_H__ */ | ||