342 lines
9.9 KiB
C
342 lines
9.9 KiB
C
// 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
|
|
|
|
#if CONFIG_MACH_SAME70 // Fixes for upstream header changes
|
|
#define US_MR_CHMODE_NORMAL US_MR_USART_CHMODE_NORMAL
|
|
#define US_MR_CPHA US_MR_SPI_CPHA
|
|
#define US_MR_CPOL US_MR_SPI_CPOL
|
|
#endif
|
|
|
|
/****************************************************************
|
|
* SPI/USART buses and pins
|
|
****************************************************************/
|
|
|
|
struct spi_info {
|
|
void *dev;
|
|
uint32_t dev_id;
|
|
uint8_t miso_pin, mosi_pin, sck_pin, rx_periph, tx_periph, sck_periph;
|
|
};
|
|
|
|
#if CONFIG_MACH_SAM3X
|
|
DECL_ENUMERATION("spi_bus", "spi0", 0);
|
|
DECL_ENUMERATION_RANGE("spi_bus", "usart0", 1, 3);
|
|
DECL_CONSTANT_STR("BUS_PINS_spi0", "PA25,PA26,PA27");
|
|
DECL_CONSTANT_STR("BUS_PINS_usart0", "PA10,PA11,PA17");
|
|
DECL_CONSTANT_STR("BUS_PINS_usart1", "PA12,PA13,PA16");
|
|
DECL_CONSTANT_STR("BUS_PINS_usart2", "PB21,PB20,PB24");
|
|
#elif CONFIG_MACH_SAM4S
|
|
DECL_ENUMERATION("spi_bus", "spi", 0);
|
|
DECL_ENUMERATION_RANGE("spi_bus", "usart0", 1, 2);
|
|
DECL_CONSTANT_STR("BUS_PINS_spi", "PA12,PA13,PA14");
|
|
DECL_CONSTANT_STR("BUS_PINS_usart0", "PA5,PA6,PA2");
|
|
DECL_CONSTANT_STR("BUS_PINS_usart1", "PA21,PA22,PA23");
|
|
#elif CONFIG_MACH_SAM4E
|
|
DECL_ENUMERATION("spi_bus", "spi", 0);
|
|
DECL_ENUMERATION_RANGE("spi_bus", "usart0", 1, 2);
|
|
DECL_CONSTANT_STR("BUS_PINS_spi", "PA12,PA13,PA14");
|
|
DECL_CONSTANT_STR("BUS_PINS_usart0", "PB0,PB1,PB13");
|
|
DECL_CONSTANT_STR("BUS_PINS_usart1", "PA21,PA22,PA23");
|
|
#elif CONFIG_MACH_SAME70
|
|
DECL_ENUMERATION_RANGE("spi_bus", "spi0", 0, 2);
|
|
DECL_ENUMERATION_RANGE("spi_bus", "usart0", 2, 3);
|
|
DECL_CONSTANT_STR("BUS_PINS_spi0", "PD20,PD21,PD22");
|
|
DECL_CONSTANT_STR("BUS_PINS_usart0", "PB0,PB1,PB13");
|
|
DECL_CONSTANT_STR("BUS_PINS_usart1", "PA21,PB4,PA23");
|
|
DECL_CONSTANT_STR("BUS_PINS_usart2", "PD15,PD16,PD17");
|
|
#endif
|
|
|
|
static const struct spi_info spi_bus[] = {
|
|
#if CONFIG_MACH_SAM3X
|
|
{ SPI0, ID_SPI0,
|
|
GPIO('A', 25), GPIO('A', 26), GPIO('A', 27), 'A', 'A', 'A'},
|
|
{ USART0, ID_USART0,
|
|
GPIO('A', 10), GPIO('A', 11), GPIO('A', 17), 'A', 'A', 'B'},
|
|
{ USART1, ID_USART1,
|
|
GPIO('A', 12), GPIO('A', 13), GPIO('A', 16), 'A', 'A', 'A'},
|
|
{ USART2, ID_USART2,
|
|
GPIO('B', 21), GPIO('B', 20), GPIO('B', 24), 'A', 'A', 'A'},
|
|
#elif CONFIG_MACH_SAM4S
|
|
{ SPI, ID_SPI,
|
|
GPIO('A', 12), GPIO('A', 13), GPIO('A', 14), 'A', 'A', 'A'},
|
|
{ USART0, ID_USART0,
|
|
GPIO('A', 5), GPIO('A', 6), GPIO('A', 2), 'A', 'A', 'B'},
|
|
{ USART1, ID_USART1,
|
|
GPIO('A', 21), GPIO('A', 22), GPIO('A', 23), 'A', 'A', 'A'},
|
|
#elif CONFIG_MACH_SAM4E
|
|
{ SPI, ID_SPI,
|
|
GPIO('A', 12), GPIO('A', 13), GPIO('A', 14), 'A', 'A', 'A'},
|
|
{ USART0, ID_USART0,
|
|
GPIO('B', 0), GPIO('B', 1), GPIO('B', 13), 'C', 'C', 'C'},
|
|
{ USART1, ID_USART1,
|
|
GPIO('A', 21), GPIO('A', 22), GPIO('A', 23), 'A', 'A', 'A'},
|
|
#elif CONFIG_MACH_SAME70
|
|
{ SPI0, ID_SPI0,
|
|
GPIO('D', 20), GPIO('D', 21), GPIO('D', 22), 'B', 'B', 'B'},
|
|
{ SPI1, ID_SPI1,
|
|
GPIO('C', 26), GPIO('C', 27), GPIO('C', 24), 'C', 'C', 'C'},
|
|
{ USART0, ID_USART0,
|
|
GPIO('B', 0), GPIO('B', 1), GPIO('B', 13), 'C', 'C', 'C'},
|
|
{ USART1, ID_USART1,
|
|
GPIO('A', 21), GPIO('B', 4), GPIO('A', 23), 'A', 'D', 'A'},
|
|
{ USART2, ID_USART2,
|
|
GPIO('D', 15), GPIO('D', 16), GPIO('D', 17), 'B', 'B', 'B'},
|
|
#endif
|
|
};
|
|
|
|
static int
|
|
is_spihw(void *dev)
|
|
{
|
|
#if CONFIG_MACH_SAM3X
|
|
return dev == SPI0;
|
|
#elif CONFIG_MACH_SAME70
|
|
return (dev == SPI0) || (dev == SPI1);
|
|
#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->rx_periph, 1);
|
|
gpio_peripheral(si->mosi_pin, si->tx_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 clockDiv, pclk = get_pclock_frequency(ID_USART0);
|
|
if (rate < pclk / 255) {
|
|
clockDiv = 255;
|
|
} else if (rate >= pclk / 2) {
|
|
clockDiv = 2;
|
|
} else {
|
|
clockDiv = pclk / (rate + 1) + 1;
|
|
}
|
|
|
|
/****** Will be written to SPI_CSRx register ******/
|
|
// CSAAT : Chip Select Active After Transfer
|
|
uint32_t 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 pclk = get_pclock_frequency(ID_USART0);
|
|
uint32_t br = DIV_ROUND_UP(pclk, 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);
|
|
}
|