Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * OMAP1 Special OptimiSed Screen Interface support
0004  *
0005  * Copyright (C) 2004-2005 Nokia Corporation
0006  * Author: Juha Yrjölä <juha.yrjola@nokia.com>
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     /* timing for read and write access */
0060     int     clk_div;
0061     u8      clk_tw0[2];
0062     u8      clk_tw1[2];
0063     /*
0064      * if last_access is the same as current we don't have to change
0065      * the timings
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      * Make sure that after conversion it still holds that:
0128      * reoff > reon, recyc >= reoff, actim > reon
0129      */
0130     reon = ps_to_sossi_ticks(t->re_on_time, div);
0131     /* reon will be exactly one sossi tick */
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     /* values less then 3 result in the SOSSI block resetting itself */
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      * access time (data hold time) will be exactly one sossi
0160      * tick
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      * Make sure that after conversion it still holds that:
0179      * weoff > weon, wecyc >= weoff
0180      */
0181     weon = ps_to_sossi_ticks(t->we_on_time, div);
0182     /* weon will be exactly one sossi tick */
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     /* values less then 3 result in the SOSSI block resetting itself */
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);    /* TE logic */
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     /* WE */
0266     sossi_clear_bits(SOSSI_INIT2_REG, 1 << 4);
0267     /* CS active low */
0268     sossi_clear_bits(SOSSI_INIT1_REG, 1 << 30);
0269 }
0270 
0271 static void sossi_stop_transfer(void)
0272 {
0273     /* WE */
0274     sossi_set_bits(SOSSI_INIT2_REG, 1 << 4);
0275     /* CS active low */
0276     sossi_set_bits(SOSSI_INIT1_REG, 1 << 30);
0277 }
0278 
0279 static void wait_end_of_write(void)
0280 {
0281     /* Before reading we must check if some writings are going on */
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     /* no CS on SOSSI, so ignore cson, csoff, cs_pulsewidth */
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      * We set explicitly the bus_pick_count as well, although
0363      * with remapping/reordering disabled it will be calculated by HW
0364      * as (32 / bus_pick_width).
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;       /* HS or VS */
0437         else
0438             mode = 3;       /* VS only */
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     /* CMD#/DATA */
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     /* CMD#/DATA */
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     /* CMD#/DATA */
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          * Wait for the sync signal and start the transfer only
0497          * then. We can't seem to be able to use HW sync DMA for
0498          * this since LCD DMA shows huge latencies, as if it
0499          * would ignore some of the DMA requests from SoSSI.
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         /* Just start the transfer right away. */
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     /* CMD#/DATA */
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      * We need the parent clock rate, which we might divide further
0583      * depending on the timing requirements of the controller. See
0584      * _set_timings.
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     /* Reset and enable the SoSSI module */
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);         /* DMACK_REQ */
0606     omap_writel(l, ARM_IDLECT2);
0607 
0608     l = sossi_read_reg(SOSSI_INIT2_REG);
0609     /* Enable and reset the SoSSI block */
0610     l |= (1 << 0) | (1 << 1);
0611     sossi_write_reg(SOSSI_INIT2_REG, l);
0612     /* Take SoSSI out of reset */
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); /* Component code */
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); /* DMA_MODE */
0640     l &= ~(1 << 31); /* REORDERING */
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