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