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