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