0001
0002
0003
0004
0005
0006
0007
0008 #include <linux/module.h>
0009 #include <linux/mm.h>
0010 #include <linux/clk.h>
0011 #include <linux/irq.h>
0012 #include <linux/io.h>
0013 #include <linux/interrupt.h>
0014
0015 #include <linux/omap-dma.h>
0016 #include <linux/soc/ti/omap1-io.h>
0017
0018 #include "omapfb.h"
0019 #include "lcd_dma.h"
0020 #include "lcdc.h"
0021
0022 #define MODULE_NAME "omapfb-sossi"
0023
0024 #define OMAP_SOSSI_BASE 0xfffbac00
0025 #define SOSSI_ID_REG 0x00
0026 #define SOSSI_INIT1_REG 0x04
0027 #define SOSSI_INIT2_REG 0x08
0028 #define SOSSI_INIT3_REG 0x0c
0029 #define SOSSI_FIFO_REG 0x10
0030 #define SOSSI_REOTABLE_REG 0x14
0031 #define SOSSI_TEARING_REG 0x18
0032 #define SOSSI_INIT1B_REG 0x1c
0033 #define SOSSI_FIFOB_REG 0x20
0034
0035 #define DMA_GSCR 0xfffedc04
0036 #define DMA_LCD_CCR 0xfffee3c2
0037 #define DMA_LCD_CTRL 0xfffee3c4
0038 #define DMA_LCD_LCH_CTRL 0xfffee3ea
0039
0040 #define CONF_SOSSI_RESET_R (1 << 23)
0041
0042 #define RD_ACCESS 0
0043 #define WR_ACCESS 1
0044
0045 #define SOSSI_MAX_XMIT_BYTES (512 * 1024)
0046
0047 static struct {
0048 void __iomem *base;
0049 struct clk *fck;
0050 unsigned long fck_hz;
0051 spinlock_t lock;
0052 int bus_pick_count;
0053 int bus_pick_width;
0054 int tearsync_mode;
0055 int tearsync_line;
0056 void (*lcdc_callback)(void *data);
0057 void *lcdc_callback_data;
0058 int vsync_dma_pending;
0059
0060 int clk_div;
0061 u8 clk_tw0[2];
0062 u8 clk_tw1[2];
0063
0064
0065
0066
0067 int last_access;
0068
0069 struct omapfb_device *fbdev;
0070 } sossi;
0071
0072 static inline u32 sossi_read_reg(int reg)
0073 {
0074 return readl(sossi.base + reg);
0075 }
0076
0077 static inline u16 sossi_read_reg16(int reg)
0078 {
0079 return readw(sossi.base + reg);
0080 }
0081
0082 static inline u8 sossi_read_reg8(int reg)
0083 {
0084 return readb(sossi.base + reg);
0085 }
0086
0087 static inline void sossi_write_reg(int reg, u32 value)
0088 {
0089 writel(value, sossi.base + reg);
0090 }
0091
0092 static inline void sossi_write_reg16(int reg, u16 value)
0093 {
0094 writew(value, sossi.base + reg);
0095 }
0096
0097 static inline void sossi_write_reg8(int reg, u8 value)
0098 {
0099 writeb(value, sossi.base + reg);
0100 }
0101
0102 static void sossi_set_bits(int reg, u32 bits)
0103 {
0104 sossi_write_reg(reg, sossi_read_reg(reg) | bits);
0105 }
0106
0107 static void sossi_clear_bits(int reg, u32 bits)
0108 {
0109 sossi_write_reg(reg, sossi_read_reg(reg) & ~bits);
0110 }
0111
0112 #define HZ_TO_PS(x) (1000000000 / (x / 1000))
0113
0114 static u32 ps_to_sossi_ticks(u32 ps, int div)
0115 {
0116 u32 clk_period = HZ_TO_PS(sossi.fck_hz) * div;
0117 return (clk_period + ps - 1) / clk_period;
0118 }
0119
0120 static int calc_rd_timings(struct extif_timings *t)
0121 {
0122 u32 tw0, tw1;
0123 int reon, reoff, recyc, actim;
0124 int div = t->clk_div;
0125
0126
0127
0128
0129
0130 reon = ps_to_sossi_ticks(t->re_on_time, div);
0131
0132 if (reon > 1)
0133 return -1;
0134
0135 reoff = ps_to_sossi_ticks(t->re_off_time, div);
0136
0137 if (reoff <= reon)
0138 reoff = reon + 1;
0139
0140 tw0 = reoff - reon;
0141 if (tw0 > 0x10)
0142 return -1;
0143
0144 recyc = ps_to_sossi_ticks(t->re_cycle_time, div);
0145 if (recyc <= reoff)
0146 recyc = reoff + 1;
0147
0148 tw1 = recyc - tw0;
0149
0150 if (tw1 < 3)
0151 tw1 = 3;
0152 if (tw1 > 0x40)
0153 return -1;
0154
0155 actim = ps_to_sossi_ticks(t->access_time, div);
0156 if (actim < reoff)
0157 actim++;
0158
0159
0160
0161
0162 if (actim - reoff > 1)
0163 return -1;
0164
0165 t->tim[0] = tw0 - 1;
0166 t->tim[1] = tw1 - 1;
0167
0168 return 0;
0169 }
0170
0171 static int calc_wr_timings(struct extif_timings *t)
0172 {
0173 u32 tw0, tw1;
0174 int weon, weoff, wecyc;
0175 int div = t->clk_div;
0176
0177
0178
0179
0180
0181 weon = ps_to_sossi_ticks(t->we_on_time, div);
0182
0183 if (weon > 1)
0184 return -1;
0185
0186 weoff = ps_to_sossi_ticks(t->we_off_time, div);
0187 if (weoff <= weon)
0188 weoff = weon + 1;
0189 tw0 = weoff - weon;
0190 if (tw0 > 0x10)
0191 return -1;
0192
0193 wecyc = ps_to_sossi_ticks(t->we_cycle_time, div);
0194 if (wecyc <= weoff)
0195 wecyc = weoff + 1;
0196
0197 tw1 = wecyc - tw0;
0198
0199 if (tw1 < 3)
0200 tw1 = 3;
0201 if (tw1 > 0x40)
0202 return -1;
0203
0204 t->tim[2] = tw0 - 1;
0205 t->tim[3] = tw1 - 1;
0206
0207 return 0;
0208 }
0209
0210 static void _set_timing(int div, int tw0, int tw1)
0211 {
0212 u32 l;
0213
0214 #ifdef VERBOSE
0215 dev_dbg(sossi.fbdev->dev, "Using TW0 = %d, TW1 = %d, div = %d\n",
0216 tw0 + 1, tw1 + 1, div);
0217 #endif
0218
0219 clk_set_rate(sossi.fck, sossi.fck_hz / div);
0220 clk_enable(sossi.fck);
0221 l = sossi_read_reg(SOSSI_INIT1_REG);
0222 l &= ~((0x0f << 20) | (0x3f << 24));
0223 l |= (tw0 << 20) | (tw1 << 24);
0224 sossi_write_reg(SOSSI_INIT1_REG, l);
0225 clk_disable(sossi.fck);
0226 }
0227
0228 static void _set_bits_per_cycle(int bus_pick_count, int bus_pick_width)
0229 {
0230 u32 l;
0231
0232 l = sossi_read_reg(SOSSI_INIT3_REG);
0233 l &= ~0x3ff;
0234 l |= ((bus_pick_count - 1) << 5) | ((bus_pick_width - 1) & 0x1f);
0235 sossi_write_reg(SOSSI_INIT3_REG, l);
0236 }
0237
0238 static void _set_tearsync_mode(int mode, unsigned line)
0239 {
0240 u32 l;
0241
0242 l = sossi_read_reg(SOSSI_TEARING_REG);
0243 l &= ~(((1 << 11) - 1) << 15);
0244 l |= line << 15;
0245 l &= ~(0x3 << 26);
0246 l |= mode << 26;
0247 sossi_write_reg(SOSSI_TEARING_REG, l);
0248 if (mode)
0249 sossi_set_bits(SOSSI_INIT2_REG, 1 << 6);
0250 else
0251 sossi_clear_bits(SOSSI_INIT2_REG, 1 << 6);
0252 }
0253
0254 static inline void set_timing(int access)
0255 {
0256 if (access != sossi.last_access) {
0257 sossi.last_access = access;
0258 _set_timing(sossi.clk_div,
0259 sossi.clk_tw0[access], sossi.clk_tw1[access]);
0260 }
0261 }
0262
0263 static void sossi_start_transfer(void)
0264 {
0265
0266 sossi_clear_bits(SOSSI_INIT2_REG, 1 << 4);
0267
0268 sossi_clear_bits(SOSSI_INIT1_REG, 1 << 30);
0269 }
0270
0271 static void sossi_stop_transfer(void)
0272 {
0273
0274 sossi_set_bits(SOSSI_INIT2_REG, 1 << 4);
0275
0276 sossi_set_bits(SOSSI_INIT1_REG, 1 << 30);
0277 }
0278
0279 static void wait_end_of_write(void)
0280 {
0281
0282 while (!(sossi_read_reg(SOSSI_INIT2_REG) & (1 << 3)));
0283 }
0284
0285 static void send_data(const void *data, unsigned int len)
0286 {
0287 while (len >= 4) {
0288 sossi_write_reg(SOSSI_FIFO_REG, *(const u32 *) data);
0289 len -= 4;
0290 data += 4;
0291 }
0292 while (len >= 2) {
0293 sossi_write_reg16(SOSSI_FIFO_REG, *(const u16 *) data);
0294 len -= 2;
0295 data += 2;
0296 }
0297 while (len) {
0298 sossi_write_reg8(SOSSI_FIFO_REG, *(const u8 *) data);
0299 len--;
0300 data++;
0301 }
0302 }
0303
0304 static void set_cycles(unsigned int len)
0305 {
0306 unsigned long nr_cycles = len / (sossi.bus_pick_width / 8);
0307
0308 BUG_ON((nr_cycles - 1) & ~0x3ffff);
0309
0310 sossi_clear_bits(SOSSI_INIT1_REG, 0x3ffff);
0311 sossi_set_bits(SOSSI_INIT1_REG, (nr_cycles - 1) & 0x3ffff);
0312 }
0313
0314 static int sossi_convert_timings(struct extif_timings *t)
0315 {
0316 int r = 0;
0317 int div = t->clk_div;
0318
0319 t->converted = 0;
0320
0321 if (div <= 0 || div > 8)
0322 return -1;
0323
0324
0325 if ((r = calc_rd_timings(t)) < 0)
0326 return r;
0327
0328 if ((r = calc_wr_timings(t)) < 0)
0329 return r;
0330
0331 t->tim[4] = div;
0332
0333 t->converted = 1;
0334
0335 return 0;
0336 }
0337
0338 static void sossi_set_timings(const struct extif_timings *t)
0339 {
0340 BUG_ON(!t->converted);
0341
0342 sossi.clk_tw0[RD_ACCESS] = t->tim[0];
0343 sossi.clk_tw1[RD_ACCESS] = t->tim[1];
0344
0345 sossi.clk_tw0[WR_ACCESS] = t->tim[2];
0346 sossi.clk_tw1[WR_ACCESS] = t->tim[3];
0347
0348 sossi.clk_div = t->tim[4];
0349 }
0350
0351 static void sossi_get_clk_info(u32 *clk_period, u32 *max_clk_div)
0352 {
0353 *clk_period = HZ_TO_PS(sossi.fck_hz);
0354 *max_clk_div = 8;
0355 }
0356
0357 static void sossi_set_bits_per_cycle(int bpc)
0358 {
0359 int bus_pick_count, bus_pick_width;
0360
0361
0362
0363
0364
0365
0366 switch (bpc) {
0367 case 8:
0368 bus_pick_count = 4;
0369 bus_pick_width = 8;
0370 break;
0371 case 16:
0372 bus_pick_count = 2;
0373 bus_pick_width = 16;
0374 break;
0375 default:
0376 BUG();
0377 return;
0378 }
0379 sossi.bus_pick_width = bus_pick_width;
0380 sossi.bus_pick_count = bus_pick_count;
0381 }
0382
0383 static int sossi_setup_tearsync(unsigned pin_cnt,
0384 unsigned hs_pulse_time, unsigned vs_pulse_time,
0385 int hs_pol_inv, int vs_pol_inv, int div)
0386 {
0387 int hs, vs;
0388 u32 l;
0389
0390 if (pin_cnt != 1 || div < 1 || div > 8)
0391 return -EINVAL;
0392
0393 hs = ps_to_sossi_ticks(hs_pulse_time, div);
0394 vs = ps_to_sossi_ticks(vs_pulse_time, div);
0395 if (vs < 8 || vs <= hs || vs >= (1 << 12))
0396 return -EDOM;
0397 vs /= 8;
0398 vs--;
0399 if (hs > 8)
0400 hs = 8;
0401 if (hs)
0402 hs--;
0403
0404 dev_dbg(sossi.fbdev->dev,
0405 "setup_tearsync: hs %d vs %d hs_inv %d vs_inv %d\n",
0406 hs, vs, hs_pol_inv, vs_pol_inv);
0407
0408 clk_enable(sossi.fck);
0409 l = sossi_read_reg(SOSSI_TEARING_REG);
0410 l &= ~((1 << 15) - 1);
0411 l |= vs << 3;
0412 l |= hs;
0413 if (hs_pol_inv)
0414 l |= 1 << 29;
0415 else
0416 l &= ~(1 << 29);
0417 if (vs_pol_inv)
0418 l |= 1 << 28;
0419 else
0420 l &= ~(1 << 28);
0421 sossi_write_reg(SOSSI_TEARING_REG, l);
0422 clk_disable(sossi.fck);
0423
0424 return 0;
0425 }
0426
0427 static int sossi_enable_tearsync(int enable, unsigned line)
0428 {
0429 int mode;
0430
0431 dev_dbg(sossi.fbdev->dev, "tearsync %d line %d\n", enable, line);
0432 if (line >= 1 << 11)
0433 return -EINVAL;
0434 if (enable) {
0435 if (line)
0436 mode = 2;
0437 else
0438 mode = 3;
0439 } else
0440 mode = 0;
0441 sossi.tearsync_line = line;
0442 sossi.tearsync_mode = mode;
0443
0444 return 0;
0445 }
0446
0447 static void sossi_write_command(const void *data, unsigned int len)
0448 {
0449 clk_enable(sossi.fck);
0450 set_timing(WR_ACCESS);
0451 _set_bits_per_cycle(sossi.bus_pick_count, sossi.bus_pick_width);
0452
0453 sossi_clear_bits(SOSSI_INIT1_REG, 1 << 18);
0454 set_cycles(len);
0455 sossi_start_transfer();
0456 send_data(data, len);
0457 sossi_stop_transfer();
0458 wait_end_of_write();
0459 clk_disable(sossi.fck);
0460 }
0461
0462 static void sossi_write_data(const void *data, unsigned int len)
0463 {
0464 clk_enable(sossi.fck);
0465 set_timing(WR_ACCESS);
0466 _set_bits_per_cycle(sossi.bus_pick_count, sossi.bus_pick_width);
0467
0468 sossi_set_bits(SOSSI_INIT1_REG, 1 << 18);
0469 set_cycles(len);
0470 sossi_start_transfer();
0471 send_data(data, len);
0472 sossi_stop_transfer();
0473 wait_end_of_write();
0474 clk_disable(sossi.fck);
0475 }
0476
0477 static void sossi_transfer_area(int width, int height,
0478 void (callback)(void *data), void *data)
0479 {
0480 BUG_ON(callback == NULL);
0481
0482 sossi.lcdc_callback = callback;
0483 sossi.lcdc_callback_data = data;
0484
0485 clk_enable(sossi.fck);
0486 set_timing(WR_ACCESS);
0487 _set_bits_per_cycle(sossi.bus_pick_count, sossi.bus_pick_width);
0488 _set_tearsync_mode(sossi.tearsync_mode, sossi.tearsync_line);
0489
0490 sossi_set_bits(SOSSI_INIT1_REG, 1 << 18);
0491 set_cycles(width * height * sossi.bus_pick_width / 8);
0492
0493 sossi_start_transfer();
0494 if (sossi.tearsync_mode) {
0495
0496
0497
0498
0499
0500
0501 unsigned long flags;
0502
0503 spin_lock_irqsave(&sossi.lock, flags);
0504 sossi.vsync_dma_pending++;
0505 spin_unlock_irqrestore(&sossi.lock, flags);
0506 } else
0507
0508 omap_enable_lcd_dma();
0509 }
0510
0511 static void sossi_dma_callback(void *data)
0512 {
0513 omap_stop_lcd_dma();
0514 sossi_stop_transfer();
0515 clk_disable(sossi.fck);
0516 sossi.lcdc_callback(sossi.lcdc_callback_data);
0517 }
0518
0519 static void sossi_read_data(void *data, unsigned int len)
0520 {
0521 clk_enable(sossi.fck);
0522 set_timing(RD_ACCESS);
0523 _set_bits_per_cycle(sossi.bus_pick_count, sossi.bus_pick_width);
0524
0525 sossi_set_bits(SOSSI_INIT1_REG, 1 << 18);
0526 set_cycles(len);
0527 sossi_start_transfer();
0528 while (len >= 4) {
0529 *(u32 *) data = sossi_read_reg(SOSSI_FIFO_REG);
0530 len -= 4;
0531 data += 4;
0532 }
0533 while (len >= 2) {
0534 *(u16 *) data = sossi_read_reg16(SOSSI_FIFO_REG);
0535 len -= 2;
0536 data += 2;
0537 }
0538 while (len) {
0539 *(u8 *) data = sossi_read_reg8(SOSSI_FIFO_REG);
0540 len--;
0541 data++;
0542 }
0543 sossi_stop_transfer();
0544 clk_disable(sossi.fck);
0545 }
0546
0547 static irqreturn_t sossi_match_irq(int irq, void *data)
0548 {
0549 unsigned long flags;
0550
0551 spin_lock_irqsave(&sossi.lock, flags);
0552 if (sossi.vsync_dma_pending) {
0553 sossi.vsync_dma_pending--;
0554 omap_enable_lcd_dma();
0555 }
0556 spin_unlock_irqrestore(&sossi.lock, flags);
0557 return IRQ_HANDLED;
0558 }
0559
0560 static int sossi_init(struct omapfb_device *fbdev)
0561 {
0562 u32 l, k;
0563 struct clk *fck;
0564 struct clk *dpll1out_ck;
0565 int r;
0566
0567 sossi.base = ioremap(OMAP_SOSSI_BASE, SZ_1K);
0568 if (!sossi.base) {
0569 dev_err(fbdev->dev, "can't ioremap SoSSI\n");
0570 return -ENOMEM;
0571 }
0572
0573 sossi.fbdev = fbdev;
0574 spin_lock_init(&sossi.lock);
0575
0576 dpll1out_ck = clk_get(fbdev->dev, "ck_dpll1out");
0577 if (IS_ERR(dpll1out_ck)) {
0578 dev_err(fbdev->dev, "can't get DPLL1OUT clock\n");
0579 return PTR_ERR(dpll1out_ck);
0580 }
0581
0582
0583
0584
0585
0586 sossi.fck_hz = clk_get_rate(dpll1out_ck);
0587 clk_put(dpll1out_ck);
0588
0589 fck = clk_get(fbdev->dev, "ck_sossi");
0590 if (IS_ERR(fck)) {
0591 dev_err(fbdev->dev, "can't get SoSSI functional clock\n");
0592 return PTR_ERR(fck);
0593 }
0594 sossi.fck = fck;
0595
0596
0597 l = omap_readl(MOD_CONF_CTRL_1);
0598 l |= CONF_SOSSI_RESET_R;
0599 omap_writel(l, MOD_CONF_CTRL_1);
0600 l &= ~CONF_SOSSI_RESET_R;
0601 omap_writel(l, MOD_CONF_CTRL_1);
0602
0603 clk_prepare_enable(sossi.fck);
0604 l = omap_readl(ARM_IDLECT2);
0605 l &= ~(1 << 8);
0606 omap_writel(l, ARM_IDLECT2);
0607
0608 l = sossi_read_reg(SOSSI_INIT2_REG);
0609
0610 l |= (1 << 0) | (1 << 1);
0611 sossi_write_reg(SOSSI_INIT2_REG, l);
0612
0613 l &= ~(1 << 1);
0614 sossi_write_reg(SOSSI_INIT2_REG, l);
0615
0616 sossi_write_reg(SOSSI_ID_REG, 0);
0617 l = sossi_read_reg(SOSSI_ID_REG);
0618 k = sossi_read_reg(SOSSI_ID_REG);
0619
0620 if (l != 0x55555555 || k != 0xaaaaaaaa) {
0621 dev_err(fbdev->dev,
0622 "invalid SoSSI sync pattern: %08x, %08x\n", l, k);
0623 r = -ENODEV;
0624 goto err;
0625 }
0626
0627 if ((r = omap_lcdc_set_dma_callback(sossi_dma_callback, NULL)) < 0) {
0628 dev_err(fbdev->dev, "can't get LCDC IRQ\n");
0629 r = -ENODEV;
0630 goto err;
0631 }
0632
0633 l = sossi_read_reg(SOSSI_ID_REG);
0634 l = sossi_read_reg(SOSSI_ID_REG);
0635 dev_info(fbdev->dev, "SoSSI version %d.%d initialized\n",
0636 l >> 16, l & 0xffff);
0637
0638 l = sossi_read_reg(SOSSI_INIT1_REG);
0639 l |= (1 << 19);
0640 l &= ~(1 << 31);
0641 sossi_write_reg(SOSSI_INIT1_REG, l);
0642
0643 if ((r = request_irq(fbdev->ext_irq, sossi_match_irq,
0644 IRQ_TYPE_EDGE_FALLING,
0645 "sossi_match", sossi.fbdev->dev)) < 0) {
0646 dev_err(sossi.fbdev->dev, "can't get SoSSI match IRQ\n");
0647 goto err;
0648 }
0649
0650 clk_disable(sossi.fck);
0651 return 0;
0652
0653 err:
0654 clk_disable_unprepare(sossi.fck);
0655 clk_put(sossi.fck);
0656 return r;
0657 }
0658
0659 static void sossi_cleanup(void)
0660 {
0661 omap_lcdc_free_dma_callback();
0662 clk_unprepare(sossi.fck);
0663 clk_put(sossi.fck);
0664 iounmap(sossi.base);
0665 }
0666
0667 struct lcd_ctrl_extif omap1_ext_if = {
0668 .init = sossi_init,
0669 .cleanup = sossi_cleanup,
0670 .get_clk_info = sossi_get_clk_info,
0671 .convert_timings = sossi_convert_timings,
0672 .set_timings = sossi_set_timings,
0673 .set_bits_per_cycle = sossi_set_bits_per_cycle,
0674 .setup_tearsync = sossi_setup_tearsync,
0675 .enable_tearsync = sossi_enable_tearsync,
0676 .write_command = sossi_write_command,
0677 .read_data = sossi_read_data,
0678 .write_data = sossi_write_data,
0679 .transfer_area = sossi_transfer_area,
0680
0681 .max_transmit_size = SOSSI_MAX_XMIT_BYTES,
0682 };
0683