684 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * OMAP1 Special OptimiSed Screen Interface support4 *5 * Copyright (C) 2004-2005 Nokia Corporation6 * 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 0xfffbac0025#define SOSSI_ID_REG 0x0026#define SOSSI_INIT1_REG 0x0427#define SOSSI_INIT2_REG 0x0828#define SOSSI_INIT3_REG 0x0c29#define SOSSI_FIFO_REG 0x1030#define SOSSI_REOTABLE_REG 0x1431#define SOSSI_TEARING_REG 0x1832#define SOSSI_INIT1B_REG 0x1c33#define SOSSI_FIFOB_REG 0x2034 35#define DMA_GSCR 0xfffedc0436#define DMA_LCD_CCR 0xfffee3c237#define DMA_LCD_CTRL 0xfffee3c438#define DMA_LCD_LCH_CTRL 0xfffee3ea39 40#define CONF_SOSSI_RESET_R (1 << 23)41 42#define RD_ACCESS 043#define WR_ACCESS 144 45#define SOSSI_MAX_XMIT_BYTES (512 * 1024)46 47static 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 change65 * the timings66 */67 int last_access;68 69 struct omapfb_device *fbdev;70} sossi;71 72static inline u32 sossi_read_reg(int reg)73{74 return readl(sossi.base + reg);75}76 77static inline u16 sossi_read_reg16(int reg)78{79 return readw(sossi.base + reg);80}81 82static inline u8 sossi_read_reg8(int reg)83{84 return readb(sossi.base + reg);85}86 87static inline void sossi_write_reg(int reg, u32 value)88{89 writel(value, sossi.base + reg);90}91 92static inline void sossi_write_reg16(int reg, u16 value)93{94 writew(value, sossi.base + reg);95}96 97static inline void sossi_write_reg8(int reg, u8 value)98{99 writeb(value, sossi.base + reg);100}101 102static void sossi_set_bits(int reg, u32 bits)103{104 sossi_write_reg(reg, sossi_read_reg(reg) | bits);105}106 107static void sossi_clear_bits(int reg, u32 bits)108{109 sossi_write_reg(reg, sossi_read_reg(reg) & ~bits);110}111 112#define HZ_TO_PS(x) (1000000000 / (x / 1000))113 114static 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 120static 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 > reon129 */130 reon = ps_to_sossi_ticks(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(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(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(t->access_time, div);156 if (actim < reoff)157 actim++;158 /*159 * access time (data hold time) will be exactly one sossi160 * tick161 */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 171static 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 >= weoff180 */181 weon = ps_to_sossi_ticks(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(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(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 210static void _set_timing(int div, int tw0, int tw1)211{212 u32 l;213 214#ifdef VERBOSE215 dev_dbg(sossi.fbdev->dev, "Using TW0 = %d, TW1 = %d, div = %d\n",216 tw0 + 1, tw1 + 1, div);217#endif218 219 clk_set_rate(sossi.fck, sossi.fck_hz / div);220 clk_enable(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, l);225 clk_disable(sossi.fck);226}227 228static 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, l);236}237 238static 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, l);248 if (mode)249 sossi_set_bits(SOSSI_INIT2_REG, 1 << 6); /* TE logic */250 else251 sossi_clear_bits(SOSSI_INIT2_REG, 1 << 6);252}253 254static inline void set_timing(int access)255{256 if (access != sossi.last_access) {257 sossi.last_access = access;258 _set_timing(sossi.clk_div,259 sossi.clk_tw0[access], sossi.clk_tw1[access]);260 }261}262 263static void sossi_start_transfer(void)264{265 /* WE */266 sossi_clear_bits(SOSSI_INIT2_REG, 1 << 4);267 /* CS active low */268 sossi_clear_bits(SOSSI_INIT1_REG, 1 << 30);269}270 271static void sossi_stop_transfer(void)272{273 /* WE */274 sossi_set_bits(SOSSI_INIT2_REG, 1 << 4);275 /* CS active low */276 sossi_set_bits(SOSSI_INIT1_REG, 1 << 30);277}278 279static 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 285static void send_data(const void *data, unsigned int len)286{287 while (len >= 4) {288 sossi_write_reg(SOSSI_FIFO_REG, *(const u32 *) data);289 len -= 4;290 data += 4;291 }292 while (len >= 2) {293 sossi_write_reg16(SOSSI_FIFO_REG, *(const u16 *) data);294 len -= 2;295 data += 2;296 }297 while (len) {298 sossi_write_reg8(SOSSI_FIFO_REG, *(const u8 *) data);299 len--;300 data++;301 }302}303 304static 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, 0x3ffff);311 sossi_set_bits(SOSSI_INIT1_REG, (nr_cycles - 1) & 0x3ffff);312}313 314static 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 338static 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 351static 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 357static 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, although363 * with remapping/reordering disabled it will be calculated by HW364 * 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 383static 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(hs_pulse_time, div);394 vs = ps_to_sossi_ticks(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(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 else416 l &= ~(1 << 29);417 if (vs_pol_inv)418 l |= 1 << 28;419 else420 l &= ~(1 << 28);421 sossi_write_reg(SOSSI_TEARING_REG, l);422 clk_disable(sossi.fck);423 424 return 0;425}426 427static 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 else438 mode = 3; /* VS only */439 } else440 mode = 0;441 sossi.tearsync_line = line;442 sossi.tearsync_mode = mode;443 444 return 0;445}446 447static void sossi_write_command(const void *data, unsigned int len)448{449 clk_enable(sossi.fck);450 set_timing(WR_ACCESS);451 _set_bits_per_cycle(sossi.bus_pick_count, sossi.bus_pick_width);452 /* CMD#/DATA */453 sossi_clear_bits(SOSSI_INIT1_REG, 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(sossi.fck);460}461 462static void sossi_write_data(const void *data, unsigned int len)463{464 clk_enable(sossi.fck);465 set_timing(WR_ACCESS);466 _set_bits_per_cycle(sossi.bus_pick_count, sossi.bus_pick_width);467 /* CMD#/DATA */468 sossi_set_bits(SOSSI_INIT1_REG, 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(sossi.fck);475}476 477static 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(sossi.fck);486 set_timing(WR_ACCESS);487 _set_bits_per_cycle(sossi.bus_pick_count, sossi.bus_pick_width);488 _set_tearsync_mode(sossi.tearsync_mode, sossi.tearsync_line);489 /* CMD#/DATA */490 sossi_set_bits(SOSSI_INIT1_REG, 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 only497 * then. We can't seem to be able to use HW sync DMA for498 * this since LCD DMA shows huge latencies, as if it499 * 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(&sossi.lock, flags);506 } else507 /* Just start the transfer right away. */508 omap_enable_lcd_dma();509}510 511static void sossi_dma_callback(void *data)512{513 omap_stop_lcd_dma();514 sossi_stop_transfer();515 clk_disable(sossi.fck);516 sossi.lcdc_callback(sossi.lcdc_callback_data);517}518 519static void sossi_read_data(void *data, unsigned int len)520{521 clk_enable(sossi.fck);522 set_timing(RD_ACCESS);523 _set_bits_per_cycle(sossi.bus_pick_count, sossi.bus_pick_width);524 /* CMD#/DATA */525 sossi_set_bits(SOSSI_INIT1_REG, 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(sossi.fck);545}546 547static 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(&sossi.lock, flags);557 return IRQ_HANDLED;558}559 560static 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(fbdev->dev, "ck_dpll1out");577 if (IS_ERR(dpll1out_ck)) {578 dev_err(fbdev->dev, "can't get DPLL1OUT clock\n");579 return PTR_ERR(dpll1out_ck);580 }581 /*582 * We need the parent clock rate, which we might divide further583 * depending on the timing requirements of the controller. See584 * _set_timings.585 */586 sossi.fck_hz = clk_get_rate(dpll1out_ck);587 clk_put(dpll1out_ck);588 589 fck = clk_get(fbdev->dev, "ck_sossi");590 if (IS_ERR(fck)) {591 dev_err(fbdev->dev, "can't get SoSSI functional clock\n");592 return PTR_ERR(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(l, MOD_CONF_CTRL_1);600 l &= ~CONF_SOSSI_RESET_R;601 omap_writel(l, MOD_CONF_CTRL_1);602 603 clk_prepare_enable(sossi.fck);604 l = omap_readl(ARM_IDLECT2);605 l &= ~(1 << 8); /* DMACK_REQ */606 omap_writel(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, l);612 /* Take SoSSI out of reset */613 l &= ~(1 << 1);614 sossi_write_reg(SOSSI_INIT2_REG, l);615 616 sossi_write_reg(SOSSI_ID_REG, 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(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, l);642 643 if ((r = request_irq(fbdev->ext_irq, sossi_match_irq,644 IRQ_TYPE_EDGE_FALLING,645 "sossi_match", 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(sossi.fck);651 return 0;652 653err:654 clk_disable_unprepare(sossi.fck);655 clk_put(sossi.fck);656 return r;657}658 659static void sossi_cleanup(void)660{661 omap_lcdc_free_dma_callback();662 clk_unprepare(sossi.fck);663 clk_put(sossi.fck);664 iounmap(sossi.base);665}666 667struct 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