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