linux/drivers/video/fbdev/omap/sossi.c
<<
>>
Prefs
   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
  45static 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
  70static inline u32 sossi_read_reg(int reg)
  71{
  72        return readl(sossi.base + reg);
  73}
  74
  75static inline u16 sossi_read_reg16(int reg)
  76{
  77        return readw(sossi.base + reg);
  78}
  79
  80static inline u8 sossi_read_reg8(int reg)
  81{
  82        return readb(sossi.base + reg);
  83}
  84
  85static inline void sossi_write_reg(int reg, u32 value)
  86{
  87        writel(value, sossi.base + reg);
  88}
  89
  90static inline void sossi_write_reg16(int reg, u16 value)
  91{
  92        writew(value, sossi.base + reg);
  93}
  94
  95static inline void sossi_write_reg8(int reg, u8 value)
  96{
  97        writeb(value, sossi.base + reg);
  98}
  99
 100static void sossi_set_bits(int reg, u32 bits)
 101{
 102        sossi_write_reg(reg, sossi_read_reg(reg) | bits);
 103}
 104
 105static 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
 112static 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
 118static 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
 169static 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
 208static 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
 226static 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
 236static 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
 252static 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
 261static 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
 269static 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
 277static 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
 283static 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
 302static 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
 312static 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
 336static 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
 349static 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
 355static 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
 381static 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
 425static 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
 445static 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
 460static 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
 475static 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
 509static 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
 517static 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
 545static 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
 558static 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
 651err:
 652        clk_disable(sossi.fck);
 653        clk_put(sossi.fck);
 654        return r;
 655}
 656
 657static void sossi_cleanup(void)
 658{
 659        omap_lcdc_free_dma_callback();
 660        clk_put(sossi.fck);
 661        iounmap(sossi.base);
 662}
 663
 664struct 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
 681