]>
Commit | Line | Data |
---|---|---|
96f60e37 RK |
1 | /* |
2 | * Copyright (C) 2012 Russell King | |
3 | * Rewritten from the dovefb driver, and Armada510 manuals. | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License version 2 as | |
7 | * published by the Free Software Foundation. | |
8 | */ | |
9 | #include <linux/clk.h> | |
d8c96083 RK |
10 | #include <linux/component.h> |
11 | #include <linux/of_device.h> | |
12 | #include <linux/platform_device.h> | |
96f60e37 | 13 | #include <drm/drmP.h> |
de503ddf | 14 | #include <drm/drm_atomic.h> |
fcd70cd3 | 15 | #include <drm/drm_probe_helper.h> |
3cb9ae4f | 16 | #include <drm/drm_plane_helper.h> |
bcd21a47 | 17 | #include <drm/drm_atomic_helper.h> |
96f60e37 RK |
18 | #include "armada_crtc.h" |
19 | #include "armada_drm.h" | |
20 | #include "armada_fb.h" | |
21 | #include "armada_gem.h" | |
22 | #include "armada_hw.h" | |
d40af7b1 | 23 | #include "armada_plane.h" |
c8a220c6 | 24 | #include "armada_trace.h" |
96f60e37 | 25 | |
96f60e37 RK |
26 | /* |
27 | * A note about interlacing. Let's consider HDMI 1920x1080i. | |
28 | * The timing parameters we have from X are: | |
29 | * Hact HsyA HsyI Htot Vact VsyA VsyI Vtot | |
30 | * 1920 2448 2492 2640 1080 1084 1094 1125 | |
31 | * Which get translated to: | |
32 | * Hact HsyA HsyI Htot Vact VsyA VsyI Vtot | |
33 | * 1920 2448 2492 2640 540 542 547 562 | |
34 | * | |
35 | * This is how it is defined by CEA-861-D - line and pixel numbers are | |
36 | * referenced to the rising edge of VSYNC and HSYNC. Total clocks per | |
37 | * line: 2640. The odd frame, the first active line is at line 21, and | |
38 | * the even frame, the first active line is 584. | |
39 | * | |
40 | * LN: 560 561 562 563 567 568 569 | |
41 | * DE: ~~~|____________________________//__________________________ | |
42 | * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____ | |
43 | * VSYNC: _________________________|~~~~~~//~~~~~~~~~~~~~~~|__________ | |
44 | * 22 blanking lines. VSYNC at 1320 (referenced to the HSYNC rising edge). | |
45 | * | |
46 | * LN: 1123 1124 1125 1 5 6 7 | |
47 | * DE: ~~~|____________________________//__________________________ | |
48 | * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____ | |
49 | * VSYNC: ____________________|~~~~~~~~~~~//~~~~~~~~~~|_______________ | |
50 | * 23 blanking lines | |
51 | * | |
52 | * The Armada LCD Controller line and pixel numbers are, like X timings, | |
53 | * referenced to the top left of the active frame. | |
54 | * | |
55 | * So, translating these to our LCD controller: | |
56 | * Odd frame, 563 total lines, VSYNC at line 543-548, pixel 1128. | |
57 | * Even frame, 562 total lines, VSYNC at line 542-547, pixel 2448. | |
58 | * Note: Vsync front porch remains constant! | |
59 | * | |
60 | * if (odd_frame) { | |
61 | * vtotal = mode->crtc_vtotal + 1; | |
62 | * vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay + 1; | |
63 | * vhorizpos = mode->crtc_hsync_start - mode->crtc_htotal / 2 | |
64 | * } else { | |
65 | * vtotal = mode->crtc_vtotal; | |
66 | * vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay; | |
67 | * vhorizpos = mode->crtc_hsync_start; | |
68 | * } | |
69 | * vfrontporch = mode->crtc_vtotal - mode->crtc_vsync_end; | |
70 | * | |
71 | * So, we need to reprogram these registers on each vsync event: | |
72 | * LCD_SPU_V_PORCH, LCD_SPU_ADV_REG, LCD_SPUT_V_H_TOTAL | |
73 | * | |
74 | * Note: we do not use the frame done interrupts because these appear | |
75 | * to happen too early, and lead to jitter on the display (presumably | |
76 | * they occur at the end of the last active line, before the vsync back | |
77 | * porch, which we're reprogramming.) | |
78 | */ | |
79 | ||
80 | void | |
81 | armada_drm_crtc_update_regs(struct armada_crtc *dcrtc, struct armada_regs *regs) | |
82 | { | |
83 | while (regs->offset != ~0) { | |
84 | void __iomem *reg = dcrtc->base + regs->offset; | |
85 | uint32_t val; | |
86 | ||
87 | val = regs->mask; | |
88 | if (val != 0) | |
89 | val &= readl_relaxed(reg); | |
90 | writel_relaxed(val | regs->val, reg); | |
91 | ++regs; | |
92 | } | |
93 | } | |
94 | ||
a0f75d24 | 95 | static void armada_drm_crtc_update(struct armada_crtc *dcrtc, bool enable) |
96f60e37 RK |
96 | { |
97 | uint32_t dumb_ctrl; | |
98 | ||
99 | dumb_ctrl = dcrtc->cfg_dumb_ctrl; | |
100 | ||
a0f75d24 | 101 | if (enable) |
96f60e37 RK |
102 | dumb_ctrl |= CFG_DUMB_ENA; |
103 | ||
104 | /* | |
105 | * When the dumb interface isn't in DUMB24_RGB888_0 mode, it might | |
106 | * be using SPI or GPIO. If we set this to DUMB_BLANK, we will | |
107 | * force LCD_D[23:0] to output blank color, overriding the GPIO or | |
108 | * SPI usage. So leave it as-is unless in DUMB24_RGB888_0 mode. | |
109 | */ | |
a0f75d24 | 110 | if (!enable && (dumb_ctrl & DUMB_MASK) == DUMB24_RGB888_0) { |
96f60e37 RK |
111 | dumb_ctrl &= ~DUMB_MASK; |
112 | dumb_ctrl |= DUMB_BLANK; | |
113 | } | |
114 | ||
155b8290 RK |
115 | armada_updatel(dumb_ctrl, |
116 | ~(CFG_INV_CSYNC | CFG_INV_HSYNC | CFG_INV_VSYNC), | |
117 | dcrtc->base + LCD_SPU_DUMB_CTRL); | |
96f60e37 RK |
118 | } |
119 | ||
dbb4ca8a RK |
120 | static void armada_drm_crtc_queue_state_event(struct drm_crtc *crtc) |
121 | { | |
122 | struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); | |
123 | struct drm_pending_vblank_event *event; | |
124 | ||
125 | /* If we have an event, we need vblank events enabled */ | |
126 | event = xchg(&crtc->state->event, NULL); | |
127 | if (event) { | |
128 | WARN_ON(drm_crtc_vblank_get(crtc) != 0); | |
129 | dcrtc->event = event; | |
130 | } | |
131 | } | |
132 | ||
96f60e37 RK |
133 | /* The mode_config.mutex will be held for this call */ |
134 | static bool armada_drm_crtc_mode_fixup(struct drm_crtc *crtc, | |
135 | const struct drm_display_mode *mode, struct drm_display_mode *adj) | |
136 | { | |
96f60e37 RK |
137 | struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); |
138 | int ret; | |
139 | ||
140 | /* We can't do interlaced modes if we don't have the SPU_ADV_REG */ | |
42e62ba7 | 141 | if (!dcrtc->variant->has_spu_adv_reg && |
96f60e37 RK |
142 | adj->flags & DRM_MODE_FLAG_INTERLACE) |
143 | return false; | |
144 | ||
145 | /* Check whether the display mode is possible */ | |
42e62ba7 | 146 | ret = dcrtc->variant->compute_clock(dcrtc, adj, NULL); |
96f60e37 RK |
147 | if (ret) |
148 | return false; | |
149 | ||
150 | return true; | |
151 | } | |
152 | ||
5922a7d0 SG |
153 | /* These are locked by dev->vbl_lock */ |
154 | static void armada_drm_crtc_disable_irq(struct armada_crtc *dcrtc, u32 mask) | |
155 | { | |
156 | if (dcrtc->irq_ena & mask) { | |
157 | dcrtc->irq_ena &= ~mask; | |
158 | writel(dcrtc->irq_ena, dcrtc->base + LCD_SPU_IRQ_ENA); | |
159 | } | |
160 | } | |
161 | ||
162 | static void armada_drm_crtc_enable_irq(struct armada_crtc *dcrtc, u32 mask) | |
163 | { | |
164 | if ((dcrtc->irq_ena & mask) != mask) { | |
165 | dcrtc->irq_ena |= mask; | |
166 | writel(dcrtc->irq_ena, dcrtc->base + LCD_SPU_IRQ_ENA); | |
167 | if (readl_relaxed(dcrtc->base + LCD_SPU_IRQ_ISR) & mask) | |
168 | writel(0, dcrtc->base + LCD_SPU_IRQ_ISR); | |
169 | } | |
170 | } | |
171 | ||
e5d9ddfb | 172 | static void armada_drm_crtc_irq(struct armada_crtc *dcrtc, u32 stat) |
96f60e37 | 173 | { |
dbb4ca8a | 174 | struct drm_pending_vblank_event *event; |
96f60e37 RK |
175 | void __iomem *base = dcrtc->base; |
176 | ||
177 | if (stat & DMA_FF_UNDERFLOW) | |
178 | DRM_ERROR("video underflow on crtc %u\n", dcrtc->num); | |
179 | if (stat & GRA_FF_UNDERFLOW) | |
180 | DRM_ERROR("graphics underflow on crtc %u\n", dcrtc->num); | |
181 | ||
182 | if (stat & VSYNC_IRQ) | |
0ac28c57 | 183 | drm_crtc_handle_vblank(&dcrtc->crtc); |
96f60e37 | 184 | |
a3f6a18f | 185 | spin_lock(&dcrtc->irq_lock); |
96f60e37 RK |
186 | if (stat & GRA_FRAME_IRQ && dcrtc->interlaced) { |
187 | int i = stat & GRA_FRAME_IRQ0 ? 0 : 1; | |
188 | uint32_t val; | |
189 | ||
190 | writel_relaxed(dcrtc->v[i].spu_v_porch, base + LCD_SPU_V_PORCH); | |
191 | writel_relaxed(dcrtc->v[i].spu_v_h_total, | |
192 | base + LCD_SPUT_V_H_TOTAL); | |
193 | ||
194 | val = readl_relaxed(base + LCD_SPU_ADV_REG); | |
195 | val &= ~(ADV_VSYNC_L_OFF | ADV_VSYNC_H_OFF | ADV_VSYNCOFFEN); | |
196 | val |= dcrtc->v[i].spu_adv_reg; | |
662af0d8 | 197 | writel_relaxed(val, base + LCD_SPU_ADV_REG); |
96f60e37 | 198 | } |
662af0d8 | 199 | |
3cb13ac9 RK |
200 | if (stat & dcrtc->irq_ena & DUMB_FRAMEDONE) { |
201 | if (dcrtc->update_pending) { | |
202 | armada_drm_crtc_update_regs(dcrtc, dcrtc->regs); | |
203 | dcrtc->update_pending = false; | |
204 | } | |
205 | if (dcrtc->cursor_update) { | |
206 | writel_relaxed(dcrtc->cursor_hw_pos, | |
207 | base + LCD_SPU_HWC_OVSA_HPXL_VLN); | |
208 | writel_relaxed(dcrtc->cursor_hw_sz, | |
209 | base + LCD_SPU_HWC_HPXL_VLN); | |
210 | armada_updatel(CFG_HWC_ENA, | |
211 | CFG_HWC_ENA | CFG_HWC_1BITMOD | | |
212 | CFG_HWC_1BITENA, | |
213 | base + LCD_SPU_DMA_CTRL0); | |
214 | dcrtc->cursor_update = false; | |
215 | } | |
662af0d8 RK |
216 | armada_drm_crtc_disable_irq(dcrtc, DUMB_FRAMEDONE_ENA); |
217 | } | |
96f60e37 RK |
218 | spin_unlock(&dcrtc->irq_lock); |
219 | ||
3cb13ac9 | 220 | if (stat & VSYNC_IRQ && !dcrtc->update_pending) { |
dbb4ca8a RK |
221 | event = xchg(&dcrtc->event, NULL); |
222 | if (event) { | |
223 | spin_lock(&dcrtc->crtc.dev->event_lock); | |
224 | drm_crtc_send_vblank_event(&dcrtc->crtc, event); | |
225 | spin_unlock(&dcrtc->crtc.dev->event_lock); | |
226 | drm_crtc_vblank_put(&dcrtc->crtc); | |
227 | } | |
228 | } | |
96f60e37 RK |
229 | } |
230 | ||
e5d9ddfb RK |
231 | static irqreturn_t armada_drm_irq(int irq, void *arg) |
232 | { | |
233 | struct armada_crtc *dcrtc = arg; | |
234 | u32 v, stat = readl_relaxed(dcrtc->base + LCD_SPU_IRQ_ISR); | |
235 | ||
236 | /* | |
92298c1c RK |
237 | * Reading the ISR appears to clear bits provided CLEAN_SPU_IRQ_ISR |
238 | * is set. Writing has some other effect to acknowledge the IRQ - | |
239 | * without this, we only get a single IRQ. | |
e5d9ddfb RK |
240 | */ |
241 | writel_relaxed(0, dcrtc->base + LCD_SPU_IRQ_ISR); | |
242 | ||
c8a220c6 RK |
243 | trace_armada_drm_irq(&dcrtc->crtc, stat); |
244 | ||
e5d9ddfb RK |
245 | /* Mask out those interrupts we haven't enabled */ |
246 | v = stat & dcrtc->irq_ena; | |
247 | ||
248 | if (v & (VSYNC_IRQ|GRA_FRAME_IRQ|DUMB_FRAMEDONE)) { | |
249 | armada_drm_crtc_irq(dcrtc, stat); | |
250 | return IRQ_HANDLED; | |
251 | } | |
252 | return IRQ_NONE; | |
253 | } | |
254 | ||
96f60e37 | 255 | /* The mode_config.mutex will be held for this call */ |
c36045e1 | 256 | static void armada_drm_crtc_mode_set_nofb(struct drm_crtc *crtc) |
96f60e37 | 257 | { |
c36045e1 | 258 | struct drm_display_mode *adj = &crtc->state->adjusted_mode; |
96f60e37 RK |
259 | struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); |
260 | struct armada_regs regs[17]; | |
261 | uint32_t lm, rm, tm, bm, val, sclk; | |
262 | unsigned long flags; | |
263 | unsigned i; | |
c36045e1 | 264 | bool interlaced = !!(adj->flags & DRM_MODE_FLAG_INTERLACE); |
96f60e37 | 265 | |
37af35c7 | 266 | i = 0; |
96f60e37 RK |
267 | rm = adj->crtc_hsync_start - adj->crtc_hdisplay; |
268 | lm = adj->crtc_htotal - adj->crtc_hsync_end; | |
269 | bm = adj->crtc_vsync_start - adj->crtc_vdisplay; | |
270 | tm = adj->crtc_vtotal - adj->crtc_vsync_end; | |
271 | ||
a61c3922 | 272 | DRM_DEBUG_KMS("[CRTC:%d:%s] mode " DRM_MODE_FMT "\n", |
0ed833ba | 273 | crtc->base.id, crtc->name, DRM_MODE_ARG(adj)); |
a61c3922 | 274 | DRM_DEBUG_KMS("lm %d rm %d tm %d bm %d\n", lm, rm, tm, bm); |
96f60e37 | 275 | |
96f60e37 | 276 | /* Now compute the divider for real */ |
42e62ba7 | 277 | dcrtc->variant->compute_clock(dcrtc, adj, &sclk); |
96f60e37 | 278 | |
96f60e37 RK |
279 | armada_reg_queue_set(regs, i, sclk, LCD_CFG_SCLK_DIV); |
280 | ||
281 | if (interlaced ^ dcrtc->interlaced) { | |
282 | if (adj->flags & DRM_MODE_FLAG_INTERLACE) | |
accbaf6e | 283 | drm_crtc_vblank_get(&dcrtc->crtc); |
96f60e37 | 284 | else |
accbaf6e | 285 | drm_crtc_vblank_put(&dcrtc->crtc); |
96f60e37 RK |
286 | dcrtc->interlaced = interlaced; |
287 | } | |
288 | ||
289 | spin_lock_irqsave(&dcrtc->irq_lock, flags); | |
290 | ||
291 | /* Even interlaced/progressive frame */ | |
292 | dcrtc->v[1].spu_v_h_total = adj->crtc_vtotal << 16 | | |
293 | adj->crtc_htotal; | |
294 | dcrtc->v[1].spu_v_porch = tm << 16 | bm; | |
295 | val = adj->crtc_hsync_start; | |
4e4b3563 | 296 | dcrtc->v[1].spu_adv_reg = val << 20 | val | ADV_VSYNCOFFEN; |
96f60e37 RK |
297 | |
298 | if (interlaced) { | |
299 | /* Odd interlaced frame */ | |
4e4b3563 RK |
300 | val -= adj->crtc_htotal / 2; |
301 | dcrtc->v[0].spu_adv_reg = val << 20 | val | ADV_VSYNCOFFEN; | |
96f60e37 RK |
302 | dcrtc->v[0].spu_v_h_total = dcrtc->v[1].spu_v_h_total + |
303 | (1 << 16); | |
304 | dcrtc->v[0].spu_v_porch = dcrtc->v[1].spu_v_porch + 1; | |
96f60e37 RK |
305 | } else { |
306 | dcrtc->v[0] = dcrtc->v[1]; | |
307 | } | |
308 | ||
309 | val = adj->crtc_vdisplay << 16 | adj->crtc_hdisplay; | |
310 | ||
311 | armada_reg_queue_set(regs, i, val, LCD_SPU_V_H_ACTIVE); | |
96f60e37 RK |
312 | armada_reg_queue_set(regs, i, (lm << 16) | rm, LCD_SPU_H_PORCH); |
313 | armada_reg_queue_set(regs, i, dcrtc->v[0].spu_v_porch, LCD_SPU_V_PORCH); | |
314 | armada_reg_queue_set(regs, i, dcrtc->v[0].spu_v_h_total, | |
315 | LCD_SPUT_V_H_TOTAL); | |
316 | ||
4e4b3563 | 317 | if (dcrtc->variant->has_spu_adv_reg) |
96f60e37 RK |
318 | armada_reg_queue_mod(regs, i, dcrtc->v[0].spu_adv_reg, |
319 | ADV_VSYNC_L_OFF | ADV_VSYNC_H_OFF | | |
320 | ADV_VSYNCOFFEN, LCD_SPU_ADV_REG); | |
321 | ||
96f60e37 RK |
322 | val = adj->flags & DRM_MODE_FLAG_NVSYNC ? CFG_VSYNC_INV : 0; |
323 | armada_reg_queue_mod(regs, i, val, CFG_VSYNC_INV, LCD_SPU_DMA_CTRL1); | |
155b8290 RK |
324 | |
325 | /* | |
326 | * The documentation doesn't indicate what the normal state of | |
327 | * the sync signals are. Sebastian Hesselbart kindly probed | |
328 | * these signals on his board to determine their state. | |
329 | * | |
330 | * The non-inverted state of the sync signals is active high. | |
331 | * Setting these bits makes the appropriate signal active low. | |
332 | */ | |
333 | val = 0; | |
334 | if (adj->flags & DRM_MODE_FLAG_NCSYNC) | |
335 | val |= CFG_INV_CSYNC; | |
336 | if (adj->flags & DRM_MODE_FLAG_NHSYNC) | |
337 | val |= CFG_INV_HSYNC; | |
338 | if (adj->flags & DRM_MODE_FLAG_NVSYNC) | |
339 | val |= CFG_INV_VSYNC; | |
340 | armada_reg_queue_mod(regs, i, val, CFG_INV_CSYNC | CFG_INV_HSYNC | | |
341 | CFG_INV_VSYNC, LCD_SPU_DUMB_CTRL); | |
96f60e37 RK |
342 | armada_reg_queue_end(regs, i); |
343 | ||
344 | armada_drm_crtc_update_regs(dcrtc, regs); | |
345 | spin_unlock_irqrestore(&dcrtc->irq_lock, flags); | |
96f60e37 RK |
346 | } |
347 | ||
c36045e1 RK |
348 | static void armada_drm_crtc_atomic_begin(struct drm_crtc *crtc, |
349 | struct drm_crtc_state *old_crtc_state) | |
350 | { | |
351 | struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); | |
c36045e1 RK |
352 | |
353 | DRM_DEBUG_KMS("[CRTC:%d:%s]\n", crtc->base.id, crtc->name); | |
354 | ||
c36045e1 RK |
355 | dcrtc->regs_idx = 0; |
356 | dcrtc->regs = dcrtc->atomic_regs; | |
357 | } | |
358 | ||
359 | static void armada_drm_crtc_atomic_flush(struct drm_crtc *crtc, | |
360 | struct drm_crtc_state *old_crtc_state) | |
361 | { | |
362 | struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); | |
c36045e1 RK |
363 | |
364 | DRM_DEBUG_KMS("[CRTC:%d:%s]\n", crtc->base.id, crtc->name); | |
365 | ||
366 | armada_reg_queue_end(dcrtc->regs, dcrtc->regs_idx); | |
367 | ||
dbb4ca8a RK |
368 | /* |
369 | * If we aren't doing a full modeset, then we need to queue | |
370 | * the event here. | |
371 | */ | |
3cb13ac9 RK |
372 | if (!drm_atomic_crtc_needs_modeset(crtc->state)) { |
373 | dcrtc->update_pending = true; | |
dbb4ca8a | 374 | armada_drm_crtc_queue_state_event(crtc); |
3cb13ac9 RK |
375 | spin_lock_irq(&dcrtc->irq_lock); |
376 | armada_drm_crtc_enable_irq(dcrtc, DUMB_FRAMEDONE_ENA); | |
377 | spin_unlock_irq(&dcrtc->irq_lock); | |
378 | } else { | |
379 | spin_lock_irq(&dcrtc->irq_lock); | |
380 | armada_drm_crtc_update_regs(dcrtc, dcrtc->regs); | |
381 | spin_unlock_irq(&dcrtc->irq_lock); | |
382 | } | |
c36045e1 RK |
383 | } |
384 | ||
34e25ed6 RK |
385 | static void armada_drm_crtc_atomic_disable(struct drm_crtc *crtc, |
386 | struct drm_crtc_state *old_state) | |
387 | { | |
388 | struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); | |
389 | struct drm_pending_vblank_event *event; | |
34e25ed6 RK |
390 | |
391 | DRM_DEBUG_KMS("[CRTC:%d:%s]\n", crtc->base.id, crtc->name); | |
392 | ||
34e25ed6 RK |
393 | drm_crtc_vblank_off(crtc); |
394 | armada_drm_crtc_update(dcrtc, false); | |
395 | ||
396 | if (!crtc->state->active) { | |
397 | /* | |
398 | * This modeset will be leaving the CRTC disabled, so | |
399 | * call the backend to disable upstream clocks etc. | |
400 | */ | |
401 | if (dcrtc->variant->disable) | |
402 | dcrtc->variant->disable(dcrtc); | |
403 | ||
404 | /* | |
405 | * We will not receive any further vblank events. | |
406 | * Send the flip_done event manually. | |
407 | */ | |
408 | event = crtc->state->event; | |
409 | crtc->state->event = NULL; | |
410 | if (event) { | |
411 | spin_lock_irq(&crtc->dev->event_lock); | |
412 | drm_crtc_send_vblank_event(crtc, event); | |
413 | spin_unlock_irq(&crtc->dev->event_lock); | |
414 | } | |
415 | } | |
416 | } | |
417 | ||
418 | static void armada_drm_crtc_atomic_enable(struct drm_crtc *crtc, | |
419 | struct drm_crtc_state *old_state) | |
420 | { | |
421 | struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); | |
422 | ||
423 | DRM_DEBUG_KMS("[CRTC:%d:%s]\n", crtc->base.id, crtc->name); | |
424 | ||
34e25ed6 RK |
425 | if (!old_state->active) { |
426 | /* | |
427 | * This modeset is enabling the CRTC after it having | |
428 | * been disabled. Reverse the call to ->disable in | |
429 | * the atomic_disable(). | |
430 | */ | |
431 | if (dcrtc->variant->enable) | |
432 | dcrtc->variant->enable(dcrtc, &crtc->state->adjusted_mode); | |
433 | } | |
434 | armada_drm_crtc_update(dcrtc, true); | |
435 | drm_crtc_vblank_on(crtc); | |
436 | ||
437 | armada_drm_crtc_queue_state_event(crtc); | |
438 | } | |
439 | ||
96f60e37 | 440 | static const struct drm_crtc_helper_funcs armada_crtc_helper_funcs = { |
96f60e37 | 441 | .mode_fixup = armada_drm_crtc_mode_fixup, |
c36045e1 | 442 | .mode_set_nofb = armada_drm_crtc_mode_set_nofb, |
c36045e1 RK |
443 | .atomic_begin = armada_drm_crtc_atomic_begin, |
444 | .atomic_flush = armada_drm_crtc_atomic_flush, | |
34e25ed6 RK |
445 | .atomic_disable = armada_drm_crtc_atomic_disable, |
446 | .atomic_enable = armada_drm_crtc_atomic_enable, | |
96f60e37 RK |
447 | }; |
448 | ||
662af0d8 RK |
449 | static void armada_load_cursor_argb(void __iomem *base, uint32_t *pix, |
450 | unsigned stride, unsigned width, unsigned height) | |
451 | { | |
452 | uint32_t addr; | |
453 | unsigned y; | |
454 | ||
455 | addr = SRAM_HWC32_RAM1; | |
456 | for (y = 0; y < height; y++) { | |
457 | uint32_t *p = &pix[y * stride]; | |
458 | unsigned x; | |
459 | ||
460 | for (x = 0; x < width; x++, p++) { | |
461 | uint32_t val = *p; | |
462 | ||
463 | val = (val & 0xff00ff00) | | |
464 | (val & 0x000000ff) << 16 | | |
465 | (val & 0x00ff0000) >> 16; | |
466 | ||
467 | writel_relaxed(val, | |
468 | base + LCD_SPU_SRAM_WRDAT); | |
469 | writel_relaxed(addr | SRAM_WRITE, | |
470 | base + LCD_SPU_SRAM_CTRL); | |
c39b0695 | 471 | readl_relaxed(base + LCD_SPU_HWC_OVSA_HPXL_VLN); |
662af0d8 RK |
472 | addr += 1; |
473 | if ((addr & 0x00ff) == 0) | |
474 | addr += 0xf00; | |
475 | if ((addr & 0x30ff) == 0) | |
476 | addr = SRAM_HWC32_RAM2; | |
477 | } | |
478 | } | |
479 | } | |
480 | ||
481 | static void armada_drm_crtc_cursor_tran(void __iomem *base) | |
482 | { | |
483 | unsigned addr; | |
484 | ||
485 | for (addr = 0; addr < 256; addr++) { | |
486 | /* write the default value */ | |
487 | writel_relaxed(0x55555555, base + LCD_SPU_SRAM_WRDAT); | |
488 | writel_relaxed(addr | SRAM_WRITE | SRAM_HWC32_TRAN, | |
489 | base + LCD_SPU_SRAM_CTRL); | |
490 | } | |
491 | } | |
492 | ||
493 | static int armada_drm_crtc_cursor_update(struct armada_crtc *dcrtc, bool reload) | |
494 | { | |
495 | uint32_t xoff, xscr, w = dcrtc->cursor_w, s; | |
496 | uint32_t yoff, yscr, h = dcrtc->cursor_h; | |
497 | uint32_t para1; | |
498 | ||
499 | /* | |
500 | * Calculate the visible width and height of the cursor, | |
501 | * screen position, and the position in the cursor bitmap. | |
502 | */ | |
503 | if (dcrtc->cursor_x < 0) { | |
504 | xoff = -dcrtc->cursor_x; | |
505 | xscr = 0; | |
506 | w -= min(xoff, w); | |
507 | } else if (dcrtc->cursor_x + w > dcrtc->crtc.mode.hdisplay) { | |
508 | xoff = 0; | |
509 | xscr = dcrtc->cursor_x; | |
510 | w = max_t(int, dcrtc->crtc.mode.hdisplay - dcrtc->cursor_x, 0); | |
511 | } else { | |
512 | xoff = 0; | |
513 | xscr = dcrtc->cursor_x; | |
514 | } | |
515 | ||
516 | if (dcrtc->cursor_y < 0) { | |
517 | yoff = -dcrtc->cursor_y; | |
518 | yscr = 0; | |
519 | h -= min(yoff, h); | |
520 | } else if (dcrtc->cursor_y + h > dcrtc->crtc.mode.vdisplay) { | |
521 | yoff = 0; | |
522 | yscr = dcrtc->cursor_y; | |
523 | h = max_t(int, dcrtc->crtc.mode.vdisplay - dcrtc->cursor_y, 0); | |
524 | } else { | |
525 | yoff = 0; | |
526 | yscr = dcrtc->cursor_y; | |
527 | } | |
528 | ||
529 | /* On interlaced modes, the vertical cursor size must be halved */ | |
530 | s = dcrtc->cursor_w; | |
531 | if (dcrtc->interlaced) { | |
532 | s *= 2; | |
533 | yscr /= 2; | |
534 | h /= 2; | |
535 | } | |
536 | ||
537 | if (!dcrtc->cursor_obj || !h || !w) { | |
538 | spin_lock_irq(&dcrtc->irq_lock); | |
662af0d8 RK |
539 | dcrtc->cursor_update = false; |
540 | armada_updatel(0, CFG_HWC_ENA, dcrtc->base + LCD_SPU_DMA_CTRL0); | |
541 | spin_unlock_irq(&dcrtc->irq_lock); | |
542 | return 0; | |
543 | } | |
544 | ||
214612f9 | 545 | spin_lock_irq(&dcrtc->irq_lock); |
662af0d8 RK |
546 | para1 = readl_relaxed(dcrtc->base + LCD_SPU_SRAM_PARA1); |
547 | armada_updatel(CFG_CSB_256x32, CFG_CSB_256x32 | CFG_PDWN256x32, | |
548 | dcrtc->base + LCD_SPU_SRAM_PARA1); | |
214612f9 | 549 | spin_unlock_irq(&dcrtc->irq_lock); |
662af0d8 RK |
550 | |
551 | /* | |
552 | * Initialize the transparency if the SRAM was powered down. | |
553 | * We must also reload the cursor data as well. | |
554 | */ | |
555 | if (!(para1 & CFG_CSB_256x32)) { | |
556 | armada_drm_crtc_cursor_tran(dcrtc->base); | |
557 | reload = true; | |
558 | } | |
559 | ||
560 | if (dcrtc->cursor_hw_sz != (h << 16 | w)) { | |
561 | spin_lock_irq(&dcrtc->irq_lock); | |
662af0d8 RK |
562 | dcrtc->cursor_update = false; |
563 | armada_updatel(0, CFG_HWC_ENA, dcrtc->base + LCD_SPU_DMA_CTRL0); | |
564 | spin_unlock_irq(&dcrtc->irq_lock); | |
565 | reload = true; | |
566 | } | |
567 | if (reload) { | |
568 | struct armada_gem_object *obj = dcrtc->cursor_obj; | |
569 | uint32_t *pix; | |
570 | /* Set the top-left corner of the cursor image */ | |
571 | pix = obj->addr; | |
572 | pix += yoff * s + xoff; | |
573 | armada_load_cursor_argb(dcrtc->base, pix, s, w, h); | |
574 | } | |
575 | ||
576 | /* Reload the cursor position, size and enable in the IRQ handler */ | |
577 | spin_lock_irq(&dcrtc->irq_lock); | |
578 | dcrtc->cursor_hw_pos = yscr << 16 | xscr; | |
579 | dcrtc->cursor_hw_sz = h << 16 | w; | |
580 | dcrtc->cursor_update = true; | |
581 | armada_drm_crtc_enable_irq(dcrtc, DUMB_FRAMEDONE_ENA); | |
582 | spin_unlock_irq(&dcrtc->irq_lock); | |
583 | ||
584 | return 0; | |
585 | } | |
586 | ||
587 | static void cursor_update(void *data) | |
588 | { | |
589 | armada_drm_crtc_cursor_update(data, true); | |
590 | } | |
591 | ||
592 | static int armada_drm_crtc_cursor_set(struct drm_crtc *crtc, | |
593 | struct drm_file *file, uint32_t handle, uint32_t w, uint32_t h) | |
594 | { | |
662af0d8 | 595 | struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); |
662af0d8 RK |
596 | struct armada_gem_object *obj = NULL; |
597 | int ret; | |
598 | ||
599 | /* If no cursor support, replicate drm's return value */ | |
42e62ba7 | 600 | if (!dcrtc->variant->has_spu_adv_reg) |
662af0d8 RK |
601 | return -ENXIO; |
602 | ||
603 | if (handle && w > 0 && h > 0) { | |
604 | /* maximum size is 64x32 or 32x64 */ | |
605 | if (w > 64 || h > 64 || (w > 32 && h > 32)) | |
606 | return -ENOMEM; | |
607 | ||
a8ad0bd8 | 608 | obj = armada_gem_object_lookup(file, handle); |
662af0d8 RK |
609 | if (!obj) |
610 | return -ENOENT; | |
611 | ||
612 | /* Must be a kernel-mapped object */ | |
613 | if (!obj->addr) { | |
4c3cf375 | 614 | drm_gem_object_put_unlocked(&obj->obj); |
662af0d8 RK |
615 | return -EINVAL; |
616 | } | |
617 | ||
618 | if (obj->obj.size < w * h * 4) { | |
619 | DRM_ERROR("buffer is too small\n"); | |
4c3cf375 | 620 | drm_gem_object_put_unlocked(&obj->obj); |
662af0d8 RK |
621 | return -ENOMEM; |
622 | } | |
623 | } | |
624 | ||
662af0d8 RK |
625 | if (dcrtc->cursor_obj) { |
626 | dcrtc->cursor_obj->update = NULL; | |
627 | dcrtc->cursor_obj->update_data = NULL; | |
4c3cf375 | 628 | drm_gem_object_put_unlocked(&dcrtc->cursor_obj->obj); |
662af0d8 RK |
629 | } |
630 | dcrtc->cursor_obj = obj; | |
631 | dcrtc->cursor_w = w; | |
632 | dcrtc->cursor_h = h; | |
633 | ret = armada_drm_crtc_cursor_update(dcrtc, true); | |
634 | if (obj) { | |
635 | obj->update_data = dcrtc; | |
636 | obj->update = cursor_update; | |
637 | } | |
662af0d8 RK |
638 | |
639 | return ret; | |
640 | } | |
641 | ||
642 | static int armada_drm_crtc_cursor_move(struct drm_crtc *crtc, int x, int y) | |
643 | { | |
662af0d8 | 644 | struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); |
662af0d8 RK |
645 | int ret; |
646 | ||
647 | /* If no cursor support, replicate drm's return value */ | |
42e62ba7 | 648 | if (!dcrtc->variant->has_spu_adv_reg) |
662af0d8 RK |
649 | return -EFAULT; |
650 | ||
662af0d8 RK |
651 | dcrtc->cursor_x = x; |
652 | dcrtc->cursor_y = y; | |
653 | ret = armada_drm_crtc_cursor_update(dcrtc, false); | |
662af0d8 RK |
654 | |
655 | return ret; | |
656 | } | |
657 | ||
96f60e37 RK |
658 | static void armada_drm_crtc_destroy(struct drm_crtc *crtc) |
659 | { | |
660 | struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); | |
661 | struct armada_private *priv = crtc->dev->dev_private; | |
662 | ||
662af0d8 | 663 | if (dcrtc->cursor_obj) |
4c3cf375 | 664 | drm_gem_object_put_unlocked(&dcrtc->cursor_obj->obj); |
662af0d8 | 665 | |
96f60e37 RK |
666 | priv->dcrtc[dcrtc->num] = NULL; |
667 | drm_crtc_cleanup(&dcrtc->crtc); | |
668 | ||
a0fbb35e RK |
669 | if (dcrtc->variant->disable) |
670 | dcrtc->variant->disable(dcrtc); | |
96f60e37 | 671 | |
e5d9ddfb RK |
672 | writel_relaxed(0, dcrtc->base + LCD_SPU_IRQ_ENA); |
673 | ||
9611cb93 RK |
674 | of_node_put(dcrtc->crtc.port); |
675 | ||
96f60e37 RK |
676 | kfree(dcrtc); |
677 | } | |
678 | ||
5922a7d0 SG |
679 | /* These are called under the vbl_lock. */ |
680 | static int armada_drm_crtc_enable_vblank(struct drm_crtc *crtc) | |
681 | { | |
682 | struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); | |
92298c1c | 683 | unsigned long flags; |
5922a7d0 | 684 | |
92298c1c | 685 | spin_lock_irqsave(&dcrtc->irq_lock, flags); |
5922a7d0 | 686 | armada_drm_crtc_enable_irq(dcrtc, VSYNC_IRQ_ENA); |
92298c1c | 687 | spin_unlock_irqrestore(&dcrtc->irq_lock, flags); |
5922a7d0 SG |
688 | return 0; |
689 | } | |
690 | ||
691 | static void armada_drm_crtc_disable_vblank(struct drm_crtc *crtc) | |
692 | { | |
693 | struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); | |
92298c1c | 694 | unsigned long flags; |
5922a7d0 | 695 | |
92298c1c | 696 | spin_lock_irqsave(&dcrtc->irq_lock, flags); |
5922a7d0 | 697 | armada_drm_crtc_disable_irq(dcrtc, VSYNC_IRQ_ENA); |
92298c1c | 698 | spin_unlock_irqrestore(&dcrtc->irq_lock, flags); |
5922a7d0 SG |
699 | } |
700 | ||
a02fb90a | 701 | static const struct drm_crtc_funcs armada_crtc_funcs = { |
c36045e1 | 702 | .reset = drm_atomic_helper_crtc_reset, |
662af0d8 RK |
703 | .cursor_set = armada_drm_crtc_cursor_set, |
704 | .cursor_move = armada_drm_crtc_cursor_move, | |
96f60e37 | 705 | .destroy = armada_drm_crtc_destroy, |
6d2f864f | 706 | .set_config = drm_atomic_helper_set_config, |
13c94d53 | 707 | .page_flip = drm_atomic_helper_page_flip, |
c36045e1 RK |
708 | .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, |
709 | .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, | |
5922a7d0 SG |
710 | .enable_vblank = armada_drm_crtc_enable_vblank, |
711 | .disable_vblank = armada_drm_crtc_disable_vblank, | |
96f60e37 RK |
712 | }; |
713 | ||
0fb2970b | 714 | static int armada_drm_crtc_create(struct drm_device *drm, struct device *dev, |
9611cb93 RK |
715 | struct resource *res, int irq, const struct armada_variant *variant, |
716 | struct device_node *port) | |
96f60e37 | 717 | { |
d8c96083 | 718 | struct armada_private *priv = drm->dev_private; |
96f60e37 | 719 | struct armada_crtc *dcrtc; |
82c702cb | 720 | struct drm_plane *primary; |
96f60e37 RK |
721 | void __iomem *base; |
722 | int ret; | |
723 | ||
a7d7a143 | 724 | base = devm_ioremap_resource(dev, res); |
c9d53c0f JH |
725 | if (IS_ERR(base)) |
726 | return PTR_ERR(base); | |
96f60e37 RK |
727 | |
728 | dcrtc = kzalloc(sizeof(*dcrtc), GFP_KERNEL); | |
729 | if (!dcrtc) { | |
730 | DRM_ERROR("failed to allocate Armada crtc\n"); | |
731 | return -ENOMEM; | |
732 | } | |
733 | ||
d8c96083 RK |
734 | if (dev != drm->dev) |
735 | dev_set_drvdata(dev, dcrtc); | |
736 | ||
42e62ba7 | 737 | dcrtc->variant = variant; |
96f60e37 | 738 | dcrtc->base = base; |
d8c96083 | 739 | dcrtc->num = drm->mode_config.num_crtc; |
96f60e37 | 740 | dcrtc->clk = ERR_PTR(-EINVAL); |
96f60e37 RK |
741 | dcrtc->cfg_dumb_ctrl = DUMB24_RGB888_0; |
742 | dcrtc->spu_iopad_ctrl = CFG_VSCALE_LN_EN | CFG_IOPAD_DUMB24; | |
743 | spin_lock_init(&dcrtc->irq_lock); | |
744 | dcrtc->irq_ena = CLEAN_SPU_IRQ_ISR; | |
96f60e37 RK |
745 | |
746 | /* Initialize some registers which we don't otherwise set */ | |
747 | writel_relaxed(0x00000001, dcrtc->base + LCD_CFG_SCLK_DIV); | |
748 | writel_relaxed(0x00000000, dcrtc->base + LCD_SPU_BLANKCOLOR); | |
749 | writel_relaxed(dcrtc->spu_iopad_ctrl, | |
750 | dcrtc->base + LCD_SPU_IOPAD_CONTROL); | |
751 | writel_relaxed(0x00000000, dcrtc->base + LCD_SPU_SRAM_PARA0); | |
752 | writel_relaxed(CFG_PDWN256x32 | CFG_PDWN256x24 | CFG_PDWN256x8 | | |
753 | CFG_PDWN32x32 | CFG_PDWN16x66 | CFG_PDWN32x66 | | |
754 | CFG_PDWN64x66, dcrtc->base + LCD_SPU_SRAM_PARA1); | |
755 | writel_relaxed(0x2032ff81, dcrtc->base + LCD_SPU_DMA_CTRL1); | |
e5d9ddfb | 756 | writel_relaxed(dcrtc->irq_ena, dcrtc->base + LCD_SPU_IRQ_ENA); |
92298c1c | 757 | readl_relaxed(dcrtc->base + LCD_SPU_IRQ_ISR); |
e5d9ddfb RK |
758 | writel_relaxed(0, dcrtc->base + LCD_SPU_IRQ_ISR); |
759 | ||
760 | ret = devm_request_irq(dev, irq, armada_drm_irq, 0, "armada_drm_crtc", | |
761 | dcrtc); | |
33cd3c07 RK |
762 | if (ret < 0) |
763 | goto err_crtc; | |
96f60e37 | 764 | |
42e62ba7 | 765 | if (dcrtc->variant->init) { |
d8c96083 | 766 | ret = dcrtc->variant->init(dcrtc, dev); |
33cd3c07 RK |
767 | if (ret) |
768 | goto err_crtc; | |
96f60e37 RK |
769 | } |
770 | ||
771 | /* Ensure AXI pipeline is enabled */ | |
772 | armada_updatel(CFG_ARBFAST_ENA, 0, dcrtc->base + LCD_SPU_DMA_CTRL0); | |
773 | ||
774 | priv->dcrtc[dcrtc->num] = dcrtc; | |
775 | ||
9611cb93 | 776 | dcrtc->crtc.port = port; |
1c914cec | 777 | |
de32301b | 778 | primary = kzalloc(sizeof(*primary), GFP_KERNEL); |
33cd3c07 RK |
779 | if (!primary) { |
780 | ret = -ENOMEM; | |
781 | goto err_crtc; | |
782 | } | |
1c914cec | 783 | |
d40af7b1 | 784 | ret = armada_drm_primary_plane_init(drm, primary); |
de32301b RK |
785 | if (ret) { |
786 | kfree(primary); | |
33cd3c07 | 787 | goto err_crtc; |
de32301b RK |
788 | } |
789 | ||
82c702cb | 790 | ret = drm_crtc_init_with_planes(drm, &dcrtc->crtc, primary, NULL, |
f9882876 | 791 | &armada_crtc_funcs, NULL); |
1c914cec RK |
792 | if (ret) |
793 | goto err_crtc_init; | |
794 | ||
96f60e37 RK |
795 | drm_crtc_helper_add(&dcrtc->crtc, &armada_crtc_helper_funcs); |
796 | ||
d8c96083 | 797 | return armada_overlay_plane_create(drm, 1 << dcrtc->num); |
1c914cec RK |
798 | |
799 | err_crtc_init: | |
82c702cb | 800 | primary->funcs->destroy(primary); |
33cd3c07 RK |
801 | err_crtc: |
802 | kfree(dcrtc); | |
803 | ||
1c914cec | 804 | return ret; |
d8c96083 RK |
805 | } |
806 | ||
807 | static int | |
808 | armada_lcd_bind(struct device *dev, struct device *master, void *data) | |
809 | { | |
810 | struct platform_device *pdev = to_platform_device(dev); | |
811 | struct drm_device *drm = data; | |
812 | struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
813 | int irq = platform_get_irq(pdev, 0); | |
814 | const struct armada_variant *variant; | |
9611cb93 | 815 | struct device_node *port = NULL; |
d8c96083 RK |
816 | |
817 | if (irq < 0) | |
818 | return irq; | |
819 | ||
820 | if (!dev->of_node) { | |
821 | const struct platform_device_id *id; | |
822 | ||
823 | id = platform_get_device_id(pdev); | |
824 | if (!id) | |
825 | return -ENXIO; | |
826 | ||
827 | variant = (const struct armada_variant *)id->driver_data; | |
828 | } else { | |
829 | const struct of_device_id *match; | |
9611cb93 | 830 | struct device_node *np, *parent = dev->of_node; |
d8c96083 RK |
831 | |
832 | match = of_match_device(dev->driver->of_match_table, dev); | |
833 | if (!match) | |
834 | return -ENXIO; | |
835 | ||
9611cb93 RK |
836 | np = of_get_child_by_name(parent, "ports"); |
837 | if (np) | |
838 | parent = np; | |
839 | port = of_get_child_by_name(parent, "port"); | |
840 | of_node_put(np); | |
841 | if (!port) { | |
4bf99144 | 842 | dev_err(dev, "no port node found in %pOF\n", parent); |
9611cb93 RK |
843 | return -ENXIO; |
844 | } | |
845 | ||
d8c96083 RK |
846 | variant = match->data; |
847 | } | |
848 | ||
9611cb93 | 849 | return armada_drm_crtc_create(drm, dev, res, irq, variant, port); |
d8c96083 RK |
850 | } |
851 | ||
852 | static void | |
853 | armada_lcd_unbind(struct device *dev, struct device *master, void *data) | |
854 | { | |
855 | struct armada_crtc *dcrtc = dev_get_drvdata(dev); | |
856 | ||
857 | armada_drm_crtc_destroy(&dcrtc->crtc); | |
96f60e37 | 858 | } |
d8c96083 RK |
859 | |
860 | static const struct component_ops armada_lcd_ops = { | |
861 | .bind = armada_lcd_bind, | |
862 | .unbind = armada_lcd_unbind, | |
863 | }; | |
864 | ||
865 | static int armada_lcd_probe(struct platform_device *pdev) | |
866 | { | |
867 | return component_add(&pdev->dev, &armada_lcd_ops); | |
868 | } | |
869 | ||
870 | static int armada_lcd_remove(struct platform_device *pdev) | |
871 | { | |
872 | component_del(&pdev->dev, &armada_lcd_ops); | |
873 | return 0; | |
96f60e37 | 874 | } |
d8c96083 | 875 | |
85909716 | 876 | static const struct of_device_id armada_lcd_of_match[] = { |
d8c96083 RK |
877 | { |
878 | .compatible = "marvell,dove-lcd", | |
879 | .data = &armada510_ops, | |
880 | }, | |
881 | {} | |
882 | }; | |
883 | MODULE_DEVICE_TABLE(of, armada_lcd_of_match); | |
884 | ||
885 | static const struct platform_device_id armada_lcd_platform_ids[] = { | |
886 | { | |
887 | .name = "armada-lcd", | |
888 | .driver_data = (unsigned long)&armada510_ops, | |
889 | }, { | |
890 | .name = "armada-510-lcd", | |
891 | .driver_data = (unsigned long)&armada510_ops, | |
892 | }, | |
893 | { }, | |
894 | }; | |
895 | MODULE_DEVICE_TABLE(platform, armada_lcd_platform_ids); | |
896 | ||
897 | struct platform_driver armada_lcd_platform_driver = { | |
898 | .probe = armada_lcd_probe, | |
899 | .remove = armada_lcd_remove, | |
900 | .driver = { | |
901 | .name = "armada-lcd", | |
902 | .owner = THIS_MODULE, | |
903 | .of_match_table = armada_lcd_of_match, | |
904 | }, | |
905 | .id_table = armada_lcd_platform_ids, | |
906 | }; |