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) version 3 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.
25 #define SPI_FIFOSIZE 64
26 #define SPI_REV_OMAP2420 0x14
27 #define SPI_REV_OMAP3530 0x21
28 #define IS_OMAP3_SPI(s) ((s)->revision >= SPI_REV_OMAP3530)
42 struct omap_mcspi_fifo_s {
46 uint8_t buf[SPI_FIFOSIZE];
51 struct omap_mcspi_ch_s {
54 uint32_t (*txrx)(void *opaque, uint32_t, int);
66 static inline void omap_mcspi_interrupt_update(struct omap_mcspi_s *s)
68 qemu_set_irq(s->irq, s->irqst & s->irqen);
71 static inline void omap_mcspi_dmarequest_update(struct omap_mcspi_s *s,
74 struct omap_mcspi_ch_s *ch = &s->ch[chnum];
75 if ((ch->control & 1) && /* EN */
76 (ch->config & (1 << 14)) && /* DMAW */
77 (ch->status & (1 << 1)) && /* TXS */
78 ((ch->config >> 12) & 3) != 1) { /* TRM */
79 if (!IS_OMAP3_SPI(s) ||
80 !(ch->config & (1 << 27)) || /* FFEW */
81 s->tx_fifo.len <= (s->xferlevel & 0x3f)) /* AEL */
82 qemu_irq_raise(ch->txdrq);
84 qemu_irq_lower(ch->txdrq);
86 if ((ch->control & 1) && /* EN */
87 (ch->config & (1 << 15)) && /* DMAW */
88 (ch->status & (1 << 0)) && /* RXS */
89 ((ch->config >> 12) & 3) != 2) { /* TRM */
90 if (!IS_OMAP3_SPI(s) ||
91 !(ch->config & (1 << 28)) || /* FFER */
92 s->rx_fifo.len >= ((s->xferlevel >> 8) & 0x3f)) /* AFL */
93 qemu_irq_raise(ch->rxdrq);
95 qemu_irq_lower(ch->rxdrq);
99 static void omap_mcspi_fifo_reset(struct omap_mcspi_s *s)
101 struct omap_mcspi_ch_s *ch;
105 s->tx_fifo.start = 0;
106 s->rx_fifo.start = 0;
107 if (s->fifo_ch < 0) {
108 s->tx_fifo.size = s->rx_fifo.size = 0;
110 ch = &s->ch[s->fifo_ch];
111 s->tx_fifo.size = ((ch->config >> 27) & 1) ? SPI_FIFOSIZE : 0;
112 s->rx_fifo.size = ((ch->config >> 28) & 1) ? SPI_FIFOSIZE : 0;
113 if (((ch->config >> 27) & 3) == 3) {
114 s->tx_fifo.size >>= 1;
115 s->rx_fifo.size >>= 1;
120 static void omap_mcspi_save_state(QEMUFile *f, void *opaque)
122 struct omap_mcspi_s *s = (struct omap_mcspi_s *)opaque;
125 qemu_put_be32(f, s->sysconfig);
126 qemu_put_be32(f, s->systest);
127 qemu_put_be32(f, s->irqst);
128 qemu_put_be32(f, s->irqen);
129 qemu_put_be32(f, s->wken);
130 qemu_put_be32(f, s->control);
131 qemu_put_be32(f, s->xferlevel);
132 qemu_put_sbe32(f, s->tx_fifo.start);
133 qemu_put_sbe32(f, s->tx_fifo.len);
134 qemu_put_sbe32(f, s->tx_fifo.size);
135 qemu_put_buffer(f, s->tx_fifo.buf, sizeof(s->tx_fifo.buf));
136 qemu_put_sbe32(f, s->rx_fifo.start);
137 qemu_put_sbe32(f, s->rx_fifo.len);
138 qemu_put_sbe32(f, s->rx_fifo.size);
139 qemu_put_buffer(f, s->rx_fifo.buf, sizeof(s->rx_fifo.buf));
140 qemu_put_sbe32(f, s->fifo_ch);
141 qemu_put_sbe32(f, s->fifo_wcnt);
142 for (i = 0; i < s->chnum; i++) {
143 qemu_put_be32(f, s->ch[i].tx);
144 qemu_put_be32(f, s->ch[i].rx);
145 qemu_put_be32(f, s->ch[i].config);
146 qemu_put_be32(f, s->ch[i].status);
147 qemu_put_be32(f, s->ch[i].control);
151 static int omap_mcspi_load_state(QEMUFile *f, void *opaque, int version_id)
153 struct omap_mcspi_s *s = (struct omap_mcspi_s *)opaque;
159 s->sysconfig = qemu_get_be32(f);
160 s->systest = qemu_get_be32(f);
161 s->irqst = qemu_get_be32(f);
162 s->irqen = qemu_get_be32(f);
163 s->wken = qemu_get_be32(f);
164 s->control = qemu_get_be32(f);
165 s->xferlevel = qemu_get_be32(f);
166 s->tx_fifo.start = qemu_get_be32(f);
167 s->tx_fifo.len = qemu_get_be32(f);
168 s->tx_fifo.size = qemu_get_be32(f);
169 qemu_get_buffer(f, s->tx_fifo.buf, sizeof(s->tx_fifo.buf));
170 s->rx_fifo.start = qemu_get_be32(f);
171 s->rx_fifo.len = qemu_get_be32(f);
172 s->rx_fifo.size = qemu_get_be32(f);
173 qemu_get_buffer(f, s->rx_fifo.buf, sizeof(s->rx_fifo.buf));
174 s->fifo_ch = qemu_get_sbe32(f);
175 s->fifo_wcnt = qemu_get_sbe32(f);
176 for (i = 0; i < s->chnum; i++) {
177 s->ch[i].tx = qemu_get_be32(f);
178 s->ch[i].rx = qemu_get_be32(f);
179 s->ch[i].config = qemu_get_be32(f);
180 s->ch[i].status = qemu_get_be32(f);
181 s->ch[i].control = qemu_get_be32(f);
182 omap_mcspi_dmarequest_update(s, i);
184 omap_mcspi_interrupt_update(s);
189 /* returns next word in FIFO or the n first bytes if there is not
190 * enough data in FIFO */
191 static uint32_t omap_mcspi_fifo_get(struct omap_mcspi_fifo_s *s, int wl)
195 for (v = 0, sh = 0; wl > 0 && s->len; wl -= 8, s->len--, sh += 8) {
196 v |= ((uint32_t)s->buf[s->start++]) << sh;
197 if (s->start >= s->size)
203 /* pushes a word to FIFO or the first n bytes of the word if the FIFO
204 * is too full to hold the full word */
205 static void omap_mcspi_fifo_put(struct omap_mcspi_fifo_s *s, int wl,
208 int p = s->start + s->len;
210 for (; wl > 0 && s->len < s->size; wl -=8, v >>= 8, s->len++) {
213 s->buf[p++] = (uint8_t)(v & 0xff);
217 static void omap_mcspi_transfer_run(struct omap_mcspi_s *s, int chnum)
219 struct omap_mcspi_ch_s *ch = s->ch + chnum;
220 int trm = (ch->config >> 12) & 3;
223 if (!(ch->control & 1)) /* EN */
225 if ((ch->status & 1) && trm != 2 && /* RXS */
226 !(ch->config & (1 << 19))) /* TURBO */
228 if ((ch->status & (1 << 1)) && trm != 1) /* TXS */
231 if (!(s->control & 1) || /* SINGLE */
232 (ch->config & (1 << 20))) { /* FORCE */
234 wl = 1 + (0x1f & (ch->config >> 7)); /* WL */
235 if (!IS_OMAP3_SPI(s) || s->fifo_ch != chnum ||
236 !((ch->config >> 27) & 3)) /* FFER | FFEW */
237 ch->rx = ch->txrx(ch->opaque, ch->tx, wl);
239 switch ((ch->config >> 27) & 3) {
240 case 1: /* !FFER, FFEW */
242 ch->tx = omap_mcspi_fifo_get(&s->tx_fifo, wl);
243 ch->rx = ch->txrx(ch->opaque, ch->tx, wl);
246 case 2: /* FFER, !FFEW */
247 ch->rx = ch->txrx(ch->opaque, ch->tx, wl);
249 omap_mcspi_fifo_put(&s->rx_fifo, wl, ch->rx);
252 case 3: /* FFER, FFEW */
253 while (s->rx_fifo.len < s->rx_fifo.size &&
254 s->tx_fifo.len && s->fifo_wcnt) {
256 ch->tx = omap_mcspi_fifo_get(&s->tx_fifo, wl);
257 ch->rx = ch->txrx(ch->opaque, ch->tx, wl);
259 omap_mcspi_fifo_put(&s->rx_fifo, wl, ch->rx);
266 if ((ch->config & (1 << 28)) && /* FFER */
267 s->rx_fifo.len >= s->rx_fifo.size)
268 ch->status |= 1 << 6; /* RXFFF */
269 ch->status &= ~(1 << 5); /* RXFFE */
270 ch->status &= ~(1 << 4); /* TXFFF */
271 if ((ch->config & (1 << 27)) && /* FFEW */
273 ch->status |= 1 << 3; /* TXFFE */
275 ((s->xferlevel >> 16) & 0xffff)) /* WCNT */
276 s->irqst |= 1 << 17; /* EOW */
282 ch->status |= 1 << 2; /* EOT */
283 ch->status |= 1 << 1; /* TXS */
285 ch->status |= 1; /* RXS */
288 if ((ch->status & 1) && trm != 2 && /* RXS */
289 !(ch->config & (1 << 19))) /* TURBO */
290 if (!IS_OMAP3_SPI(s) || s->fifo_ch != chnum ||
291 !((ch->config >> 28) & 1) || /* FFER */
292 s->rx_fifo.len >= ((s->xferlevel >> 8) & 0x3f)) /* AFL */
293 s->irqst |= 1 << (2 + 4 * chnum); /* RX_FULL */
294 if ((ch->status & (1 << 1)) && trm != 1) /* TXS */
295 if (!IS_OMAP3_SPI(s) || s->fifo_ch != chnum ||
296 !((ch->config >> 27) & 1) || /* FFEW */
297 s->tx_fifo.len <= (s->xferlevel & 0x3f)) /* AEL */
298 s->irqst |= 1 << (4 * chnum); /* TX_EMPTY */
299 omap_mcspi_interrupt_update(s);
300 omap_mcspi_dmarequest_update(s, chnum);
303 void omap_mcspi_reset(struct omap_mcspi_s *s)
315 omap_mcspi_fifo_reset(s);
317 for (ch = 0; ch < s->chnum; ch ++) {
318 s->ch[ch].config = 0x060000;
319 s->ch[ch].status = 2; /* TXS */
320 s->ch[ch].control = 0;
322 omap_mcspi_dmarequest_update(s, ch);
325 omap_mcspi_interrupt_update(s);
328 static uint32_t omap_mcspi_read(void *opaque, target_phys_addr_t addr)
330 struct omap_mcspi_s *s = (struct omap_mcspi_s *) opaque;
335 case 0x00: /* MCSPI_REVISION */
338 case 0x10: /* MCSPI_SYSCONFIG */
341 case 0x14: /* MCSPI_SYSSTATUS */
342 return 1; /* RESETDONE */
344 case 0x18: /* MCSPI_IRQSTATUS */
347 case 0x1c: /* MCSPI_IRQENABLE */
350 case 0x20: /* MCSPI_WAKEUPENABLE */
353 case 0x24: /* MCSPI_SYST */
356 case 0x28: /* MCSPI_MODULCTRL */
362 case 0x2c: /* MCSPI_CHCONF */
363 return (ch < s->chnum) ? s->ch[ch].config : 0;
368 case 0x30: /* MCSPI_CHSTAT */
369 return (ch < s->chnum) ? s->ch[ch].status : 0;
374 case 0x34: /* MCSPI_CHCTRL */
375 return (ch < s->chnum) ? s->ch[ch].control : 0;
380 case 0x38: /* MCSPI_TX */
388 case 0x3c: /* MCSPI_RX */
390 if (!IS_OMAP3_SPI(s) || ch != s->fifo_ch ||
391 !(s->ch[ch].config & (1 << 28))) { /* FFER */
392 s->ch[ch].status &= ~1; /* RXS */
394 omap_mcspi_transfer_run(s, ch);
398 fprintf(stderr, "%s: rxfifo underflow!\n", __FUNCTION__);
400 qemu_irq_lower(s->ch[ch].rxdrq);
401 s->ch[ch].status &= ~(1 << 6); /* RXFFF */
402 if (((s->ch[ch].config >> 12) & 3) != 2) /* TRM */
403 ret = omap_mcspi_fifo_get(&s->rx_fifo,
404 1 + ((s->ch[ch].config >> 7) & 0x1f)); /* WL */
407 if (!s->rx_fifo.len) {
408 s->ch[ch].status &= ~1; /* RXS */
409 s->ch[ch].status |= 1 << 5; /* RXFFE */
410 omap_mcspi_transfer_run(s, ch);
417 case 0x7c: /* MCSPI_XFERLEVEL */
418 if (IS_OMAP3_SPI(s)) {
419 if ((s->xferlevel >> 16) & 0xffff) /* WCNT */
420 ret = ((s->xferlevel & 0xffff0000) - (s->fifo_wcnt << 16));
422 ret = ((-s->fifo_wcnt) & 0xffff) << 16;
423 return (s->xferlevel & 0xffff) | ret;
435 static void omap_mcspi_write(void *opaque, target_phys_addr_t addr,
438 struct omap_mcspi_s *s = (struct omap_mcspi_s *) opaque;
443 case 0x00: /* MCSPI_REVISION */
444 case 0x14: /* MCSPI_SYSSTATUS */
445 case 0x30: /* MCSPI_CHSTAT0 */
446 case 0x3c: /* MCSPI_RX0 */
447 case 0x44: /* MCSPI_CHSTAT1 */
448 case 0x50: /* MCSPI_RX1 */
449 case 0x58: /* MCSPI_CHSTAT2 */
450 case 0x64: /* MCSPI_RX2 */
451 case 0x6c: /* MCSPI_CHSTAT3 */
452 case 0x78: /* MCSPI_RX3 */
453 OMAP_RO_REGV(addr, value);
456 case 0x10: /* MCSPI_SYSCONFIG */
457 if (value & (1 << 1)) /* SOFTRESET */
459 s->sysconfig = value & 0x31d;
462 case 0x18: /* MCSPI_IRQSTATUS */
463 if (!((s->control & (1 << 3)) && (s->systest & (1 << 11)))) {
465 omap_mcspi_interrupt_update(s);
469 case 0x1c: /* MCSPI_IRQENABLE */
470 s->irqen = value & (IS_OMAP3_SPI(s) ? 0x3777f : 0x1777f);
471 omap_mcspi_interrupt_update(s);
474 case 0x20: /* MCSPI_WAKEUPENABLE */
478 case 0x24: /* MCSPI_SYST */
479 if (s->control & (1 << 3)) /* SYSTEM_TEST */
480 if (value & (1 << 11)) { /* SSB */
482 omap_mcspi_interrupt_update(s);
484 s->systest = value & 0xfff;
487 case 0x28: /* MCSPI_MODULCTRL */
488 if (value & (1 << 3)) /* SYSTEM_TEST */
489 if (s->systest & (1 << 11)) { /* SSB */
490 s->irqst |= IS_OMAP3_SPI(s) ? 0x3777f : 0x1777f;
491 omap_mcspi_interrupt_update(s);
493 s->control = value & 0xf;
499 case 0x2c: /* MCSPI_CHCONF */
501 old = s->ch[ch].config;
502 s->ch[ch].config = value & (IS_OMAP3_SPI(s)
503 ? 0x3fffffff : 0x7fffff);
504 if (IS_OMAP3_SPI(s) &&
505 ((value ^ old) & (3 << 27))) { /* FFER | FFEW */
506 s->fifo_ch = ((value & (3 << 27))) ? ch : -1;
507 omap_mcspi_fifo_reset(s);
509 if (((value ^ old) & (3 << 14)) || /* DMAR | DMAW */
511 ((value ^ old) & (3 << 27)))) /* FFER | FFEW */
512 omap_mcspi_dmarequest_update(s, ch);
513 if (((value >> 12) & 3) == 3) /* TRM */
514 fprintf(stderr, "%s: invalid TRM value (3)\n",
516 if (((value >> 7) & 0x1f) < 3) /* WL */
517 fprintf(stderr, "%s: invalid WL value (%i)\n",
518 __FUNCTION__, (value >> 7) & 0x1f);
519 if (IS_OMAP3_SPI(s) &&
520 ((value >> 23) & 1)) /* SBE */
521 fprintf(stderr, "%s: start-bit mode is not supported\n",
529 case 0x34: /* MCSPI_CHCTRL */
531 old = s->ch[ch].control;
532 s->ch[ch].control = value & (IS_OMAP3_SPI(s) ? 0xff01 : 1);
533 if (value & ~old & 1) { /* EN */
534 if (IS_OMAP3_SPI(s) && s->fifo_ch == ch)
535 omap_mcspi_fifo_reset(s);
536 omap_mcspi_transfer_run(s, ch);
544 case 0x38: /* MCSPI_TX */
546 if (!IS_OMAP3_SPI(s) || s->fifo_ch != ch ||
547 !(s->ch[ch].config & (1 << 27))) { /* FFEW */
548 s->ch[ch].tx = value;
549 s->ch[ch].status &= ~(1 << 1); /* TXS */
550 omap_mcspi_transfer_run(s, ch);
552 if (s->tx_fifo.len >= s->tx_fifo.size)
553 fprintf(stderr, "%s: txfifo overflow!\n",
556 qemu_irq_lower(s->ch[ch].txdrq);
557 s->ch[ch].status &= ~0x0a; /* TXFFE | TXS */
558 if (((s->ch[ch].config >> 12) & 3) != 1) { /* TRM */
561 1 + ((s->ch[ch].config >> 7) & 0x1f), /* WL */
563 if (s->tx_fifo.len >= s->tx_fifo.size)
564 s->ch[ch].status |= 1 << 4; /* TXFFF */
565 if (s->tx_fifo.len >= (s->xferlevel & 0x3f))
566 omap_mcspi_transfer_run(s, ch);
568 s->ch[ch].tx = value;
569 omap_mcspi_transfer_run(s, ch);
577 if (IS_OMAP3_SPI(s)) {
578 if (value != s->xferlevel) {
579 s->fifo_wcnt = (value >> 16) & 0xffff;
580 s->xferlevel = value & 0xffff3f3f;
581 omap_mcspi_fifo_reset(s);
584 OMAP_BAD_REGV(addr, value);
588 OMAP_BAD_REGV(addr, value);
593 static CPUReadMemoryFunc *omap_mcspi_readfn[] = {
594 omap_badwidth_read32,
595 omap_badwidth_read32,
599 static CPUWriteMemoryFunc *omap_mcspi_writefn[] = {
600 omap_badwidth_write32,
601 omap_badwidth_write32,
605 struct omap_mcspi_s *omap_mcspi_init(struct omap_target_agent_s *ta,
606 struct omap_mpu_state_s *mpu,
607 int chnum, qemu_irq irq, qemu_irq *drq,
608 omap_clk fclk, omap_clk iclk)
610 struct omap_mcspi_s *s = (struct omap_mcspi_s *)
611 qemu_mallocz(sizeof(struct omap_mcspi_s) +
612 chnum * sizeof(struct omap_mcspi_ch_s));
613 struct omap_mcspi_ch_s *ch = s->ch;
617 /* revision was hardcoded as 0x91 in original code -- odd */
618 s->revision = cpu_class_omap3(mpu) ? SPI_REV_OMAP3530 : SPI_REV_OMAP2420;
626 omap_l4_attach(ta, 0, l4_register_io_memory(0, omap_mcspi_readfn,
627 omap_mcspi_writefn, s));
628 register_savevm("omap_mcspi", (ta->base >> 8), 0,
629 omap_mcspi_save_state, omap_mcspi_load_state, s);
633 void omap_mcspi_attach(struct omap_mcspi_s *s,
634 uint32_t (*txrx)(void *opaque, uint32_t, int),
638 if (chipselect < 0 || chipselect >= s->chnum)
639 cpu_abort(cpu_single_env, "%s: Bad chipselect %i\n",
640 __FUNCTION__, chipselect);
642 s->ch[chipselect].txrx = txrx;
643 s->ch[chipselect].opaque = opaque;