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