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