1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * OMAP1 Special OptimiSed Screen Interface support |
4 | * |
5 | * Copyright (C) 2004-2005 Nokia Corporation |
6 | * Author: Juha Yrjölä <juha.yrjola@nokia.com> |
7 | */ |
8 | #include <linux/module.h> |
9 | #include <linux/mm.h> |
10 | #include <linux/clk.h> |
11 | #include <linux/irq.h> |
12 | #include <linux/io.h> |
13 | #include <linux/interrupt.h> |
14 | |
15 | #include <linux/omap-dma.h> |
16 | #include <linux/soc/ti/omap1-io.h> |
17 | |
18 | #include "omapfb.h" |
19 | #include "lcd_dma.h" |
20 | #include "lcdc.h" |
21 | |
22 | #define MODULE_NAME "omapfb-sossi" |
23 | |
24 | #define OMAP_SOSSI_BASE 0xfffbac00 |
25 | #define SOSSI_ID_REG 0x00 |
26 | #define SOSSI_INIT1_REG 0x04 |
27 | #define SOSSI_INIT2_REG 0x08 |
28 | #define SOSSI_INIT3_REG 0x0c |
29 | #define SOSSI_FIFO_REG 0x10 |
30 | #define SOSSI_REOTABLE_REG 0x14 |
31 | #define SOSSI_TEARING_REG 0x18 |
32 | #define SOSSI_INIT1B_REG 0x1c |
33 | #define SOSSI_FIFOB_REG 0x20 |
34 | |
35 | #define DMA_GSCR 0xfffedc04 |
36 | #define DMA_LCD_CCR 0xfffee3c2 |
37 | #define DMA_LCD_CTRL 0xfffee3c4 |
38 | #define DMA_LCD_LCH_CTRL 0xfffee3ea |
39 | |
40 | #define CONF_SOSSI_RESET_R (1 << 23) |
41 | |
42 | #define RD_ACCESS 0 |
43 | #define WR_ACCESS 1 |
44 | |
45 | #define SOSSI_MAX_XMIT_BYTES (512 * 1024) |
46 | |
47 | static struct { |
48 | void __iomem *base; |
49 | struct clk *fck; |
50 | unsigned long fck_hz; |
51 | spinlock_t lock; |
52 | int bus_pick_count; |
53 | int bus_pick_width; |
54 | int tearsync_mode; |
55 | int tearsync_line; |
56 | void (*lcdc_callback)(void *data); |
57 | void *lcdc_callback_data; |
58 | int vsync_dma_pending; |
59 | /* timing for read and write access */ |
60 | int clk_div; |
61 | u8 clk_tw0[2]; |
62 | u8 clk_tw1[2]; |
63 | /* |
64 | * if last_access is the same as current we don't have to change |
65 | * the timings |
66 | */ |
67 | int last_access; |
68 | |
69 | struct omapfb_device *fbdev; |
70 | } sossi; |
71 | |
72 | static inline u32 sossi_read_reg(int reg) |
73 | { |
74 | return readl(addr: sossi.base + reg); |
75 | } |
76 | |
77 | static inline u16 sossi_read_reg16(int reg) |
78 | { |
79 | return readw(addr: sossi.base + reg); |
80 | } |
81 | |
82 | static inline u8 sossi_read_reg8(int reg) |
83 | { |
84 | return readb(addr: sossi.base + reg); |
85 | } |
86 | |
87 | static inline void sossi_write_reg(int reg, u32 value) |
88 | { |
89 | writel(val: value, addr: sossi.base + reg); |
90 | } |
91 | |
92 | static inline void sossi_write_reg16(int reg, u16 value) |
93 | { |
94 | writew(val: value, addr: sossi.base + reg); |
95 | } |
96 | |
97 | static inline void sossi_write_reg8(int reg, u8 value) |
98 | { |
99 | writeb(val: value, addr: sossi.base + reg); |
100 | } |
101 | |
102 | static void sossi_set_bits(int reg, u32 bits) |
103 | { |
104 | sossi_write_reg(reg, value: sossi_read_reg(reg) | bits); |
105 | } |
106 | |
107 | static void sossi_clear_bits(int reg, u32 bits) |
108 | { |
109 | sossi_write_reg(reg, value: sossi_read_reg(reg) & ~bits); |
110 | } |
111 | |
112 | #define HZ_TO_PS(x) (1000000000 / (x / 1000)) |
113 | |
114 | static u32 ps_to_sossi_ticks(u32 ps, int div) |
115 | { |
116 | u32 clk_period = HZ_TO_PS(sossi.fck_hz) * div; |
117 | return (clk_period + ps - 1) / clk_period; |
118 | } |
119 | |
120 | static int calc_rd_timings(struct extif_timings *t) |
121 | { |
122 | u32 tw0, tw1; |
123 | int reon, reoff, recyc, actim; |
124 | int div = t->clk_div; |
125 | |
126 | /* |
127 | * Make sure that after conversion it still holds that: |
128 | * reoff > reon, recyc >= reoff, actim > reon |
129 | */ |
130 | reon = ps_to_sossi_ticks(ps: t->re_on_time, div); |
131 | /* reon will be exactly one sossi tick */ |
132 | if (reon > 1) |
133 | return -1; |
134 | |
135 | reoff = ps_to_sossi_ticks(ps: t->re_off_time, div); |
136 | |
137 | if (reoff <= reon) |
138 | reoff = reon + 1; |
139 | |
140 | tw0 = reoff - reon; |
141 | if (tw0 > 0x10) |
142 | return -1; |
143 | |
144 | recyc = ps_to_sossi_ticks(ps: t->re_cycle_time, div); |
145 | if (recyc <= reoff) |
146 | recyc = reoff + 1; |
147 | |
148 | tw1 = recyc - tw0; |
149 | /* values less then 3 result in the SOSSI block resetting itself */ |
150 | if (tw1 < 3) |
151 | tw1 = 3; |
152 | if (tw1 > 0x40) |
153 | return -1; |
154 | |
155 | actim = ps_to_sossi_ticks(ps: t->access_time, div); |
156 | if (actim < reoff) |
157 | actim++; |
158 | /* |
159 | * access time (data hold time) will be exactly one sossi |
160 | * tick |
161 | */ |
162 | if (actim - reoff > 1) |
163 | return -1; |
164 | |
165 | t->tim[0] = tw0 - 1; |
166 | t->tim[1] = tw1 - 1; |
167 | |
168 | return 0; |
169 | } |
170 | |
171 | static int calc_wr_timings(struct extif_timings *t) |
172 | { |
173 | u32 tw0, tw1; |
174 | int weon, weoff, wecyc; |
175 | int div = t->clk_div; |
176 | |
177 | /* |
178 | * Make sure that after conversion it still holds that: |
179 | * weoff > weon, wecyc >= weoff |
180 | */ |
181 | weon = ps_to_sossi_ticks(ps: t->we_on_time, div); |
182 | /* weon will be exactly one sossi tick */ |
183 | if (weon > 1) |
184 | return -1; |
185 | |
186 | weoff = ps_to_sossi_ticks(ps: t->we_off_time, div); |
187 | if (weoff <= weon) |
188 | weoff = weon + 1; |
189 | tw0 = weoff - weon; |
190 | if (tw0 > 0x10) |
191 | return -1; |
192 | |
193 | wecyc = ps_to_sossi_ticks(ps: t->we_cycle_time, div); |
194 | if (wecyc <= weoff) |
195 | wecyc = weoff + 1; |
196 | |
197 | tw1 = wecyc - tw0; |
198 | /* values less then 3 result in the SOSSI block resetting itself */ |
199 | if (tw1 < 3) |
200 | tw1 = 3; |
201 | if (tw1 > 0x40) |
202 | return -1; |
203 | |
204 | t->tim[2] = tw0 - 1; |
205 | t->tim[3] = tw1 - 1; |
206 | |
207 | return 0; |
208 | } |
209 | |
210 | static void _set_timing(int div, int tw0, int tw1) |
211 | { |
212 | u32 l; |
213 | |
214 | #ifdef VERBOSE |
215 | dev_dbg(sossi.fbdev->dev, "Using TW0 = %d, TW1 = %d, div = %d\n" , |
216 | tw0 + 1, tw1 + 1, div); |
217 | #endif |
218 | |
219 | clk_set_rate(clk: sossi.fck, rate: sossi.fck_hz / div); |
220 | clk_enable(clk: sossi.fck); |
221 | l = sossi_read_reg(SOSSI_INIT1_REG); |
222 | l &= ~((0x0f << 20) | (0x3f << 24)); |
223 | l |= (tw0 << 20) | (tw1 << 24); |
224 | sossi_write_reg(SOSSI_INIT1_REG, value: l); |
225 | clk_disable(clk: sossi.fck); |
226 | } |
227 | |
228 | static void _set_bits_per_cycle(int bus_pick_count, int bus_pick_width) |
229 | { |
230 | u32 l; |
231 | |
232 | l = sossi_read_reg(SOSSI_INIT3_REG); |
233 | l &= ~0x3ff; |
234 | l |= ((bus_pick_count - 1) << 5) | ((bus_pick_width - 1) & 0x1f); |
235 | sossi_write_reg(SOSSI_INIT3_REG, value: l); |
236 | } |
237 | |
238 | static void _set_tearsync_mode(int mode, unsigned line) |
239 | { |
240 | u32 l; |
241 | |
242 | l = sossi_read_reg(SOSSI_TEARING_REG); |
243 | l &= ~(((1 << 11) - 1) << 15); |
244 | l |= line << 15; |
245 | l &= ~(0x3 << 26); |
246 | l |= mode << 26; |
247 | sossi_write_reg(SOSSI_TEARING_REG, value: l); |
248 | if (mode) |
249 | sossi_set_bits(SOSSI_INIT2_REG, bits: 1 << 6); /* TE logic */ |
250 | else |
251 | sossi_clear_bits(SOSSI_INIT2_REG, bits: 1 << 6); |
252 | } |
253 | |
254 | static inline void set_timing(int access) |
255 | { |
256 | if (access != sossi.last_access) { |
257 | sossi.last_access = access; |
258 | _set_timing(div: sossi.clk_div, |
259 | tw0: sossi.clk_tw0[access], tw1: sossi.clk_tw1[access]); |
260 | } |
261 | } |
262 | |
263 | static void sossi_start_transfer(void) |
264 | { |
265 | /* WE */ |
266 | sossi_clear_bits(SOSSI_INIT2_REG, bits: 1 << 4); |
267 | /* CS active low */ |
268 | sossi_clear_bits(SOSSI_INIT1_REG, bits: 1 << 30); |
269 | } |
270 | |
271 | static void sossi_stop_transfer(void) |
272 | { |
273 | /* WE */ |
274 | sossi_set_bits(SOSSI_INIT2_REG, bits: 1 << 4); |
275 | /* CS active low */ |
276 | sossi_set_bits(SOSSI_INIT1_REG, bits: 1 << 30); |
277 | } |
278 | |
279 | static void wait_end_of_write(void) |
280 | { |
281 | /* Before reading we must check if some writings are going on */ |
282 | while (!(sossi_read_reg(SOSSI_INIT2_REG) & (1 << 3))); |
283 | } |
284 | |
285 | static void send_data(const void *data, unsigned int len) |
286 | { |
287 | while (len >= 4) { |
288 | sossi_write_reg(SOSSI_FIFO_REG, value: *(const u32 *) data); |
289 | len -= 4; |
290 | data += 4; |
291 | } |
292 | while (len >= 2) { |
293 | sossi_write_reg16(SOSSI_FIFO_REG, value: *(const u16 *) data); |
294 | len -= 2; |
295 | data += 2; |
296 | } |
297 | while (len) { |
298 | sossi_write_reg8(SOSSI_FIFO_REG, value: *(const u8 *) data); |
299 | len--; |
300 | data++; |
301 | } |
302 | } |
303 | |
304 | static void set_cycles(unsigned int len) |
305 | { |
306 | unsigned long nr_cycles = len / (sossi.bus_pick_width / 8); |
307 | |
308 | BUG_ON((nr_cycles - 1) & ~0x3ffff); |
309 | |
310 | sossi_clear_bits(SOSSI_INIT1_REG, bits: 0x3ffff); |
311 | sossi_set_bits(SOSSI_INIT1_REG, bits: (nr_cycles - 1) & 0x3ffff); |
312 | } |
313 | |
314 | static int sossi_convert_timings(struct extif_timings *t) |
315 | { |
316 | int r = 0; |
317 | int div = t->clk_div; |
318 | |
319 | t->converted = 0; |
320 | |
321 | if (div <= 0 || div > 8) |
322 | return -1; |
323 | |
324 | /* no CS on SOSSI, so ignore cson, csoff, cs_pulsewidth */ |
325 | if ((r = calc_rd_timings(t)) < 0) |
326 | return r; |
327 | |
328 | if ((r = calc_wr_timings(t)) < 0) |
329 | return r; |
330 | |
331 | t->tim[4] = div; |
332 | |
333 | t->converted = 1; |
334 | |
335 | return 0; |
336 | } |
337 | |
338 | static void sossi_set_timings(const struct extif_timings *t) |
339 | { |
340 | BUG_ON(!t->converted); |
341 | |
342 | sossi.clk_tw0[RD_ACCESS] = t->tim[0]; |
343 | sossi.clk_tw1[RD_ACCESS] = t->tim[1]; |
344 | |
345 | sossi.clk_tw0[WR_ACCESS] = t->tim[2]; |
346 | sossi.clk_tw1[WR_ACCESS] = t->tim[3]; |
347 | |
348 | sossi.clk_div = t->tim[4]; |
349 | } |
350 | |
351 | static void sossi_get_clk_info(u32 *clk_period, u32 *max_clk_div) |
352 | { |
353 | *clk_period = HZ_TO_PS(sossi.fck_hz); |
354 | *max_clk_div = 8; |
355 | } |
356 | |
357 | static void sossi_set_bits_per_cycle(int bpc) |
358 | { |
359 | int bus_pick_count, bus_pick_width; |
360 | |
361 | /* |
362 | * We set explicitly the bus_pick_count as well, although |
363 | * with remapping/reordering disabled it will be calculated by HW |
364 | * as (32 / bus_pick_width). |
365 | */ |
366 | switch (bpc) { |
367 | case 8: |
368 | bus_pick_count = 4; |
369 | bus_pick_width = 8; |
370 | break; |
371 | case 16: |
372 | bus_pick_count = 2; |
373 | bus_pick_width = 16; |
374 | break; |
375 | default: |
376 | BUG(); |
377 | return; |
378 | } |
379 | sossi.bus_pick_width = bus_pick_width; |
380 | sossi.bus_pick_count = bus_pick_count; |
381 | } |
382 | |
383 | static int sossi_setup_tearsync(unsigned pin_cnt, |
384 | unsigned hs_pulse_time, unsigned vs_pulse_time, |
385 | int hs_pol_inv, int vs_pol_inv, int div) |
386 | { |
387 | int hs, vs; |
388 | u32 l; |
389 | |
390 | if (pin_cnt != 1 || div < 1 || div > 8) |
391 | return -EINVAL; |
392 | |
393 | hs = ps_to_sossi_ticks(ps: hs_pulse_time, div); |
394 | vs = ps_to_sossi_ticks(ps: vs_pulse_time, div); |
395 | if (vs < 8 || vs <= hs || vs >= (1 << 12)) |
396 | return -EDOM; |
397 | vs /= 8; |
398 | vs--; |
399 | if (hs > 8) |
400 | hs = 8; |
401 | if (hs) |
402 | hs--; |
403 | |
404 | dev_dbg(sossi.fbdev->dev, |
405 | "setup_tearsync: hs %d vs %d hs_inv %d vs_inv %d\n" , |
406 | hs, vs, hs_pol_inv, vs_pol_inv); |
407 | |
408 | clk_enable(clk: sossi.fck); |
409 | l = sossi_read_reg(SOSSI_TEARING_REG); |
410 | l &= ~((1 << 15) - 1); |
411 | l |= vs << 3; |
412 | l |= hs; |
413 | if (hs_pol_inv) |
414 | l |= 1 << 29; |
415 | else |
416 | l &= ~(1 << 29); |
417 | if (vs_pol_inv) |
418 | l |= 1 << 28; |
419 | else |
420 | l &= ~(1 << 28); |
421 | sossi_write_reg(SOSSI_TEARING_REG, value: l); |
422 | clk_disable(clk: sossi.fck); |
423 | |
424 | return 0; |
425 | } |
426 | |
427 | static int sossi_enable_tearsync(int enable, unsigned line) |
428 | { |
429 | int mode; |
430 | |
431 | dev_dbg(sossi.fbdev->dev, "tearsync %d line %d\n" , enable, line); |
432 | if (line >= 1 << 11) |
433 | return -EINVAL; |
434 | if (enable) { |
435 | if (line) |
436 | mode = 2; /* HS or VS */ |
437 | else |
438 | mode = 3; /* VS only */ |
439 | } else |
440 | mode = 0; |
441 | sossi.tearsync_line = line; |
442 | sossi.tearsync_mode = mode; |
443 | |
444 | return 0; |
445 | } |
446 | |
447 | static void sossi_write_command(const void *data, unsigned int len) |
448 | { |
449 | clk_enable(clk: sossi.fck); |
450 | set_timing(WR_ACCESS); |
451 | _set_bits_per_cycle(bus_pick_count: sossi.bus_pick_count, bus_pick_width: sossi.bus_pick_width); |
452 | /* CMD#/DATA */ |
453 | sossi_clear_bits(SOSSI_INIT1_REG, bits: 1 << 18); |
454 | set_cycles(len); |
455 | sossi_start_transfer(); |
456 | send_data(data, len); |
457 | sossi_stop_transfer(); |
458 | wait_end_of_write(); |
459 | clk_disable(clk: sossi.fck); |
460 | } |
461 | |
462 | static void sossi_write_data(const void *data, unsigned int len) |
463 | { |
464 | clk_enable(clk: sossi.fck); |
465 | set_timing(WR_ACCESS); |
466 | _set_bits_per_cycle(bus_pick_count: sossi.bus_pick_count, bus_pick_width: sossi.bus_pick_width); |
467 | /* CMD#/DATA */ |
468 | sossi_set_bits(SOSSI_INIT1_REG, bits: 1 << 18); |
469 | set_cycles(len); |
470 | sossi_start_transfer(); |
471 | send_data(data, len); |
472 | sossi_stop_transfer(); |
473 | wait_end_of_write(); |
474 | clk_disable(clk: sossi.fck); |
475 | } |
476 | |
477 | static void sossi_transfer_area(int width, int height, |
478 | void (callback)(void *data), void *data) |
479 | { |
480 | BUG_ON(callback == NULL); |
481 | |
482 | sossi.lcdc_callback = callback; |
483 | sossi.lcdc_callback_data = data; |
484 | |
485 | clk_enable(clk: sossi.fck); |
486 | set_timing(WR_ACCESS); |
487 | _set_bits_per_cycle(bus_pick_count: sossi.bus_pick_count, bus_pick_width: sossi.bus_pick_width); |
488 | _set_tearsync_mode(mode: sossi.tearsync_mode, line: sossi.tearsync_line); |
489 | /* CMD#/DATA */ |
490 | sossi_set_bits(SOSSI_INIT1_REG, bits: 1 << 18); |
491 | set_cycles(width * height * sossi.bus_pick_width / 8); |
492 | |
493 | sossi_start_transfer(); |
494 | if (sossi.tearsync_mode) { |
495 | /* |
496 | * Wait for the sync signal and start the transfer only |
497 | * then. We can't seem to be able to use HW sync DMA for |
498 | * this since LCD DMA shows huge latencies, as if it |
499 | * would ignore some of the DMA requests from SoSSI. |
500 | */ |
501 | unsigned long flags; |
502 | |
503 | spin_lock_irqsave(&sossi.lock, flags); |
504 | sossi.vsync_dma_pending++; |
505 | spin_unlock_irqrestore(lock: &sossi.lock, flags); |
506 | } else |
507 | /* Just start the transfer right away. */ |
508 | omap_enable_lcd_dma(); |
509 | } |
510 | |
511 | static void sossi_dma_callback(void *data) |
512 | { |
513 | omap_stop_lcd_dma(); |
514 | sossi_stop_transfer(); |
515 | clk_disable(clk: sossi.fck); |
516 | sossi.lcdc_callback(sossi.lcdc_callback_data); |
517 | } |
518 | |
519 | static void sossi_read_data(void *data, unsigned int len) |
520 | { |
521 | clk_enable(clk: sossi.fck); |
522 | set_timing(RD_ACCESS); |
523 | _set_bits_per_cycle(bus_pick_count: sossi.bus_pick_count, bus_pick_width: sossi.bus_pick_width); |
524 | /* CMD#/DATA */ |
525 | sossi_set_bits(SOSSI_INIT1_REG, bits: 1 << 18); |
526 | set_cycles(len); |
527 | sossi_start_transfer(); |
528 | while (len >= 4) { |
529 | *(u32 *) data = sossi_read_reg(SOSSI_FIFO_REG); |
530 | len -= 4; |
531 | data += 4; |
532 | } |
533 | while (len >= 2) { |
534 | *(u16 *) data = sossi_read_reg16(SOSSI_FIFO_REG); |
535 | len -= 2; |
536 | data += 2; |
537 | } |
538 | while (len) { |
539 | *(u8 *) data = sossi_read_reg8(SOSSI_FIFO_REG); |
540 | len--; |
541 | data++; |
542 | } |
543 | sossi_stop_transfer(); |
544 | clk_disable(clk: sossi.fck); |
545 | } |
546 | |
547 | static irqreturn_t sossi_match_irq(int irq, void *data) |
548 | { |
549 | unsigned long flags; |
550 | |
551 | spin_lock_irqsave(&sossi.lock, flags); |
552 | if (sossi.vsync_dma_pending) { |
553 | sossi.vsync_dma_pending--; |
554 | omap_enable_lcd_dma(); |
555 | } |
556 | spin_unlock_irqrestore(lock: &sossi.lock, flags); |
557 | return IRQ_HANDLED; |
558 | } |
559 | |
560 | static int sossi_init(struct omapfb_device *fbdev) |
561 | { |
562 | u32 l, k; |
563 | struct clk *fck; |
564 | struct clk *dpll1out_ck; |
565 | int r; |
566 | |
567 | sossi.base = ioremap(OMAP_SOSSI_BASE, SZ_1K); |
568 | if (!sossi.base) { |
569 | dev_err(fbdev->dev, "can't ioremap SoSSI\n" ); |
570 | return -ENOMEM; |
571 | } |
572 | |
573 | sossi.fbdev = fbdev; |
574 | spin_lock_init(&sossi.lock); |
575 | |
576 | dpll1out_ck = clk_get(dev: fbdev->dev, id: "ck_dpll1out" ); |
577 | if (IS_ERR(ptr: dpll1out_ck)) { |
578 | dev_err(fbdev->dev, "can't get DPLL1OUT clock\n" ); |
579 | return PTR_ERR(ptr: dpll1out_ck); |
580 | } |
581 | /* |
582 | * We need the parent clock rate, which we might divide further |
583 | * depending on the timing requirements of the controller. See |
584 | * _set_timings. |
585 | */ |
586 | sossi.fck_hz = clk_get_rate(clk: dpll1out_ck); |
587 | clk_put(clk: dpll1out_ck); |
588 | |
589 | fck = clk_get(dev: fbdev->dev, id: "ck_sossi" ); |
590 | if (IS_ERR(ptr: fck)) { |
591 | dev_err(fbdev->dev, "can't get SoSSI functional clock\n" ); |
592 | return PTR_ERR(ptr: fck); |
593 | } |
594 | sossi.fck = fck; |
595 | |
596 | /* Reset and enable the SoSSI module */ |
597 | l = omap_readl(MOD_CONF_CTRL_1); |
598 | l |= CONF_SOSSI_RESET_R; |
599 | omap_writel(v: l, MOD_CONF_CTRL_1); |
600 | l &= ~CONF_SOSSI_RESET_R; |
601 | omap_writel(v: l, MOD_CONF_CTRL_1); |
602 | |
603 | clk_prepare_enable(clk: sossi.fck); |
604 | l = omap_readl(ARM_IDLECT2); |
605 | l &= ~(1 << 8); /* DMACK_REQ */ |
606 | omap_writel(v: l, ARM_IDLECT2); |
607 | |
608 | l = sossi_read_reg(SOSSI_INIT2_REG); |
609 | /* Enable and reset the SoSSI block */ |
610 | l |= (1 << 0) | (1 << 1); |
611 | sossi_write_reg(SOSSI_INIT2_REG, value: l); |
612 | /* Take SoSSI out of reset */ |
613 | l &= ~(1 << 1); |
614 | sossi_write_reg(SOSSI_INIT2_REG, value: l); |
615 | |
616 | sossi_write_reg(SOSSI_ID_REG, value: 0); |
617 | l = sossi_read_reg(SOSSI_ID_REG); |
618 | k = sossi_read_reg(SOSSI_ID_REG); |
619 | |
620 | if (l != 0x55555555 || k != 0xaaaaaaaa) { |
621 | dev_err(fbdev->dev, |
622 | "invalid SoSSI sync pattern: %08x, %08x\n" , l, k); |
623 | r = -ENODEV; |
624 | goto err; |
625 | } |
626 | |
627 | if ((r = omap_lcdc_set_dma_callback(callback: sossi_dma_callback, NULL)) < 0) { |
628 | dev_err(fbdev->dev, "can't get LCDC IRQ\n" ); |
629 | r = -ENODEV; |
630 | goto err; |
631 | } |
632 | |
633 | l = sossi_read_reg(SOSSI_ID_REG); /* Component code */ |
634 | l = sossi_read_reg(SOSSI_ID_REG); |
635 | dev_info(fbdev->dev, "SoSSI version %d.%d initialized\n" , |
636 | l >> 16, l & 0xffff); |
637 | |
638 | l = sossi_read_reg(SOSSI_INIT1_REG); |
639 | l |= (1 << 19); /* DMA_MODE */ |
640 | l &= ~(1 << 31); /* REORDERING */ |
641 | sossi_write_reg(SOSSI_INIT1_REG, value: l); |
642 | |
643 | if ((r = request_irq(irq: fbdev->ext_irq, handler: sossi_match_irq, |
644 | flags: IRQ_TYPE_EDGE_FALLING, |
645 | name: "sossi_match" , dev: sossi.fbdev->dev)) < 0) { |
646 | dev_err(sossi.fbdev->dev, "can't get SoSSI match IRQ\n" ); |
647 | goto err; |
648 | } |
649 | |
650 | clk_disable(clk: sossi.fck); |
651 | return 0; |
652 | |
653 | err: |
654 | clk_disable_unprepare(clk: sossi.fck); |
655 | clk_put(clk: sossi.fck); |
656 | return r; |
657 | } |
658 | |
659 | static void sossi_cleanup(void) |
660 | { |
661 | omap_lcdc_free_dma_callback(); |
662 | clk_unprepare(clk: sossi.fck); |
663 | clk_put(clk: sossi.fck); |
664 | iounmap(addr: sossi.base); |
665 | } |
666 | |
667 | struct lcd_ctrl_extif omap1_ext_if = { |
668 | .init = sossi_init, |
669 | .cleanup = sossi_cleanup, |
670 | .get_clk_info = sossi_get_clk_info, |
671 | .convert_timings = sossi_convert_timings, |
672 | .set_timings = sossi_set_timings, |
673 | .set_bits_per_cycle = sossi_set_bits_per_cycle, |
674 | .setup_tearsync = sossi_setup_tearsync, |
675 | .enable_tearsync = sossi_enable_tearsync, |
676 | .write_command = sossi_write_command, |
677 | .read_data = sossi_read_data, |
678 | .write_data = sossi_write_data, |
679 | .transfer_area = sossi_transfer_area, |
680 | |
681 | .max_transmit_size = SOSSI_MAX_XMIT_BYTES, |
682 | }; |
683 | |
684 | |