#include #include #include #include #include #include #include #include /* internal DMA cache information */ static struct { uint32_t keyframe; sh7305_dma_channel_t channel[6]; } dmac_info = { .keyframe = 0x00000000, .channel = { { .id = { .INVALID = 1, .IDX = 0b111, .KEYFRAME = 0 }, .hw = &SH7305_DMA.DMA0 }, { .id = { .INVALID = 1, .IDX = 0b111, .KEYFRAME = 0 }, .hw = &SH7305_DMA.DMA1 }, { .id = { .INVALID = 1, .IDX = 0b111, .KEYFRAME = 0 }, .hw = &SH7305_DMA.DMA2 }, { .id = { .INVALID = 1, .IDX = 0b111, .KEYFRAME = 0 }, .hw = &SH7305_DMA.DMA3 }, { .id = { .INVALID = 1, .IDX = 0b111, .KEYFRAME = 0 }, .hw = &SH7305_DMA.DMA4 }, { .id = { .INVALID = 1, .IDX = 0b111, .KEYFRAME = 0 }, .hw = &SH7305_DMA.DMA5 }, } }; //--- // Driver internals //--- /* dma_translate(): Translate virtual address to DMA-suitable form */ static uintptr_t sh7305_dma_translate_addr(uintptr_t a) { /* Preserve RS addresses (as of SH7724 Reference, 11.2.2) */ if(a >= 0xfd800000 && a < 0xfd800800) return a; /* Translate virtual addresses to IL memory to physical addresses; the same address is used (as of SH7724 Reference, 10.3.3) */ if(a >= 0xe5200000 && a < 0xe5204000) return a; /* First additional on-chip memory area (XRAM) */ if(a >= 0xe5007000 && a < 0xe5009000) return a; /* Second on-chip memory area (YRAM) */ if(a >= 0xe5017000 && a < 0xe5019000) return a; /* Translate P1 and P2 addresses to ROM and RAM to physical form */ if(a >= 0x80000000 && a < 0xc0000000) return a & 0x1fffffff; /* By default: I don't know what this is, let's preserve it */ return a; } /* sh7305_dma_channel_reserve() : wait until a channel is free and reserve it */ static sh7305_dma_channel_t *sh7305_dma_channel_reserve(void) { sh7305_dma_channel_t *ch; ch = NULL; while (1) { cpu_atomic_start(); for (int i = 5; i >= 0; --i) { if (dmac_info.channel[i].id.INVALID == 0) continue; dmac_info.keyframe += 1; ch = &dmac_info.channel[i]; ch->id.INVALID = 0; ch->id.IDX = i; ch->id.KEYFRAME = dmac_info.keyframe; break; } cpu_atomic_end(); if (ch != NULL) break; __asm__ volatile ("sleep"); } return ch; } #if 0 /* sh7305_dma_channel_release() : release DMA channel */ static int sh7305_dma_channel_release(sh7305_dma_channel_t * ch, int atomic) { if (sh7305_dma_channel_wait(ch, atomic) != 0) return -1; ch->id.INVALID = 1; return 0; } #endif /* sh7305_dma_channel_find() : find a channel using the ID */ sh7305_dma_channel_t *sh7305_dma_channel_find(sh7305_dma_id_t id) { if (id.INVALID == 1) return NULL; if (id.IDX < 0 && id.IDX >= 6) return NULL; return &dmac_info.channel[id.IDX]; } /* sh7305_dma_wait() : wait DAM channel */ int sh7305_dma_channel_wait(sh7305_dma_channel_t *ch, int atomic) { int find; find = -1; cpu_atomic_start(); for (int i = 0; i < 6; ++i) { if (ch != &dmac_info.channel[i]) continue; find = 0; break; } cpu_atomic_end(); if (find != 0) return find; while (1) { if (ch->hw->CHCR.DE == 0) break; if (atomic == 1) __asm__ volatile ("sleep"); } return 0; } //--- // Driver public API //--- /* sh7305_dma_transfert_async() : start asynchronous transfert */ dma_id_t sh7305_dma_transfert_async( uintptr_t src, dma_addr_op_t src_op, uintptr_t dst, dma_addr_op_t dst_op, dma_block_size_t block_size, int block_count ) { sh7305_dma_channel_t *ch; ch = sh7305_dma_channel_reserve(); if (ch == NULL) return -1; /* disable channel */ ch->hw->CHCR.DE = 0; /* set source / destination address and block counter */ ch->hw->SAR = sh7305_dma_translate_addr(src); ch->hw->DAR = sh7305_dma_translate_addr(dst); ch->hw->TCR = block_count & 0x0fffffff; /* common channel configuration <> enable release of the bus mastership between reading and writting <> select normal mode (channel 0..3) <> select asynchronous DREQ sampling (channel 0) <> detect overrun 0 (channel 0) <> clear half-end flags <> disable half-end interruption <> select DACK output at read cycle (channel 0) <> select low-active output of DACK (channel 0) <> set the approriate destination addressing mode <> set the approriate source addressing mode <> select auto-request transfert <> select DREQ detection on low-level (channel 0) <> select cycle steal mode <> enable interruption <> clear interrupt flags */ ch->hw->CHCR.LCKN = 1; ch->hw->CHCR.RPT = 0b000; ch->hw->CHCR.DA = 0; ch->hw->CHCR.DO = 0; ch->hw->CHCR.HE = 0; ch->hw->CHCR.HIE = 0; ch->hw->CHCR.AM = 0; ch->hw->CHCR.AL = 0; ch->hw->CHCR.DM = dst_op; ch->hw->CHCR.SM = src_op; ch->hw->CHCR.RS = 0b0100; ch->hw->CHCR.DL = 0; ch->hw->CHCR.DS = 0; ch->hw->CHCR.TB = 0; ch->hw->CHCR.IE = 1; ch->hw->CHCR.TE = 0; /* set the block size */ ch->hw->CHCR.TS3 = (block_size & 0b1000) >> 3; ch->hw->CHCR.TS2 = (block_size & 0b0100) >> 2; ch->hw->CHCR.TS1 = (block_size & 0b0010) >> 1; ch->hw->CHCR.TS0 = (block_size & 0b0001) >> 0; /* start DMA channel */ ch->hw->CHCR.DE = 1; return ch->id.lword; } //--- // Kernel-level API //--- /* Interrupt handler for all finished DMA transfers */ static void sh7305_dma_interrupt_transfer_ended(sh7305_dma_channel_t *ch) { ch->hw->CHCR.IE = 0; ch->hw->CHCR.DE = 0; ch->hw->CHCR.TE = 0; SH7305_DMA.DMAOR.AE = 0; SH7305_DMA.DMAOR.NMIF = 0; ch->id.INVALID = 1; } /* DMA error handler (interrupt) */ static void sh7305_dma_error_handler(void) { dclear(C_WHITE); dprint(0, 0, C_BLACK, "DMA error handler !!!\n" " DMA0.CHCR = %p\n" " DMA1.CHCR = %p\n" " DMA2.CHCR = %p\n" " DMA3.CHCR = %p\n" " DMA4.CHCR = %p\n" " DMA5.CHCR = %p\n" " DMAOR = %p\n" "reset the device..." , SH7305_DMA.DMA0.CHCR.lword, SH7305_DMA.DMA1.CHCR.lword, SH7305_DMA.DMA2.CHCR.lword, SH7305_DMA.DMA3.CHCR.lword, SH7305_DMA.DMA4.CHCR.lword, SH7305_DMA.DMA5.CHCR.lword, SH7305_DMA.DMAOR.word ); dupdate(); while (1) { __asm__ volatile ("sleep"); } } //--- // Declare driver //--- //FIXME: avoid manualy sync DMAOR with header o(x_x)o struct dma_ctx { volatile struct __sh7305_dma_channel ch[6]; word_union(DMAOR, uint16_t CMS :4; /* Cycle steal Mode Select */ uint16_t :2; uint16_t PR :2; /* PRiority mode */ uint16_t :4; uint16_t :1; uint16_t AE :1; /* Address Error flag */ uint16_t NMIF :1; /* NMI Flag */ uint16_t DME :1; /* DMA Master Enable */ ); }; static bool __dma_hpowered(void) { return (SH7305_POWER.MSTPCR0.DMA == 0); } static int __dma_hpoweron(void) { SH7305_POWER.MSTPCR0.DMA = 0; return 0; } static int __dma_hpoweroff(void) { SH7305_POWER.MSTPCR0.DMA = 1; return 0; } static void __dma_configure(struct dma_ctx *s) { extern void sh7305_dma_inth_te(void); extern void sh7305_dma_inth_ae(void); int codes[] = { 0x800, 0x820, 0x840, 0x860, 0xb80, 0xba0 }; for(int i = 0; i < 6; i++) { sh7305_intc_install_inth_generic( codes[i], VHEX_CALL( &sh7305_dma_interrupt_transfer_ended, (void*)&dmac_info.channel[i] ) ); s->ch[i].SAR = 0x00000000; s->ch[i].DAR = 0x00000000; s->ch[i].TCR = 0x00000000; s->ch[i].CHCR.DE = 0; dmac_info.channel[i].id.INVALID = 1; } /* Configure the DMA operations <> select normal cycle steal mode <> select round-robin mode <> clear address error flags <> clear NMIF flags <> enable the master switch */ s->DMAOR.CMS = 0b0000; s->DMAOR.PR = 0b11; s->DMAOR.AE = 0; s->DMAOR.NMIF = 0; s->DMAOR.DME = 1; /* Install Address Error interrupt gate */ sh7305_intc_install_inth_generic( 0xbc0, VHEX_CALL(&sh7305_dma_error_handler) ); /* Set interrupt priority to 3, except for the channels that are used by the USB driver (channel 0) */ sh7305_intc_priority(INTC_DMA_DEI0, 9); sh7305_intc_priority(INTC_DMA_DEI1, 3); sh7305_intc_priority(INTC_DMA_DEI2, 3); sh7305_intc_priority(INTC_DMA_DEI3, 3); sh7305_intc_priority(INTC_DMA_DEI4, 3); sh7305_intc_priority(INTC_DMA_DEI5, 3); sh7305_intc_priority(INTC_DMA_DADERR, 3); } static void __dma_hsave(struct dma_ctx *s) { sh7305_dma_channel_t * ch; for(int i = 0; i < 6; i++) { ch = &dmac_info.channel[i]; sh7305_dma_channel_wait(ch, 1); s->ch[i].SAR = ch->hw->SAR; s->ch[i].DAR = ch->hw->DAR; s->ch[i].TCR = ch->hw->TCR; s->ch[i].CHCR.lword = ch->hw->CHCR.lword; } s->DMAOR.word = SH7305_DMA.DMAOR.word; } static void __dma_hrestore(struct dma_ctx *s) { sh7305_dma_channel_t * ch; SH7305_DMA.DMAOR.DME = 0; for(int i = 0; i < 6; i++) { ch = &dmac_info.channel[i]; ch->hw->SAR = s->ch[i].SAR; ch->hw->DAR = s->ch[i].DAR; ch->hw->TCR = s->ch[i].TCR; ch->hw->CHCR.lword = s->ch[i].CHCR.lword; } SH7305_DMA.DMAOR.word = s->DMAOR.word; } struct vhex_driver drv_dma = { .name = "DMA", .hpowered = (void*)&__dma_hpowered, .hpoweron = (void*)&__dma_hpoweron, .hpoweroff = (void*)&__dma_hpoweroff, .hsave = (void*)&__dma_hsave, .hrestore = (void*)&__dma_hrestore, .configure = (void*)&__dma_configure, .state_size = sizeof(struct dma_ctx), .flags = { .DMA = 1, .SHARED = 0, .UNUSED = 0, }, .module_data = &(struct dma_drv_interface){ .dma_memcpy = &sh7305_dma_memcpy, .dma_memset = &sh7305_dma_memset, .dma_wait = &sh7305_dma_wait, .dma_spinwait = &sh7305_dma_spinwait } }; VHEX_DECLARE_DRIVER(05, drv_dma);