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, unsigned size) 138 { 139 struct omap_mcspi_s *s = opaque; 140 int ch = 0; 141 uint32_t ret; 142 143 if (size != 4) { 144 return omap_badwidth_read32(opaque, addr); 145 } 146 147 switch (addr) { 148 case 0x00: /* MCSPI_REVISION */ 149 return 0x91; 150 151 case 0x10: /* MCSPI_SYSCONFIG */ 152 return s->sysconfig; 153 154 case 0x14: /* MCSPI_SYSSTATUS */ 155 return 1; /* RESETDONE */ 156 157 case 0x18: /* MCSPI_IRQSTATUS */ 158 return s->irqst; 159 160 case 0x1c: /* MCSPI_IRQENABLE */ 161 return s->irqen; 162 163 case 0x20: /* MCSPI_WAKEUPENABLE */ 164 return s->wken; 165 166 case 0x24: /* MCSPI_SYST */ 167 return s->systest; 168 169 case 0x28: /* MCSPI_MODULCTRL */ 170 return s->control; 171 172 case 0x68: ch ++; 173 /* fall through */ 174 case 0x54: ch ++; 175 /* fall through */ 176 case 0x40: ch ++; 177 /* fall through */ 178 case 0x2c: /* MCSPI_CHCONF */ 179 return s->ch[ch].config; 180 181 case 0x6c: ch ++; 182 /* fall through */ 183 case 0x58: ch ++; 184 /* fall through */ 185 case 0x44: ch ++; 186 /* fall through */ 187 case 0x30: /* MCSPI_CHSTAT */ 188 return s->ch[ch].status; 189 190 case 0x70: ch ++; 191 /* fall through */ 192 case 0x5c: ch ++; 193 /* fall through */ 194 case 0x48: ch ++; 195 /* fall through */ 196 case 0x34: /* MCSPI_CHCTRL */ 197 return s->ch[ch].control; 198 199 case 0x74: ch ++; 200 /* fall through */ 201 case 0x60: ch ++; 202 /* fall through */ 203 case 0x4c: ch ++; 204 /* fall through */ 205 case 0x38: /* MCSPI_TX */ 206 return s->ch[ch].tx; 207 208 case 0x78: ch ++; 209 /* fall through */ 210 case 0x64: ch ++; 211 /* fall through */ 212 case 0x50: ch ++; 213 /* fall through */ 214 case 0x3c: /* MCSPI_RX */ 215 s->ch[ch].status &= ~(1 << 0); /* RXS */ 216 ret = s->ch[ch].rx; 217 omap_mcspi_transfer_run(s, ch); 218 return ret; 219 } 220 221 OMAP_BAD_REG(addr); 222 return 0; 223 } 224 225 static void omap_mcspi_write(void *opaque, hwaddr addr, 226 uint64_t value, unsigned size) 227 { 228 struct omap_mcspi_s *s = opaque; 229 int ch = 0; 230 231 if (size != 4) { 232 omap_badwidth_write32(opaque, addr, value); 233 return; 234 } 235 236 switch (addr) { 237 case 0x00: /* MCSPI_REVISION */ 238 case 0x14: /* MCSPI_SYSSTATUS */ 239 case 0x30: /* MCSPI_CHSTAT0 */ 240 case 0x3c: /* MCSPI_RX0 */ 241 case 0x44: /* MCSPI_CHSTAT1 */ 242 case 0x50: /* MCSPI_RX1 */ 243 case 0x58: /* MCSPI_CHSTAT2 */ 244 case 0x64: /* MCSPI_RX2 */ 245 case 0x6c: /* MCSPI_CHSTAT3 */ 246 case 0x78: /* MCSPI_RX3 */ 247 OMAP_RO_REG(addr); 248 return; 249 250 case 0x10: /* MCSPI_SYSCONFIG */ 251 if (value & (1 << 1)) /* SOFTRESET */ 252 omap_mcspi_reset(s); 253 s->sysconfig = value & 0x31d; 254 break; 255 256 case 0x18: /* MCSPI_IRQSTATUS */ 257 if (!((s->control & (1 << 3)) && (s->systest & (1 << 11)))) { 258 s->irqst &= ~value; 259 omap_mcspi_interrupt_update(s); 260 } 261 break; 262 263 case 0x1c: /* MCSPI_IRQENABLE */ 264 s->irqen = value & 0x1777f; 265 omap_mcspi_interrupt_update(s); 266 break; 267 268 case 0x20: /* MCSPI_WAKEUPENABLE */ 269 s->wken = value & 1; 270 break; 271 272 case 0x24: /* MCSPI_SYST */ 273 if (s->control & (1 << 3)) /* SYSTEM_TEST */ 274 if (value & (1 << 11)) { /* SSB */ 275 s->irqst |= 0x1777f; 276 omap_mcspi_interrupt_update(s); 277 } 278 s->systest = value & 0xfff; 279 break; 280 281 case 0x28: /* MCSPI_MODULCTRL */ 282 if (value & (1 << 3)) /* SYSTEM_TEST */ 283 if (s->systest & (1 << 11)) { /* SSB */ 284 s->irqst |= 0x1777f; 285 omap_mcspi_interrupt_update(s); 286 } 287 s->control = value & 0xf; 288 break; 289 290 case 0x68: ch ++; 291 /* fall through */ 292 case 0x54: ch ++; 293 /* fall through */ 294 case 0x40: ch ++; 295 /* fall through */ 296 case 0x2c: /* MCSPI_CHCONF */ 297 if ((value ^ s->ch[ch].config) & (3 << 14)) /* DMAR | DMAW */ 298 omap_mcspi_dmarequest_update(s->ch + ch); 299 if (((value >> 12) & 3) == 3) { /* TRM */ 300 qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid TRM value (3)\n", 301 __func__); 302 } 303 if (((value >> 7) & 0x1f) < 3) { /* WL */ 304 qemu_log_mask(LOG_GUEST_ERROR, 305 "%s: invalid WL value (%" PRIx64 ")\n", 306 __func__, (value >> 7) & 0x1f); 307 } 308 s->ch[ch].config = value & 0x7fffff; 309 break; 310 311 case 0x70: ch ++; 312 /* fall through */ 313 case 0x5c: ch ++; 314 /* fall through */ 315 case 0x48: ch ++; 316 /* fall through */ 317 case 0x34: /* MCSPI_CHCTRL */ 318 if (value & ~s->ch[ch].control & 1) { /* EN */ 319 s->ch[ch].control |= 1; 320 omap_mcspi_transfer_run(s, ch); 321 } else 322 s->ch[ch].control = value & 1; 323 break; 324 325 case 0x74: ch ++; 326 /* fall through */ 327 case 0x60: ch ++; 328 /* fall through */ 329 case 0x4c: ch ++; 330 /* fall through */ 331 case 0x38: /* MCSPI_TX */ 332 s->ch[ch].tx = value; 333 s->ch[ch].status &= ~(1 << 1); /* TXS */ 334 omap_mcspi_transfer_run(s, ch); 335 break; 336 337 default: 338 OMAP_BAD_REG(addr); 339 return; 340 } 341 } 342 343 static const MemoryRegionOps omap_mcspi_ops = { 344 .read = omap_mcspi_read, 345 .write = omap_mcspi_write, 346 .endianness = DEVICE_NATIVE_ENDIAN, 347 }; 348 349 struct omap_mcspi_s *omap_mcspi_init(struct omap_target_agent_s *ta, int chnum, 350 qemu_irq irq, qemu_irq *drq, omap_clk fclk, omap_clk iclk) 351 { 352 struct omap_mcspi_s *s = g_new0(struct omap_mcspi_s, 1); 353 struct omap_mcspi_ch_s *ch = s->ch; 354 355 s->irq = irq; 356 s->chnum = chnum; 357 while (chnum --) { 358 ch->txdrq = *drq ++; 359 ch->rxdrq = *drq ++; 360 ch ++; 361 } 362 omap_mcspi_reset(s); 363 364 memory_region_init_io(&s->iomem, NULL, &omap_mcspi_ops, s, "omap.mcspi", 365 omap_l4_region_size(ta, 0)); 366 omap_l4_attach(ta, 0, &s->iomem); 367 368 return s; 369 } 370 371 void omap_mcspi_attach(struct omap_mcspi_s *s, 372 uint32_t (*txrx)(void *opaque, uint32_t, int), void *opaque, 373 int chipselect) 374 { 375 if (chipselect < 0 || chipselect >= s->chnum) 376 hw_error("%s: Bad chipselect %i\n", __func__, chipselect); 377 378 s->ch[chipselect].txrx = txrx; 379 s->ch[chipselect].opaque = opaque; 380 } 381