xref: /openbmc/qemu/hw/ssi/omap_spi.c (revision 073d9f2c)
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 */
28 struct 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 
55 static inline void omap_mcspi_interrupt_update(struct omap_mcspi_s *s)
56 {
57     qemu_set_irq(s->irq, s->irqst & s->irqen);
58 }
59 
60 static 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 
74 static 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 
101 intr_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 
113 void 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 
135 static 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 
224 static 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 
342 static const MemoryRegionOps omap_mcspi_ops = {
343     .read = omap_mcspi_read,
344     .write = omap_mcspi_write,
345     .endianness = DEVICE_NATIVE_ENDIAN,
346 };
347 
348 struct 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 
370 void 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