1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) STMicroelectronics SA 2014 |
4 | * Authors: Benjamin Gaignard <benjamin.gaignard@st.com> |
5 | * Fabien Dessenne <fabien.dessenne@st.com> |
6 | * Vincent Abriou <vincent.abriou@st.com> |
7 | * for STMicroelectronics. |
8 | */ |
9 | |
10 | #include <linux/module.h> |
11 | #include <linux/io.h> |
12 | #include <linux/notifier.h> |
13 | #include <linux/of_platform.h> |
14 | #include <linux/platform_device.h> |
15 | |
16 | #include <drm/drm_modes.h> |
17 | #include <drm/drm_print.h> |
18 | |
19 | #include "sti_drv.h" |
20 | #include "sti_vtg.h" |
21 | |
22 | #define VTG_MODE_MASTER 0 |
23 | |
24 | /* registers offset */ |
25 | #define VTG_MODE 0x0000 |
26 | #define VTG_CLKLN 0x0008 |
27 | #define VTG_HLFLN 0x000C |
28 | #define VTG_DRST_AUTOC 0x0010 |
29 | #define VTG_VID_TFO 0x0040 |
30 | #define VTG_VID_TFS 0x0044 |
31 | #define VTG_VID_BFO 0x0048 |
32 | #define VTG_VID_BFS 0x004C |
33 | |
34 | #define VTG_HOST_ITS 0x0078 |
35 | #define VTG_HOST_ITS_BCLR 0x007C |
36 | #define VTG_HOST_ITM_BCLR 0x0088 |
37 | #define VTG_HOST_ITM_BSET 0x008C |
38 | |
39 | #define VTG_H_HD_1 0x00C0 |
40 | #define VTG_TOP_V_VD_1 0x00C4 |
41 | #define VTG_BOT_V_VD_1 0x00C8 |
42 | #define VTG_TOP_V_HD_1 0x00CC |
43 | #define VTG_BOT_V_HD_1 0x00D0 |
44 | |
45 | #define VTG_H_HD_2 0x00E0 |
46 | #define VTG_TOP_V_VD_2 0x00E4 |
47 | #define VTG_BOT_V_VD_2 0x00E8 |
48 | #define VTG_TOP_V_HD_2 0x00EC |
49 | #define VTG_BOT_V_HD_2 0x00F0 |
50 | |
51 | #define VTG_H_HD_3 0x0100 |
52 | #define VTG_TOP_V_VD_3 0x0104 |
53 | #define VTG_BOT_V_VD_3 0x0108 |
54 | #define VTG_TOP_V_HD_3 0x010C |
55 | #define VTG_BOT_V_HD_3 0x0110 |
56 | |
57 | #define VTG_H_HD_4 0x0120 |
58 | #define VTG_TOP_V_VD_4 0x0124 |
59 | #define VTG_BOT_V_VD_4 0x0128 |
60 | #define VTG_TOP_V_HD_4 0x012c |
61 | #define VTG_BOT_V_HD_4 0x0130 |
62 | |
63 | #define VTG_IRQ_BOTTOM BIT(0) |
64 | #define VTG_IRQ_TOP BIT(1) |
65 | #define VTG_IRQ_MASK (VTG_IRQ_TOP | VTG_IRQ_BOTTOM) |
66 | |
67 | /* Delay introduced by the HDMI in nb of pixel */ |
68 | #define HDMI_DELAY (5) |
69 | |
70 | /* Delay introduced by the DVO in nb of pixel */ |
71 | #define DVO_DELAY (7) |
72 | |
73 | /* delay introduced by the Arbitrary Waveform Generator in nb of pixels */ |
74 | #define AWG_DELAY_HD (-9) |
75 | #define AWG_DELAY_ED (-8) |
76 | #define AWG_DELAY_SD (-7) |
77 | |
78 | /* |
79 | * STI VTG register offset structure |
80 | * |
81 | *@h_hd: stores the VTG_H_HD_x register offset |
82 | *@top_v_vd: stores the VTG_TOP_V_VD_x register offset |
83 | *@bot_v_vd: stores the VTG_BOT_V_VD_x register offset |
84 | *@top_v_hd: stores the VTG_TOP_V_HD_x register offset |
85 | *@bot_v_hd: stores the VTG_BOT_V_HD_x register offset |
86 | */ |
87 | struct sti_vtg_regs_offs { |
88 | u32 h_hd; |
89 | u32 top_v_vd; |
90 | u32 bot_v_vd; |
91 | u32 top_v_hd; |
92 | u32 bot_v_hd; |
93 | }; |
94 | |
95 | #define VTG_MAX_SYNC_OUTPUT 4 |
96 | static const struct sti_vtg_regs_offs vtg_regs_offs[VTG_MAX_SYNC_OUTPUT] = { |
97 | { VTG_H_HD_1, |
98 | VTG_TOP_V_VD_1, VTG_BOT_V_VD_1, VTG_TOP_V_HD_1, VTG_BOT_V_HD_1 }, |
99 | { VTG_H_HD_2, |
100 | VTG_TOP_V_VD_2, VTG_BOT_V_VD_2, VTG_TOP_V_HD_2, VTG_BOT_V_HD_2 }, |
101 | { VTG_H_HD_3, |
102 | VTG_TOP_V_VD_3, VTG_BOT_V_VD_3, VTG_TOP_V_HD_3, VTG_BOT_V_HD_3 }, |
103 | { VTG_H_HD_4, |
104 | VTG_TOP_V_VD_4, VTG_BOT_V_VD_4, VTG_TOP_V_HD_4, VTG_BOT_V_HD_4 } |
105 | }; |
106 | |
107 | /* |
108 | * STI VTG synchronisation parameters structure |
109 | * |
110 | *@hsync: sample number falling and rising edge |
111 | *@vsync_line_top: vertical top field line number falling and rising edge |
112 | *@vsync_line_bot: vertical bottom field line number falling and rising edge |
113 | *@vsync_off_top: vertical top field sample number rising and falling edge |
114 | *@vsync_off_bot: vertical bottom field sample number rising and falling edge |
115 | */ |
116 | struct sti_vtg_sync_params { |
117 | u32 hsync; |
118 | u32 vsync_line_top; |
119 | u32 vsync_line_bot; |
120 | u32 vsync_off_top; |
121 | u32 vsync_off_bot; |
122 | }; |
123 | |
124 | /* |
125 | * STI VTG structure |
126 | * |
127 | * @regs: register mapping |
128 | * @sync_params: synchronisation parameters used to generate timings |
129 | * @irq: VTG irq |
130 | * @irq_status: store the IRQ status value |
131 | * @notifier_list: notifier callback |
132 | * @crtc: the CRTC for vblank event |
133 | */ |
134 | struct sti_vtg { |
135 | void __iomem *regs; |
136 | struct sti_vtg_sync_params sync_params[VTG_MAX_SYNC_OUTPUT]; |
137 | int irq; |
138 | u32 irq_status; |
139 | struct raw_notifier_head notifier_list; |
140 | struct drm_crtc *crtc; |
141 | }; |
142 | |
143 | struct sti_vtg *of_vtg_find(struct device_node *np) |
144 | { |
145 | struct platform_device *pdev; |
146 | |
147 | pdev = of_find_device_by_node(np); |
148 | if (!pdev) |
149 | return NULL; |
150 | |
151 | return (struct sti_vtg *)platform_get_drvdata(pdev); |
152 | } |
153 | |
154 | static void vtg_reset(struct sti_vtg *vtg) |
155 | { |
156 | writel(val: 1, addr: vtg->regs + VTG_DRST_AUTOC); |
157 | } |
158 | |
159 | static void vtg_set_output_window(void __iomem *regs, |
160 | const struct drm_display_mode *mode) |
161 | { |
162 | u32 video_top_field_start; |
163 | u32 video_top_field_stop; |
164 | u32 video_bottom_field_start; |
165 | u32 video_bottom_field_stop; |
166 | u32 xstart = sti_vtg_get_pixel_number(mode: *mode, x: 0); |
167 | u32 ystart = sti_vtg_get_line_number(mode: *mode, y: 0); |
168 | u32 xstop = sti_vtg_get_pixel_number(mode: *mode, x: mode->hdisplay - 1); |
169 | u32 ystop = sti_vtg_get_line_number(mode: *mode, y: mode->vdisplay - 1); |
170 | |
171 | /* Set output window to fit the display mode selected */ |
172 | video_top_field_start = (ystart << 16) | xstart; |
173 | video_top_field_stop = (ystop << 16) | xstop; |
174 | |
175 | /* Only progressive supported for now */ |
176 | video_bottom_field_start = video_top_field_start; |
177 | video_bottom_field_stop = video_top_field_stop; |
178 | |
179 | writel(val: video_top_field_start, addr: regs + VTG_VID_TFO); |
180 | writel(val: video_top_field_stop, addr: regs + VTG_VID_TFS); |
181 | writel(val: video_bottom_field_start, addr: regs + VTG_VID_BFO); |
182 | writel(val: video_bottom_field_stop, addr: regs + VTG_VID_BFS); |
183 | } |
184 | |
185 | static void vtg_set_hsync_vsync_pos(struct sti_vtg_sync_params *sync, |
186 | int delay, |
187 | const struct drm_display_mode *mode) |
188 | { |
189 | long clocksperline, start, stop; |
190 | u32 risesync_top, fallsync_top; |
191 | u32 risesync_offs_top, fallsync_offs_top; |
192 | |
193 | clocksperline = mode->htotal; |
194 | |
195 | /* Get the hsync position */ |
196 | start = 0; |
197 | stop = mode->hsync_end - mode->hsync_start; |
198 | |
199 | start += delay; |
200 | stop += delay; |
201 | |
202 | if (start < 0) |
203 | start += clocksperline; |
204 | else if (start >= clocksperline) |
205 | start -= clocksperline; |
206 | |
207 | if (stop < 0) |
208 | stop += clocksperline; |
209 | else if (stop >= clocksperline) |
210 | stop -= clocksperline; |
211 | |
212 | sync->hsync = (stop << 16) | start; |
213 | |
214 | /* Get the vsync position */ |
215 | if (delay >= 0) { |
216 | risesync_top = 1; |
217 | fallsync_top = risesync_top; |
218 | fallsync_top += mode->vsync_end - mode->vsync_start; |
219 | |
220 | fallsync_offs_top = (u32)delay; |
221 | risesync_offs_top = (u32)delay; |
222 | } else { |
223 | risesync_top = mode->vtotal; |
224 | fallsync_top = mode->vsync_end - mode->vsync_start; |
225 | |
226 | fallsync_offs_top = clocksperline + delay; |
227 | risesync_offs_top = clocksperline + delay; |
228 | } |
229 | |
230 | sync->vsync_line_top = (fallsync_top << 16) | risesync_top; |
231 | sync->vsync_off_top = (fallsync_offs_top << 16) | risesync_offs_top; |
232 | |
233 | /* Only progressive supported for now */ |
234 | sync->vsync_line_bot = sync->vsync_line_top; |
235 | sync->vsync_off_bot = sync->vsync_off_top; |
236 | } |
237 | |
238 | static void vtg_set_mode(struct sti_vtg *vtg, |
239 | int type, |
240 | struct sti_vtg_sync_params *sync, |
241 | const struct drm_display_mode *mode) |
242 | { |
243 | unsigned int i; |
244 | |
245 | /* Set the number of clock cycles per line */ |
246 | writel(val: mode->htotal, addr: vtg->regs + VTG_CLKLN); |
247 | |
248 | /* Set Half Line Per Field (only progressive supported for now) */ |
249 | writel(val: mode->vtotal * 2, addr: vtg->regs + VTG_HLFLN); |
250 | |
251 | /* Program output window */ |
252 | vtg_set_output_window(regs: vtg->regs, mode); |
253 | |
254 | /* Set hsync and vsync position for HDMI */ |
255 | vtg_set_hsync_vsync_pos(sync: &sync[VTG_SYNC_ID_HDMI - 1], HDMI_DELAY, mode); |
256 | |
257 | /* Set hsync and vsync position for HD DCS */ |
258 | vtg_set_hsync_vsync_pos(sync: &sync[VTG_SYNC_ID_HDDCS - 1], delay: 0, mode); |
259 | |
260 | /* Set hsync and vsync position for HDF */ |
261 | vtg_set_hsync_vsync_pos(sync: &sync[VTG_SYNC_ID_HDF - 1], AWG_DELAY_HD, mode); |
262 | |
263 | /* Set hsync and vsync position for DVO */ |
264 | vtg_set_hsync_vsync_pos(sync: &sync[VTG_SYNC_ID_DVO - 1], DVO_DELAY, mode); |
265 | |
266 | /* Progam the syncs outputs */ |
267 | for (i = 0; i < VTG_MAX_SYNC_OUTPUT ; i++) { |
268 | writel(val: sync[i].hsync, |
269 | addr: vtg->regs + vtg_regs_offs[i].h_hd); |
270 | writel(val: sync[i].vsync_line_top, |
271 | addr: vtg->regs + vtg_regs_offs[i].top_v_vd); |
272 | writel(val: sync[i].vsync_line_bot, |
273 | addr: vtg->regs + vtg_regs_offs[i].bot_v_vd); |
274 | writel(val: sync[i].vsync_off_top, |
275 | addr: vtg->regs + vtg_regs_offs[i].top_v_hd); |
276 | writel(val: sync[i].vsync_off_bot, |
277 | addr: vtg->regs + vtg_regs_offs[i].bot_v_hd); |
278 | } |
279 | |
280 | /* mode */ |
281 | writel(val: type, addr: vtg->regs + VTG_MODE); |
282 | } |
283 | |
284 | static void vtg_enable_irq(struct sti_vtg *vtg) |
285 | { |
286 | /* clear interrupt status and mask */ |
287 | writel(val: 0xFFFF, addr: vtg->regs + VTG_HOST_ITS_BCLR); |
288 | writel(val: 0xFFFF, addr: vtg->regs + VTG_HOST_ITM_BCLR); |
289 | writel(VTG_IRQ_MASK, addr: vtg->regs + VTG_HOST_ITM_BSET); |
290 | } |
291 | |
292 | void sti_vtg_set_config(struct sti_vtg *vtg, |
293 | const struct drm_display_mode *mode) |
294 | { |
295 | /* write configuration */ |
296 | vtg_set_mode(vtg, VTG_MODE_MASTER, sync: vtg->sync_params, mode); |
297 | |
298 | vtg_reset(vtg); |
299 | |
300 | vtg_enable_irq(vtg); |
301 | } |
302 | |
303 | /** |
304 | * sti_vtg_get_line_number |
305 | * |
306 | * @mode: display mode to be used |
307 | * @y: line |
308 | * |
309 | * Return the line number according to the display mode taking |
310 | * into account the Sync and Back Porch information. |
311 | * Video frame line numbers start at 1, y starts at 0. |
312 | * In interlaced modes the start line is the field line number of the odd |
313 | * field, but y is still defined as a progressive frame. |
314 | */ |
315 | u32 sti_vtg_get_line_number(struct drm_display_mode mode, int y) |
316 | { |
317 | u32 start_line = mode.vtotal - mode.vsync_start + 1; |
318 | |
319 | if (mode.flags & DRM_MODE_FLAG_INTERLACE) |
320 | start_line *= 2; |
321 | |
322 | return start_line + y; |
323 | } |
324 | |
325 | /** |
326 | * sti_vtg_get_pixel_number |
327 | * |
328 | * @mode: display mode to be used |
329 | * @x: row |
330 | * |
331 | * Return the pixel number according to the display mode taking |
332 | * into account the Sync and Back Porch information. |
333 | * Pixels are counted from 0. |
334 | */ |
335 | u32 sti_vtg_get_pixel_number(struct drm_display_mode mode, int x) |
336 | { |
337 | return mode.htotal - mode.hsync_start + x; |
338 | } |
339 | |
340 | int sti_vtg_register_client(struct sti_vtg *vtg, struct notifier_block *nb, |
341 | struct drm_crtc *crtc) |
342 | { |
343 | vtg->crtc = crtc; |
344 | return raw_notifier_chain_register(nh: &vtg->notifier_list, nb); |
345 | } |
346 | |
347 | int sti_vtg_unregister_client(struct sti_vtg *vtg, struct notifier_block *nb) |
348 | { |
349 | return raw_notifier_chain_unregister(nh: &vtg->notifier_list, nb); |
350 | } |
351 | |
352 | static irqreturn_t vtg_irq_thread(int irq, void *arg) |
353 | { |
354 | struct sti_vtg *vtg = arg; |
355 | u32 event; |
356 | |
357 | event = (vtg->irq_status & VTG_IRQ_TOP) ? |
358 | VTG_TOP_FIELD_EVENT : VTG_BOTTOM_FIELD_EVENT; |
359 | |
360 | raw_notifier_call_chain(nh: &vtg->notifier_list, val: event, v: vtg->crtc); |
361 | |
362 | return IRQ_HANDLED; |
363 | } |
364 | |
365 | static irqreturn_t vtg_irq(int irq, void *arg) |
366 | { |
367 | struct sti_vtg *vtg = arg; |
368 | |
369 | vtg->irq_status = readl(addr: vtg->regs + VTG_HOST_ITS); |
370 | |
371 | writel(val: vtg->irq_status, addr: vtg->regs + VTG_HOST_ITS_BCLR); |
372 | |
373 | /* force sync bus write */ |
374 | readl(addr: vtg->regs + VTG_HOST_ITS); |
375 | |
376 | return IRQ_WAKE_THREAD; |
377 | } |
378 | |
379 | static int vtg_probe(struct platform_device *pdev) |
380 | { |
381 | struct device *dev = &pdev->dev; |
382 | struct sti_vtg *vtg; |
383 | struct resource *res; |
384 | int ret; |
385 | |
386 | vtg = devm_kzalloc(dev, size: sizeof(*vtg), GFP_KERNEL); |
387 | if (!vtg) |
388 | return -ENOMEM; |
389 | |
390 | /* Get Memory ressources */ |
391 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
392 | if (!res) { |
393 | DRM_ERROR("Get memory resource failed\n" ); |
394 | return -ENOMEM; |
395 | } |
396 | vtg->regs = devm_ioremap(dev, offset: res->start, size: resource_size(res)); |
397 | if (!vtg->regs) { |
398 | DRM_ERROR("failed to remap I/O memory\n" ); |
399 | return -ENOMEM; |
400 | } |
401 | |
402 | vtg->irq = platform_get_irq(pdev, 0); |
403 | if (vtg->irq < 0) { |
404 | DRM_ERROR("Failed to get VTG interrupt\n" ); |
405 | return vtg->irq; |
406 | } |
407 | |
408 | RAW_INIT_NOTIFIER_HEAD(&vtg->notifier_list); |
409 | |
410 | ret = devm_request_threaded_irq(dev, irq: vtg->irq, handler: vtg_irq, |
411 | thread_fn: vtg_irq_thread, IRQF_ONESHOT, |
412 | devname: dev_name(dev), dev_id: vtg); |
413 | if (ret < 0) { |
414 | DRM_ERROR("Failed to register VTG interrupt\n" ); |
415 | return ret; |
416 | } |
417 | |
418 | platform_set_drvdata(pdev, data: vtg); |
419 | |
420 | DRM_INFO("%s %s\n" , __func__, dev_name(dev)); |
421 | |
422 | return 0; |
423 | } |
424 | |
425 | static const struct of_device_id vtg_of_match[] = { |
426 | { .compatible = "st,vtg" , }, |
427 | { /* sentinel */ } |
428 | }; |
429 | MODULE_DEVICE_TABLE(of, vtg_of_match); |
430 | |
431 | struct platform_driver sti_vtg_driver = { |
432 | .driver = { |
433 | .name = "sti-vtg" , |
434 | .owner = THIS_MODULE, |
435 | .of_match_table = vtg_of_match, |
436 | }, |
437 | .probe = vtg_probe, |
438 | }; |
439 | |
440 | MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>" ); |
441 | MODULE_DESCRIPTION("STMicroelectronics SoC DRM driver" ); |
442 | MODULE_LICENSE("GPL" ); |
443 | |