2
* TI OMAP processor's Multichannel SPI emulation.
4
* Copyright (C) 2007-2009 Nokia Corporation
6
* Original code for OMAP2 by Andrzej Zaborowski <andrew@openedhand.com>
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.
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.
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.
23
#include "hw/arm/omap.h"
28
#define TRACE(fmt,...) fprintf(stderr, "%s@%d: " fmt "\n", __FUNCTION__, \
29
__LINE__, ##__VA_ARGS__);
34
#define SPI_FIFOSIZE 64
35
#define SPI_REV_OMAP2420 0x14
36
#define SPI_REV_OMAP3430 0x21
37
#define IS_OMAP3_SPI(s) ((s)->revision >= SPI_REV_OMAP3430)
53
struct omap_mcspi_fifo_s {
57
uint8_t buf[SPI_FIFOSIZE];
62
struct omap_mcspi_ch_s {
65
uint32_t (*txrx)(void *opaque, uint32_t, int);
77
static inline void omap_mcspi_interrupt_update(struct omap_mcspi_s *s)
79
qemu_set_irq(s->irq, s->irqst & s->irqen);
82
static inline void omap_mcspi_dmarequest_update(struct omap_mcspi_s *s,
85
struct omap_mcspi_ch_s *ch = &s->ch[chnum];
86
if ((ch->control & 1) && /* EN */
87
(ch->config & (1 << 14)) && /* DMAW */
88
(ch->status & (1 << 1)) && /* TXS */
89
((ch->config >> 12) & 3) != 1) { /* TRM */
90
if (!IS_OMAP3_SPI(s) ||
91
!(ch->config & (1 << 27)) || /* FFEW */
92
s->tx_fifo.len <= (s->xferlevel & 0x3f)) /* AEL */
93
qemu_irq_raise(ch->txdrq);
95
qemu_irq_lower(ch->txdrq);
97
if ((ch->control & 1) && /* EN */
98
(ch->config & (1 << 15)) && /* DMAW */
99
(ch->status & (1 << 0)) && /* RXS */
100
((ch->config >> 12) & 3) != 2) { /* TRM */
101
if (!IS_OMAP3_SPI(s) ||
102
!(ch->config & (1 << 28)) || /* FFER */
103
s->rx_fifo.len >= ((s->xferlevel >> 8) & 0x3f)) /* AFL */
104
qemu_irq_raise(ch->rxdrq);
106
qemu_irq_lower(ch->rxdrq);
110
static void omap_mcspi_fifo_reset(struct omap_mcspi_s *s)
112
struct omap_mcspi_ch_s *ch;
116
s->tx_fifo.start = 0;
117
s->rx_fifo.start = 0;
118
if (s->fifo_ch < 0) {
119
s->tx_fifo.size = s->rx_fifo.size = 0;
121
ch = &s->ch[s->fifo_ch];
122
s->tx_fifo.size = ((ch->config >> 27) & 1) ? SPI_FIFOSIZE : 0;
123
s->rx_fifo.size = ((ch->config >> 28) & 1) ? SPI_FIFOSIZE : 0;
124
if (((ch->config >> 27) & 3) == 3) {
125
s->tx_fifo.size >>= 1;
126
s->rx_fifo.size >>= 1;
131
/* returns next word in FIFO or the n first bytes if there is not
132
* enough data in FIFO */
133
static uint32_t omap_mcspi_fifo_get(struct omap_mcspi_fifo_s *s, int wl)
137
for (v = 0, sh = 0; wl > 0 && s->len; wl -= 8, s->len--, sh += 8) {
138
v |= ((uint32_t)s->buf[s->start++]) << sh;
139
if (s->start >= s->size)
145
/* pushes a word to FIFO or the first n bytes of the word if the FIFO
146
* is too full to hold the full word */
147
static void omap_mcspi_fifo_put(struct omap_mcspi_fifo_s *s, int wl,
150
int p = s->start + s->len;
152
for (; wl > 0 && s->len < s->size; wl -=8, v >>= 8, s->len++) {
155
s->buf[p++] = (uint8_t)(v & 0xff);
159
static void omap_mcspi_transfer_run(struct omap_mcspi_s *s, int chnum)
161
struct omap_mcspi_ch_s *ch = s->ch + chnum;
162
int trm = (ch->config >> 12) & 3;
165
if (!(ch->control & 1)) /* EN */
167
if ((ch->status & 1) && trm != 2 && /* RXS */
168
!(ch->config & (1 << 19))) /* TURBO */
170
if ((ch->status & (1 << 1)) && trm != 1) /* TXS */
173
if (!(s->control & 1) || /* SINGLE */
174
(ch->config & (1 << 20))) { /* FORCE */
176
wl = 1 + (0x1f & (ch->config >> 7)); /* WL */
177
if (!IS_OMAP3_SPI(s) || s->fifo_ch != chnum ||
178
!((ch->config >> 27) & 3)) { /* FFER | FFEW */
179
ch->rx = ch->txrx(ch->opaque, ch->tx, wl);
181
switch ((ch->config >> 27) & 3) {
182
case 1: /* !FFER, FFEW */
184
ch->tx = omap_mcspi_fifo_get(&s->tx_fifo, wl);
185
ch->rx = ch->txrx(ch->opaque, ch->tx, wl);
188
case 2: /* FFER, !FFEW */
189
ch->rx = ch->txrx(ch->opaque, ch->tx, wl);
191
omap_mcspi_fifo_put(&s->rx_fifo, wl, ch->rx);
194
case 3: /* FFER, FFEW */
195
while (s->rx_fifo.len < s->rx_fifo.size &&
196
s->tx_fifo.len && s->fifo_wcnt) {
198
ch->tx = omap_mcspi_fifo_get(&s->tx_fifo, wl);
199
ch->rx = ch->txrx(ch->opaque, ch->tx, wl);
201
omap_mcspi_fifo_put(&s->rx_fifo, wl, ch->rx);
208
if ((ch->config & (1 << 28)) && /* FFER */
209
s->rx_fifo.len >= s->rx_fifo.size)
210
ch->status |= 1 << 6; /* RXFFF */
211
ch->status &= ~(1 << 5); /* RXFFE */
212
ch->status &= ~(1 << 4); /* TXFFF */
213
if ((ch->config & (1 << 27)) && /* FFEW */
215
ch->status |= 1 << 3; /* TXFFE */
217
((s->xferlevel >> 16) & 0xffff)) /* WCNT */
218
s->irqst |= 1 << 17; /* EOW */
224
ch->status |= 1 << 2; /* EOT */
225
ch->status |= 1 << 1; /* TXS */
227
ch->status |= 1; /* RXS */
230
if ((ch->status & 1) && trm != 2 && /* RXS */
231
!(ch->config & (1 << 19))) /* TURBO */
232
if (!IS_OMAP3_SPI(s) || s->fifo_ch != chnum ||
233
!((ch->config >> 28) & 1) || /* FFER */
234
s->rx_fifo.len >= ((s->xferlevel >> 8) & 0x3f)) /* AFL */
235
s->irqst |= 1 << (2 + 4 * chnum); /* RX_FULL */
236
if ((ch->status & (1 << 1)) && trm != 1) /* TXS */
237
if (!IS_OMAP3_SPI(s) || s->fifo_ch != chnum ||
238
!((ch->config >> 27) & 1) || /* FFEW */
239
s->tx_fifo.len <= (s->xferlevel & 0x3f)) /* AEL */
240
s->irqst |= 1 << (4 * chnum); /* TX_EMPTY */
241
omap_mcspi_interrupt_update(s);
242
omap_mcspi_dmarequest_update(s, chnum);
245
void omap_mcspi_reset(struct omap_mcspi_s *s)
257
omap_mcspi_fifo_reset(s);
259
for (ch = 0; ch < s->chnum; ch ++) {
260
s->ch[ch].config = 0x060000;
261
s->ch[ch].status = 2; /* TXS */
262
s->ch[ch].control = 0;
264
omap_mcspi_dmarequest_update(s, ch);
267
omap_mcspi_interrupt_update(s);
270
static uint64_t omap_mcspi_read(void *opaque, hwaddr addr,
273
struct omap_mcspi_s *s = (struct omap_mcspi_s *) opaque;
278
return omap_badwidth_read32(opaque, addr);
282
case 0x00: /* MCSPI_REVISION */
283
TRACE("REVISION = 0x%08x", s->revision);
286
case 0x10: /* MCSPI_SYSCONFIG */
287
TRACE("SYSCONFIG = 0x%08x", s->sysconfig);
290
case 0x14: /* MCSPI_SYSSTATUS */
291
TRACE("SYSSTATUS = 0x00000001");
292
return 1; /* RESETDONE */
294
case 0x18: /* MCSPI_IRQSTATUS */
295
TRACE("IRQSTATUS = 0x%08x", s->irqst);
298
case 0x1c: /* MCSPI_IRQENABLE */
299
TRACE("IRQENABLE = 0x%08x", s->irqen);
302
case 0x20: /* MCSPI_WAKEUPENABLE */
303
TRACE("WAKEUPENABLE = 0x%08x", s->wken);
306
case 0x24: /* MCSPI_SYST */
307
TRACE("SYST = 0x%08x", s->systest);
310
case 0x28: /* MCSPI_MODULCTRL */
311
TRACE("MODULCTRL = 0x%08x", s->control);
320
case 0x2c: /* MCSPI_CHCONF */
321
TRACE("CHCONF%d = 0x%08x", ch,
322
(ch < s->chnum) ? s->ch[ch].config : 0);
323
return (ch < s->chnum) ? s->ch[ch].config : 0;
331
case 0x30: /* MCSPI_CHSTAT */
332
TRACE("CHSTAT%d = 0x%08x", ch,
333
(ch < s->chnum) ? s->ch[ch].status : 0);
334
return (ch < s->chnum) ? s->ch[ch].status : 0;
342
case 0x34: /* MCSPI_CHCTRL */
343
TRACE("CHCTRL%d = 0x%08x", ch,
344
(ch < s->chnum) ? s->ch[ch].control : 0);
345
return (ch < s->chnum) ? s->ch[ch].control : 0;
353
case 0x38: /* MCSPI_TX */
354
TRACE("TX%d = 0x%08x", ch,
355
(ch < s->chnum) ? s->ch[ch].tx : 0);
356
return (ch < s->chnum) ? s->ch[ch].tx : 0;
364
case 0x3c: /* MCSPI_RX */
366
if (!IS_OMAP3_SPI(s) || ch != s->fifo_ch ||
367
!(s->ch[ch].config & (1 << 28))) { /* FFER */
368
s->ch[ch].status &= ~1; /* RXS */
370
TRACE("RX%d = 0x%08x", ch, ret);
371
omap_mcspi_transfer_run(s, ch);
374
if (!s->rx_fifo.len) {
375
TRACE("rxfifo underflow!");
377
qemu_irq_lower(s->ch[ch].rxdrq);
378
s->ch[ch].status &= ~(1 << 6); /* RXFFF */
379
if (((s->ch[ch].config >> 12) & 3) != 2) /* TRM */
380
ret = omap_mcspi_fifo_get(&s->rx_fifo,
381
1 + ((s->ch[ch].config >> 7) & 0x1f)); /* WL */
384
TRACE("RX%d = 0x%08x", ch, ret);
385
if (!s->rx_fifo.len) {
386
s->ch[ch].status &= ~1; /* RXS */
387
s->ch[ch].status |= 1 << 5; /* RXFFE */
388
omap_mcspi_transfer_run(s, ch);
393
TRACE("RX%d = 0x00000000", ch);
396
case 0x7c: /* MCSPI_XFERLEVEL */
397
if (IS_OMAP3_SPI(s)) {
398
if ((s->xferlevel >> 16) & 0xffff) /* WCNT */
399
ret = ((s->xferlevel & 0xffff0000) - (s->fifo_wcnt << 16));
401
ret = ((-s->fifo_wcnt) & 0xffff) << 16;
402
TRACE("XFERLEVEL = 0x%08x", (s->xferlevel & 0xffff) | ret);
403
return (s->xferlevel & 0xffff) | ret;
415
static void omap_mcspi_write(void *opaque, hwaddr addr,
416
uint64_t value, unsigned size)
418
struct omap_mcspi_s *s = (struct omap_mcspi_s *) opaque;
423
return omap_badwidth_write32(opaque, addr, value);
427
case 0x00: /* MCSPI_REVISION */
428
case 0x14: /* MCSPI_SYSSTATUS */
429
case 0x30: /* MCSPI_CHSTAT0 */
430
case 0x3c: /* MCSPI_RX0 */
431
case 0x44: /* MCSPI_CHSTAT1 */
432
case 0x50: /* MCSPI_RX1 */
433
case 0x58: /* MCSPI_CHSTAT2 */
434
case 0x64: /* MCSPI_RX2 */
435
case 0x6c: /* MCSPI_CHSTAT3 */
436
case 0x78: /* MCSPI_RX3 */
437
/* silently ignore */
438
//OMAP_RO_REGV(addr, value);
441
case 0x10: /* MCSPI_SYSCONFIG */
442
TRACE("SYSCONFIG = 0x%08x", value);
443
if (value & (1 << 1)) /* SOFTRESET */
445
s->sysconfig = value & 0x31d;
448
case 0x18: /* MCSPI_IRQSTATUS */
449
TRACE("IRQSTATUS = 0x%08x", value);
450
if (!((s->control & (1 << 3)) && (s->systest & (1 << 11)))) {
452
omap_mcspi_interrupt_update(s);
456
case 0x1c: /* MCSPI_IRQENABLE */
457
TRACE("IRQENABLE = 0x%08x", value);
458
s->irqen = value & (IS_OMAP3_SPI(s) ? 0x3777f : 0x1777f);
459
omap_mcspi_interrupt_update(s);
462
case 0x20: /* MCSPI_WAKEUPENABLE */
463
TRACE("WAKEUPENABLE = 0x%08x", value);
467
case 0x24: /* MCSPI_SYST */
468
TRACE("SYST = 0x%08x", value);
469
if (s->control & (1 << 3)) /* SYSTEM_TEST */
470
if (value & (1 << 11)) { /* SSB */
472
omap_mcspi_interrupt_update(s);
474
s->systest = value & 0xfff;
477
case 0x28: /* MCSPI_MODULCTRL */
478
TRACE("MODULCTRL = 0x%08x", value);
479
if (value & (1 << 3)) /* SYSTEM_TEST */
480
if (s->systest & (1 << 11)) { /* SSB */
481
s->irqst |= IS_OMAP3_SPI(s) ? 0x3777f : 0x1777f;
482
omap_mcspi_interrupt_update(s);
484
s->control = value & 0xf;
493
case 0x2c: /* MCSPI_CHCONF */
494
TRACE("CHCONF%d = 0x%08x", ch, value);
496
old = s->ch[ch].config;
497
s->ch[ch].config = value & (IS_OMAP3_SPI(s)
498
? 0x3fffffff : 0x7fffff);
499
if (IS_OMAP3_SPI(s) &&
500
((value ^ old) & (3 << 27))) { /* FFER | FFEW */
501
s->fifo_ch = ((value & (3 << 27))) ? ch : -1;
502
omap_mcspi_fifo_reset(s);
504
if (((value ^ old) & (3 << 14)) || /* DMAR | DMAW */
506
((value ^ old) & (3 << 27)))) /* FFER | FFEW */
507
omap_mcspi_dmarequest_update(s, ch);
508
if (((value >> 12) & 3) == 3) { /* TRM */
509
TRACE("invalid TRM value (3)");
511
if (((value >> 7) & 0x1f) < 3) { /* WL */
512
TRACE("invalid WL value (%" PRIx64 ")", (value >> 7) & 0x1f);
514
if (IS_OMAP3_SPI(s) && ((value >> 23) & 1)) { /* SBE */
515
TRACE("start-bit mode is not supported");
526
case 0x34: /* MCSPI_CHCTRL */
527
TRACE("CHCTRL%d = 0x%08x", ch, value);
529
old = s->ch[ch].control;
530
s->ch[ch].control = value & (IS_OMAP3_SPI(s) ? 0xff01 : 1);
531
if (value & ~old & 1) { /* EN */
532
if (IS_OMAP3_SPI(s) && s->fifo_ch == ch)
533
omap_mcspi_fifo_reset(s);
534
omap_mcspi_transfer_run(s, ch);
545
case 0x38: /* MCSPI_TX */
546
TRACE("TX%d = 0x%08x", ch, value);
548
if (!IS_OMAP3_SPI(s) || s->fifo_ch != ch ||
549
!(s->ch[ch].config & (1 << 27))) { /* FFEW */
550
s->ch[ch].tx = value;
551
s->ch[ch].status &= ~0x06; /* EOT | TXS */
552
omap_mcspi_transfer_run(s, ch);
554
if (s->tx_fifo.len >= s->tx_fifo.size) {
555
TRACE("txfifo overflow!");
557
qemu_irq_lower(s->ch[ch].txdrq);
558
s->ch[ch].status &= ~0x0e; /* TXFFE | EOT | TXS */
559
if (((s->ch[ch].config >> 12) & 3) != 1) { /* TRM */
562
1 + ((s->ch[ch].config >> 7) & 0x1f), /* WL */
564
if (s->tx_fifo.len >= s->tx_fifo.size)
565
s->ch[ch].status |= 1 << 4; /* TXFFF */
566
if (s->tx_fifo.len >= (s->xferlevel & 0x3f))
567
omap_mcspi_transfer_run(s, ch);
569
s->ch[ch].tx = value;
570
omap_mcspi_transfer_run(s, ch);
577
case 0x7c: /* MCSPI_XFERLEVEL */
578
TRACE("XFERLEVEL = 0x%08x", value);
579
if (IS_OMAP3_SPI(s)) {
580
if (value != s->xferlevel) {
581
s->fifo_wcnt = (value >> 16) & 0xffff;
582
s->xferlevel = value & 0xffff3f3f;
583
omap_mcspi_fifo_reset(s);
595
static const MemoryRegionOps omap_mcspi_ops = {
596
.read = omap_mcspi_read,
597
.write = omap_mcspi_write,
598
.endianness = DEVICE_NATIVE_ENDIAN,
601
struct omap_mcspi_s *omap_mcspi_init(struct omap_target_agent_s *ta,
602
struct omap_mpu_state_s *mpu,
603
int chnum, qemu_irq irq, qemu_irq *drq,
604
omap_clk fclk, omap_clk iclk)
606
struct omap_mcspi_s *s = g_malloc0(sizeof(*s) +
607
chnum * sizeof(struct omap_mcspi_ch_s));
608
struct omap_mcspi_ch_s *ch = s->ch;
612
/* revision was hardcoded as 0x91 in original code -- odd */
613
s->revision = cpu_class_omap3(mpu) ? SPI_REV_OMAP3430 : SPI_REV_OMAP2420;
621
memory_region_init_io(&s->iomem, NULL, &omap_mcspi_ops, s, "omap.mcspi",
622
omap_l4_region_size(ta, 0));
623
omap_l4_attach(ta, 0, &s->iomem);
628
void omap_mcspi_attach(struct omap_mcspi_s *s,
629
uint32_t (*txrx)(void *opaque, uint32_t, int),
633
if (chipselect < 0 || chipselect >= s->chnum)
634
hw_error("%s: Bad chipselect %i\n", __FUNCTION__, chipselect);
636
s->ch[chipselect].txrx = txrx;
637
s->ch[chipselect].opaque = opaque;