sd2snes/src/spi.c
2010-09-23 00:18:44 +02:00

277 lines
7.7 KiB
C

/* Sd2iec - SD/MMC to Commodore serial bus interface/controller
Copyright (C) 2007-2010 Ingo Korb <ingo@akana.de>
Inspiration and low-level SD/MMC access based on code from MMC2IEC
by Lars Pontoppidan et al., see sdcard.c|h and config.h.
FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h.
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; version 2 of the License only.
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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
spi.c: Low-level SPI routines
*/
#include <arm/NXP/LPC17xx/LPC17xx.h>
#include "bits.h"
#include "config.h"
#include "spi.h"
#include "uart.h"
#define SSP_TFE 0 // Transmit FIFO empty
#define SSP_TNF 1 // Transmit FIFO not full
#define SSP_RNE 2 // Receive FIFO not empty
#define SSP_RFF 3 // Receive FIFO full
#define SSP_BSY 4 // Busy
// #define SSP_REGS LPC_SSP1
// #define SSP_PCLKREG PCLKSEL0
/* SSP0: PCLKSEL1
SSP1: PCLKSEL0 */
// #define SSP_PCLKBIT 20
/* SSP0: 10
SSP1: 20 */
// #define SSP_DMAID_TX 2
/* SSP0: 0
SSP1: 2 */
// #define SSP_DMAID_RX 3
/* SSP0: 1
SSP1: 3 */
typedef struct {
LPC_SSP_TypeDef *SSP_REGS;
LPC_GPDMACH_TypeDef *SSP_DMACH;
uint32_t *SSP_PCLKREG;
int SSP_PCLKBIT;
int SSP_DMAID_TX;
int SSP_DMAID_RX;
} ssp_props;
static ssp_props SSP_SEL[2] = {
{ LPC_SSP0, LPC_GPDMACH0, (uint32_t*)&(LPC_SC->PCLKSEL1), 10, 0, 1 }, /* SSP0 */
{ LPC_SSP1, LPC_GPDMACH1, (uint32_t*)&(LPC_SC->PCLKSEL0), 20, 2, 3 } /* SSP1 */
};
void spi_preinit(int device) {
/* select interface */
ssp_props *ssp = &(SSP_SEL[device]);
/* Set clock prescaler to 1:1 */
BITBAND(*(ssp->SSP_PCLKREG), ssp->SSP_PCLKBIT) = 1;
}
void spi_init(spi_speed_t speed, int device) {
/* select interface */
ssp_props *ssp = &(SSP_SEL[device]);
/* configure data format - 8 bits, SPI, CPOL=0, CPHA=0, 1 clock per bit */
ssp->SSP_REGS->CR0 = (8-1);
/* set clock prescaler */
if (speed == SPI_SPEED_FAST) {
ssp->SSP_REGS->CPSR = SSP_CLK_DIVISOR_FAST;
} else if (speed == SPI_SPEED_SLOW) {
ssp->SSP_REGS->CPSR = SSP_CLK_DIVISOR_SLOW;
} else if (speed == SPI_SPEED_FPGA_FAST) {
ssp->SSP_REGS->CPSR = SSP_CLK_DIVISOR_FPGA_FAST;
} else {
ssp->SSP_REGS->CPSR = SSP_CLK_DIVISOR_FPGA_SLOW;
}
/* Enable SSP */
ssp->SSP_REGS->CR1 = BV(1);
/* Enable DMA controller, little-endian mode */
BITBAND(LPC_SC->PCONP, 29) = 1;
LPC_GPDMA->DMACConfig = 1;
}
void spi_tx_sync(int device) {
/* select interface */
ssp_props *ssp = &(SSP_SEL[device]);
/* Wait until TX fifo is flushed */
while (BITBAND(ssp->SSP_REGS->SR, SSP_BSY)) ;
}
void spi_tx_byte(uint8_t data, int device) {
/* select interface */
ssp_props *ssp = &(SSP_SEL[device]);
/* Wait until TX fifo can accept data */
while (!BITBAND(ssp->SSP_REGS->SR, SSP_TNF)) ;
/* Send byte */
ssp->SSP_REGS->DR = data;
}
uint8_t spi_txrx_byte(uint8_t data, int device) {
/* select interface */
ssp_props *ssp = &(SSP_SEL[device]);
/* Wait until SSP is not busy */
while (BITBAND(ssp->SSP_REGS->SR, SSP_BSY)) ;
/* Clear RX fifo */
while (BITBAND(ssp->SSP_REGS->SR, SSP_RNE))
(void) ssp->SSP_REGS->DR;
/* Transmit a single dummy byte */
ssp->SSP_REGS->DR = data;
/* Wait until answer has been received */
while (!BITBAND(ssp->SSP_REGS->SR, SSP_RNE)) ;
return ssp->SSP_REGS->DR;
}
uint8_t spi_rx_byte(int device) {
/* select interface */
ssp_props *ssp = &(SSP_SEL[device]);
/* Wait until SSP is not busy */
while (BITBAND(ssp->SSP_REGS->SR, SSP_BSY)) ;
/* Clear RX fifo */
while (BITBAND(ssp->SSP_REGS->SR, SSP_RNE))
(void) ssp->SSP_REGS->DR;
/* Transmit a single dummy byte */
ssp->SSP_REGS->DR = 0xff;
/* Wait until answer has been received */
while (!BITBAND(ssp->SSP_REGS->SR, SSP_RNE)) ;
return ssp->SSP_REGS->DR;
}
void spi_tx_block(const void *ptr, unsigned int length, int device) {
const uint8_t *data = (const uint8_t *)ptr;
/* select interface */
ssp_props *ssp = &(SSP_SEL[device]);
while (length--) {
/* Wait until TX fifo can accept data */
while (!BITBAND(ssp->SSP_REGS->SR, SSP_TNF)) ;
ssp->SSP_REGS->DR = *data++;
}
}
void spi_rx_block(void *ptr, unsigned int length, int device) {
uint8_t *data = (uint8_t *)ptr;
unsigned int txlen = length;
/* select interface */
ssp_props *ssp = &(SSP_SEL[device]);
/* Wait until SSP is not busy */
while (BITBAND(ssp->SSP_REGS->SR, SSP_BSY)) ;
/* Clear RX fifo */
while (BITBAND(ssp->SSP_REGS->SR, SSP_RNE))
(void) ssp->SSP_REGS->DR;
if ((length & 3) != 0 || ((uint32_t)ptr & 3) != 0) {
/* Odd length or unaligned buffer */
while (length > 0) {
/* Wait until TX or RX FIFO are ready */
while (txlen > 0 && !BITBAND(ssp->SSP_REGS->SR, SSP_TNF) &&
!BITBAND(ssp->SSP_REGS->SR, SSP_RNE)) ;
/* Try to receive data */
while (length > 0 && BITBAND(ssp->SSP_REGS->SR, SSP_RNE)) {
*data++ = ssp->SSP_REGS->DR;
length--;
}
/* Send dummy data until TX full or RX ready */
while (txlen > 0 && BITBAND(ssp->SSP_REGS->SR, SSP_TNF) && !BITBAND(ssp->SSP_REGS->SR, SSP_RNE)) {
txlen--;
ssp->SSP_REGS->DR = 0xff;
}
}
} else {
/* Clear interrupt flags of DMA channels 0 */
LPC_GPDMA->DMACIntTCClear = BV(device);
LPC_GPDMA->DMACIntErrClr = BV(device);
/* Set up RX DMA channel */
ssp->SSP_DMACH->DMACCSrcAddr = (uint32_t)&ssp->SSP_REGS->DR;
ssp->SSP_DMACH->DMACCDestAddr = (uint32_t)ptr;
ssp->SSP_DMACH->DMACCLLI = 0; // no linked list
ssp->SSP_DMACH->DMACCControl = length
| (0 << 12) // source burst size 1 (FIXME: Check if larger possible/useful)
| (0 << 15) // destination burst size 1
| (0 << 18) // source transfer width 1 byte
| (2 << 21) // destination transfer width 4 bytes
| (0 << 26) // source address not incremented
| (1 << 27) // destination address incremented
;
ssp->SSP_DMACH->DMACCConfig = 1 // enable channel
| (ssp->SSP_DMAID_RX << 1) // data source SSP RX
| (2 << 11) // transfer from peripheral to memory
;
/* Enable RX FIFO DMA */
ssp->SSP_REGS->DMACR = 1;
/* Write <length> bytes into TX FIFO */
// FIXME: Any value in doing this using DMA too?
while (txlen > 0) {
while (txlen > 0 && BITBAND(ssp->SSP_REGS->SR, SSP_TNF)) {
txlen--;
ssp->SSP_REGS->DR = 0xff;
}
}
/* Wait until DMA channel disables itself */
while (ssp->SSP_DMACH->DMACCConfig & 1) ;
/* Disable RX FIFO DMA */
ssp->SSP_REGS->DMACR = 0;
}
}
void spi_set_speed(spi_speed_t speed, int device) {
/* select interface */
ssp_props *ssp = &(SSP_SEL[device]);
/* Wait until TX fifo is empty */
while (!BITBAND(ssp->SSP_REGS->SR, 0)) ;
/* Disable SSP (FIXME: Is this required?) */
ssp->SSP_REGS->CR1 = 0;
/* Change clock divisor */
if (speed == SPI_SPEED_FAST) {
ssp->SSP_REGS->CPSR = SSP_CLK_DIVISOR_FAST;
} else if (speed == SPI_SPEED_SLOW) {
ssp->SSP_REGS->CPSR = SSP_CLK_DIVISOR_SLOW;
} else if (speed == SPI_SPEED_FPGA_FAST) {
ssp->SSP_REGS->CPSR = SSP_CLK_DIVISOR_FPGA_FAST;
} else {
ssp->SSP_REGS->CPSR = SSP_CLK_DIVISOR_FPGA_SLOW;
}
/* Enable SSP */
ssp->SSP_REGS->CR1 = BV(1);
}