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