Commit | Line | Data |
---|---|---|
90374b5c LP |
1 | /* |
2 | * rcar_du_lvdsenc.c -- R-Car Display Unit LVDS Encoder | |
3 | * | |
36d50464 | 4 | * Copyright (C) 2013-2014 Renesas Electronics Corporation |
90374b5c LP |
5 | * |
6 | * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published by | |
10 | * the Free Software Foundation; either version 2 of the License, or | |
11 | * (at your option) any later version. | |
12 | */ | |
13 | ||
14 | #include <linux/clk.h> | |
15 | #include <linux/delay.h> | |
16 | #include <linux/io.h> | |
17 | #include <linux/platform_device.h> | |
18 | #include <linux/slab.h> | |
19 | ||
20 | #include "rcar_du_drv.h" | |
21 | #include "rcar_du_encoder.h" | |
22 | #include "rcar_du_lvdsenc.h" | |
23 | #include "rcar_lvds_regs.h" | |
24 | ||
25 | struct rcar_du_lvdsenc { | |
26 | struct rcar_du_device *dev; | |
27 | ||
28 | unsigned int index; | |
29 | void __iomem *mmio; | |
30 | struct clk *clock; | |
44ef7ed5 | 31 | bool enabled; |
90374b5c LP |
32 | |
33 | enum rcar_lvds_input input; | |
34 | }; | |
35 | ||
36 | static void rcar_lvds_write(struct rcar_du_lvdsenc *lvds, u32 reg, u32 data) | |
37 | { | |
38 | iowrite32(data, lvds->mmio + reg); | |
39 | } | |
40 | ||
6bc2e15c KM |
41 | static void rcar_du_lvdsenc_start_gen2(struct rcar_du_lvdsenc *lvds, |
42 | struct rcar_du_crtc *rcrtc) | |
90374b5c LP |
43 | { |
44 | const struct drm_display_mode *mode = &rcrtc->crtc.mode; | |
45 | unsigned int freq = mode->clock; | |
46 | u32 lvdcr0; | |
47 | u32 pllcr; | |
90374b5c LP |
48 | |
49 | /* PLL clock configuration */ | |
5e1ac3bd | 50 | if (freq < 39000) |
90374b5c | 51 | pllcr = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_38M; |
5e1ac3bd | 52 | else if (freq < 61000) |
90374b5c | 53 | pllcr = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_60M; |
5e1ac3bd | 54 | else if (freq < 121000) |
90374b5c LP |
55 | pllcr = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_121M; |
56 | else | |
57 | pllcr = LVDPLLCR_PLLDLYCNT_150M; | |
58 | ||
59 | rcar_lvds_write(lvds, LVDPLLCR, pllcr); | |
60 | ||
90374b5c LP |
61 | /* Select the input, hardcode mode 0, enable LVDS operation and turn |
62 | * bias circuitry on. | |
63 | */ | |
64 | lvdcr0 = LVDCR0_BEN | LVDCR0_LVEN; | |
65 | if (rcrtc->index == 2) | |
66 | lvdcr0 |= LVDCR0_DUSEL; | |
67 | rcar_lvds_write(lvds, LVDCR0, lvdcr0); | |
68 | ||
69 | /* Turn all the channels on. */ | |
6bc2e15c KM |
70 | rcar_lvds_write(lvds, LVDCR1, |
71 | LVDCR1_CHSTBY_GEN2(3) | LVDCR1_CHSTBY_GEN2(2) | | |
72 | LVDCR1_CHSTBY_GEN2(1) | LVDCR1_CHSTBY_GEN2(0) | | |
73 | LVDCR1_CLKSTBY_GEN2); | |
90374b5c LP |
74 | |
75 | /* Turn the PLL on, wait for the startup delay, and turn the output | |
76 | * on. | |
77 | */ | |
82e7c5e4 | 78 | lvdcr0 |= LVDCR0_PLLON; |
90374b5c LP |
79 | rcar_lvds_write(lvds, LVDCR0, lvdcr0); |
80 | ||
81 | usleep_range(100, 150); | |
82 | ||
83 | lvdcr0 |= LVDCR0_LVRES; | |
84 | rcar_lvds_write(lvds, LVDCR0, lvdcr0); | |
6bc2e15c KM |
85 | } |
86 | ||
87 | static void rcar_du_lvdsenc_start_gen3(struct rcar_du_lvdsenc *lvds, | |
88 | struct rcar_du_crtc *rcrtc) | |
89 | { | |
90 | const struct drm_display_mode *mode = &rcrtc->crtc.mode; | |
91 | unsigned int freq = mode->clock; | |
92 | u32 lvdcr0; | |
93 | u32 pllcr; | |
94 | ||
95 | /* PLL clock configuration */ | |
96 | if (freq < 42000) | |
97 | pllcr = LVDPLLCR_PLLDIVCNT_42M; | |
98 | else if (freq < 85000) | |
99 | pllcr = LVDPLLCR_PLLDIVCNT_85M; | |
100 | else if (freq < 128000) | |
101 | pllcr = LVDPLLCR_PLLDIVCNT_128M; | |
102 | else | |
103 | pllcr = LVDPLLCR_PLLDIVCNT_148M; | |
104 | ||
105 | rcar_lvds_write(lvds, LVDPLLCR, pllcr); | |
106 | ||
107 | /* Turn the PLL on, set it to LVDS normal mode, wait for the startup | |
108 | * delay and turn the output on. | |
109 | */ | |
110 | lvdcr0 = LVDCR0_PLLON; | |
111 | rcar_lvds_write(lvds, LVDCR0, lvdcr0); | |
112 | ||
113 | lvdcr0 |= LVDCR0_PWD; | |
114 | rcar_lvds_write(lvds, LVDCR0, lvdcr0); | |
115 | ||
116 | usleep_range(100, 150); | |
117 | ||
118 | lvdcr0 |= LVDCR0_LVRES; | |
119 | rcar_lvds_write(lvds, LVDCR0, lvdcr0); | |
120 | ||
121 | /* Turn all the channels on. */ | |
122 | rcar_lvds_write(lvds, LVDCR1, | |
123 | LVDCR1_CHSTBY_GEN3(3) | LVDCR1_CHSTBY_GEN3(2) | | |
124 | LVDCR1_CHSTBY_GEN3(1) | LVDCR1_CHSTBY_GEN3(0) | | |
125 | LVDCR1_CLKSTBY_GEN3); | |
126 | } | |
127 | ||
128 | static int rcar_du_lvdsenc_start(struct rcar_du_lvdsenc *lvds, | |
129 | struct rcar_du_crtc *rcrtc) | |
130 | { | |
131 | u32 lvdhcr; | |
132 | int ret; | |
133 | ||
134 | if (lvds->enabled) | |
135 | return 0; | |
136 | ||
137 | ret = clk_prepare_enable(lvds->clock); | |
138 | if (ret < 0) | |
139 | return ret; | |
140 | ||
141 | /* Hardcode the channels and control signals routing for now. | |
142 | * | |
143 | * HSYNC -> CTRL0 | |
144 | * VSYNC -> CTRL1 | |
145 | * DISP -> CTRL2 | |
146 | * 0 -> CTRL3 | |
147 | */ | |
148 | rcar_lvds_write(lvds, LVDCTRCR, LVDCTRCR_CTR3SEL_ZERO | | |
149 | LVDCTRCR_CTR2SEL_DISP | LVDCTRCR_CTR1SEL_VSYNC | | |
150 | LVDCTRCR_CTR0SEL_HSYNC); | |
151 | ||
152 | if (rcar_du_needs(lvds->dev, RCAR_DU_QUIRK_LVDS_LANES)) | |
153 | lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 3) | |
154 | | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 1); | |
155 | else | |
156 | lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 1) | |
157 | | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 3); | |
158 | ||
159 | rcar_lvds_write(lvds, LVDCHCR, lvdhcr); | |
160 | ||
161 | /* Perform generation-specific initialization. */ | |
162 | if (lvds->dev->info->gen < 3) | |
163 | rcar_du_lvdsenc_start_gen2(lvds, rcrtc); | |
164 | else | |
165 | rcar_du_lvdsenc_start_gen3(lvds, rcrtc); | |
90374b5c | 166 | |
44ef7ed5 | 167 | lvds->enabled = true; |
6bc2e15c | 168 | |
90374b5c LP |
169 | return 0; |
170 | } | |
171 | ||
172 | static void rcar_du_lvdsenc_stop(struct rcar_du_lvdsenc *lvds) | |
173 | { | |
44ef7ed5 | 174 | if (!lvds->enabled) |
90374b5c LP |
175 | return; |
176 | ||
177 | rcar_lvds_write(lvds, LVDCR0, 0); | |
178 | rcar_lvds_write(lvds, LVDCR1, 0); | |
179 | ||
180 | clk_disable_unprepare(lvds->clock); | |
181 | ||
44ef7ed5 | 182 | lvds->enabled = false; |
90374b5c LP |
183 | } |
184 | ||
44ef7ed5 LP |
185 | int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds, struct drm_crtc *crtc, |
186 | bool enable) | |
90374b5c | 187 | { |
44ef7ed5 | 188 | if (!enable) { |
90374b5c LP |
189 | rcar_du_lvdsenc_stop(lvds); |
190 | return 0; | |
191 | } else if (crtc) { | |
192 | struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); | |
193 | return rcar_du_lvdsenc_start(lvds, rcrtc); | |
194 | } else | |
195 | return -EINVAL; | |
196 | } | |
197 | ||
d9829a32 LP |
198 | void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds, |
199 | struct drm_display_mode *mode) | |
200 | { | |
6bc2e15c KM |
201 | struct rcar_du_device *rcdu = lvds->dev; |
202 | ||
203 | /* The internal LVDS encoder has a restricted clock frequency operating | |
204 | * range (30MHz to 150MHz on Gen2, 25.175MHz to 148.5MHz on Gen3). Clamp | |
205 | * the clock accordingly. | |
d9829a32 | 206 | */ |
6bc2e15c KM |
207 | if (rcdu->info->gen < 3) |
208 | mode->clock = clamp(mode->clock, 30000, 150000); | |
209 | else | |
210 | mode->clock = clamp(mode->clock, 25175, 148500); | |
d9829a32 LP |
211 | } |
212 | ||
90374b5c LP |
213 | static int rcar_du_lvdsenc_get_resources(struct rcar_du_lvdsenc *lvds, |
214 | struct platform_device *pdev) | |
215 | { | |
216 | struct resource *mem; | |
217 | char name[7]; | |
218 | ||
219 | sprintf(name, "lvds.%u", lvds->index); | |
220 | ||
221 | mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); | |
90374b5c | 222 | lvds->mmio = devm_ioremap_resource(&pdev->dev, mem); |
c8ca9d6a WY |
223 | if (IS_ERR(lvds->mmio)) |
224 | return PTR_ERR(lvds->mmio); | |
90374b5c LP |
225 | |
226 | lvds->clock = devm_clk_get(&pdev->dev, name); | |
227 | if (IS_ERR(lvds->clock)) { | |
228 | dev_err(&pdev->dev, "failed to get clock for %s\n", name); | |
229 | return PTR_ERR(lvds->clock); | |
230 | } | |
231 | ||
232 | return 0; | |
233 | } | |
234 | ||
235 | int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu) | |
236 | { | |
237 | struct platform_device *pdev = to_platform_device(rcdu->dev); | |
238 | struct rcar_du_lvdsenc *lvds; | |
239 | unsigned int i; | |
240 | int ret; | |
241 | ||
242 | for (i = 0; i < rcdu->info->num_lvds; ++i) { | |
243 | lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL); | |
244 | if (lvds == NULL) { | |
245 | dev_err(&pdev->dev, "failed to allocate private data\n"); | |
246 | return -ENOMEM; | |
247 | } | |
248 | ||
249 | lvds->dev = rcdu; | |
250 | lvds->index = i; | |
251 | lvds->input = i ? RCAR_LVDS_INPUT_DU1 : RCAR_LVDS_INPUT_DU0; | |
44ef7ed5 | 252 | lvds->enabled = false; |
90374b5c LP |
253 | |
254 | ret = rcar_du_lvdsenc_get_resources(lvds, pdev); | |
255 | if (ret < 0) | |
256 | return ret; | |
257 | ||
258 | rcdu->lvds[i] = lvds; | |
259 | } | |
260 | ||
261 | return 0; | |
262 | } |