qemu/hw/ssi/omap_spi.c
<<
>>
Prefs
   1/*
   2 * TI OMAP processor's Multichannel SPI emulation.
   3 *
   4 * Copyright (C) 2007-2009 Nokia Corporation
   5 *
   6 * Original code for OMAP2 by Andrzej Zaborowski <andrew@openedhand.com>
   7 *
   8 * This program is free software; you can redistribute it and/or
   9 * modify it under the terms of the GNU General Public License as
  10 * published by the Free Software Foundation; either version 2 or
  11 * (at your option) any later version of the License.
  12 *
  13 * This program is distributed in the hope that it will be useful,
  14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16 * GNU General Public License for more details.
  17 *
  18 * You should have received a copy of the GNU General Public License along
  19 * with this program; if not, write to the Free Software Foundation, Inc.,
  20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  21 */
  22#include "qemu/osdep.h"
  23#include "hw/hw.h"
  24#include "hw/arm/omap.h"
  25
  26/* Multichannel SPI */
  27struct omap_mcspi_s {
  28    MemoryRegion iomem;
  29    qemu_irq irq;
  30    int chnum;
  31
  32    uint32_t sysconfig;
  33    uint32_t systest;
  34    uint32_t irqst;
  35    uint32_t irqen;
  36    uint32_t wken;
  37    uint32_t control;
  38
  39    struct omap_mcspi_ch_s {
  40        qemu_irq txdrq;
  41        qemu_irq rxdrq;
  42        uint32_t (*txrx)(void *opaque, uint32_t, int);
  43        void *opaque;
  44
  45        uint32_t tx;
  46        uint32_t rx;
  47
  48        uint32_t config;
  49        uint32_t status;
  50        uint32_t control;
  51    } ch[4];
  52};
  53
  54static inline void omap_mcspi_interrupt_update(struct omap_mcspi_s *s)
  55{
  56    qemu_set_irq(s->irq, s->irqst & s->irqen);
  57}
  58
  59static inline void omap_mcspi_dmarequest_update(struct omap_mcspi_ch_s *ch)
  60{
  61    qemu_set_irq(ch->txdrq,
  62                    (ch->control & 1) &&                /* EN */
  63                    (ch->config & (1 << 14)) &&         /* DMAW */
  64                    (ch->status & (1 << 1)) &&          /* TXS */
  65                    ((ch->config >> 12) & 3) != 1);     /* TRM */
  66    qemu_set_irq(ch->rxdrq,
  67                    (ch->control & 1) &&                /* EN */
  68                    (ch->config & (1 << 15)) &&         /* DMAW */
  69                    (ch->status & (1 << 0)) &&          /* RXS */
  70                    ((ch->config >> 12) & 3) != 2);     /* TRM */
  71}
  72
  73static void omap_mcspi_transfer_run(struct omap_mcspi_s *s, int chnum)
  74{
  75    struct omap_mcspi_ch_s *ch = s->ch + chnum;
  76
  77    if (!(ch->control & 1))                             /* EN */
  78        return;
  79    if ((ch->status & (1 << 0)) &&                      /* RXS */
  80                    ((ch->config >> 12) & 3) != 2 &&    /* TRM */
  81                    !(ch->config & (1 << 19)))          /* TURBO */
  82        goto intr_update;
  83    if ((ch->status & (1 << 1)) &&                      /* TXS */
  84                    ((ch->config >> 12) & 3) != 1)      /* TRM */
  85        goto intr_update;
  86
  87    if (!(s->control & 1) ||                            /* SINGLE */
  88                    (ch->config & (1 << 20))) {         /* FORCE */
  89        if (ch->txrx)
  90            ch->rx = ch->txrx(ch->opaque, ch->tx,       /* WL */
  91                            1 + (0x1f & (ch->config >> 7)));
  92    }
  93
  94    ch->tx = 0;
  95    ch->status |= 1 << 2;                               /* EOT */
  96    ch->status |= 1 << 1;                               /* TXS */
  97    if (((ch->config >> 12) & 3) != 2)                  /* TRM */
  98        ch->status |= 1 << 0;                           /* RXS */
  99
 100intr_update:
 101    if ((ch->status & (1 << 0)) &&                      /* RXS */
 102                    ((ch->config >> 12) & 3) != 2 &&    /* TRM */
 103                    !(ch->config & (1 << 19)))          /* TURBO */
 104        s->irqst |= 1 << (2 + 4 * chnum);               /* RX_FULL */
 105    if ((ch->status & (1 << 1)) &&                      /* TXS */
 106                    ((ch->config >> 12) & 3) != 1)      /* TRM */
 107        s->irqst |= 1 << (0 + 4 * chnum);               /* TX_EMPTY */
 108    omap_mcspi_interrupt_update(s);
 109    omap_mcspi_dmarequest_update(ch);
 110}
 111
 112void omap_mcspi_reset(struct omap_mcspi_s *s)
 113{
 114    int ch;
 115
 116    s->sysconfig = 0;
 117    s->systest = 0;
 118    s->irqst = 0;
 119    s->irqen = 0;
 120    s->wken = 0;
 121    s->control = 4;
 122
 123    for (ch = 0; ch < 4; ch ++) {
 124        s->ch[ch].config = 0x060000;
 125        s->ch[ch].status = 2;                           /* TXS */
 126        s->ch[ch].control = 0;
 127
 128        omap_mcspi_dmarequest_update(s->ch + ch);
 129    }
 130
 131    omap_mcspi_interrupt_update(s);
 132}
 133
 134static uint64_t omap_mcspi_read(void *opaque, hwaddr addr,
 135                                unsigned size)
 136{
 137    struct omap_mcspi_s *s = (struct omap_mcspi_s *) opaque;
 138    int ch = 0;
 139    uint32_t ret;
 140
 141    if (size != 4) {
 142        return omap_badwidth_read32(opaque, addr);
 143    }
 144
 145    switch (addr) {
 146    case 0x00:  /* MCSPI_REVISION */
 147        return 0x91;
 148
 149    case 0x10:  /* MCSPI_SYSCONFIG */
 150        return s->sysconfig;
 151
 152    case 0x14:  /* MCSPI_SYSSTATUS */
 153        return 1;                                       /* RESETDONE */
 154
 155    case 0x18:  /* MCSPI_IRQSTATUS */
 156        return s->irqst;
 157
 158    case 0x1c:  /* MCSPI_IRQENABLE */
 159        return s->irqen;
 160
 161    case 0x20:  /* MCSPI_WAKEUPENABLE */
 162        return s->wken;
 163
 164    case 0x24:  /* MCSPI_SYST */
 165        return s->systest;
 166
 167    case 0x28:  /* MCSPI_MODULCTRL */
 168        return s->control;
 169
 170    case 0x68: ch ++;
 171        /* fall through */
 172    case 0x54: ch ++;
 173        /* fall through */
 174    case 0x40: ch ++;
 175        /* fall through */
 176    case 0x2c:  /* MCSPI_CHCONF */
 177        return s->ch[ch].config;
 178
 179    case 0x6c: ch ++;
 180        /* fall through */
 181    case 0x58: ch ++;
 182        /* fall through */
 183    case 0x44: ch ++;
 184        /* fall through */
 185    case 0x30:  /* MCSPI_CHSTAT */
 186        return s->ch[ch].status;
 187
 188    case 0x70: ch ++;
 189        /* fall through */
 190    case 0x5c: ch ++;
 191        /* fall through */
 192    case 0x48: ch ++;
 193        /* fall through */
 194    case 0x34:  /* MCSPI_CHCTRL */
 195        return s->ch[ch].control;
 196
 197    case 0x74: ch ++;
 198        /* fall through */
 199    case 0x60: ch ++;
 200        /* fall through */
 201    case 0x4c: ch ++;
 202        /* fall through */
 203    case 0x38:  /* MCSPI_TX */
 204        return s->ch[ch].tx;
 205
 206    case 0x78: ch ++;
 207        /* fall through */
 208    case 0x64: ch ++;
 209        /* fall through */
 210    case 0x50: ch ++;
 211        /* fall through */
 212    case 0x3c:  /* MCSPI_RX */
 213        s->ch[ch].status &= ~(1 << 0);                  /* RXS */
 214        ret = s->ch[ch].rx;
 215        omap_mcspi_transfer_run(s, ch);
 216        return ret;
 217    }
 218
 219    OMAP_BAD_REG(addr);
 220    return 0;
 221}
 222
 223static void omap_mcspi_write(void *opaque, hwaddr addr,
 224                             uint64_t value, unsigned size)
 225{
 226    struct omap_mcspi_s *s = (struct omap_mcspi_s *) opaque;
 227    int ch = 0;
 228
 229    if (size != 4) {
 230        omap_badwidth_write32(opaque, addr, value);
 231        return;
 232    }
 233
 234    switch (addr) {
 235    case 0x00:  /* MCSPI_REVISION */
 236    case 0x14:  /* MCSPI_SYSSTATUS */
 237    case 0x30:  /* MCSPI_CHSTAT0 */
 238    case 0x3c:  /* MCSPI_RX0 */
 239    case 0x44:  /* MCSPI_CHSTAT1 */
 240    case 0x50:  /* MCSPI_RX1 */
 241    case 0x58:  /* MCSPI_CHSTAT2 */
 242    case 0x64:  /* MCSPI_RX2 */
 243    case 0x6c:  /* MCSPI_CHSTAT3 */
 244    case 0x78:  /* MCSPI_RX3 */
 245        OMAP_RO_REG(addr);
 246        return;
 247
 248    case 0x10:  /* MCSPI_SYSCONFIG */
 249        if (value & (1 << 1))                           /* SOFTRESET */
 250            omap_mcspi_reset(s);
 251        s->sysconfig = value & 0x31d;
 252        break;
 253
 254    case 0x18:  /* MCSPI_IRQSTATUS */
 255        if (!((s->control & (1 << 3)) && (s->systest & (1 << 11)))) {
 256            s->irqst &= ~value;
 257            omap_mcspi_interrupt_update(s);
 258        }
 259        break;
 260
 261    case 0x1c:  /* MCSPI_IRQENABLE */
 262        s->irqen = value & 0x1777f;
 263        omap_mcspi_interrupt_update(s);
 264        break;
 265
 266    case 0x20:  /* MCSPI_WAKEUPENABLE */
 267        s->wken = value & 1;
 268        break;
 269
 270    case 0x24:  /* MCSPI_SYST */
 271        if (s->control & (1 << 3))                      /* SYSTEM_TEST */
 272            if (value & (1 << 11)) {                    /* SSB */
 273                s->irqst |= 0x1777f;
 274                omap_mcspi_interrupt_update(s);
 275            }
 276        s->systest = value & 0xfff;
 277        break;
 278
 279    case 0x28:  /* MCSPI_MODULCTRL */
 280        if (value & (1 << 3))                           /* SYSTEM_TEST */
 281            if (s->systest & (1 << 11)) {               /* SSB */
 282                s->irqst |= 0x1777f;
 283                omap_mcspi_interrupt_update(s);
 284            }
 285        s->control = value & 0xf;
 286        break;
 287
 288    case 0x68: ch ++;
 289        /* fall through */
 290    case 0x54: ch ++;
 291        /* fall through */
 292    case 0x40: ch ++;
 293        /* fall through */
 294    case 0x2c:  /* MCSPI_CHCONF */
 295        if ((value ^ s->ch[ch].config) & (3 << 14))     /* DMAR | DMAW */
 296            omap_mcspi_dmarequest_update(s->ch + ch);
 297        if (((value >> 12) & 3) == 3)                   /* TRM */
 298            fprintf(stderr, "%s: invalid TRM value (3)\n", __FUNCTION__);
 299        if (((value >> 7) & 0x1f) < 3)                  /* WL */
 300            fprintf(stderr, "%s: invalid WL value (%" PRIx64 ")\n",
 301                            __FUNCTION__, (value >> 7) & 0x1f);
 302        s->ch[ch].config = value & 0x7fffff;
 303        break;
 304
 305    case 0x70: ch ++;
 306        /* fall through */
 307    case 0x5c: ch ++;
 308        /* fall through */
 309    case 0x48: ch ++;
 310        /* fall through */
 311    case 0x34:  /* MCSPI_CHCTRL */
 312        if (value & ~s->ch[ch].control & 1) {           /* EN */
 313            s->ch[ch].control |= 1;
 314            omap_mcspi_transfer_run(s, ch);
 315        } else
 316            s->ch[ch].control = value & 1;
 317        break;
 318
 319    case 0x74: ch ++;
 320        /* fall through */
 321    case 0x60: ch ++;
 322        /* fall through */
 323    case 0x4c: ch ++;
 324        /* fall through */
 325    case 0x38:  /* MCSPI_TX */
 326        s->ch[ch].tx = value;
 327        s->ch[ch].status &= ~(1 << 1);                  /* TXS */
 328        omap_mcspi_transfer_run(s, ch);
 329        break;
 330
 331    default:
 332        OMAP_BAD_REG(addr);
 333        return;
 334    }
 335}
 336
 337static const MemoryRegionOps omap_mcspi_ops = {
 338    .read = omap_mcspi_read,
 339    .write = omap_mcspi_write,
 340    .endianness = DEVICE_NATIVE_ENDIAN,
 341};
 342
 343struct omap_mcspi_s *omap_mcspi_init(struct omap_target_agent_s *ta, int chnum,
 344                qemu_irq irq, qemu_irq *drq, omap_clk fclk, omap_clk iclk)
 345{
 346    struct omap_mcspi_s *s = g_new0(struct omap_mcspi_s, 1);
 347    struct omap_mcspi_ch_s *ch = s->ch;
 348
 349    s->irq = irq;
 350    s->chnum = chnum;
 351    while (chnum --) {
 352        ch->txdrq = *drq ++;
 353        ch->rxdrq = *drq ++;
 354        ch ++;
 355    }
 356    omap_mcspi_reset(s);
 357
 358    memory_region_init_io(&s->iomem, NULL, &omap_mcspi_ops, s, "omap.mcspi",
 359                          omap_l4_region_size(ta, 0));
 360    omap_l4_attach(ta, 0, &s->iomem);
 361
 362    return s;
 363}
 364
 365void omap_mcspi_attach(struct omap_mcspi_s *s,
 366                uint32_t (*txrx)(void *opaque, uint32_t, int), void *opaque,
 367                int chipselect)
 368{
 369    if (chipselect < 0 || chipselect >= s->chnum)
 370        hw_error("%s: Bad chipselect %i\n", __FUNCTION__, chipselect);
 371
 372    s->ch[chipselect].txrx = txrx;
 373    s->ch[chipselect].opaque = opaque;
 374}
 375