/* * This file is part of open_agb_firm * Copyright (C) 2021 derrek, profi200 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "types.h" #include "mem_map.h" #include "arm11/drivers/spi.h" #include "arm11/drivers/cfg11.h" #include "arm11/drivers/interrupt.h" static const struct { u8 busId; u8 csClk; } g_spiDevTable[4] = { {SPI_BUS1, NSPI_CS_0 | NSPI_CLK_2MHZ}, {SPI_BUS1, NSPI_CS_1 | NSPI_CLK_4MHZ}, // Used with the old interface in Horizon OS? {SPI_BUS1, NSPI_CS_2 | NSPI_CLK_4MHZ}, // Used with the old interface in Horizon OS. {SPI_BUS2, NSPI_CS_0 | NSPI_CLK_16MHZ}, //{SPI_BUS2, NSPI_CS_1 | NSPI_CLK_512KHZ}, // Unused. //{SPI_BUS2, NSPI_CS_2 | NSPI_CLK_512KHZ}, // Unused. //{SPI_BUS3, NSPI_CS_0 | NSPI_CLK_512KHZ} // Debugger? }; static NspiBus* getBusRegs(const u8 busId) { // Don't force inline here. return getNspiBusRegs(busId); } static inline void nspiWaitBusy(const NspiBus *const nspiBus) { while(nspiBus->cnt & NSPI_EN); } static inline void nspiWaitFifoBusy(const NspiBus *const nspiBus) { while(nspiBus->fifo_stat & NSPI_FIFO_BUSY); } void NSPI_init(void) { static bool inited = false; if(inited) return; inited = true; // Switch all 3 buses to the new interface. getCfg11Regs()->spi_cnt = SPI_CNT_SPI3_NEW_IF | SPI_CNT_SPI2_NEW_IF | SPI_CNT_SPI1_NEW_IF; NspiBus *nspiBus = getBusRegs(SPI_BUS1); nspiBus->int_mask = NSPI_INT_TRAN_END; // Disable interrupt 1. nspiBus->int_stat = NSPI_INT_AP_TMOUT | NSPI_INT_AP_MATCH | NSPI_INT_TRAN_END; // Acknowledge. nspiBus = getBusRegs(SPI_BUS2); nspiBus->int_mask = NSPI_INT_TRAN_END; nspiBus->int_stat = NSPI_INT_AP_TMOUT | NSPI_INT_AP_MATCH | NSPI_INT_TRAN_END; nspiBus = getBusRegs(SPI_BUS3); nspiBus->int_mask = NSPI_INT_TRAN_END; nspiBus->int_stat = NSPI_INT_AP_TMOUT | NSPI_INT_AP_MATCH | NSPI_INT_TRAN_END; IRQ_registerIsr(IRQ_SPI2, 14, 0, NULL); IRQ_registerIsr(IRQ_SPI3, 14, 0, NULL); IRQ_registerIsr(IRQ_SPI1, 14, 0, NULL); } bool NSPI_autoPollBit(SpiDevice dev, const u32 apParams) { dev &= 0x7Fu; NspiBus *const nspiBus = getBusRegs(g_spiDevTable[dev].busId); nspiBus->cnt = g_spiDevTable[dev].csClk; nspiBus->autopoll = NSPI_AP_START | apParams; u32 res; do { __wfi(); res = nspiBus->int_stat; } while(!(res & (NSPI_INT_AP_TMOUT | NSPI_INT_AP_MATCH))); nspiBus->int_stat = res; // Aknowledge. return (res & NSPI_INT_AP_TMOUT) == 0; // Timeout error. } // Note: Removing the counter check very slightly reduces performance. void NSPI_sendRecv(const SpiDevice dev, const void *in, void *out, const u32 inSize, const u32 outSize) { NspiBus *const nspiBus = getBusRegs(g_spiDevTable[dev & 0x7Fu].busId); const u32 cntParams = NSPI_EN | g_spiDevTable[dev & 0x7Fu].csClk; const u32 *in32 = in; u32 *out32 = out; if(in32) { nspiBus->blklen = inSize; nspiBus->cnt = cntParams | NSPI_DIR_S; u32 counter = 0; do { if((counter & 31) == 0) nspiWaitFifoBusy(nspiBus); nspiBus->fifo = *in32++; counter += 4; } while(counter < inSize); nspiWaitBusy(nspiBus); } if(out32) { nspiBus->blklen = outSize; nspiBus->cnt = cntParams | NSPI_DIR_R; u32 counter = 0; do { if((counter & 31) == 0) nspiWaitFifoBusy(nspiBus); *out32++ = nspiBus->fifo; counter += 4; } while(counter < outSize); nspiWaitBusy(nspiBus); } if(dev & NSPI_DEV_CS_HIGH) nspiBus->cs = NSPI_CS_HIGH; }