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