/* 3b2_dmac.c: AM9517 DMA Controller Copyright (c) 2021-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Except as contained in this notice, the name of the author shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from the author. */ #include "3b2_dmac.h" #if defined(REV2) #include "3b2_id.h" #endif #include "3b2_cpu.h" #include "3b2_if.h" #include "3b2_iu.h" #include "3b2_mem.h" #include "3b2_stddev.h" #include "3b2_csr.h" DMA_STATE dma_state; UNIT dmac_unit[] = { { UDATA (NULL, 0, 0), 0, 0 }, { UDATA (NULL, 0, 0), 0, 1 }, { UDATA (NULL, 0, 0), 0, 2 }, { UDATA (NULL, 0, 0), 0, 3 }, { NULL } }; REG dmac_reg[] = { { NULL } }; DEVICE dmac_dev = { "DMAC", dmac_unit, dmac_reg, NULL, 1, 16, 8, 4, 16, 32, NULL, NULL, &dmac_reset, NULL, NULL, NULL, NULL, DEV_DEBUG, 0, sys_deb_tab }; dmac_dma_handler device_dma_handlers[] = { #if defined(REV2) {DMA_ID_CHAN, IDBASE+ID_DATA_REG, &id_drq, dmac_generic_dma, id_after_dma}, #endif {DMA_IF_CHAN, IFBASE+IF_DATA_REG, &if_state.drq, dmac_generic_dma, if_after_dma}, {DMA_IUA_CHAN, IUBASE+IUA_DATA_REG, &iu_console.drq, iu_dma_console, NULL}, {DMA_IUB_CHAN, IUBASE+IUB_DATA_REG, &iu_contty.drq, iu_dma_contty, NULL}, {0, 0, NULL, NULL, NULL } }; uint32 dma_address(uint8 channel, uint32 offset) { uint32 addr, page; if (DMA_DECR(channel)) { addr = (PHYS_MEM_BASE + (uint32)(dma_state.channels[channel].addr) - offset); } else { addr = (PHYS_MEM_BASE + (uint32)(dma_state.channels[channel].addr) + offset); } #if defined (REV3) page = (uint32)dma_state.channels[channel].page; #else /* In Rev 2, the top bit of the page address is a R/W bit, so we mask it here */ page = (uint32)dma_state.channels[channel].page & 0x7f; #endif addr |= page << 16; return addr; } t_stat dmac_reset(DEVICE *dptr) { int i; memset(&dma_state, 0, sizeof(dma_state)); for (i = 0; i < 4; i++) { dma_state.channels[i].page = 0; dma_state.channels[i].addr = 0; dma_state.channels[i].mode = 0; dma_state.channels[i].wcount = 0; dma_state.channels[i].addr_c = 0; dma_state.channels[i].wcount_c = -1; dma_state.channels[i].ptr = 0; } return SCPE_OK; } uint32 dmac_read(uint32 pa, size_t size) { uint8 reg, base, data; base = (uint8) (pa >> 12); reg = pa & 0xff; switch (base) { case DMA_C: switch (reg) { case 0: /* channel 0 current address reg */ data = ((dma_state.channels[0].addr_c) >> (dma_state.bff * 8)) & 0xff; sim_debug(READ_MSG, &dmac_dev, "Reading Channel 0 Addr Reg: %08x\n", data); dma_state.bff ^= 1; break; case 1: /* channel 0 current address reg */ data = ((dma_state.channels[0].wcount_c) >> (dma_state.bff * 8)) & 0xff; sim_debug(READ_MSG, &dmac_dev, "Reading Channel 0 Addr Count Reg: %08x\n", data); dma_state.bff ^= 1; break; case 2: /* channel 1 current address reg */ data = ((dma_state.channels[1].addr_c) >> (dma_state.bff * 8)) & 0xff; sim_debug(READ_MSG, &dmac_dev, "Reading Channel 1 Addr Reg: %08x\n", data); dma_state.bff ^= 1; break; case 3: /* channel 1 current address reg */ data = ((dma_state.channels[1].wcount_c) >> (dma_state.bff * 8)) & 0xff; sim_debug(READ_MSG, &dmac_dev, "Reading Channel 1 Addr Count Reg: %08x\n", data); dma_state.bff ^= 1; break; case 4: /* channel 2 current address reg */ data = ((dma_state.channels[2].addr_c) >> (dma_state.bff * 8)) & 0xff; sim_debug(READ_MSG, &dmac_dev, "Reading Channel 2 Addr Reg: %08x\n", data); dma_state.bff ^= 1; break; case 5: /* channel 2 current address reg */ data = ((dma_state.channels[2].wcount_c) >> (dma_state.bff * 8)) & 0xff; sim_debug(READ_MSG, &dmac_dev, "Reading Channel 2 Addr Count Reg: %08x\n", data); dma_state.bff ^= 1; break; case 6: /* channel 3 current address reg */ data = ((dma_state.channels[3].addr_c) >> (dma_state.bff * 8)) & 0xff; sim_debug(READ_MSG, &dmac_dev, "Reading Channel 3 Addr Reg: %08x\n", data); dma_state.bff ^= 1; break; case 7: /* channel 3 current address reg */ data = ((dma_state.channels[3].wcount_c) >> (dma_state.bff * 8)) & 0xff; sim_debug(READ_MSG, &dmac_dev, "Reading Channel 3 Addr Count Reg: %08x\n", data); dma_state.bff ^= 1; break; case 8: data = dma_state.status; sim_debug(READ_MSG, &dmac_dev, "Reading DMAC Status %08x\n", data); dma_state.status = 0; break; default: sim_debug(READ_MSG, &dmac_dev, "DMAC READ %" SIZE_T_FMT "u B @ %08x\n", size, pa); data = 0; } return data; default: sim_debug(READ_MSG, &dmac_dev, "[BASE: %08x] DMAC READ %" SIZE_T_FMT "u B @ %08x\n", base, size, pa); return 0; } } /* * Program the DMAC */ void dmac_program(uint8 reg, uint8 val) { uint8 channel_id, i, chan_num; dma_channel *channel; #if defined(REV3) /* TODO: More general DMA interrupt clearing */ CPU_CLR_INT(INT_UART_DMA); CLR_CSR(CSRDMA); #endif if (reg < 8) { switch (reg) { case 0: case 1: chan_num = 0; break; case 2: case 3: chan_num = 1; break; case 4: case 5: chan_num = 2; break; case 6: case 7: chan_num = 3; break; default: chan_num = 0; break; } channel = &dma_state.channels[chan_num]; switch (reg & 1) { case 0: /* Address */ channel->addr &= ~(0xff << dma_state.bff * 8); channel->addr |= (val & 0xff) << (dma_state.bff * 8); channel->addr_c = channel->addr; sim_debug(WRITE_MSG, &dmac_dev, "Set address channel %d byte %d = %08x\n", chan_num, dma_state.bff, channel->addr); break; case 1: /* Word Count */ channel->wcount &= ~(0xff << dma_state.bff * 8); channel->wcount |= (val & 0xff) << (dma_state.bff * 8); channel->wcount_c = channel->wcount; channel->ptr = 0; sim_debug(WRITE_MSG, &dmac_dev, "Set word count channel %d byte %d = %08x\n", chan_num, dma_state.bff, channel->wcount); break; } /* Toggle the byte flip-flop */ dma_state.bff ^= 1; /* Handled. */ return; } /* If it hasn't been handled, it must be one of the following registers. */ switch (reg) { case 8: /* Command */ dma_state.command = val; sim_debug(WRITE_MSG, &dmac_dev, "Command: val=%02x\n", val); break; case 9: /* Request */ sim_debug(WRITE_MSG, &dmac_dev, "Request set: val=%02x\n", val); dma_state.request = val; break; case 10: /* Write Single Mask Register Bit */ channel_id = val & 3; /* "Clear or Set" is bit 2 */ if ((val >> 2) & 1) { dma_state.mask |= (1 << channel_id); } else { dma_state.mask &= ~(1 << channel_id); /* Set the appropriate DRQ */ /* *dmac_drq_handlers[channel_id].drq = TRUE; */ } sim_debug(WRITE_MSG, &dmac_dev, "Write Single Mask Register Bit. channel=%d set/clear=%02x\n", channel_id, (val >> 2) & 1); break; case 11: /* Mode */ channel_id = val & 3; sim_debug(WRITE_MSG, &dmac_dev, "Mode Set. channel=%d val=%02x\n", channel_id, val); dma_state.channels[channel_id].mode = val; break; case 12: /* Clear Byte Pointer Flip/Flop */ dma_state.bff = 0; break; case 13: /* Master Clear */ dma_state.bff = 0; dma_state.command = 0; dma_state.status = 0; for (i = 0; i < 4; i++) { dma_state.channels[i].page = 0; dma_state.channels[i].addr = 0; dma_state.channels[i].wcount = 0; dma_state.channels[i].addr_c = 0; dma_state.channels[i].wcount_c = -1; dma_state.channels[i].ptr = 0; } break; case 15: /* Write All Mask Register Bits */ sim_debug(WRITE_MSG, &dmac_dev, "Write DMAC mask (all bits). Val=%02x\n", val); dma_state.mask = val & 0xf; break; case 16: /* Clear DMAC Interrupt */ sim_debug(WRITE_MSG, &dmac_dev, "Clear DMA Interrupt in DMAC. val=%02x\n", val); break; default: sim_debug(WRITE_MSG, &dmac_dev, "Unhandled DMAC write. reg=%x val=%02x\n", reg, val); break; } } void dmac_page_update(uint8 base, uint8 reg, uint8 val) { uint8 shift = 0; /* Sanity check */ if (reg > 3) { return; } #if defined(REV2) /* In Rev2 systems, the actual register is a 32-bit, byte-addressed register, so that address 4x000 is the highest byte, 4x003 is the lowest byte. */ shift = -(reg - 3) * 8; #endif switch (base) { #if defined (REV2) case DMA_ID: sim_debug(WRITE_MSG, &dmac_dev, "Set page channel 0 = %x\n", val); dma_state.channels[DMA_ID_CHAN].page &= ~(0xff << shift); dma_state.channels[DMA_ID_CHAN].page |= ((uint16)val << shift); break; #endif case DMA_IF: sim_debug(WRITE_MSG, &dmac_dev, "Set page channel 1 = %x\n", val); dma_state.channels[DMA_IF_CHAN].page &= ~(0xff << shift); dma_state.channels[DMA_IF_CHAN].page |= ((uint16)val << shift); break; case DMA_IUA: sim_debug(WRITE_MSG, &dmac_dev, "Set page channel 2 = %x\n", val); dma_state.channels[DMA_IUA_CHAN].page &= ~(0xff << shift); dma_state.channels[DMA_IUA_CHAN].page |= ((uint16)val << shift); break; case DMA_IUB: sim_debug(WRITE_MSG, &dmac_dev, "Set page channel 3 = %x\n", val); dma_state.channels[DMA_IUB_CHAN].page &= ~(0xff << shift); dma_state.channels[DMA_IUB_CHAN].page |= ((uint16)val << shift); break; } } void dmac_write(uint32 pa, uint32 val, size_t size) { uint8 reg, base; base = (uint8) (pa >> 12); reg = pa & 0xff; switch (base) { case DMA_C: dmac_program(reg, (uint8) val); break; #if defined (REV2) case DMA_ID: #endif case DMA_IUA: case DMA_IUB: case DMA_IF: dmac_page_update(base, reg, (uint8) val); break; } } void dmac_generic_dma(uint8 channel, uint32 service_address) { uint8 data; int32 i; uint32 addr; dma_channel *chan = &dma_state.channels[channel]; i = chan->wcount_c; /* TODO: This assumes every transfer is a block mode, which is not guaranteed to be valid, but is likely safe? */ switch (DMA_XFER(channel)) { case DMA_XFER_VERIFY: sim_debug(EXECUTE_MSG, &dmac_dev, "[dmac_generic_dma channel=%d] unhandled VERIFY request.\n", channel); break; case DMA_XFER_WRITE: sim_debug(EXECUTE_MSG, &dmac_dev, "[dmac_generic_dma channel=%d] write: %d bytes to %08x from %08x (page=%04x addr=%08x)\n", channel, chan->wcount + 1, dma_address(channel, 0), service_address, dma_state.channels[channel].page, dma_state.channels[channel].addr); for (; i >= 0; i--) { chan->wcount_c--; addr = dma_address(channel, chan->ptr++); chan->addr_c = addr; data = pread_b(service_address, BUS_PER); write_b(addr, data, BUS_PER); } break; case DMA_XFER_READ: sim_debug(EXECUTE_MSG, &dmac_dev, "[dmac_generic_dma channel=%d] read: %d bytes from %08x to %08x\n", channel, chan->wcount + 1, dma_address(channel, 0), service_address); for (; i >= 0; i--) { chan->wcount_c = i; addr = dma_address(channel, chan->ptr++); chan->addr_c = addr; data = pread_b(addr, BUS_PER); write_b(service_address, data, BUS_PER); } break; } /* End of Process must set the channel's mask bit */ dma_state.mask |= (1 << channel); dma_state.status |= (1 << channel); } /* * Service pending DRQs */ void dmac_service_drqs() { volatile dmac_dma_handler *h; for (h = &device_dma_handlers[0]; h->drq != NULL; h++) { /* Only trigger if the channel has a DRQ set and its channel's mask bit is 0 */ if (*h->drq && ((dma_state.mask >> h->channel) & 0x1) == 0) { h->dma_handler(h->channel, h->service_address); /* Each handler is responsible for clearing its own DRQ line! */ if (h->after_dma_callback != NULL) { h->after_dma_callback(); } } } }