diff options
author | Liu Ying <Ying.Liu@freescale.com> | 2014-03-10 06:31:51 -0400 |
---|---|---|
committer | Nitin Garg <nitin.garg@freescale.com> | 2014-04-16 09:57:57 -0400 |
commit | cb9327ca90a29af88a3116edd59d5eabe2c0d79e (patch) | |
tree | 7f3cc6d757069c86197b78c34f77c7e811d3373b | |
parent | 16a37658d909957b8badc4dc835cdc808e2bd350 (diff) |
ENGR00302472-6 video: mxc: LDB driver refactor
This patch almost reworks the LDB driver to make the
implementation simpler and clearer. The new version
should support all the LDB modules embedded in imx53,
imx6qdl and imx6sx. The lvds-channel subsidiary DT
node is introduced to represent each LVDS channel.
People may specify a channel's CRTC, working mode(dual
mode or split mode), data width, data mapping, display
timing and if it is a primary channel in the node.
Change logs:
* Use CTRC concept so that the driver may support both
IPU and LCDIF as the display engines.
* Add mxc dispdrv enable() callback.
* Cache LDB ctrl register value at probe()/setup()/
enable() stages and finally write to the register at
enable() stage.
* Simplify logics for setting ctrl/bus muxing/clocks.
* Use regmap to write crtl and bus muxing registers.
* Remove LDB description in DT binding doc fsl_ipuv3_fb.txt.
Instead, add a new one in fsl,ldb.txt.
Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
-rw-r--r-- | Documentation/devicetree/bindings/fb/fsl_ipuv3_fb.txt | 45 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/video/fsl,ldb.txt | 95 | ||||
-rw-r--r-- | arch/arm/boot/dts/imx6dl-sabreauto.dts | 9 | ||||
-rw-r--r-- | arch/arm/boot/dts/imx6dl-sabresd-common.dtsi | 9 | ||||
-rw-r--r-- | arch/arm/boot/dts/imx6dl.dtsi | 17 | ||||
-rw-r--r-- | arch/arm/boot/dts/imx6q-sabreauto.dts | 10 | ||||
-rw-r--r-- | arch/arm/boot/dts/imx6q-sabresd.dts | 10 | ||||
-rw-r--r-- | arch/arm/boot/dts/imx6q.dtsi | 17 | ||||
-rw-r--r-- | arch/arm/boot/dts/imx6qdl-sabreauto.dtsi | 49 | ||||
-rw-r--r-- | arch/arm/boot/dts/imx6qdl-sabresd.dtsi | 49 | ||||
-rw-r--r-- | arch/arm/boot/dts/imx6qdl.dtsi | 27 | ||||
-rw-r--r-- | drivers/video/mxc/Kconfig | 1 | ||||
-rw-r--r-- | drivers/video/mxc/ldb.c | 1595 |
13 files changed, 984 insertions, 949 deletions
diff --git a/Documentation/devicetree/bindings/fb/fsl_ipuv3_fb.txt b/Documentation/devicetree/bindings/fb/fsl_ipuv3_fb.txt index 3a59da799620..b8d27ef108fe 100644 --- a/Documentation/devicetree/bindings/fb/fsl_ipuv3_fb.txt +++ b/Documentation/devicetree/bindings/fb/fsl_ipuv3_fb.txt | |||
@@ -8,21 +8,6 @@ display device. | |||
8 | Two IPU units are on the imx6q SOC while only one IPU unit on the imx6dl SOC. | 8 | Two IPU units are on the imx6q SOC while only one IPU unit on the imx6dl SOC. |
9 | Each IPU unit has two display interfaces. | 9 | Each IPU unit has two display interfaces. |
10 | 10 | ||
11 | For LDB/LVDS panel, there are two LVDS channels(LVDS0 and LVDS1) which can | ||
12 | transfer video data, these two channels can be used as | ||
13 | split/dual/single/separate mode. | ||
14 | -split mode means display data from DI0 or DI1 will send to both channels | ||
15 | LVDS0+LVDS1. | ||
16 | -dual mode means display data from DI0 or DI1 will be duplicated on LVDS0 | ||
17 | and LVDS1, it said, LVDS0 and LVDS1 has the same content. | ||
18 | -single mode means only work for DI0/DI1->LVDS0 or DI0/DI1->LVDS1. | ||
19 | -separate mode means you can make DI0/DI1->LVDS0 and DI0/DI1->LVDS1 work | ||
20 | at the same time. | ||
21 | "ldb=spl0/1" -- split mode on DI0/1 | ||
22 | "ldb=dul0/1" -- dual mode on DI0/1 | ||
23 | "ldb=sin0/1" -- single mode on LVDS0/1 | ||
24 | "ldb=sep0/1" -- separate mode begin from LVDS0/1 | ||
25 | |||
26 | Required properties for IPU: | 11 | Required properties for IPU: |
27 | - bypass_reset :Bypass reset to avoid display channel being. | 12 | - bypass_reset :Bypass reset to avoid display channel being. |
28 | stopped by probe since it may start to work in bootloader: 0 or 1. | 13 | stopped by probe since it may start to work in bootloader: 0 or 1. |
@@ -37,8 +22,7 @@ Required properties for IPU: | |||
37 | Required properties for fb: | 22 | Required properties for fb: |
38 | - compatible : should be "fsl,mxc_sdc_fb". | 23 | - compatible : should be "fsl,mxc_sdc_fb". |
39 | - disp_dev : display device: "ldb", "lcd", "hdmi", "mipi_dsi". | 24 | - disp_dev : display device: "ldb", "lcd", "hdmi", "mipi_dsi". |
40 | - mode_str : video mode string: "LDB-XGA" or "LDB-1080P60" for ldb, | 25 | - mode_str : "CLAA-WVGA" for lcd, "TRULY-WVGA" for TRULY mipi_dsi lcd panel, |
41 | "CLAA-WVGA" for lcd, "TRULY-WVGA" for TRULY mipi_dsi lcd panel, | ||
42 | "1920x1080M@60" for hdmi. | 26 | "1920x1080M@60" for hdmi. |
43 | - default_bpp : default bits per pixel: 8/16/24/32 | 27 | - default_bpp : default bits per pixel: 8/16/24/32 |
44 | - int_clk : use internal clock as pixel clock: 0 or 1 | 28 | - int_clk : use internal clock as pixel clock: 0 or 1 |
@@ -51,14 +35,13 @@ Required properties for fb: | |||
51 | BGR24 IPU_PIX_FMT_BGR24 | 35 | BGR24 IPU_PIX_FMT_BGR24 |
52 | GBR24 IPU_PIX_FMT_GBR24 | 36 | GBR24 IPU_PIX_FMT_GBR24 |
53 | YUV444 IPU_PIX_FMT_YUV444 | 37 | YUV444 IPU_PIX_FMT_YUV444 |
54 | LVDS666 IPU_PIX_FMT_LVDS666 | ||
55 | YUYV IPU_PIX_FMT_YUYV | 38 | YUYV IPU_PIX_FMT_YUYV |
56 | UYVY IPU_PIX_FMT_UYVY | 39 | UYVY IPU_PIX_FMT_UYVY |
57 | YVYV IPU_PIX_FMT_YVYU | 40 | YVYV IPU_PIX_FMT_YVYU |
58 | VYUY IPU_PIX_FMT_VYUY | 41 | VYUY IPU_PIX_FMT_VYUY |
59 | 42 | ||
60 | Required properties for display: | 43 | Required properties for display: |
61 | - compatible : should be "fsl,lcd" for lcd panel, "fsl,imx6q-ldb" for ldb | 44 | - compatible : should be "fsl,lcd" for lcd panel |
62 | - reg : the register address range if necessary to have. | 45 | - reg : the register address range if necessary to have. |
63 | - interrupts : the error and sync interrupts if necessary to have. | 46 | - interrupts : the error and sync interrupts if necessary to have. |
64 | - clocks : the clock sources that it depends on if necessary to have. | 47 | - clocks : the clock sources that it depends on if necessary to have. |
@@ -69,19 +52,6 @@ Required properties for display: | |||
69 | - pinctrl-names : should be "default" | 52 | - pinctrl-names : should be "default" |
70 | - pinctrl-0 : should be pinctrl_ipu1_1 or pinctrl_ipu2_1, which depends on the | 53 | - pinctrl-0 : should be pinctrl_ipu1_1 or pinctrl_ipu2_1, which depends on the |
71 | IPU connected. | 54 | IPU connected. |
72 | - sec_ipu_id : secondary ipu id for the second display device(ldb only): 0 or 1 | ||
73 | - sec_disp_id : secondary display interface id for the second display | ||
74 | device(ldb only): 0 or 1 | ||
75 | - ext_ref : reference resistor select for ldb only: 0 or 1 | ||
76 | - mode : ldb mode as below: | ||
77 | spl0 LDB_SPL_DI0 | ||
78 | spl1 LDB_SPL_DI1 | ||
79 | dul0 LDB_DUL_DI0 | ||
80 | dul1 LDB_DUL_DI1 | ||
81 | sin0 LDB_SIN0 | ||
82 | sin1 LDB_SIN1 | ||
83 | sep0 LDB_SEP0 | ||
84 | sep1 LDB_SEP1 | ||
85 | - gpr : the mux controller for the display engine's display interfaces and the display encoder | 55 | - gpr : the mux controller for the display engine's display interfaces and the display encoder |
86 | (only valid for mipi dsi now). | 56 | (only valid for mipi dsi now). |
87 | - disp-power-on-supply : the regulator to control display panel's power. | 57 | - disp-power-on-supply : the regulator to control display panel's power. |
@@ -118,17 +88,6 @@ Example for fb: | |||
118 | status = "okay"; | 88 | status = "okay"; |
119 | }; | 89 | }; |
120 | 90 | ||
121 | Example for ldb display: | ||
122 | ldb@020e0000 { | ||
123 | ipu_id = <1>; | ||
124 | disp_id = <0>; | ||
125 | ext_ref = <1>; | ||
126 | mode = "sep0"; | ||
127 | sec_ipu_id = <1>; | ||
128 | sec_disp_id = <1>; | ||
129 | status = "okay"; | ||
130 | }; | ||
131 | |||
132 | Example for mipi dsi display: | 91 | Example for mipi dsi display: |
133 | mipi_dsi: mipi@021e0000 { | 92 | mipi_dsi: mipi@021e0000 { |
134 | compatible = "fsl,imx6q-mipi-dsi"; | 93 | compatible = "fsl,imx6q-mipi-dsi"; |
diff --git a/Documentation/devicetree/bindings/video/fsl,ldb.txt b/Documentation/devicetree/bindings/video/fsl,ldb.txt new file mode 100644 index 000000000000..f843e184f253 --- /dev/null +++ b/Documentation/devicetree/bindings/video/fsl,ldb.txt | |||
@@ -0,0 +1,95 @@ | |||
1 | * Freescale LVDS Display Bridge(LDB) | ||
2 | |||
3 | The LVDS Display Bridge (LDB) connects a display engine, | ||
4 | such as IPU and LCDIF, to an external LVDS display interface. | ||
5 | The purpose of the LDB is to support flow of synchronous RGB | ||
6 | data from the display engine to external display devices | ||
7 | through LVDS interfaces. This support covers all aspects of | ||
8 | these activities: | ||
9 | * Connectivity to relevant devices - Displays with LVDS receivers. | ||
10 | * Arranging the data as required by the external display receiver | ||
11 | and by LVDS display standards. | ||
12 | * Synchronization and control capabilities. | ||
13 | |||
14 | Required properties: | ||
15 | - compatible: Should be "fsl,<soc>-ldb". | ||
16 | - #address-cells: Must be <1>. | ||
17 | - #size-cells: Must be <0>. | ||
18 | - gpr: gpr is the phandle to general purpose register node. | ||
19 | - clocks: The clocks provided by the SoC to LDB, including ldb_di0/1, | ||
20 | dix_sel, ldb_di0/1_div_3_5, ldb_di0/1_div_7 and ldb_di0/1_div_sel | ||
21 | clocks. | ||
22 | - clock-names: Must contain entries for each entry in clocks. | ||
23 | |||
24 | Optional properties: | ||
25 | - ext-ref: Provide this bool property if your LDB uses an external | ||
26 | reference resistor for bandgap. | ||
27 | - split-mode: Provide this bool property if your board uses LDB split | ||
28 | mode to drive a high resolution display, say 1080P@60. In this | ||
29 | mode, two LVDS channels will drive one display. | ||
30 | - dual-mode: Provide this bool property if your board uses LDB dual | ||
31 | mode to drive two displays. In this mode, one display engine will | ||
32 | drive two displays which have the same timings and display content. | ||
33 | |||
34 | Subnode for LVDS Channel | ||
35 | ======================== | ||
36 | |||
37 | Each LVDS Channel has to contain a display-timings node that describes the | ||
38 | video timings for the connected LVDS display. For detailed information, also | ||
39 | have a look at Documentation/devicetree/bindings/video/display-timing.txt. | ||
40 | |||
41 | Required properties: | ||
42 | - reg: Should be <0> or <1>. | ||
43 | - fsl,data-mapping: Should be "spwg" or "jeida". | ||
44 | This describes how the color bits are laid out in the serialized LVDS signal. | ||
45 | - fsl,data-width: Should be <18> or <24>. | ||
46 | - crtc: Should be "ipu1/2-di0/1" for i.MX6Q, "ipu1-di0/1" or "LCDIF" for i.MX6DL, | ||
47 | "ipu-di0/1" for i.MX53 and "lcdif1/2" for i.MX6sx. | ||
48 | |||
49 | Optional properties: | ||
50 | - primary: Provide this bool property if this channel is the one found by a | ||
51 | framebuffer with a smaller node id. This property is not valid for | ||
52 | dual mode and split mode. | ||
53 | |||
54 | ldb: ldb@020e0008 { | ||
55 | #address-cells = <1>; | ||
56 | #size-cells = <0>; | ||
57 | gpr = <&gpr>; | ||
58 | compatible = "fsl,imx6dl-ldb", "fsl,imx53-ldb"; | ||
59 | gpr = <&gpr>; | ||
60 | clocks = <&clks 135>, <&clks 136>, | ||
61 | <&clks 39>, <&clks 40>, | ||
62 | <&clks 41>, | ||
63 | <&clks 184>, <&clks 185>, | ||
64 | <&clks 205>, <&clks 206>, | ||
65 | <&clks 207>, <&clks 208>; | ||
66 | clock-names = "ldb_di0", "ldb_di1", | ||
67 | "di0_sel", "di1_sel", | ||
68 | "di2_sel", | ||
69 | "ldb_di0_div_3_5", "ldb_di1_div_3_5", | ||
70 | "ldb_di0_div_7", "ldb_di1_div_7", | ||
71 | "ldb_di0_div_sel", "ldb_di1_div_sel"; | ||
72 | |||
73 | lvds-channel@0 { | ||
74 | reg = <0>; | ||
75 | fsl,data-mapping = "spwg"; | ||
76 | fsl,data-width = <18>; | ||
77 | crtc = "ipu1-di0"; | ||
78 | |||
79 | display-timings { | ||
80 | /* ... */ | ||
81 | }; | ||
82 | }; | ||
83 | |||
84 | lvds-channel@1 { | ||
85 | reg = <1>; | ||
86 | fsl,data-mapping = "spwg"; | ||
87 | fsl,data-width = <18>; | ||
88 | crtc = "ipu1-di1"; | ||
89 | primary; | ||
90 | |||
91 | display-timings { | ||
92 | /* ... */ | ||
93 | }; | ||
94 | }; | ||
95 | }; | ||
diff --git a/arch/arm/boot/dts/imx6dl-sabreauto.dts b/arch/arm/boot/dts/imx6dl-sabreauto.dts index ac2e29dfb780..9e633b375574 100644 --- a/arch/arm/boot/dts/imx6dl-sabreauto.dts +++ b/arch/arm/boot/dts/imx6dl-sabreauto.dts | |||
@@ -17,8 +17,13 @@ | |||
17 | }; | 17 | }; |
18 | 18 | ||
19 | &ldb { | 19 | &ldb { |
20 | ipu_id = <0>; | 20 | lvds-channel@0 { |
21 | sec_ipu_id = <0>; | 21 | crtc = "ipu1-di0"; |
22 | }; | ||
23 | |||
24 | lvds-channel@1 { | ||
25 | crtc = "ipu1-di1"; | ||
26 | }; | ||
22 | }; | 27 | }; |
23 | 28 | ||
24 | &mxcfb1 { | 29 | &mxcfb1 { |
diff --git a/arch/arm/boot/dts/imx6dl-sabresd-common.dtsi b/arch/arm/boot/dts/imx6dl-sabresd-common.dtsi index 2a075345f58a..515a2c713d4a 100644 --- a/arch/arm/boot/dts/imx6dl-sabresd-common.dtsi +++ b/arch/arm/boot/dts/imx6dl-sabresd-common.dtsi | |||
@@ -113,8 +113,13 @@ | |||
113 | }; | 113 | }; |
114 | 114 | ||
115 | &ldb { | 115 | &ldb { |
116 | ipu_id = <0>; | 116 | lvds-channel@0 { |
117 | sec_ipu_id = <0>; | 117 | crtc = "ipu1-di0"; |
118 | }; | ||
119 | |||
120 | lvds-channel@1 { | ||
121 | crtc = "ipu1-di1"; | ||
122 | }; | ||
118 | }; | 123 | }; |
119 | 124 | ||
120 | &mxcfb1 { | 125 | &mxcfb1 { |
diff --git a/arch/arm/boot/dts/imx6dl.dtsi b/arch/arm/boot/dts/imx6dl.dtsi index abad136c5487..86ed41366031 100644 --- a/arch/arm/boot/dts/imx6dl.dtsi +++ b/arch/arm/boot/dts/imx6dl.dtsi | |||
@@ -206,3 +206,20 @@ | |||
206 | }; | 206 | }; |
207 | }; | 207 | }; |
208 | }; | 208 | }; |
209 | |||
210 | &ldb { | ||
211 | compatible = "fsl,imx6dl-ldb", "fsl,imx53-ldb"; | ||
212 | |||
213 | clocks = <&clks 135>, <&clks 136>, | ||
214 | <&clks 39>, <&clks 40>, | ||
215 | <&clks 41>, | ||
216 | <&clks 184>, <&clks 185>, | ||
217 | <&clks 205>, <&clks 206>, | ||
218 | <&clks 207>, <&clks 208>; | ||
219 | clock-names = "ldb_di0", "ldb_di1", | ||
220 | "di0_sel", "di1_sel", | ||
221 | "di2_sel", | ||
222 | "ldb_di0_div_3_5", "ldb_di1_div_3_5", | ||
223 | "ldb_di0_div_7", "ldb_di1_div_7", | ||
224 | "ldb_di0_div_sel", "ldb_di1_div_sel"; | ||
225 | }; | ||
diff --git a/arch/arm/boot/dts/imx6q-sabreauto.dts b/arch/arm/boot/dts/imx6q-sabreauto.dts index 8a50ca1baec7..ddd493111e5d 100644 --- a/arch/arm/boot/dts/imx6q-sabreauto.dts +++ b/arch/arm/boot/dts/imx6q-sabreauto.dts | |||
@@ -20,6 +20,16 @@ | |||
20 | compatible = "fsl,imx6q-sabreauto", "fsl,imx6q"; | 20 | compatible = "fsl,imx6q-sabreauto", "fsl,imx6q"; |
21 | }; | 21 | }; |
22 | 22 | ||
23 | &ldb { | ||
24 | lvds-channel@0 { | ||
25 | crtc = "ipu2-di0"; | ||
26 | }; | ||
27 | |||
28 | lvds-channel@1 { | ||
29 | crtc = "ipu2-di1"; | ||
30 | }; | ||
31 | }; | ||
32 | |||
23 | &mxcfb1 { | 33 | &mxcfb1 { |
24 | status = "okay"; | 34 | status = "okay"; |
25 | }; | 35 | }; |
diff --git a/arch/arm/boot/dts/imx6q-sabresd.dts b/arch/arm/boot/dts/imx6q-sabresd.dts index 5e5ff56690e6..4f17cb58ed9f 100644 --- a/arch/arm/boot/dts/imx6q-sabresd.dts +++ b/arch/arm/boot/dts/imx6q-sabresd.dts | |||
@@ -127,6 +127,16 @@ | |||
127 | }; | 127 | }; |
128 | }; | 128 | }; |
129 | 129 | ||
130 | &ldb { | ||
131 | lvds-channel@0 { | ||
132 | crtc = "ipu2-di0"; | ||
133 | }; | ||
134 | |||
135 | lvds-channel@1 { | ||
136 | crtc = "ipu2-di1"; | ||
137 | }; | ||
138 | }; | ||
139 | |||
130 | &mxcfb1 { | 140 | &mxcfb1 { |
131 | status = "okay"; | 141 | status = "okay"; |
132 | }; | 142 | }; |
diff --git a/arch/arm/boot/dts/imx6q.dtsi b/arch/arm/boot/dts/imx6q.dtsi index 794b4ce825b8..69c8eacc4f82 100644 --- a/arch/arm/boot/dts/imx6q.dtsi +++ b/arch/arm/boot/dts/imx6q.dtsi | |||
@@ -242,3 +242,20 @@ | |||
242 | }; | 242 | }; |
243 | }; | 243 | }; |
244 | }; | 244 | }; |
245 | |||
246 | &ldb { | ||
247 | compatible = "fsl,imx6q-ldb", "fsl,imx53-ldb"; | ||
248 | |||
249 | clocks = <&clks 135>, <&clks 136>, | ||
250 | <&clks 39>, <&clks 40>, | ||
251 | <&clks 41>, <&clks 42>, | ||
252 | <&clks 184>, <&clks 185>, | ||
253 | <&clks 205>, <&clks 206>, | ||
254 | <&clks 207>, <&clks 208>; | ||
255 | clock-names = "ldb_di0", "ldb_di1", | ||
256 | "di0_sel", "di1_sel", | ||
257 | "di2_sel", "di3_sel", | ||
258 | "ldb_di0_div_3_5", "ldb_di1_div_3_5", | ||
259 | "ldb_di0_div_7", "ldb_di1_div_7", | ||
260 | "ldb_di0_div_sel", "ldb_di1_div_sel"; | ||
261 | }; | ||
diff --git a/arch/arm/boot/dts/imx6qdl-sabreauto.dtsi b/arch/arm/boot/dts/imx6qdl-sabreauto.dtsi index d9c3af9f58e9..c5dacc0d528d 100644 --- a/arch/arm/boot/dts/imx6qdl-sabreauto.dtsi +++ b/arch/arm/boot/dts/imx6qdl-sabreauto.dtsi | |||
@@ -588,13 +588,50 @@ | |||
588 | }; | 588 | }; |
589 | 589 | ||
590 | &ldb { | 590 | &ldb { |
591 | ipu_id = <1>; | ||
592 | disp_id = <0>; | ||
593 | ext_ref = <1>; | ||
594 | mode = "sep0"; | ||
595 | sec_ipu_id = <1>; | ||
596 | sec_disp_id = <1>; | ||
597 | status = "okay"; | 591 | status = "okay"; |
592 | |||
593 | lvds-channel@0 { | ||
594 | fsl,data-mapping = "spwg"; | ||
595 | fsl,data-width = <18>; | ||
596 | primary; | ||
597 | status = "okay"; | ||
598 | |||
599 | display-timings { | ||
600 | native-mode = <&timing0>; | ||
601 | timing0: hsd100pxn1 { | ||
602 | clock-frequency = <65000000>; | ||
603 | hactive = <1024>; | ||
604 | vactive = <768>; | ||
605 | hback-porch = <220>; | ||
606 | hfront-porch = <40>; | ||
607 | vback-porch = <21>; | ||
608 | vfront-porch = <7>; | ||
609 | hsync-len = <60>; | ||
610 | vsync-len = <10>; | ||
611 | }; | ||
612 | }; | ||
613 | }; | ||
614 | |||
615 | lvds-channel@1 { | ||
616 | fsl,data-mapping = "spwg"; | ||
617 | fsl,data-width = <18>; | ||
618 | status = "okay"; | ||
619 | |||
620 | display-timings { | ||
621 | native-mode = <&timing1>; | ||
622 | timing1: hsd100pxn1 { | ||
623 | clock-frequency = <65000000>; | ||
624 | hactive = <1024>; | ||
625 | vactive = <768>; | ||
626 | hback-porch = <220>; | ||
627 | hfront-porch = <40>; | ||
628 | vback-porch = <21>; | ||
629 | vfront-porch = <7>; | ||
630 | hsync-len = <60>; | ||
631 | vsync-len = <10>; | ||
632 | }; | ||
633 | }; | ||
634 | }; | ||
598 | }; | 635 | }; |
599 | 636 | ||
600 | &mipi_csi { | 637 | &mipi_csi { |
diff --git a/arch/arm/boot/dts/imx6qdl-sabresd.dtsi b/arch/arm/boot/dts/imx6qdl-sabresd.dtsi index 5d774e5a7ec7..8caa16b45094 100644 --- a/arch/arm/boot/dts/imx6qdl-sabresd.dtsi +++ b/arch/arm/boot/dts/imx6qdl-sabresd.dtsi | |||
@@ -502,13 +502,50 @@ | |||
502 | }; | 502 | }; |
503 | 503 | ||
504 | &ldb { | 504 | &ldb { |
505 | ipu_id = <1>; | ||
506 | disp_id = <1>; | ||
507 | ext_ref = <1>; | ||
508 | mode = "sep1"; | ||
509 | sec_ipu_id = <1>; | ||
510 | sec_disp_id = <0>; | ||
511 | status = "okay"; | 505 | status = "okay"; |
506 | |||
507 | lvds-channel@0 { | ||
508 | fsl,data-mapping = "spwg"; | ||
509 | fsl,data-width = <18>; | ||
510 | status = "okay"; | ||
511 | |||
512 | display-timings { | ||
513 | native-mode = <&timing0>; | ||
514 | timing0: hsd100pxn1 { | ||
515 | clock-frequency = <65000000>; | ||
516 | hactive = <1024>; | ||
517 | vactive = <768>; | ||
518 | hback-porch = <220>; | ||
519 | hfront-porch = <40>; | ||
520 | vback-porch = <21>; | ||
521 | vfront-porch = <7>; | ||
522 | hsync-len = <60>; | ||
523 | vsync-len = <10>; | ||
524 | }; | ||
525 | }; | ||
526 | }; | ||
527 | |||
528 | lvds-channel@1 { | ||
529 | fsl,data-mapping = "spwg"; | ||
530 | fsl,data-width = <18>; | ||
531 | primary; | ||
532 | status = "okay"; | ||
533 | |||
534 | display-timings { | ||
535 | native-mode = <&timing1>; | ||
536 | timing1: hsd100pxn1 { | ||
537 | clock-frequency = <65000000>; | ||
538 | hactive = <1024>; | ||
539 | vactive = <768>; | ||
540 | hback-porch = <220>; | ||
541 | hfront-porch = <40>; | ||
542 | vback-porch = <21>; | ||
543 | vfront-porch = <7>; | ||
544 | hsync-len = <60>; | ||
545 | vsync-len = <10>; | ||
546 | }; | ||
547 | }; | ||
548 | }; | ||
512 | }; | 549 | }; |
513 | 550 | ||
514 | &mipi_csi { | 551 | &mipi_csi { |
diff --git a/arch/arm/boot/dts/imx6qdl.dtsi b/arch/arm/boot/dts/imx6qdl.dtsi index a31e566c7da3..01414a575ab6 100644 --- a/arch/arm/boot/dts/imx6qdl.dtsi +++ b/arch/arm/boot/dts/imx6qdl.dtsi | |||
@@ -690,21 +690,20 @@ | |||
690 | }; | 690 | }; |
691 | 691 | ||
692 | ldb: ldb@020e0008 { | 692 | ldb: ldb@020e0008 { |
693 | compatible = "fsl,imx6q-ldb", "fsl,imx53-ldb"; | 693 | #address-cells = <1>; |
694 | reg = <0x020e0000 0x4000>; | 694 | #size-cells = <0>; |
695 | clocks = <&clks 135>, <&clks 136>, | 695 | gpr = <&gpr>; |
696 | <&clks 39>, <&clks 40>, | ||
697 | <&clks 41>, <&clks 42>, | ||
698 | <&clks 184>, <&clks 185>, | ||
699 | <&clks 205>, <&clks 206>, | ||
700 | <&clks 207>, <&clks 208>; | ||
701 | clock-names = "ldb_di0", "ldb_di1", | ||
702 | "ipu1_di0_sel", "ipu1_di1_sel", | ||
703 | "ipu2_di0_sel", "ipu2_di1_sel", | ||
704 | "di0_div_3_5", "di1_div_3_5", | ||
705 | "di0_div_7", "di1_div_7", | ||
706 | "di0_div_sel", "di1_div_sel"; | ||
707 | status = "disabled"; | 696 | status = "disabled"; |
697 | |||
698 | lvds-channel@0 { | ||
699 | reg = <0>; | ||
700 | status = "disabled"; | ||
701 | }; | ||
702 | |||
703 | lvds-channel@1 { | ||
704 | reg = <1>; | ||
705 | status = "disabled"; | ||
706 | }; | ||
708 | }; | 707 | }; |
709 | 708 | ||
710 | dcic1: dcic@020e4000 { | 709 | dcic1: dcic@020e4000 { |
diff --git a/drivers/video/mxc/Kconfig b/drivers/video/mxc/Kconfig index 2269de1e02cb..a86288adedbf 100644 --- a/drivers/video/mxc/Kconfig +++ b/drivers/video/mxc/Kconfig | |||
@@ -22,6 +22,7 @@ config FB_MXC_LDB | |||
22 | tristate "MXC LDB" | 22 | tristate "MXC LDB" |
23 | depends on FB_MXC_SYNC_PANEL | 23 | depends on FB_MXC_SYNC_PANEL |
24 | depends on MXC_IPU_V3 | 24 | depends on MXC_IPU_V3 |
25 | select VIDEOMODE_HELPERS | ||
25 | 26 | ||
26 | config FB_MXC_MIPI_DSI | 27 | config FB_MXC_MIPI_DSI |
27 | tristate "MXC MIPI_DSI" | 28 | tristate "MXC MIPI_DSI" |
diff --git a/drivers/video/mxc/ldb.c b/drivers/video/mxc/ldb.c index 8e9f26011146..08ca0243a5af 100644 --- a/drivers/video/mxc/ldb.c +++ b/drivers/video/mxc/ldb.c | |||
@@ -18,1024 +18,867 @@ | |||
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | 18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
19 | */ | 19 | */ |
20 | 20 | ||
21 | /*! | ||
22 | * @file mxc_ldb.c | ||
23 | * | ||
24 | * @brief This file contains the LDB driver device interface and fops | ||
25 | * functions. | ||
26 | */ | ||
27 | #include <linux/types.h> | ||
28 | #include <linux/init.h> | ||
29 | #include <linux/module.h> | ||
30 | #include <linux/platform_device.h> | ||
31 | #include <linux/err.h> | ||
32 | #include <linux/clk.h> | 21 | #include <linux/clk.h> |
33 | #include <linux/console.h> | 22 | #include <linux/err.h> |
23 | #include <linux/init.h> | ||
34 | #include <linux/io.h> | 24 | #include <linux/io.h> |
35 | #include <linux/ipu.h> | 25 | #include <linux/mfd/syscon.h> |
36 | #include <linux/mxcfb.h> | 26 | #include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> |
37 | #include <linux/regulator/consumer.h> | 27 | #include <linux/module.h> |
38 | #include <linux/spinlock.h> | ||
39 | #include <linux/of_device.h> | 28 | #include <linux/of_device.h> |
40 | #include <linux/mod_devicetable.h> | 29 | #include <linux/platform_device.h> |
30 | #include <linux/regmap.h> | ||
31 | #include <linux/types.h> | ||
32 | #include <video/of_videomode.h> | ||
33 | #include <video/videomode.h> | ||
41 | #include "mxc_dispdrv.h" | 34 | #include "mxc_dispdrv.h" |
42 | 35 | ||
43 | #define DISPDRV_LDB "ldb" | 36 | #define DRIVER_NAME "ldb" |
44 | |||
45 | #define LDB_BGREF_RMODE_MASK 0x00008000 | ||
46 | #define LDB_BGREF_RMODE_INT 0x00008000 | ||
47 | #define LDB_BGREF_RMODE_EXT 0x0 | ||
48 | |||
49 | #define LDB_DI1_VS_POL_MASK 0x00000400 | ||
50 | #define LDB_DI1_VS_POL_ACT_LOW 0x00000400 | ||
51 | #define LDB_DI1_VS_POL_ACT_HIGH 0x0 | ||
52 | #define LDB_DI0_VS_POL_MASK 0x00000200 | ||
53 | #define LDB_DI0_VS_POL_ACT_LOW 0x00000200 | ||
54 | #define LDB_DI0_VS_POL_ACT_HIGH 0x0 | ||
55 | |||
56 | #define LDB_BIT_MAP_CH1_MASK 0x00000100 | ||
57 | #define LDB_BIT_MAP_CH1_JEIDA 0x00000100 | ||
58 | #define LDB_BIT_MAP_CH1_SPWG 0x0 | ||
59 | #define LDB_BIT_MAP_CH0_MASK 0x00000040 | ||
60 | #define LDB_BIT_MAP_CH0_JEIDA 0x00000040 | ||
61 | #define LDB_BIT_MAP_CH0_SPWG 0x0 | ||
62 | |||
63 | #define LDB_DATA_WIDTH_CH1_MASK 0x00000080 | ||
64 | #define LDB_DATA_WIDTH_CH1_24 0x00000080 | ||
65 | #define LDB_DATA_WIDTH_CH1_18 0x0 | ||
66 | #define LDB_DATA_WIDTH_CH0_MASK 0x00000020 | ||
67 | #define LDB_DATA_WIDTH_CH0_24 0x00000020 | ||
68 | #define LDB_DATA_WIDTH_CH0_18 0x0 | ||
69 | |||
70 | #define LDB_CH1_MODE_MASK 0x0000000C | ||
71 | #define LDB_CH1_MODE_EN_TO_DI1 0x0000000C | ||
72 | #define LDB_CH1_MODE_EN_TO_DI0 0x00000004 | ||
73 | #define LDB_CH1_MODE_DISABLE 0x0 | ||
74 | #define LDB_CH0_MODE_MASK 0x00000003 | ||
75 | #define LDB_CH0_MODE_EN_TO_DI1 0x00000003 | ||
76 | #define LDB_CH0_MODE_EN_TO_DI0 0x00000001 | ||
77 | #define LDB_CH0_MODE_DISABLE 0x0 | ||
78 | |||
79 | #define LDB_SPLIT_MODE_EN 0x00000010 | ||
80 | 37 | ||
81 | enum { | 38 | #define LDB_BGREF_RMODE_INT (0x1 << 15) |
82 | IMX6_LDB, | 39 | |
40 | #define LDB_DI1_VS_POL_ACT_LOW (0x1 << 10) | ||
41 | #define LDB_DI0_VS_POL_ACT_LOW (0x1 << 9) | ||
42 | |||
43 | #define LDB_BIT_MAP_CH1_JEIDA (0x1 << 8) | ||
44 | #define LDB_BIT_MAP_CH0_JEIDA (0x1 << 6) | ||
45 | |||
46 | #define LDB_DATA_WIDTH_CH1_24 (0x1 << 7) | ||
47 | #define LDB_DATA_WIDTH_CH0_24 (0x1 << 5) | ||
48 | |||
49 | #define LDB_CH1_MODE_MASK (0x3 << 2) | ||
50 | #define LDB_CH1_MODE_EN_TO_DI1 (0x3 << 2) | ||
51 | #define LDB_CH1_MODE_EN_TO_DI0 (0x1 << 2) | ||
52 | #define LDB_CH0_MODE_MASK (0x3 << 0) | ||
53 | #define LDB_CH0_MODE_EN_TO_DI1 (0x3 << 0) | ||
54 | #define LDB_CH0_MODE_EN_TO_DI0 (0x1 << 0) | ||
55 | |||
56 | #define LDB_SPLIT_MODE_EN (0x1 << 4) | ||
57 | |||
58 | #define INVALID_BUS_REG (~0UL) | ||
59 | |||
60 | struct crtc_mux { | ||
61 | enum crtc crtc; | ||
62 | u32 val; | ||
83 | }; | 63 | }; |
84 | 64 | ||
85 | enum { | 65 | struct bus_mux { |
86 | LDB_IMX6 = 1, | 66 | int reg; |
67 | int shift; | ||
68 | int mask; | ||
69 | int crtc_mux_num; | ||
70 | const struct crtc_mux *crtcs; | ||
71 | }; | ||
72 | |||
73 | struct ldb_info { | ||
74 | bool split_cap; | ||
75 | bool dual_cap; | ||
76 | bool ext_bgref_cap; | ||
77 | int ctrl_reg; | ||
78 | int bus_mux_num; | ||
79 | const struct bus_mux *buses; | ||
87 | }; | 80 | }; |
88 | 81 | ||
89 | struct fsl_mxc_ldb_platform_data { | 82 | struct ldb_data; |
90 | int devtype; | 83 | |
91 | u32 ext_ref; | 84 | struct ldb_chan { |
92 | #define LDB_SPL_DI0 1 | 85 | struct ldb_data *ldb; |
93 | #define LDB_SPL_DI1 2 | 86 | struct fb_info *fbi; |
94 | #define LDB_DUL_DI0 3 | 87 | struct videomode vm; |
95 | #define LDB_DUL_DI1 4 | 88 | enum crtc crtc; |
96 | #define LDB_SIN0 5 | 89 | int chno; |
97 | #define LDB_SIN1 6 | 90 | bool is_used; |
98 | #define LDB_SEP0 7 | 91 | bool online; |
99 | #define LDB_SEP1 8 | ||
100 | int mode; | ||
101 | int ipu_id; | ||
102 | int disp_id; | ||
103 | |||
104 | /*only work for separate mode*/ | ||
105 | int sec_ipu_id; | ||
106 | int sec_disp_id; | ||
107 | }; | 92 | }; |
108 | 93 | ||
109 | struct ldb_data { | 94 | struct ldb_data { |
110 | struct platform_device *pdev; | 95 | struct regmap *regmap; |
111 | struct mxc_dispdrv_handle *disp_ldb; | 96 | struct device *dev; |
112 | uint32_t *reg; | 97 | struct mxc_dispdrv_handle *mddh; |
113 | uint32_t *control_reg; | 98 | struct ldb_chan chan[2]; |
114 | uint32_t *gpr3_reg; | 99 | int bus_mux_num; |
115 | uint32_t control_reg_data; | 100 | const struct bus_mux *buses; |
116 | struct regulator *lvds_bg_reg; | 101 | int primary_chno; |
117 | int mode; | 102 | int ctrl_reg; |
118 | bool inited; | 103 | u32 ctrl; |
119 | struct ldb_setting { | 104 | bool spl_mode; |
120 | struct clk *di_clk; | 105 | bool dual_mode; |
121 | struct clk *ldb_di_clk; | 106 | struct clk *di_clk[4]; |
122 | struct clk *div_3_5_clk; | 107 | struct clk *ldb_di_clk[2]; |
123 | struct clk *div_7_clk; | 108 | struct clk *div_3_5_clk[2]; |
124 | struct clk *div_sel_clk; | 109 | struct clk *div_7_clk[2]; |
125 | bool active; | 110 | struct clk *div_sel_clk[2]; |
126 | bool clk_en; | ||
127 | int ipu; | ||
128 | int di; | ||
129 | uint32_t ch_mask; | ||
130 | uint32_t ch_val; | ||
131 | } setting[2]; | ||
132 | struct notifier_block nb; | ||
133 | }; | 111 | }; |
134 | 112 | ||
135 | static int g_ldb_mode; | 113 | static const struct crtc_mux imx6q_lvds0_crtc_mux[] = { |
114 | { | ||
115 | .crtc = CRTC_IPU1_DI0, | ||
116 | .val = IMX6Q_GPR3_LVDS0_MUX_CTL_IPU1_DI0, | ||
117 | }, { | ||
118 | .crtc = CRTC_IPU1_DI1, | ||
119 | .val = IMX6Q_GPR3_LVDS0_MUX_CTL_IPU1_DI1, | ||
120 | }, { | ||
121 | .crtc = CRTC_IPU2_DI0, | ||
122 | .val = IMX6Q_GPR3_LVDS0_MUX_CTL_IPU2_DI0, | ||
123 | }, { | ||
124 | .crtc = CRTC_IPU2_DI1, | ||
125 | .val = IMX6Q_GPR3_LVDS0_MUX_CTL_IPU2_DI1, | ||
126 | } | ||
127 | }; | ||
136 | 128 | ||
137 | static struct fb_videomode ldb_modedb[] = { | 129 | static const struct crtc_mux imx6q_lvds1_crtc_mux[] = { |
138 | { | 130 | { |
139 | "LDB-WXGA", 60, 1280, 800, 14065, | 131 | .crtc = CRTC_IPU1_DI0, |
140 | 40, 40, | 132 | .val = IMX6Q_GPR3_LVDS1_MUX_CTL_IPU1_DI0, |
141 | 10, 3, | 133 | }, { |
142 | 80, 10, | 134 | .crtc = CRTC_IPU1_DI1, |
143 | 0, | 135 | .val = IMX6Q_GPR3_LVDS1_MUX_CTL_IPU1_DI1, |
144 | FB_VMODE_NONINTERLACED, | 136 | }, { |
145 | FB_MODE_IS_DETAILED,}, | 137 | .crtc = CRTC_IPU2_DI0, |
138 | .val = IMX6Q_GPR3_LVDS1_MUX_CTL_IPU2_DI0, | ||
139 | }, { | ||
140 | .crtc = CRTC_IPU2_DI1, | ||
141 | .val = IMX6Q_GPR3_LVDS1_MUX_CTL_IPU2_DI1, | ||
142 | } | ||
143 | }; | ||
144 | |||
145 | static const struct bus_mux imx6q_ldb_buses[] = { | ||
146 | { | 146 | { |
147 | "LDB-XGA", 60, 1024, 768, 15385, | 147 | .reg = IOMUXC_GPR3, |
148 | 220, 40, | 148 | .shift = 6, |
149 | 21, 7, | 149 | .mask = IMX6Q_GPR3_LVDS0_MUX_CTL_MASK, |
150 | 60, 10, | 150 | .crtc_mux_num = ARRAY_SIZE(imx6q_lvds0_crtc_mux), |
151 | 0, | 151 | .crtcs = imx6q_lvds0_crtc_mux, |
152 | FB_VMODE_NONINTERLACED, | 152 | }, { |
153 | FB_MODE_IS_DETAILED,}, | 153 | .reg = IOMUXC_GPR3, |
154 | .shift = 8, | ||
155 | .mask = IMX6Q_GPR3_LVDS1_MUX_CTL_MASK, | ||
156 | .crtc_mux_num = ARRAY_SIZE(imx6q_lvds1_crtc_mux), | ||
157 | .crtcs = imx6q_lvds1_crtc_mux, | ||
158 | } | ||
159 | }; | ||
160 | |||
161 | static const struct ldb_info imx6q_ldb_info = { | ||
162 | .split_cap = true, | ||
163 | .dual_cap = true, | ||
164 | .ext_bgref_cap = false, | ||
165 | .ctrl_reg = IOMUXC_GPR2, | ||
166 | .bus_mux_num = ARRAY_SIZE(imx6q_ldb_buses), | ||
167 | .buses = imx6q_ldb_buses, | ||
168 | }; | ||
169 | |||
170 | static const struct crtc_mux imx6dl_lvds0_crtc_mux[] = { | ||
154 | { | 171 | { |
155 | "LDB-1080P60", 60, 1920, 1080, 7692, | 172 | .crtc = CRTC_IPU1_DI0, |
156 | 100, 40, | 173 | .val = IMX6DL_GPR3_LVDS0_MUX_CTL_IPU1_DI0, |
157 | 30, 3, | 174 | }, { |
158 | 10, 2, | 175 | .crtc = CRTC_IPU1_DI1, |
159 | 0, | 176 | .val = IMX6DL_GPR3_LVDS0_MUX_CTL_IPU1_DI1, |
160 | FB_VMODE_NONINTERLACED, | 177 | }, { |
161 | FB_MODE_IS_DETAILED,}, | 178 | .crtc = CRTC_LCDIF1, |
179 | .val = IMX6DL_GPR3_LVDS0_MUX_CTL_LCDIF, | ||
180 | } | ||
162 | }; | 181 | }; |
163 | static int ldb_modedb_sz = ARRAY_SIZE(ldb_modedb); | ||
164 | 182 | ||
165 | static inline int is_imx6_ldb(struct fsl_mxc_ldb_platform_data *plat_data) | 183 | static const struct crtc_mux imx6dl_lvds1_crtc_mux[] = { |
166 | { | 184 | { |
167 | return (plat_data->devtype == LDB_IMX6); | 185 | .crtc = CRTC_IPU1_DI0, |
168 | } | 186 | .val = IMX6DL_GPR3_LVDS1_MUX_CTL_IPU1_DI0, |
187 | }, { | ||
188 | .crtc = CRTC_IPU1_DI1, | ||
189 | .val = IMX6DL_GPR3_LVDS1_MUX_CTL_IPU1_DI1, | ||
190 | }, { | ||
191 | .crtc = CRTC_LCDIF1, | ||
192 | .val = IMX6DL_GPR3_LVDS1_MUX_CTL_LCDIF, | ||
193 | } | ||
194 | }; | ||
195 | |||
196 | static const struct bus_mux imx6dl_ldb_buses[] = { | ||
197 | { | ||
198 | .reg = IOMUXC_GPR3, | ||
199 | .shift = 6, | ||
200 | .mask = IMX6DL_GPR3_LVDS0_MUX_CTL_MASK, | ||
201 | .crtc_mux_num = ARRAY_SIZE(imx6dl_lvds0_crtc_mux), | ||
202 | .crtcs = imx6dl_lvds0_crtc_mux, | ||
203 | }, { | ||
204 | .reg = IOMUXC_GPR3, | ||
205 | .shift = 8, | ||
206 | .mask = IMX6DL_GPR3_LVDS1_MUX_CTL_MASK, | ||
207 | .crtc_mux_num = ARRAY_SIZE(imx6dl_lvds1_crtc_mux), | ||
208 | .crtcs = imx6dl_lvds1_crtc_mux, | ||
209 | } | ||
210 | }; | ||
211 | |||
212 | static const struct ldb_info imx6dl_ldb_info = { | ||
213 | .split_cap = true, | ||
214 | .dual_cap = true, | ||
215 | .ext_bgref_cap = false, | ||
216 | .ctrl_reg = IOMUXC_GPR2, | ||
217 | .bus_mux_num = ARRAY_SIZE(imx6dl_ldb_buses), | ||
218 | .buses = imx6dl_ldb_buses, | ||
219 | }; | ||
220 | |||
221 | static const struct crtc_mux imx6sx_lvds_crtc_mux[] = { | ||
222 | { | ||
223 | .crtc = CRTC_LCDIF1, | ||
224 | .val = IMX6SX_GPR5_DISP_MUX_LDB_CTRL_LCDIF1, | ||
225 | }, { | ||
226 | .crtc = CRTC_LCDIF2, | ||
227 | .val = IMX6SX_GPR5_DISP_MUX_LDB_CTRL_LCDIF2, | ||
228 | } | ||
229 | }; | ||
230 | |||
231 | static const struct bus_mux imx6sx_ldb_buses[] = { | ||
232 | { | ||
233 | .reg = IOMUXC_GPR5, | ||
234 | .shift = 3, | ||
235 | .mask = IMX6SX_GPR5_DISP_MUX_LDB_CTRL_MASK, | ||
236 | .crtc_mux_num = ARRAY_SIZE(imx6sx_lvds_crtc_mux), | ||
237 | .crtcs = imx6sx_lvds_crtc_mux, | ||
238 | } | ||
239 | }; | ||
240 | |||
241 | static const struct ldb_info imx6sx_ldb_info = { | ||
242 | .split_cap = false, | ||
243 | .dual_cap = false, | ||
244 | .ext_bgref_cap = false, | ||
245 | .ctrl_reg = IOMUXC_GPR6, | ||
246 | .bus_mux_num = ARRAY_SIZE(imx6sx_ldb_buses), | ||
247 | .buses = imx6sx_ldb_buses, | ||
248 | }; | ||
249 | |||
250 | static const struct crtc_mux imx53_lvds0_crtc_mux[] = { | ||
251 | { .crtc = CRTC_IPU1_DI0, }, | ||
252 | }; | ||
253 | |||
254 | static const struct crtc_mux imx53_lvds1_crtc_mux[] = { | ||
255 | { .crtc = CRTC_IPU1_DI1, } | ||
256 | }; | ||
257 | |||
258 | static const struct bus_mux imx53_ldb_buses[] = { | ||
259 | { | ||
260 | .reg = INVALID_BUS_REG, | ||
261 | .crtc_mux_num = ARRAY_SIZE(imx53_lvds0_crtc_mux), | ||
262 | .crtcs = imx53_lvds0_crtc_mux, | ||
263 | }, { | ||
264 | .reg = INVALID_BUS_REG, | ||
265 | .crtc_mux_num = ARRAY_SIZE(imx53_lvds1_crtc_mux), | ||
266 | .crtcs = imx53_lvds1_crtc_mux, | ||
267 | } | ||
268 | }; | ||
269 | |||
270 | static const struct ldb_info imx53_ldb_info = { | ||
271 | .split_cap = true, | ||
272 | .dual_cap = false, | ||
273 | .ext_bgref_cap = true, | ||
274 | .ctrl_reg = IOMUXC_GPR2, | ||
275 | .bus_mux_num = ARRAY_SIZE(imx53_ldb_buses), | ||
276 | .buses = imx53_ldb_buses, | ||
277 | }; | ||
169 | 278 | ||
170 | static int bits_per_pixel(int pixel_fmt) | 279 | static const struct of_device_id ldb_dt_ids[] = { |
280 | { .compatible = "fsl,imx6q-ldb", .data = &imx6q_ldb_info, }, | ||
281 | { .compatible = "fsl,imx6dl-ldb", .data = &imx6dl_ldb_info, }, | ||
282 | { .compatible = "fsl,imx6sx-ldb", .data = &imx6sx_ldb_info, }, | ||
283 | { .compatible = "fsl,imx53-ldb", .data = &imx53_ldb_info, }, | ||
284 | { /* sentinel */ } | ||
285 | }; | ||
286 | MODULE_DEVICE_TABLE(of, ldb_dt_ids); | ||
287 | |||
288 | static int ldb_init(struct mxc_dispdrv_handle *mddh, | ||
289 | struct mxc_dispdrv_setting *setting) | ||
171 | { | 290 | { |
172 | switch (pixel_fmt) { | 291 | struct ldb_data *ldb = mxc_dispdrv_getdata(mddh); |
173 | case IPU_PIX_FMT_BGR24: | 292 | struct device *dev = ldb->dev; |
174 | case IPU_PIX_FMT_RGB24: | 293 | struct fb_info *fbi = setting->fbi; |
175 | return 24; | 294 | struct ldb_chan *chan; |
176 | break; | 295 | struct fb_videomode fb_vm; |
177 | case IPU_PIX_FMT_BGR666: | 296 | int chno; |
178 | case IPU_PIX_FMT_RGB666: | 297 | |
179 | case IPU_PIX_FMT_LVDS666: | 298 | chno = ldb->chan[ldb->primary_chno].is_used ? |
180 | return 18; | 299 | !ldb->primary_chno : ldb->primary_chno; |
181 | break; | 300 | |
182 | default: | 301 | chan = &ldb->chan[chno]; |
183 | break; | 302 | |
303 | if (chan->is_used) { | ||
304 | dev_err(dev, "LVDS channel%d is already used\n", chno); | ||
305 | return -EBUSY; | ||
306 | } | ||
307 | if (!chan->online) { | ||
308 | dev_err(dev, "LVDS channel%d is not online\n", chno); | ||
309 | return -ENODEV; | ||
184 | } | 310 | } |
311 | |||
312 | chan->is_used = true; | ||
313 | |||
314 | chan->fbi = fbi; | ||
315 | |||
316 | fb_videomode_from_videomode(&chan->vm, &fb_vm); | ||
317 | |||
318 | INIT_LIST_HEAD(&fbi->modelist); | ||
319 | fb_add_videomode(&fb_vm, &fbi->modelist); | ||
320 | fb_videomode_to_var(&fbi->var, &fb_vm); | ||
321 | |||
322 | setting->crtc = chan->crtc; | ||
323 | |||
185 | return 0; | 324 | return 0; |
186 | } | 325 | } |
187 | 326 | ||
188 | static int valid_mode(int pixel_fmt) | 327 | static int get_di_clk_id(struct ldb_chan chan, int *id) |
189 | { | 328 | { |
190 | return ((pixel_fmt == IPU_PIX_FMT_RGB24) || | 329 | struct ldb_data *ldb = chan.ldb; |
191 | (pixel_fmt == IPU_PIX_FMT_BGR24) || | 330 | int i = 0, chno = chan.chno, mask, shift; |
192 | (pixel_fmt == IPU_PIX_FMT_LVDS666) || | 331 | enum crtc crtc; |
193 | (pixel_fmt == IPU_PIX_FMT_RGB666) || | 332 | u32 val; |
194 | (pixel_fmt == IPU_PIX_FMT_BGR666)); | 333 | |
195 | } | 334 | /* no pre-muxing, such as mx53 */ |
335 | if (ldb->buses[chno].reg == INVALID_BUS_REG) { | ||
336 | *id = chno; | ||
337 | return 0; | ||
338 | } | ||
196 | 339 | ||
197 | static int parse_ldb_mode(char *mode) | 340 | for (; i < ldb->buses[chno].crtc_mux_num; i++) { |
198 | { | 341 | crtc = ldb->buses[chno].crtcs[i].crtc; |
199 | int ldb_mode; | 342 | val = ldb->buses[chno].crtcs[i].val; |
200 | 343 | mask = ldb->buses[chno].mask; | |
201 | if (!strcmp(mode, "spl0")) | 344 | shift = ldb->buses[chno].shift; |
202 | ldb_mode = LDB_SPL_DI0; | 345 | if (chan.crtc == crtc) { |
203 | else if (!strcmp(mode, "spl1")) | 346 | *id = (val & mask) >> shift; |
204 | ldb_mode = LDB_SPL_DI1; | 347 | return 0; |
205 | else if (!strcmp(mode, "dul0")) | 348 | } |
206 | ldb_mode = LDB_DUL_DI0; | 349 | } |
207 | else if (!strcmp(mode, "dul1")) | ||
208 | ldb_mode = LDB_DUL_DI1; | ||
209 | else if (!strcmp(mode, "sin0")) | ||
210 | ldb_mode = LDB_SIN0; | ||
211 | else if (!strcmp(mode, "sin1")) | ||
212 | ldb_mode = LDB_SIN1; | ||
213 | else if (!strcmp(mode, "sep0")) | ||
214 | ldb_mode = LDB_SEP0; | ||
215 | else if (!strcmp(mode, "sep1")) | ||
216 | ldb_mode = LDB_SEP1; | ||
217 | else | ||
218 | ldb_mode = -EINVAL; | ||
219 | |||
220 | return ldb_mode; | ||
221 | } | ||
222 | 350 | ||
223 | #ifndef MODULE | 351 | return -EINVAL; |
224 | /* | ||
225 | * "ldb=spl0/1" -- split mode on DI0/1 | ||
226 | * "ldb=dul0/1" -- dual mode on DI0/1 | ||
227 | * "ldb=sin0/1" -- single mode on LVDS0/1 | ||
228 | * "ldb=sep0/1" -- separate mode begin from LVDS0/1 | ||
229 | * | ||
230 | * there are two LVDS channels(LVDS0 and LVDS1) which can transfer video | ||
231 | * datas, there two channels can be used as split/dual/single/separate mode. | ||
232 | * | ||
233 | * split mode means display data from DI0 or DI1 will send to both channels | ||
234 | * LVDS0+LVDS1. | ||
235 | * dual mode means display data from DI0 or DI1 will be duplicated on LVDS0 | ||
236 | * and LVDS1, it said, LVDS0 and LVDS1 has the same content. | ||
237 | * single mode means only work for DI0/DI1->LVDS0 or DI0/DI1->LVDS1. | ||
238 | * separate mode means you can make DI0/DI1->LVDS0 and DI0/DI1->LVDS1 work | ||
239 | * at the same time. | ||
240 | */ | ||
241 | static int __init ldb_setup(char *options) | ||
242 | { | ||
243 | g_ldb_mode = parse_ldb_mode(options); | ||
244 | return (g_ldb_mode < 0) ? 0 : 1; | ||
245 | } | 352 | } |
246 | __setup("ldb=", ldb_setup); | ||
247 | #endif | ||
248 | 353 | ||
249 | static int ldb_get_of_property(struct platform_device *pdev, | 354 | static int get_mux_val(struct bus_mux bus_mux, enum crtc crtc, |
250 | struct fsl_mxc_ldb_platform_data *plat_data) | 355 | u32 *mux_val) |
251 | { | 356 | { |
252 | struct device_node *np = pdev->dev.of_node; | 357 | int i = 0; |
253 | int err; | ||
254 | u32 ipu_id, disp_id; | ||
255 | u32 sec_ipu_id, sec_disp_id; | ||
256 | char *mode; | ||
257 | u32 ext_ref; | ||
258 | |||
259 | err = of_property_read_string(np, "mode", (const char **)&mode); | ||
260 | if (err) { | ||
261 | dev_dbg(&pdev->dev, "get of property mode fail\n"); | ||
262 | return err; | ||
263 | } | ||
264 | err = of_property_read_u32(np, "ext_ref", &ext_ref); | ||
265 | if (err) { | ||
266 | dev_dbg(&pdev->dev, "get of property ext_ref fail\n"); | ||
267 | return err; | ||
268 | } | ||
269 | err = of_property_read_u32(np, "ipu_id", &ipu_id); | ||
270 | if (err) { | ||
271 | dev_dbg(&pdev->dev, "get of property ipu_id fail\n"); | ||
272 | return err; | ||
273 | } | ||
274 | err = of_property_read_u32(np, "disp_id", &disp_id); | ||
275 | if (err) { | ||
276 | dev_dbg(&pdev->dev, "get of property disp_id fail\n"); | ||
277 | return err; | ||
278 | } | ||
279 | err = of_property_read_u32(np, "sec_ipu_id", &sec_ipu_id); | ||
280 | if (err) { | ||
281 | dev_dbg(&pdev->dev, "get of property sec_ipu_id fail\n"); | ||
282 | return err; | ||
283 | } | ||
284 | err = of_property_read_u32(np, "sec_disp_id", &sec_disp_id); | ||
285 | if (err) { | ||
286 | dev_dbg(&pdev->dev, "get of property sec_disp_id fail\n"); | ||
287 | return err; | ||
288 | } | ||
289 | 358 | ||
290 | plat_data->mode = parse_ldb_mode(mode); | 359 | for (; i < bus_mux.crtc_mux_num; i++) |
291 | plat_data->ext_ref = ext_ref; | 360 | if (bus_mux.crtcs[i].crtc == crtc) { |
292 | plat_data->ipu_id = ipu_id; | 361 | *mux_val = bus_mux.crtcs[i].val; |
293 | plat_data->disp_id = disp_id; | 362 | return 0; |
294 | plat_data->sec_ipu_id = sec_ipu_id; | 363 | } |
295 | plat_data->sec_disp_id = sec_disp_id; | ||
296 | 364 | ||
297 | return err; | 365 | return -EINVAL; |
298 | } | 366 | } |
299 | 367 | ||
300 | static int find_ldb_setting(struct ldb_data *ldb, struct fb_info *fbi) | 368 | static int find_ldb_chno(struct ldb_data *ldb, |
369 | struct fb_info *fbi, int *chno) | ||
301 | { | 370 | { |
302 | char *id_di[] = { | 371 | struct device *dev = ldb->dev; |
303 | "DISP3 BG", | 372 | int i = 0; |
304 | "DISP3 BG - DI1", | 373 | |
305 | }; | 374 | for (; i < 2; i++) |
306 | char id[16]; | 375 | if (ldb->chan[i].fbi == fbi) { |
307 | int i; | 376 | *chno = ldb->chan[i].chno; |
308 | 377 | return 0; | |
309 | for (i = 0; i < 2; i++) { | ||
310 | if (ldb->setting[i].active) { | ||
311 | memset(id, 0, 16); | ||
312 | memcpy(id, id_di[ldb->setting[i].di], | ||
313 | strlen(id_di[ldb->setting[i].di])); | ||
314 | id[4] += ldb->setting[i].ipu; | ||
315 | if (!strcmp(id, fbi->fix.id)) | ||
316 | return i; | ||
317 | } | 378 | } |
318 | } | 379 | dev_err(dev, "failed to find channel number\n"); |
319 | return -EINVAL; | 380 | return -EINVAL; |
320 | } | 381 | } |
321 | 382 | ||
322 | static int ldb_disp_setup(struct mxc_dispdrv_handle *disp, struct fb_info *fbi) | 383 | static int ldb_setup(struct mxc_dispdrv_handle *mddh, |
384 | struct fb_info *fbi) | ||
323 | { | 385 | { |
324 | uint32_t reg, val; | 386 | struct ldb_data *ldb = mxc_dispdrv_getdata(mddh); |
325 | uint32_t pixel_clk, rounded_pixel_clk; | 387 | struct ldb_chan chan; |
326 | struct clk *ldb_clk_parent; | 388 | struct device *dev = ldb->dev; |
327 | struct ldb_data *ldb = mxc_dispdrv_getdata(disp); | 389 | struct clk *ldb_di_parent, *ldb_di_sel, *ldb_di_sel_parent; |
328 | int setting_idx, di; | 390 | struct clk *other_ldb_di_sel = NULL; |
329 | int ret; | 391 | struct bus_mux bus_mux; |
392 | int ret = 0, id = 0, chno, other_chno; | ||
393 | unsigned long serial_clk; | ||
394 | u32 mux_val; | ||
395 | |||
396 | ret = find_ldb_chno(ldb, fbi, &chno); | ||
397 | if (ret < 0) | ||
398 | return ret; | ||
330 | 399 | ||
331 | setting_idx = find_ldb_setting(ldb, fbi); | 400 | other_chno = chno ? 0 : 1; |
332 | if (setting_idx < 0) | 401 | |
333 | return setting_idx; | 402 | chan = ldb->chan[chno]; |
334 | 403 | ||
335 | di = ldb->setting[setting_idx].di; | 404 | bus_mux = ldb->buses[chno]; |
336 | 405 | ||
337 | /* restore channel mode setting */ | 406 | ret = get_di_clk_id(chan, &id); |
338 | val = readl(ldb->control_reg); | ||
339 | val |= ldb->setting[setting_idx].ch_val; | ||
340 | writel(val, ldb->control_reg); | ||
341 | dev_dbg(&ldb->pdev->dev, "LDB setup, control reg:0x%x\n", | ||
342 | readl(ldb->control_reg)); | ||
343 | |||
344 | /* vsync setup */ | ||
345 | reg = readl(ldb->control_reg); | ||
346 | if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT) { | ||
347 | if (di == 0) | ||
348 | reg = (reg & ~LDB_DI0_VS_POL_MASK) | ||
349 | | LDB_DI0_VS_POL_ACT_HIGH; | ||
350 | else | ||
351 | reg = (reg & ~LDB_DI1_VS_POL_MASK) | ||
352 | | LDB_DI1_VS_POL_ACT_HIGH; | ||
353 | } else { | ||
354 | if (di == 0) | ||
355 | reg = (reg & ~LDB_DI0_VS_POL_MASK) | ||
356 | | LDB_DI0_VS_POL_ACT_LOW; | ||
357 | else | ||
358 | reg = (reg & ~LDB_DI1_VS_POL_MASK) | ||
359 | | LDB_DI1_VS_POL_ACT_LOW; | ||
360 | } | ||
361 | writel(reg, ldb->control_reg); | ||
362 | |||
363 | /* clk setup */ | ||
364 | if (ldb->setting[setting_idx].clk_en) | ||
365 | clk_disable_unprepare(ldb->setting[setting_idx].ldb_di_clk); | ||
366 | pixel_clk = (PICOS2KHZ(fbi->var.pixclock)) * 1000UL; | ||
367 | ldb_clk_parent = clk_get_parent(ldb->setting[setting_idx].ldb_di_clk); | ||
368 | if (IS_ERR(ldb_clk_parent)) { | ||
369 | dev_err(&ldb->pdev->dev, "get ldb di parent clk fail\n"); | ||
370 | return PTR_ERR(ldb_clk_parent); | ||
371 | } | ||
372 | if ((ldb->mode == LDB_SPL_DI0) || (ldb->mode == LDB_SPL_DI1)) | ||
373 | ret = clk_set_rate(ldb_clk_parent, pixel_clk * 7 / 2); | ||
374 | else | ||
375 | ret = clk_set_rate(ldb_clk_parent, pixel_clk * 7); | ||
376 | if (ret < 0) { | 407 | if (ret < 0) { |
377 | dev_err(&ldb->pdev->dev, "set ldb parent clk fail:%d\n", ret); | 408 | dev_err(dev, "failed to get ch%d di clk id\n", |
409 | chan.chno); | ||
378 | return ret; | 410 | return ret; |
379 | } | 411 | } |
380 | rounded_pixel_clk = clk_round_rate(ldb->setting[setting_idx].ldb_di_clk, | 412 | |
381 | pixel_clk); | 413 | ret = get_mux_val(bus_mux, chan.crtc, &mux_val); |
382 | dev_dbg(&ldb->pdev->dev, "pixel_clk:%d, rounded_pixel_clk:%d\n", | ||
383 | pixel_clk, rounded_pixel_clk); | ||
384 | ret = clk_set_rate(ldb->setting[setting_idx].ldb_di_clk, | ||
385 | rounded_pixel_clk); | ||
386 | if (ret < 0) { | 414 | if (ret < 0) { |
387 | dev_err(&ldb->pdev->dev, "set ldb di clk fail:%d\n", ret); | 415 | dev_err(dev, "failed to get ch%d mux val\n", |
416 | chan.chno); | ||
388 | return ret; | 417 | return ret; |
389 | } | 418 | } |
390 | ret = clk_prepare_enable(ldb->setting[setting_idx].ldb_di_clk); | 419 | |
391 | if (ret < 0) { | 420 | /* |
392 | dev_err(&ldb->pdev->dev, "enable ldb di clk fail:%d\n", ret); | 421 | * ldb_di_sel_parent(plls) -> ldb_di_sel -> |
393 | return ret; | 422 | * |
423 | * -> div_3_5[chno] -> | ||
424 | * -> | |-> div_sel[chno] -> | ||
425 | * -> div_7[chno] -> | ||
426 | * | ||
427 | * -> ldb_di[chno] -> di[id] | ||
428 | */ | ||
429 | clk_set_parent(ldb->di_clk[id], ldb->ldb_di_clk[chno]); | ||
430 | ldb_di_parent = ldb->spl_mode ? ldb->div_3_5_clk[chno] : | ||
431 | ldb->div_7_clk[chno]; | ||
432 | clk_set_parent(ldb->div_sel_clk[chno], ldb_di_parent); | ||
433 | ldb_di_sel = clk_get_parent(ldb_di_parent); | ||
434 | ldb_di_sel_parent = clk_get_parent(ldb_di_sel); | ||
435 | serial_clk = ldb->spl_mode ? chan.vm.pixelclock * 7 / 2 : | ||
436 | chan.vm.pixelclock * 7; | ||
437 | clk_set_rate(ldb_di_sel_parent, serial_clk); | ||
438 | |||
439 | /* | ||
440 | * split mode or dual mode: | ||
441 | * clock tree for the other channel | ||
442 | */ | ||
443 | if (ldb->spl_mode) { | ||
444 | clk_set_parent(ldb->div_sel_clk[other_chno], | ||
445 | ldb->div_3_5_clk[other_chno]); | ||
446 | other_ldb_di_sel = | ||
447 | clk_get_parent(ldb->div_3_5_clk[other_chno]);; | ||
448 | } | ||
449 | |||
450 | if (ldb->dual_mode) { | ||
451 | clk_set_parent(ldb->div_sel_clk[other_chno], | ||
452 | ldb->div_7_clk[other_chno]); | ||
453 | other_ldb_di_sel = | ||
454 | clk_get_parent(ldb->div_7_clk[other_chno]);; | ||
455 | } | ||
456 | |||
457 | if (ldb->spl_mode || ldb->dual_mode) | ||
458 | clk_set_parent(other_ldb_di_sel, ldb_di_sel_parent); | ||
459 | |||
460 | if (!(chan.fbi->var.sync & FB_SYNC_VERT_HIGH_ACT)) { | ||
461 | if (ldb->spl_mode && bus_mux.reg == INVALID_BUS_REG) | ||
462 | /* no pre-muxing, such as mx53 */ | ||
463 | ldb->ctrl |= (id == 0 ? LDB_DI0_VS_POL_ACT_LOW : | ||
464 | LDB_DI1_VS_POL_ACT_LOW); | ||
465 | else | ||
466 | ldb->ctrl |= (chno == 0 ? LDB_DI0_VS_POL_ACT_LOW : | ||
467 | LDB_DI1_VS_POL_ACT_LOW); | ||
394 | } | 468 | } |
395 | 469 | ||
396 | if (!ldb->setting[setting_idx].clk_en) | 470 | if (bus_mux.reg != INVALID_BUS_REG) |
397 | ldb->setting[setting_idx].clk_en = true; | 471 | regmap_update_bits(ldb->regmap, bus_mux.reg, |
472 | bus_mux.mask, mux_val); | ||
398 | 473 | ||
399 | return 0; | 474 | regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ctrl); |
475 | return ret; | ||
400 | } | 476 | } |
401 | 477 | ||
402 | int ldb_fb_event(struct notifier_block *nb, unsigned long val, void *v) | 478 | static int ldb_enable(struct mxc_dispdrv_handle *mddh, |
479 | struct fb_info *fbi) | ||
403 | { | 480 | { |
404 | struct ldb_data *ldb = container_of(nb, struct ldb_data, nb); | 481 | struct ldb_data *ldb = mxc_dispdrv_getdata(mddh); |
405 | struct fb_event *event = v; | 482 | struct ldb_chan chan; |
406 | struct fb_info *fbi = event->info; | 483 | struct device *dev = ldb->dev; |
407 | int index; | 484 | struct bus_mux bus_mux; |
408 | uint32_t data; | 485 | int ret = 0, id = 0, chno, other_chno; |
409 | |||
410 | index = find_ldb_setting(ldb, fbi); | ||
411 | if (index < 0) | ||
412 | return 0; | ||
413 | 486 | ||
414 | fbi->mode = (struct fb_videomode *)fb_match_mode(&fbi->var, | 487 | ret = find_ldb_chno(ldb, fbi, &chno); |
415 | &fbi->modelist); | 488 | if (ret < 0) |
416 | 489 | return ret; | |
417 | if (!fbi->mode) { | ||
418 | dev_warn(&ldb->pdev->dev, | ||
419 | "LDB: can not find mode for xres=%d, yres=%d\n", | ||
420 | fbi->var.xres, fbi->var.yres); | ||
421 | if (ldb->setting[index].clk_en) { | ||
422 | clk_disable(ldb->setting[index].ldb_di_clk); | ||
423 | ldb->setting[index].clk_en = false; | ||
424 | data = readl(ldb->control_reg); | ||
425 | data &= ~ldb->setting[index].ch_mask; | ||
426 | writel(data, ldb->control_reg); | ||
427 | } | ||
428 | return 0; | ||
429 | } | ||
430 | 490 | ||
431 | switch (val) { | 491 | chan = ldb->chan[chno]; |
432 | case FB_EVENT_BLANK: | 492 | |
433 | { | 493 | bus_mux = ldb->buses[chno]; |
434 | if (*((int *)event->data) == FB_BLANK_UNBLANK) { | 494 | |
435 | if (!ldb->setting[index].clk_en) { | 495 | if (ldb->spl_mode || ldb->dual_mode) { |
436 | clk_enable(ldb->setting[index].ldb_di_clk); | 496 | other_chno = chno ? 0 : 1; |
437 | ldb->setting[index].clk_en = true; | 497 | clk_prepare_enable(ldb->ldb_di_clk[other_chno]); |
438 | } | ||
439 | } else { | ||
440 | if (ldb->setting[index].clk_en) { | ||
441 | clk_disable(ldb->setting[index].ldb_di_clk); | ||
442 | ldb->setting[index].clk_en = false; | ||
443 | data = readl(ldb->control_reg); | ||
444 | data &= ~ldb->setting[index].ch_mask; | ||
445 | writel(data, ldb->control_reg); | ||
446 | dev_dbg(&ldb->pdev->dev, | ||
447 | "LDB blank, control reg:0x%x\n", | ||
448 | readl(ldb->control_reg)); | ||
449 | } | ||
450 | } | ||
451 | break; | ||
452 | } | 498 | } |
453 | case FB_EVENT_SUSPEND: | 499 | |
454 | if (ldb->setting[index].clk_en) { | 500 | if ((ldb->spl_mode || ldb->dual_mode) && |
455 | clk_disable(ldb->setting[index].ldb_di_clk); | 501 | bus_mux.reg == INVALID_BUS_REG) { |
456 | ldb->setting[index].clk_en = false; | 502 | /* no pre-muxing, such as mx53 */ |
503 | ret = get_di_clk_id(chan, &id); | ||
504 | if (ret < 0) { | ||
505 | dev_err(dev, "failed to get ch%d di clk id\n", | ||
506 | chan.chno); | ||
507 | return ret; | ||
457 | } | 508 | } |
458 | break; | 509 | |
459 | default: | 510 | ldb->ctrl |= id ? |
460 | break; | 511 | (LDB_CH0_MODE_EN_TO_DI1 | LDB_CH1_MODE_EN_TO_DI1) : |
512 | (LDB_CH0_MODE_EN_TO_DI0 | LDB_CH1_MODE_EN_TO_DI0); | ||
513 | } else { | ||
514 | if (ldb->spl_mode || ldb->dual_mode) | ||
515 | ldb->ctrl |= LDB_CH0_MODE_EN_TO_DI0 | | ||
516 | LDB_CH1_MODE_EN_TO_DI0; | ||
517 | else | ||
518 | ldb->ctrl |= chno ? LDB_CH1_MODE_EN_TO_DI1 : | ||
519 | LDB_CH0_MODE_EN_TO_DI0; | ||
461 | } | 520 | } |
521 | |||
522 | regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ctrl); | ||
462 | return 0; | 523 | return 0; |
463 | } | 524 | } |
464 | 525 | ||
465 | #define LVDS_MUX_CTL_WIDTH 2 | 526 | static void ldb_disable(struct mxc_dispdrv_handle *mddh, |
466 | #define LVDS_MUX_CTL_MASK 3 | 527 | struct fb_info *fbi) |
467 | #define LVDS0_MUX_CTL_OFFS 6 | ||
468 | #define LVDS1_MUX_CTL_OFFS 8 | ||
469 | #define LVDS0_MUX_CTL_MASK (LVDS_MUX_CTL_MASK << 6) | ||
470 | #define LVDS1_MUX_CTL_MASK (LVDS_MUX_CTL_MASK << 8) | ||
471 | #define ROUTE_IPU_DI(ipu, di) (((ipu << 1) | di) & LVDS_MUX_CTL_MASK) | ||
472 | static int ldb_ipu_ldb_route(int ipu, int di, struct ldb_data *ldb) | ||
473 | { | 528 | { |
474 | uint32_t reg; | 529 | struct ldb_data *ldb = mxc_dispdrv_getdata(mddh); |
475 | int channel; | 530 | int ret, chno, other_chno; |
476 | int shift; | ||
477 | int mode = ldb->mode; | ||
478 | |||
479 | reg = readl(ldb->gpr3_reg); | ||
480 | if (mode < LDB_SIN0) { | ||
481 | reg &= ~(LVDS0_MUX_CTL_MASK | LVDS1_MUX_CTL_MASK); | ||
482 | reg |= (ROUTE_IPU_DI(ipu, di) << LVDS0_MUX_CTL_OFFS) | | ||
483 | (ROUTE_IPU_DI(ipu, di) << LVDS1_MUX_CTL_OFFS); | ||
484 | dev_dbg(&ldb->pdev->dev, | ||
485 | "Dual/Split mode both channels route to IPU%d-DI%d\n", | ||
486 | ipu, di); | ||
487 | } else if ((mode == LDB_SIN0) || (mode == LDB_SIN1)) { | ||
488 | reg &= ~(LVDS0_MUX_CTL_MASK | LVDS1_MUX_CTL_MASK); | ||
489 | channel = mode - LDB_SIN0; | ||
490 | shift = LVDS0_MUX_CTL_OFFS + channel * LVDS_MUX_CTL_WIDTH; | ||
491 | reg |= ROUTE_IPU_DI(ipu, di) << shift; | ||
492 | dev_dbg(&ldb->pdev->dev, | ||
493 | "Single mode channel %d route to IPU%d-DI%d\n", | ||
494 | channel, ipu, di); | ||
495 | } else { | ||
496 | static bool first = true; | ||
497 | |||
498 | if (first) { | ||
499 | if (mode == LDB_SEP0) { | ||
500 | reg &= ~LVDS0_MUX_CTL_MASK; | ||
501 | channel = 0; | ||
502 | } else { | ||
503 | reg &= ~LVDS1_MUX_CTL_MASK; | ||
504 | channel = 1; | ||
505 | } | ||
506 | first = false; | ||
507 | } else { | ||
508 | if (mode == LDB_SEP0) { | ||
509 | reg &= ~LVDS1_MUX_CTL_MASK; | ||
510 | channel = 1; | ||
511 | } else { | ||
512 | reg &= ~LVDS0_MUX_CTL_MASK; | ||
513 | channel = 0; | ||
514 | } | ||
515 | } | ||
516 | 531 | ||
517 | shift = LVDS0_MUX_CTL_OFFS + channel * LVDS_MUX_CTL_WIDTH; | 532 | ret = find_ldb_chno(ldb, fbi, &chno); |
518 | reg |= ROUTE_IPU_DI(ipu, di) << shift; | 533 | if (ret < 0) |
534 | return; | ||
519 | 535 | ||
520 | dev_dbg(&ldb->pdev->dev, | 536 | if (ldb->spl_mode || ldb->dual_mode) { |
521 | "Separate mode channel %d route to IPU%d-DI%d\n", | 537 | ldb->ctrl &= ~(LDB_CH1_MODE_MASK | LDB_CH0_MODE_MASK); |
522 | channel, ipu, di); | 538 | other_chno = chno ? 0 : 1; |
539 | clk_disable_unprepare(ldb->ldb_di_clk[other_chno]); | ||
540 | } else { | ||
541 | ldb->ctrl &= ~(chno ? LDB_CH1_MODE_MASK : | ||
542 | LDB_CH0_MODE_MASK); | ||
523 | } | 543 | } |
524 | writel(reg, ldb->gpr3_reg); | ||
525 | 544 | ||
526 | return 0; | 545 | regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ctrl); |
546 | return; | ||
527 | } | 547 | } |
528 | 548 | ||
529 | static int ldb_disp_init(struct mxc_dispdrv_handle *disp, | 549 | static struct mxc_dispdrv_driver ldb_drv = { |
530 | struct mxc_dispdrv_setting *setting) | 550 | .name = DRIVER_NAME, |
531 | { | 551 | .init = ldb_init, |
532 | int ret = 0, i, lvds_channel = 0, dev_id, ipu_di; | 552 | .setup = ldb_setup, |
533 | struct ldb_data *ldb = mxc_dispdrv_getdata(disp); | 553 | .enable = ldb_enable, |
534 | struct fsl_mxc_ldb_platform_data *plat_data = ldb->pdev->dev.platform_data; | 554 | .disable = ldb_disable |
535 | struct resource *res; | 555 | }; |
536 | uint32_t reg, setting_idx; | ||
537 | uint32_t ch_mask = 0, ch_val = 0; | ||
538 | uint32_t ipu_id, disp_id; | ||
539 | char di_clk[] = "ipu1_di0_sel"; | ||
540 | char ldb_clk[] = "ldb_di0"; | ||
541 | char div_3_5_clk[] = "di0_div_3_5"; | ||
542 | char div_7_clk[] = "di0_div_7"; | ||
543 | char div_sel_clk[] = "di0_div_sel"; | ||
544 | |||
545 | /* if input format not valid, make RGB666 as default*/ | ||
546 | if (!valid_mode(setting->if_fmt)) { | ||
547 | dev_warn(&ldb->pdev->dev, "Input pixel format not valid" | ||
548 | " use default RGB666\n"); | ||
549 | setting->if_fmt = IPU_PIX_FMT_RGB666; | ||
550 | } | ||
551 | 556 | ||
552 | if (!ldb->inited) { | 557 | enum { |
553 | setting_idx = 0; | 558 | LVDS_BIT_MAP_SPWG, |
554 | res = platform_get_resource(ldb->pdev, IORESOURCE_MEM, 0); | 559 | LVDS_BIT_MAP_JEIDA, |
555 | if (!res) { | 560 | }; |
556 | dev_err(&ldb->pdev->dev, "get iomem fail.\n"); | ||
557 | return -ENOMEM; | ||
558 | } | ||
559 | 561 | ||
560 | ldb->reg = devm_ioremap(&ldb->pdev->dev, res->start, | 562 | static const char *ldb_bit_mappings[] = { |
561 | resource_size(res)); | 563 | [LVDS_BIT_MAP_SPWG] = "spwg", |
562 | ldb->control_reg = ldb->reg + 2; | 564 | [LVDS_BIT_MAP_JEIDA] = "jeida", |
563 | ldb->gpr3_reg = ldb->reg + 3; | 565 | }; |
564 | 566 | ||
565 | /* ipu selected by platform data setting */ | 567 | static int of_get_data_mapping(struct device_node *np) |
566 | dev_id = plat_data->ipu_id; | 568 | { |
569 | const char *bm; | ||
570 | int ret, i; | ||
567 | 571 | ||
568 | reg = readl(ldb->control_reg); | 572 | ret = of_property_read_string(np, "fsl,data-mapping", &bm); |
573 | if (ret < 0) | ||
574 | return ret; | ||
569 | 575 | ||
570 | /* refrence resistor select */ | 576 | for (i = 0; i < ARRAY_SIZE(ldb_bit_mappings); i++) |
571 | reg &= ~LDB_BGREF_RMODE_MASK; | 577 | if (!strcasecmp(bm, ldb_bit_mappings[i])) |
572 | if (plat_data->ext_ref) | 578 | return i; |
573 | reg |= LDB_BGREF_RMODE_EXT; | ||
574 | else | ||
575 | reg |= LDB_BGREF_RMODE_INT; | ||
576 | 579 | ||
577 | /* TODO: now only use SPWG data mapping for both channel */ | 580 | return -EINVAL; |
578 | reg &= ~(LDB_BIT_MAP_CH0_MASK | LDB_BIT_MAP_CH1_MASK); | 581 | } |
579 | reg |= LDB_BIT_MAP_CH0_SPWG | LDB_BIT_MAP_CH1_SPWG; | ||
580 | 582 | ||
581 | /* channel mode setting */ | 583 | static const char *ldb_crtc_mappings[] = { |
582 | reg &= ~(LDB_CH0_MODE_MASK | LDB_CH1_MODE_MASK); | 584 | [CRTC_IPU_DI0] = "ipu-di0", |
583 | reg &= ~(LDB_DATA_WIDTH_CH0_MASK | LDB_DATA_WIDTH_CH1_MASK); | 585 | [CRTC_IPU_DI1] = "ipu-di1", |
586 | [CRTC_IPU1_DI0] = "ipu1-di0", | ||
587 | [CRTC_IPU1_DI1] = "ipu1-di1", | ||
588 | [CRTC_IPU2_DI0] = "ipu2-di0", | ||
589 | [CRTC_IPU2_DI1] = "ipu2-di1", | ||
590 | [CRTC_LCDIF] = "lcdif", | ||
591 | [CRTC_LCDIF1] = "lcdif1", | ||
592 | [CRTC_LCDIF2] = "lcdif2", | ||
593 | }; | ||
584 | 594 | ||
585 | if (bits_per_pixel(setting->if_fmt) == 24) | 595 | static enum crtc of_get_crtc_mapping(struct device_node *np) |
586 | reg |= LDB_DATA_WIDTH_CH0_24 | LDB_DATA_WIDTH_CH1_24; | 596 | { |
587 | else | 597 | const char *cm; |
588 | reg |= LDB_DATA_WIDTH_CH0_18 | LDB_DATA_WIDTH_CH1_18; | 598 | enum crtc i; |
599 | int ret; | ||
589 | 600 | ||
590 | if (g_ldb_mode >= LDB_SPL_DI0) | 601 | ret = of_property_read_string(np, "crtc", &cm); |
591 | ldb->mode = g_ldb_mode; | 602 | if (ret < 0) |
592 | else | 603 | return ret; |
593 | ldb->mode = plat_data->mode; | ||
594 | |||
595 | if ((ldb->mode == LDB_SIN0) || (ldb->mode == LDB_SIN1)) { | ||
596 | ret = ldb->mode - LDB_SIN0; | ||
597 | if (plat_data->disp_id != ret) { | ||
598 | dev_warn(&ldb->pdev->dev, | ||
599 | "change IPU DI%d to IPU DI%d for LDB " | ||
600 | "channel%d.\n", | ||
601 | plat_data->disp_id, ret, ret); | ||
602 | plat_data->disp_id = ret; | ||
603 | } | ||
604 | } else if (((ldb->mode == LDB_SEP0) || (ldb->mode == LDB_SEP1)) | ||
605 | && is_imx6_ldb(plat_data)) { | ||
606 | if (plat_data->disp_id == plat_data->sec_disp_id) { | ||
607 | dev_err(&ldb->pdev->dev, | ||
608 | "For LVDS separate mode," | ||
609 | "two DIs should be different!\n"); | ||
610 | return -EINVAL; | ||
611 | } | ||
612 | 604 | ||
613 | if (((!plat_data->disp_id) && (ldb->mode == LDB_SEP1)) | 605 | for (i = 0; i < ARRAY_SIZE(ldb_crtc_mappings); i++) |
614 | || ((plat_data->disp_id) && | 606 | if (!strcasecmp(cm, ldb_crtc_mappings[i])) { |
615 | (ldb->mode == LDB_SEP0))) { | 607 | switch (i) { |
616 | dev_dbg(&ldb->pdev->dev, | 608 | case CRTC_IPU_DI0: |
617 | "LVDS separate mode:" | 609 | i = CRTC_IPU1_DI0; |
618 | "swap DI configuration!\n"); | 610 | break; |
619 | ipu_id = plat_data->ipu_id; | 611 | case CRTC_IPU_DI1: |
620 | disp_id = plat_data->disp_id; | 612 | i = CRTC_IPU1_DI1; |
621 | plat_data->ipu_id = plat_data->sec_ipu_id; | 613 | break; |
622 | plat_data->disp_id = plat_data->sec_disp_id; | 614 | case CRTC_LCDIF: |
623 | plat_data->sec_ipu_id = ipu_id; | 615 | i = CRTC_LCDIF1; |
624 | plat_data->sec_disp_id = disp_id; | 616 | break; |
617 | default: | ||
618 | break; | ||
625 | } | 619 | } |
620 | return i; | ||
626 | } | 621 | } |
627 | 622 | ||
628 | if (ldb->mode == LDB_SPL_DI0) { | 623 | return -EINVAL; |
629 | reg |= LDB_SPLIT_MODE_EN | LDB_CH0_MODE_EN_TO_DI0 | 624 | } |
630 | | LDB_CH1_MODE_EN_TO_DI0; | 625 | |
631 | ipu_di = 0; | 626 | static int mux_count(struct ldb_data *ldb) |
632 | } else if (ldb->mode == LDB_SPL_DI1) { | 627 | { |
633 | reg |= LDB_SPLIT_MODE_EN | LDB_CH0_MODE_EN_TO_DI1 | 628 | int i, j, count = 0; |
634 | | LDB_CH1_MODE_EN_TO_DI1; | 629 | bool should_count[CRTC_MAX]; |
635 | ipu_di = 1; | 630 | enum crtc crtc; |
636 | } else if (ldb->mode == LDB_DUL_DI0) { | 631 | |
637 | reg &= ~LDB_SPLIT_MODE_EN; | 632 | for (i = 0; i < CRTC_MAX; i++) |
638 | reg |= LDB_CH0_MODE_EN_TO_DI0 | LDB_CH1_MODE_EN_TO_DI0; | 633 | should_count[i] = true; |
639 | ipu_di = 0; | 634 | |
640 | } else if (ldb->mode == LDB_DUL_DI1) { | 635 | for (i = 0; i < ldb->bus_mux_num; i++) { |
641 | reg &= ~LDB_SPLIT_MODE_EN; | 636 | for (j = 0; j < ldb->buses[i].crtc_mux_num; j++) { |
642 | reg |= LDB_CH0_MODE_EN_TO_DI1 | LDB_CH1_MODE_EN_TO_DI1; | 637 | crtc = ldb->buses[i].crtcs[j].crtc; |
643 | ipu_di = 1; | 638 | if (should_count[crtc]) { |
644 | } else if (ldb->mode == LDB_SIN0) { | 639 | count++; |
645 | reg &= ~LDB_SPLIT_MODE_EN; | 640 | should_count[crtc] = false; |
646 | ipu_di = plat_data->disp_id; | ||
647 | if (ipu_di == 0) | ||
648 | reg |= LDB_CH0_MODE_EN_TO_DI0; | ||
649 | else | ||
650 | reg |= LDB_CH0_MODE_EN_TO_DI1; | ||
651 | ch_mask = LDB_CH0_MODE_MASK; | ||
652 | ch_val = reg & LDB_CH0_MODE_MASK; | ||
653 | } else if (ldb->mode == LDB_SIN1) { | ||
654 | reg &= ~LDB_SPLIT_MODE_EN; | ||
655 | ipu_di = plat_data->disp_id; | ||
656 | if (ipu_di == 0) | ||
657 | reg |= LDB_CH1_MODE_EN_TO_DI0; | ||
658 | else | ||
659 | reg |= LDB_CH1_MODE_EN_TO_DI1; | ||
660 | ch_mask = LDB_CH1_MODE_MASK; | ||
661 | ch_val = reg & LDB_CH1_MODE_MASK; | ||
662 | } else { /* separate mode*/ | ||
663 | ipu_di = plat_data->disp_id; | ||
664 | |||
665 | /* first output is LVDS0 or LVDS1 */ | ||
666 | if (ldb->mode == LDB_SEP0) | ||
667 | lvds_channel = 0; | ||
668 | else | ||
669 | lvds_channel = 1; | ||
670 | |||
671 | reg &= ~LDB_SPLIT_MODE_EN; | ||
672 | |||
673 | if ((lvds_channel == 0) && (ipu_di == 0)) | ||
674 | reg |= LDB_CH0_MODE_EN_TO_DI0; | ||
675 | else if ((lvds_channel == 0) && (ipu_di == 1)) | ||
676 | reg |= LDB_CH0_MODE_EN_TO_DI1; | ||
677 | else if ((lvds_channel == 1) && (ipu_di == 0)) | ||
678 | reg |= LDB_CH1_MODE_EN_TO_DI0; | ||
679 | else | ||
680 | reg |= LDB_CH1_MODE_EN_TO_DI1; | ||
681 | ch_mask = lvds_channel ? LDB_CH1_MODE_MASK : | ||
682 | LDB_CH0_MODE_MASK; | ||
683 | ch_val = reg & ch_mask; | ||
684 | |||
685 | if (bits_per_pixel(setting->if_fmt) == 24) { | ||
686 | if (lvds_channel == 0) | ||
687 | reg &= ~LDB_DATA_WIDTH_CH1_24; | ||
688 | else | ||
689 | reg &= ~LDB_DATA_WIDTH_CH0_24; | ||
690 | } else { | ||
691 | if (lvds_channel == 0) | ||
692 | reg &= ~LDB_DATA_WIDTH_CH1_18; | ||
693 | else | ||
694 | reg &= ~LDB_DATA_WIDTH_CH0_18; | ||
695 | } | 641 | } |
696 | } | 642 | } |
643 | } | ||
697 | 644 | ||
698 | writel(reg, ldb->control_reg); | 645 | return count; |
699 | if (ldb->mode < LDB_SIN0) { | 646 | } |
700 | ch_mask = LDB_CH0_MODE_MASK | LDB_CH1_MODE_MASK; | ||
701 | ch_val = reg & (LDB_CH0_MODE_MASK | LDB_CH1_MODE_MASK); | ||
702 | } | ||
703 | } else { /* second time for separate mode */ | ||
704 | if ((ldb->mode == LDB_SPL_DI0) || | ||
705 | (ldb->mode == LDB_SPL_DI1) || | ||
706 | (ldb->mode == LDB_DUL_DI0) || | ||
707 | (ldb->mode == LDB_DUL_DI1) || | ||
708 | (ldb->mode == LDB_SIN0) || | ||
709 | (ldb->mode == LDB_SIN1)) { | ||
710 | dev_err(&ldb->pdev->dev, "for second ldb disp" | ||
711 | "ldb mode should in separate mode\n"); | ||
712 | return -EINVAL; | ||
713 | } | ||
714 | 647 | ||
715 | setting_idx = 1; | 648 | static bool is_valid_crtc(struct ldb_data *ldb, enum crtc crtc, |
716 | if (is_imx6_ldb(plat_data)) { | 649 | int chno) |
717 | dev_id = plat_data->sec_ipu_id; | 650 | { |
718 | ipu_di = plat_data->sec_disp_id; | 651 | int i = 0; |
719 | } else { | ||
720 | dev_id = plat_data->ipu_id; | ||
721 | ipu_di = !plat_data->disp_id; | ||
722 | } | ||
723 | if (ipu_di == ldb->setting[0].di) { | ||
724 | dev_err(&ldb->pdev->dev, "Err: for second ldb disp in" | ||
725 | "separate mode, DI should be different!\n"); | ||
726 | return -EINVAL; | ||
727 | } | ||
728 | 652 | ||
729 | /* second output is LVDS0 or LVDS1 */ | 653 | if (chno > ldb->bus_mux_num - 1) |
730 | if (ldb->mode == LDB_SEP0) | 654 | return false; |
731 | lvds_channel = 1; | ||
732 | else | ||
733 | lvds_channel = 0; | ||
734 | |||
735 | reg = readl(ldb->control_reg); | ||
736 | if ((lvds_channel == 0) && (ipu_di == 0)) | ||
737 | reg |= LDB_CH0_MODE_EN_TO_DI0; | ||
738 | else if ((lvds_channel == 0) && (ipu_di == 1)) | ||
739 | reg |= LDB_CH0_MODE_EN_TO_DI1; | ||
740 | else if ((lvds_channel == 1) && (ipu_di == 0)) | ||
741 | reg |= LDB_CH1_MODE_EN_TO_DI0; | ||
742 | else | ||
743 | reg |= LDB_CH1_MODE_EN_TO_DI1; | ||
744 | ch_mask = lvds_channel ? LDB_CH1_MODE_MASK : | ||
745 | LDB_CH0_MODE_MASK; | ||
746 | ch_val = reg & ch_mask; | ||
747 | |||
748 | if (bits_per_pixel(setting->if_fmt) == 24) { | ||
749 | if (lvds_channel == 0) | ||
750 | reg |= LDB_DATA_WIDTH_CH0_24; | ||
751 | else | ||
752 | reg |= LDB_DATA_WIDTH_CH1_24; | ||
753 | } else { | ||
754 | if (lvds_channel == 0) | ||
755 | reg |= LDB_DATA_WIDTH_CH0_18; | ||
756 | else | ||
757 | reg |= LDB_DATA_WIDTH_CH1_18; | ||
758 | } | ||
759 | writel(reg, ldb->control_reg); | ||
760 | } | ||
761 | 655 | ||
762 | /* get clocks */ | 656 | for (; i < ldb->buses[chno].crtc_mux_num; i++) |
763 | if (is_imx6_ldb(plat_data) && | 657 | if (ldb->buses[chno].crtcs[i].crtc == crtc) |
764 | ((ldb->mode == LDB_SEP0) || (ldb->mode == LDB_SEP1))) { | 658 | return true; |
765 | ldb_clk[6] += lvds_channel; | ||
766 | div_3_5_clk[2] += lvds_channel; | ||
767 | div_7_clk[2] += lvds_channel; | ||
768 | div_sel_clk[2] += lvds_channel; | ||
769 | } else { | ||
770 | ldb_clk[6] += ipu_di; | ||
771 | div_3_5_clk[2] += ipu_di; | ||
772 | div_7_clk[2] += ipu_di; | ||
773 | div_sel_clk[2] += ipu_di; | ||
774 | } | ||
775 | ldb->setting[setting_idx].ldb_di_clk = clk_get(&ldb->pdev->dev, | ||
776 | ldb_clk); | ||
777 | if (IS_ERR(ldb->setting[setting_idx].ldb_di_clk)) { | ||
778 | dev_err(&ldb->pdev->dev, "get ldb clk failed\n"); | ||
779 | return PTR_ERR(ldb->setting[setting_idx].ldb_di_clk); | ||
780 | } | ||
781 | 659 | ||
782 | ldb->setting[setting_idx].div_3_5_clk = clk_get(&ldb->pdev->dev, | 660 | return false; |
783 | div_3_5_clk); | 661 | } |
784 | if (IS_ERR(ldb->setting[setting_idx].div_3_5_clk)) { | ||
785 | dev_err(&ldb->pdev->dev, "get div 3.5 clk failed\n"); | ||
786 | return PTR_ERR(ldb->setting[setting_idx].div_3_5_clk); | ||
787 | } | ||
788 | ldb->setting[setting_idx].div_7_clk = clk_get(&ldb->pdev->dev, | ||
789 | div_7_clk); | ||
790 | if (IS_ERR(ldb->setting[setting_idx].div_7_clk)) { | ||
791 | dev_err(&ldb->pdev->dev, "get div 7 clk failed\n"); | ||
792 | return PTR_ERR(ldb->setting[setting_idx].div_7_clk); | ||
793 | } | ||
794 | 662 | ||
795 | ldb->setting[setting_idx].div_sel_clk = clk_get(&ldb->pdev->dev, | 663 | static int ldb_probe(struct platform_device *pdev) |
796 | div_sel_clk); | 664 | { |
797 | if (IS_ERR(ldb->setting[setting_idx].div_sel_clk)) { | 665 | struct device *dev = &pdev->dev; |
798 | dev_err(&ldb->pdev->dev, "get div sel clk failed\n"); | 666 | const struct of_device_id *of_id = |
799 | return PTR_ERR(ldb->setting[setting_idx].div_sel_clk); | 667 | of_match_device(ldb_dt_ids, dev); |
800 | } | 668 | const struct ldb_info *ldb_info = |
669 | (const struct ldb_info *)of_id->data; | ||
670 | struct device_node *np = dev->of_node, *child; | ||
671 | struct ldb_data *ldb; | ||
672 | bool ext_ref; | ||
673 | int i, data_width, mapping, child_count = 0; | ||
674 | char clkname[16]; | ||
801 | 675 | ||
802 | di_clk[3] += dev_id; | 676 | ldb = devm_kzalloc(dev, sizeof(*ldb), GFP_KERNEL); |
803 | di_clk[7] += ipu_di; | 677 | if (!ldb) |
804 | ldb->setting[setting_idx].di_clk = clk_get(&ldb->pdev->dev, | 678 | return -ENOMEM; |
805 | di_clk); | ||
806 | if (IS_ERR(ldb->setting[setting_idx].di_clk)) { | ||
807 | dev_err(&ldb->pdev->dev, "get di clk failed\n"); | ||
808 | return PTR_ERR(ldb->setting[setting_idx].di_clk); | ||
809 | } | ||
810 | 679 | ||
811 | ldb->setting[setting_idx].ch_mask = ch_mask; | 680 | ldb->regmap = syscon_regmap_lookup_by_phandle(np, "gpr"); |
812 | ldb->setting[setting_idx].ch_val = ch_val; | 681 | if (IS_ERR(ldb->regmap)) { |
813 | 682 | dev_err(dev, "failed to get parent regmap\n"); | |
814 | if (is_imx6_ldb(plat_data)) | 683 | return PTR_ERR(ldb->regmap); |
815 | ldb_ipu_ldb_route(dev_id, ipu_di, ldb); | ||
816 | |||
817 | /* must use spec video mode defined by driver */ | ||
818 | ret = fb_find_mode(&setting->fbi->var, setting->fbi, setting->dft_mode_str, | ||
819 | ldb_modedb, ldb_modedb_sz, NULL, setting->default_bpp); | ||
820 | if (ret != 1) | ||
821 | fb_videomode_to_var(&setting->fbi->var, &ldb_modedb[0]); | ||
822 | |||
823 | INIT_LIST_HEAD(&setting->fbi->modelist); | ||
824 | for (i = 0; i < ldb_modedb_sz; i++) { | ||
825 | struct fb_videomode m; | ||
826 | fb_var_to_videomode(&m, &setting->fbi->var); | ||
827 | if (fb_mode_is_equal(&m, &ldb_modedb[i])) { | ||
828 | fb_add_videomode(&ldb_modedb[i], | ||
829 | &setting->fbi->modelist); | ||
830 | break; | ||
831 | } | ||
832 | } | 684 | } |
833 | 685 | ||
834 | ret = ipu_di_to_crtc(&ldb->pdev->dev, dev_id, | 686 | ldb->dev = dev; |
835 | ipu_di, &setting->crtc); | 687 | ldb->bus_mux_num = ldb_info->bus_mux_num; |
836 | if (ret < 0) | 688 | ldb->buses = ldb_info->buses; |
837 | return ret; | 689 | ldb->ctrl_reg = ldb_info->ctrl_reg; |
690 | ldb->primary_chno = -1; | ||
838 | 691 | ||
839 | ldb->setting[setting_idx].ipu = dev_id; | 692 | ext_ref = of_property_read_bool(np, "ext-ref"); |
840 | ldb->setting[setting_idx].di = ipu_di; | 693 | if (!ext_ref && ldb_info->ext_bgref_cap) |
694 | ldb->ctrl |= LDB_BGREF_RMODE_INT; | ||
841 | 695 | ||
842 | return ret; | 696 | ldb->spl_mode = of_property_read_bool(np, "split-mode"); |
843 | } | 697 | if (ldb->spl_mode) { |
844 | 698 | if (ldb_info->split_cap) { | |
845 | static int ldb_post_disp_init(struct mxc_dispdrv_handle *disp, | 699 | ldb->ctrl |= LDB_SPLIT_MODE_EN; |
846 | int ipu_id, int disp_id) | 700 | dev_info(dev, "split mode\n"); |
847 | { | 701 | } else { |
848 | struct ldb_data *ldb = mxc_dispdrv_getdata(disp); | 702 | dev_err(dev, "cannot support split mode\n"); |
849 | int setting_idx = ldb->inited ? 1 : 0; | 703 | return -EINVAL; |
850 | int ret = 0; | 704 | } |
705 | } | ||
851 | 706 | ||
852 | if (!ldb->inited) { | 707 | ldb->dual_mode = of_property_read_bool(np, "dual-mode"); |
853 | ldb->nb.notifier_call = ldb_fb_event; | 708 | if (ldb->dual_mode) { |
854 | fb_register_client(&ldb->nb); | 709 | if (ldb_info->dual_cap) { |
710 | dev_info(dev, "dual mode\n"); | ||
711 | } else { | ||
712 | dev_err(dev, "cannot support dual mode\n"); | ||
713 | return -EINVAL; | ||
714 | } | ||
855 | } | 715 | } |
856 | 716 | ||
857 | ret = clk_set_parent(ldb->setting[setting_idx].di_clk, | 717 | if (ldb->dual_mode && ldb->spl_mode) { |
858 | ldb->setting[setting_idx].ldb_di_clk); | 718 | dev_err(dev, "cannot support dual mode and split mode " |
859 | if (ret) { | 719 | "simultaneously\n"); |
860 | dev_err(&ldb->pdev->dev, "fail to set ldb_di clk as" | 720 | return -EINVAL; |
861 | "the parent of ipu_di clk\n"); | ||
862 | return ret; | ||
863 | } | 721 | } |
864 | 722 | ||
865 | if ((ldb->mode == LDB_SPL_DI0) || (ldb->mode == LDB_SPL_DI1)) { | 723 | for (i = 0; i < mux_count(ldb); i++) { |
866 | ret = clk_set_parent(ldb->setting[setting_idx].div_sel_clk, | 724 | sprintf(clkname, "di%d_sel", i); |
867 | ldb->setting[setting_idx].div_3_5_clk); | 725 | ldb->di_clk[i] = devm_clk_get(dev, clkname); |
868 | if (ret) { | 726 | if (IS_ERR(ldb->di_clk[i])) { |
869 | dev_err(&ldb->pdev->dev, "fail to set div 3.5 clk as" | 727 | dev_err(dev, "failed to get clk %s\n", clkname); |
870 | "the parent of div sel clk\n"); | 728 | return PTR_ERR(ldb->di_clk[i]); |
871 | return ret; | ||
872 | } | ||
873 | } else { | ||
874 | ret = clk_set_parent(ldb->setting[setting_idx].div_sel_clk, | ||
875 | ldb->setting[setting_idx].div_7_clk); | ||
876 | if (ret) { | ||
877 | dev_err(&ldb->pdev->dev, "fail to set div 7 clk as" | ||
878 | "the parent of div sel clk\n"); | ||
879 | return ret; | ||
880 | } | 729 | } |
881 | } | 730 | } |
882 | 731 | ||
883 | /* save active ldb setting for fb notifier */ | 732 | for_each_child_of_node(np, child) { |
884 | ldb->setting[setting_idx].active = true; | 733 | struct ldb_chan *chan; |
734 | enum crtc crtc; | ||
735 | bool is_primary; | ||
736 | int ret; | ||
885 | 737 | ||
886 | ldb->inited = true; | 738 | ret = of_property_read_u32(child, "reg", &i); |
887 | return ret; | 739 | if (ret || i < 0 || i > 1 || i >= ldb->bus_mux_num) { |
888 | } | 740 | dev_err(dev, "wrong LVDS channel number\n"); |
889 | 741 | return -EINVAL; | |
890 | static void ldb_disp_deinit(struct mxc_dispdrv_handle *disp) | 742 | } |
891 | { | ||
892 | struct ldb_data *ldb = mxc_dispdrv_getdata(disp); | ||
893 | int i; | ||
894 | |||
895 | writel(0, ldb->control_reg); | ||
896 | 743 | ||
897 | for (i = 0; i < 2; i++) { | 744 | if ((ldb->spl_mode || ldb->dual_mode) && i > 0) { |
898 | clk_disable(ldb->setting[i].ldb_di_clk); | 745 | dev_warn(dev, "split mode or dual mode, ignoring " |
899 | clk_put(ldb->setting[i].ldb_di_clk); | 746 | "second output\n"); |
900 | clk_put(ldb->setting[i].div_3_5_clk); | 747 | continue; |
901 | clk_put(ldb->setting[i].div_7_clk); | 748 | } |
902 | clk_put(ldb->setting[i].div_sel_clk); | ||
903 | } | ||
904 | 749 | ||
905 | fb_unregister_client(&ldb->nb); | 750 | if (!of_device_is_available(child)) |
906 | } | 751 | continue; |
907 | 752 | ||
908 | static struct mxc_dispdrv_driver ldb_drv = { | 753 | if (++child_count > ldb->bus_mux_num) { |
909 | .name = DISPDRV_LDB, | 754 | dev_err(dev, "too many LVDS channels\n"); |
910 | .init = ldb_disp_init, | 755 | return -EINVAL; |
911 | .post_init = ldb_post_disp_init, | 756 | } |
912 | .deinit = ldb_disp_deinit, | ||
913 | .setup = ldb_disp_setup, | ||
914 | }; | ||
915 | 757 | ||
916 | static int ldb_suspend(struct platform_device *pdev, pm_message_t state) | 758 | chan = &ldb->chan[i]; |
917 | { | 759 | chan->chno = i; |
918 | struct ldb_data *ldb = dev_get_drvdata(&pdev->dev); | 760 | chan->ldb = ldb; |
919 | uint32_t data; | 761 | chan->online = true; |
920 | 762 | ||
921 | if (!ldb->inited) | 763 | is_primary = of_property_read_bool(child, "primary"); |
922 | return 0; | ||
923 | data = readl(ldb->control_reg); | ||
924 | ldb->control_reg_data = data; | ||
925 | data &= ~(LDB_CH0_MODE_MASK | LDB_CH1_MODE_MASK); | ||
926 | writel(data, ldb->control_reg); | ||
927 | 764 | ||
928 | return 0; | 765 | if (ldb->bus_mux_num == 1 || (ldb->primary_chno == -1 && |
929 | } | 766 | (is_primary || ldb->spl_mode || ldb->dual_mode))) |
767 | ldb->primary_chno = chan->chno; | ||
930 | 768 | ||
931 | static int ldb_resume(struct platform_device *pdev) | 769 | ret = of_property_read_u32(child, "fsl,data-width", |
932 | { | 770 | &data_width); |
933 | struct ldb_data *ldb = dev_get_drvdata(&pdev->dev); | 771 | if (ret || (data_width != 18 && data_width != 24)) { |
772 | dev_err(dev, "data width not specified or invalid\n"); | ||
773 | return -EINVAL; | ||
774 | } | ||
934 | 775 | ||
935 | if (!ldb->inited) | 776 | mapping = of_get_data_mapping(child); |
936 | return 0; | 777 | switch (mapping) { |
937 | writel(ldb->control_reg_data, ldb->control_reg); | 778 | case LVDS_BIT_MAP_SPWG: |
779 | if (data_width == 24) { | ||
780 | if (i == 0 || ldb->spl_mode || ldb->dual_mode) | ||
781 | ldb->ctrl |= LDB_DATA_WIDTH_CH0_24; | ||
782 | if (i == 1 || ldb->spl_mode || ldb->dual_mode) | ||
783 | ldb->ctrl |= LDB_DATA_WIDTH_CH1_24; | ||
784 | } | ||
785 | break; | ||
786 | case LVDS_BIT_MAP_JEIDA: | ||
787 | if (data_width == 18) { | ||
788 | dev_err(dev, "JEIDA only support 24bit\n"); | ||
789 | return -EINVAL; | ||
790 | } | ||
791 | if (i == 0 || ldb->spl_mode || ldb->dual_mode) | ||
792 | ldb->ctrl |= LDB_DATA_WIDTH_CH0_24 | | ||
793 | LDB_BIT_MAP_CH0_JEIDA; | ||
794 | if (i == 1 || ldb->spl_mode || ldb->dual_mode) | ||
795 | ldb->ctrl |= LDB_DATA_WIDTH_CH1_24 | | ||
796 | LDB_BIT_MAP_CH1_JEIDA; | ||
797 | break; | ||
798 | default: | ||
799 | dev_err(dev, "data mapping not specified or invalid\n"); | ||
800 | return -EINVAL; | ||
801 | } | ||
938 | 802 | ||
939 | return 0; | 803 | crtc = of_get_crtc_mapping(child); |
940 | } | 804 | if (is_valid_crtc(ldb, crtc, chan->chno)) { |
805 | ldb->chan[i].crtc = crtc; | ||
806 | } else { | ||
807 | dev_err(dev, "crtc not specified or invalid\n"); | ||
808 | return -EINVAL; | ||
809 | } | ||
941 | 810 | ||
942 | static struct platform_device_id imx_ldb_devtype[] = { | 811 | ret = of_get_videomode(child, &chan->vm, 0); |
943 | { | 812 | if (ret) |
944 | .name = "ldb-imx6", | 813 | return -EINVAL; |
945 | .driver_data = LDB_IMX6, | ||
946 | }, { | ||
947 | /* sentinel */ | ||
948 | } | ||
949 | }; | ||
950 | 814 | ||
951 | static const struct of_device_id imx_ldb_dt_ids[] = { | 815 | sprintf(clkname, "ldb_di%d", i); |
952 | { .compatible = "fsl,imx6q-ldb", .data = &imx_ldb_devtype[IMX6_LDB],}, | 816 | ldb->ldb_di_clk[i] = devm_clk_get(dev, clkname); |
953 | { /* sentinel */ } | 817 | if (IS_ERR(ldb->ldb_di_clk[i])) { |
954 | }; | 818 | dev_err(dev, "failed to get clk %s\n", clkname); |
819 | return PTR_ERR(ldb->ldb_di_clk[i]); | ||
820 | } | ||
955 | 821 | ||
956 | /*! | 822 | sprintf(clkname, "ldb_di%d_div_3_5", i); |
957 | * This function is called by the driver framework to initialize the LDB | 823 | ldb->div_3_5_clk[i] = devm_clk_get(dev, clkname); |
958 | * device. | 824 | if (IS_ERR(ldb->div_3_5_clk[i])) { |
959 | * | 825 | dev_err(dev, "failed to get clk %s\n", clkname); |
960 | * @param dev The device structure for the LDB passed in by the | 826 | return PTR_ERR(ldb->div_3_5_clk[i]); |
961 | * driver framework. | 827 | } |
962 | * | ||
963 | * @return Returns 0 on success or negative error code on error | ||
964 | */ | ||
965 | static int ldb_probe(struct platform_device *pdev) | ||
966 | { | ||
967 | int ret = 0; | ||
968 | struct ldb_data *ldb; | ||
969 | struct fsl_mxc_ldb_platform_data *plat_data; | ||
970 | const struct of_device_id *of_id = | ||
971 | of_match_device(imx_ldb_dt_ids, &pdev->dev); | ||
972 | 828 | ||
973 | dev_dbg(&pdev->dev, "%s enter\n", __func__); | 829 | sprintf(clkname, "ldb_di%d_div_7", i); |
974 | ldb = devm_kzalloc(&pdev->dev, sizeof(struct ldb_data), GFP_KERNEL); | 830 | ldb->div_7_clk[i] = devm_clk_get(dev, clkname); |
975 | if (!ldb) | 831 | if (IS_ERR(ldb->div_7_clk[i])) { |
976 | return -ENOMEM; | 832 | dev_err(dev, "failed to get clk %s\n", clkname); |
833 | return PTR_ERR(ldb->div_7_clk[i]); | ||
834 | } | ||
977 | 835 | ||
978 | plat_data = devm_kzalloc(&pdev->dev, | 836 | sprintf(clkname, "ldb_di%d_div_sel", i); |
979 | sizeof(struct fsl_mxc_ldb_platform_data), | 837 | ldb->div_sel_clk[i] = devm_clk_get(dev, clkname); |
980 | GFP_KERNEL); | 838 | if (IS_ERR(ldb->div_sel_clk[i])) { |
981 | if (!plat_data) | 839 | dev_err(dev, "failed to get clk %s\n", clkname); |
982 | return -ENOMEM; | 840 | return PTR_ERR(ldb->div_sel_clk[i]); |
983 | pdev->dev.platform_data = plat_data; | 841 | } |
984 | if (of_id) | 842 | } |
985 | pdev->id_entry = of_id->data; | ||
986 | plat_data->devtype = pdev->id_entry->driver_data; | ||
987 | 843 | ||
988 | ret = ldb_get_of_property(pdev, plat_data); | 844 | if (child_count == 0) { |
989 | if (ret < 0) { | 845 | dev_err(dev, "failed to find valid LVDS channel\n"); |
990 | dev_err(&pdev->dev, "get ldb of property fail\n"); | 846 | return -EINVAL; |
991 | return ret; | ||
992 | } | 847 | } |
993 | 848 | ||
994 | ldb->pdev = pdev; | 849 | if (ldb->primary_chno == -1) { |
995 | ldb->disp_ldb = mxc_dispdrv_register(&ldb_drv); | 850 | dev_err(dev, "failed to know primary channel\n"); |
996 | mxc_dispdrv_setdata(ldb->disp_ldb, ldb); | 851 | return -EINVAL; |
852 | } | ||
997 | 853 | ||
854 | ldb->mddh = mxc_dispdrv_register(&ldb_drv); | ||
855 | mxc_dispdrv_setdata(ldb->mddh, ldb); | ||
998 | dev_set_drvdata(&pdev->dev, ldb); | 856 | dev_set_drvdata(&pdev->dev, ldb); |
999 | 857 | ||
1000 | dev_dbg(&pdev->dev, "%s exit\n", __func__); | 858 | return 0; |
1001 | return ret; | ||
1002 | } | 859 | } |
1003 | 860 | ||
1004 | static int ldb_remove(struct platform_device *pdev) | 861 | static int ldb_remove(struct platform_device *pdev) |
1005 | { | 862 | { |
1006 | struct ldb_data *ldb = dev_get_drvdata(&pdev->dev); | 863 | struct ldb_data *ldb = dev_get_drvdata(&pdev->dev); |
1007 | 864 | ||
1008 | if (!ldb->inited) | 865 | mxc_dispdrv_puthandle(ldb->mddh); |
1009 | return 0; | 866 | mxc_dispdrv_unregister(ldb->mddh); |
1010 | mxc_dispdrv_puthandle(ldb->disp_ldb); | ||
1011 | mxc_dispdrv_unregister(ldb->disp_ldb); | ||
1012 | return 0; | 867 | return 0; |
1013 | } | 868 | } |
1014 | 869 | ||
1015 | static struct platform_driver mxcldb_driver = { | 870 | static struct platform_driver ldb_driver = { |
1016 | .driver = { | 871 | .driver = { |
1017 | .name = "mxc_ldb", | 872 | .name = DRIVER_NAME, |
1018 | .of_match_table = imx_ldb_dt_ids, | 873 | .of_match_table = ldb_dt_ids, |
1019 | }, | 874 | }, |
1020 | .probe = ldb_probe, | 875 | .probe = ldb_probe, |
1021 | .remove = ldb_remove, | 876 | .remove = ldb_remove, |
1022 | .suspend = ldb_suspend, | ||
1023 | .resume = ldb_resume, | ||
1024 | }; | 877 | }; |
1025 | 878 | ||
1026 | static int __init ldb_init(void) | 879 | module_platform_driver(ldb_driver); |
1027 | { | ||
1028 | return platform_driver_register(&mxcldb_driver); | ||
1029 | } | ||
1030 | |||
1031 | static void __exit ldb_uninit(void) | ||
1032 | { | ||
1033 | platform_driver_unregister(&mxcldb_driver); | ||
1034 | } | ||
1035 | |||
1036 | module_init(ldb_init); | ||
1037 | module_exit(ldb_uninit); | ||
1038 | 880 | ||
1039 | MODULE_AUTHOR("Freescale Semiconductor, Inc."); | 881 | MODULE_AUTHOR("Freescale Semiconductor, Inc."); |
1040 | MODULE_DESCRIPTION("MXC LDB driver"); | 882 | MODULE_DESCRIPTION("LDB driver"); |
1041 | MODULE_LICENSE("GPL"); | 883 | MODULE_LICENSE("GPL"); |
884 | MODULE_ALIAS("platform:" DRIVER_NAME); | ||