diff options
Diffstat (limited to 'src/atsam/spi.c')
-rw-r--r-- | src/atsam/spi.c | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/src/atsam/spi.c b/src/atsam/spi.c new file mode 100644 index 00000000..5f79a02c --- /dev/null +++ b/src/atsam/spi.c @@ -0,0 +1,285 @@ +// SPI transmissions on sam3 +// +// Copyright (C) 2018 Petri Honkala <cruwaller@gmail.com> +// Copyright (C) 2018 Florian Heilmann <Florian.Heilmann@gmx.net> +// Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net> +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "command.h" // shutdown +#include "gpio.h" // spi_setup +#include "internal.h" // gpio_peripheral +#include "sched.h" // sched_shutdown + + +/**************************************************************** + * SPI/USART buses and pins + ****************************************************************/ + +struct spi_info { + void *dev; + uint32_t dev_id; + uint8_t miso_pin, mosi_pin, sck_pin, rxtx_periph, sck_periph; +}; + +static const struct spi_info spi_bus[] = { +#if CONFIG_MACH_SAM3X8E + { SPI0, ID_SPI0, GPIO('A', 25), GPIO('A', 26), GPIO('A', 27), 'A', 'A' }, + { USART0, ID_USART0, GPIO('A', 10), GPIO('A', 11), GPIO('A', 17), 'A', 'B'}, + { USART1, ID_USART1, GPIO('A', 12), GPIO('A', 13), GPIO('A', 16), 'A', 'A'}, + { USART2, ID_USART2, GPIO('B', 21), GPIO('B', 20), GPIO('B', 24), 'A', 'A'}, +#elif CONFIG_MACH_SAM4S8C + { SPI, ID_SPI, GPIO('A', 12), GPIO('A', 13), GPIO('A', 14), 'A', 'A' }, + { USART0, ID_USART0, GPIO('A', 5), GPIO('A', 6), GPIO('A', 2), 'A', 'B' }, + { USART1, ID_USART1, GPIO('A', 21), GPIO('A', 22), GPIO('A', 23), 'A', 'A'}, +#elif CONFIG_MACH_SAM4E8E + { USART0, ID_USART0, GPIO('B', 0), GPIO('B', 1), GPIO('B', 13), 'C', 'C' }, + { USART1, ID_USART1, GPIO('A', 21), GPIO('A', 22), GPIO('A', 23), 'A', 'A'}, + { SPI, ID_SPI, GPIO('A', 12), GPIO('A', 13), GPIO('A', 14), 'A', 'A' }, +#endif +}; + +static int +is_spihw(void *dev) +{ +#if CONFIG_MACH_SAM3X8E + return dev == SPI0; +#else + return dev == SPI; +#endif +} + +static void +init_pins(uint32_t bus) +{ + const struct spi_info *si = &spi_bus[bus]; + gpio_peripheral(si->sck_pin, si->sck_periph, 0); + gpio_peripheral(si->miso_pin, si->rxtx_periph, 1); + gpio_peripheral(si->mosi_pin, si->rxtx_periph, 0); + enable_pclock(si->dev_id); +} + + +/**************************************************************** + * SPI hardware + ****************************************************************/ + +#define CHANNEL 0 // Use same channel for all + +static void +spihw_init(uint32_t bus) +{ + init_pins(bus); + Spi *pSpi = spi_bus[bus].dev; + + /* Disable SPI */ + pSpi->SPI_CR = SPI_CR_SPIDIS; + /* Execute a software reset of the SPI twice */ + pSpi->SPI_CR = SPI_CR_SWRST; + pSpi->SPI_CR = SPI_CR_SWRST; + + pSpi->SPI_MR = ( SPI_MR_MSTR | // Set master mode + SPI_MR_MODFDIS | // Disable fault detection + SPI_MR_PCS(CHANNEL) // Fixes peripheral select + ); + pSpi->SPI_IDR = 0xffffffff; // Disable ISRs + + /* Clear Chip Select Registers */ + pSpi->SPI_CSR[0] = 0; + pSpi->SPI_CSR[1] = 0; + pSpi->SPI_CSR[2] = 0; + pSpi->SPI_CSR[3] = 0; + + /* Set basic channel config */ + pSpi->SPI_CSR[CHANNEL] = 0; + /* Enable SPI */ + pSpi->SPI_CR = SPI_CR_SPIEN; +} + +static struct spi_config +spihw_setup(uint32_t bus, uint8_t mode, uint32_t rate) +{ + // Make sure bus is enabled + spihw_init(bus); + + uint32_t config = 0; + uint32_t clockDiv; + if (rate < (CHIP_FREQ_CPU_MAX / 255)) { + clockDiv = 255; + } else if (rate >= (CHIP_FREQ_CPU_MAX / 2)) { + clockDiv = 2; + } else { + clockDiv = (CHIP_FREQ_CPU_MAX / (rate + 1)) + 1; + } + + /****** Will be written to SPI_CSRx register ******/ + // CSAAT : Chip Select Active After Transfer + config = SPI_CSR_CSAAT; + config |= SPI_CSR_BITS_8_BIT; // TODO: support for SPI_CSR_BITS_16_BIT + // NOTE: NCPHA is inverted, CPHA normal!! + switch(mode) { + case 0: + config |= SPI_CSR_NCPHA; + break; + case 1: + config |= 0; + break; + case 2: + config |= SPI_CSR_NCPHA; + config |= SPI_CSR_CPOL; + break; + case 3: + config |= SPI_CSR_CPOL; + break; + }; + + config |= ((clockDiv & 0xffu) << SPI_CSR_SCBR_Pos); + return (struct spi_config){ .spidev = spi_bus[bus].dev, .cfg = config }; +} + +static void +spihw_prepare(struct spi_config config) +{ + Spi *pSpi = config.spidev; + pSpi->SPI_CSR[CHANNEL] = config.cfg; +} + +static void +spihw_transfer(struct spi_config config, uint8_t receive_data + , uint8_t len, uint8_t *data) +{ + Spi *pSpi = config.spidev; + if (receive_data) { + while (len--) { + pSpi->SPI_TDR = *data; + // wait for receive register + while (!(pSpi->SPI_SR & SPI_SR_RDRF)) + ; + // get data + *data++ = pSpi->SPI_RDR; + } + } else { + while (len--) { + pSpi->SPI_TDR = *data++; + // wait for receive register + while (!(pSpi->SPI_SR & SPI_SR_RDRF)) + ; + // read data (to clear RDRF) + pSpi->SPI_RDR; + } + } +} + + +/**************************************************************** + * USART hardware + ****************************************************************/ + +static struct spi_config +usart_setup(uint32_t bus, uint8_t mode, uint32_t rate) +{ + init_pins(bus); + Usart *p_usart = spi_bus[bus].dev; + + p_usart->US_MR = 0; + p_usart->US_RTOR = 0; + p_usart->US_TTGR = 0; + + p_usart->US_CR = US_CR_RSTTX | US_CR_RSTRX | US_CR_TXDIS | US_CR_RXDIS; + + uint32_t br = DIV_ROUND_UP(CHIP_FREQ_CPU_MAX, rate); + p_usart->US_BRGR = br << US_BRGR_CD_Pos; + + uint32_t reg = US_MR_CHRL_8_BIT | + US_MR_USART_MODE_SPI_MASTER | + US_MR_CLKO | + US_MR_CHMODE_NORMAL; + switch (mode) { + case 0: + reg |= US_MR_CPHA; + reg &= ~US_MR_CPOL; + break; + case 1: + reg &= ~US_MR_CPHA; + reg &= ~US_MR_CPOL; + break; + case 2: + reg |= US_MR_CPHA; + reg |= US_MR_CPOL; + break; + case 3: + reg &= ~US_MR_CPHA; + reg |= US_MR_CPOL; + break; + } + + p_usart->US_MR |= reg; + p_usart->US_CR = US_CR_RXEN | US_CR_TXEN; + return (struct spi_config){ .spidev=p_usart, .cfg=p_usart->US_MR }; +} + +static void +usart_prepare(struct spi_config config) +{ +} + +static void +usart_transfer(struct spi_config config, uint8_t receive_data + , uint8_t len, uint8_t *data) +{ + Usart *p_usart = config.spidev; + if (receive_data) { + for (uint32_t i = 0; i < len; ++i) { + uint32_t co = (uint32_t)*data & 0x000000FF; + while(!(p_usart->US_CSR & US_CSR_TXRDY)) {} + p_usart->US_THR = US_THR_TXCHR(co); + uint32_t ci = 0; + while(!(p_usart->US_CSR & US_CSR_RXRDY)) {} + ci = p_usart->US_RHR & US_RHR_RXCHR_Msk; + *data++ = (uint8_t)ci; + } + } else { + for (uint32_t i = 0; i < len; ++i) { + uint32_t co = (uint32_t)*data & 0x000000FF; + while(!(p_usart->US_CSR & US_CSR_TXRDY)) {} + p_usart->US_THR = US_THR_TXCHR(co); + while(!(p_usart->US_CSR & US_CSR_RXRDY)) {} + (void)(p_usart->US_RHR & US_RHR_RXCHR_Msk); + (void)*data++; + } + } +} + + +/**************************************************************** + * Interface + ****************************************************************/ + +struct spi_config +spi_setup(uint32_t bus, uint8_t mode, uint32_t rate) +{ + if (bus >= ARRAY_SIZE(spi_bus)) + shutdown("Invalid spi bus"); + if (is_spihw(spi_bus[bus].dev)) + return spihw_setup(bus, mode, rate); + return usart_setup(bus, mode, rate); +} + +void +spi_prepare(struct spi_config config) +{ + if (is_spihw(config.spidev)) + spihw_prepare(config); + else + usart_prepare(config); +} + +void +spi_transfer(struct spi_config config, uint8_t receive_data + , uint8_t len, uint8_t *data) +{ + if (is_spihw(config.spidev)) + spihw_transfer(config, receive_data, len, data); + else + usart_transfer(config, receive_data, len, data); +} |