mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
e7fbaf3815
- removed the __attribute__((naked)) from ISRs - removed ISR_ENTER() and ISR_EXIT() macros Rationale: Cortex-Mx MCUs save registers R0-R4 automatically on calling ISRs. The naked attribute tells the compiler not to save any other registers. This is fine, as long as the code in the ISR is not nested. If nested, it will use also R4 and R5, which will then lead to currupted registers on exit of the ISR. Removing the naked will fix this.
408 lines
9.5 KiB
C
408 lines
9.5 KiB
C
/*
|
|
* Copyright (C) 2014 Hamburg University of Applied Sciences
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser General
|
|
* Public License v2.1. See the file LICENSE in the top level directory for more
|
|
* details.
|
|
*/
|
|
|
|
/**
|
|
* @ingroup cpu_sam3x8e
|
|
* @{
|
|
*
|
|
* @file spi.c
|
|
* @brief Low-level SPI driver implementation
|
|
*
|
|
* @author Maxime Blanloeil <maxime.blanloeil@phelma.grenoble-inp.fr>
|
|
* @author Peter Kietzmann <peter.kietzmann@haw-hamburg.de>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#include "cpu.h"
|
|
#include "sched.h"
|
|
#include "thread.h"
|
|
#include "periph/gpio.h"
|
|
#include "periph_conf.h"
|
|
#include "periph/spi.h"
|
|
#include "sam3x8e.h"
|
|
|
|
/* guard this file in case no SPI device is defined */
|
|
#if SPI_NUMOF
|
|
|
|
typedef struct {
|
|
char(*cb)(char data);
|
|
} spi_state_t;
|
|
|
|
static inline void irq_handler_transfer(Spi *spi, spi_t dev);
|
|
|
|
static spi_state_t spi_config[SPI_NUMOF];
|
|
|
|
void spi_poweron(spi_t dev)
|
|
{
|
|
switch (dev) {
|
|
#if SPI_0_EN
|
|
case SPI_0:
|
|
SPI_0_CLKEN();
|
|
SPI_0_MISO_PORT_CLKEN();
|
|
SPI_0_MOSI_PORT_CLKEN();
|
|
SPI_0_SCK_PORT_CLKEN();
|
|
break;
|
|
#endif /* SPI_0_EN */
|
|
}
|
|
}
|
|
|
|
void spi_poweroff(spi_t dev)
|
|
{
|
|
switch (dev) {
|
|
#if SPI_0_EN
|
|
case SPI_0:
|
|
while (!(SPI_0_DEV->SPI_SR & SPI_SR_SPIENS)); /* not busy anymore */
|
|
SPI_0_CLKDIS();
|
|
NVIC_DisableIRQ(SPI_0_IRQ);
|
|
break;
|
|
#endif /* SPI_0_EN */
|
|
}
|
|
}
|
|
|
|
int spi_init_master(spi_t dev, spi_conf_t conf, spi_speed_t speed)
|
|
{
|
|
uint8_t speed_divider;
|
|
Spi *spi_port;
|
|
|
|
spi_poweron(dev);
|
|
|
|
switch (speed) {
|
|
case SPI_SPEED_400KHZ:
|
|
speed_divider = 210;
|
|
break;
|
|
|
|
case SPI_SPEED_1MHZ:
|
|
speed_divider = 84;
|
|
break;
|
|
|
|
case SPI_SPEED_5MHZ:
|
|
speed_divider = 17;
|
|
break;
|
|
|
|
case SPI_SPEED_10MHZ: /* this might be too fast */
|
|
speed_divider = 8;
|
|
break;
|
|
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
switch (dev) {
|
|
#if SPI_0_EN
|
|
case SPI_0:
|
|
spi_port = SPI_0_DEV;
|
|
break;
|
|
#endif /* SPI_0_EN */
|
|
default:
|
|
return -2;
|
|
}
|
|
|
|
/* Configure SCK, MISO and MOSI pin */
|
|
spi_conf_pins(dev);
|
|
|
|
/***************** SPI-Init *****************/
|
|
|
|
/* Chip Select Register */
|
|
spi_port->SPI_CSR[0] = 0; /* This is index 0 since we don't use internal CS-Signals */
|
|
|
|
switch (conf) {
|
|
case SPI_CONF_FIRST_RISING:
|
|
spi_port->SPI_CSR[0] &= ~SPI_CSR_CPOL;
|
|
spi_port->SPI_CSR[0] |= SPI_CSR_NCPHA;
|
|
break;
|
|
|
|
case SPI_CONF_SECOND_RISING:
|
|
spi_port->SPI_CSR[0] &= ~SPI_CSR_CPOL;
|
|
spi_port->SPI_CSR[0] &= ~SPI_CSR_NCPHA;
|
|
break;
|
|
|
|
case SPI_CONF_FIRST_FALLING:
|
|
spi_port->SPI_CSR[0] |= SPI_CSR_CPOL;
|
|
spi_port->SPI_CSR[0] |= SPI_CSR_NCPHA;
|
|
break;
|
|
|
|
case SPI_CONF_SECOND_FALLING:
|
|
spi_port->SPI_CSR[0] |= SPI_CSR_CPOL;
|
|
spi_port->SPI_CSR[0] &= ~ SPI_CSR_NCPHA;
|
|
break;
|
|
|
|
default:
|
|
return -2;
|
|
}
|
|
|
|
spi_port->SPI_CSR[0] |= SPI_CSR_SCBR(speed_divider);
|
|
spi_port->SPI_CSR[0] |= SPI_CSR_BITS_8_BIT;
|
|
|
|
/* Control Register */
|
|
spi_port->SPI_CR |= SPI_CR_SPIEN;
|
|
/* Mode Register */
|
|
spi_port->SPI_MR = 0;
|
|
spi_port->SPI_MR |= SPI_MR_MSTR;
|
|
spi_port->SPI_MR |= SPI_MR_MODFDIS;
|
|
spi_port->SPI_MR &= ~SPI_MR_PS;
|
|
spi_port->SPI_MR &= ~SPI_MR_PCS(0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int spi_init_slave(spi_t dev, spi_conf_t conf, char(*cb)(char data))
|
|
{
|
|
Spi *spi_port;
|
|
|
|
spi_poweron(dev);
|
|
|
|
switch (dev) {
|
|
#if SPI_0_EN
|
|
case SPI_0:
|
|
spi_port = SPI_0_DEV;
|
|
NVIC_SetPriority(SPI_0_IRQ, SPI_0_IRQ_PRIO);
|
|
NVIC_EnableIRQ(SPI_0_IRQ);
|
|
/* Initialize predefined NSS pin as output so it is "disabled" */
|
|
PIOA->PIO_PER |= PIO_PA28A_SPI0_NPCS0;
|
|
PIOA->PIO_OER |= PIO_PA28A_SPI0_NPCS0;
|
|
break;
|
|
#endif /* SPI_0_EN */
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
/* Configure SCK, MISO and MOSI pin */
|
|
spi_conf_pins(dev);
|
|
|
|
/***************** SPI-Init *****************/
|
|
|
|
/* Chip Select Register */
|
|
spi_port->SPI_CSR[0] = 0;
|
|
|
|
switch (conf) {
|
|
case SPI_CONF_FIRST_RISING:
|
|
spi_port->SPI_CSR[0] &= ~SPI_CSR_CPOL;
|
|
spi_port->SPI_CSR[0] |= SPI_CSR_NCPHA;
|
|
break;
|
|
|
|
case SPI_CONF_SECOND_RISING:
|
|
spi_port->SPI_CSR[0] &= ~SPI_CSR_CPOL;
|
|
spi_port->SPI_CSR[0] &= ~SPI_CSR_NCPHA;
|
|
break;
|
|
|
|
case SPI_CONF_FIRST_FALLING:
|
|
spi_port->SPI_CSR[0] |= SPI_CSR_CPOL;
|
|
spi_port->SPI_CSR[0] |= SPI_CSR_NCPHA;
|
|
break;
|
|
|
|
case SPI_CONF_SECOND_FALLING:
|
|
spi_port->SPI_CSR[0] |= SPI_CSR_CPOL;
|
|
spi_port->SPI_CSR[0] &= ~ SPI_CSR_NCPHA;
|
|
break;
|
|
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
/* Control Register */
|
|
spi_port->SPI_CR |= SPI_CR_SPIEN;
|
|
/* Mode Register */
|
|
spi_port->SPI_MR = 0;
|
|
spi_port->SPI_MR |= SPI_MR_MODFDIS;
|
|
/* Enable SPI interrupts */
|
|
spi_port->SPI_IER = 0;
|
|
spi_port->SPI_IDR = ~(0);
|
|
spi_port->SPI_IER |= 1;
|
|
spi_port->SPI_IDR &= ~SPI_IDR_RDRF;
|
|
|
|
/* Set callback */
|
|
spi_config[dev].cb = cb;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int spi_conf_pins(spi_t dev)
|
|
{
|
|
switch (dev) {
|
|
#if SPI_0_EN
|
|
case SPI_0:
|
|
/***************** PIO-Init *****************/
|
|
/* Push-pull configuration */
|
|
SPI_0_MISO_PORT->PIO_MDER &= ~SPI_0_MISO_PIN;
|
|
SPI_0_MISO_PORT->PIO_MDDR |= SPI_0_MISO_PIN;
|
|
SPI_0_MOSI_PORT->PIO_MDER &= ~SPI_0_MOSI_PIN;
|
|
SPI_0_MOSI_PORT->PIO_MDDR |= SPI_0_MOSI_PIN;
|
|
SPI_0_SCK_PORT->PIO_MDER &= ~SPI_0_SCK_PIN;
|
|
SPI_0_SCK_PORT->PIO_MDDR |= SPI_0_SCK_PIN;
|
|
|
|
/* With pull-up resistors */
|
|
SPI_0_MISO_PORT->PIO_PUDR &= ~SPI_0_MISO_PIN;
|
|
SPI_0_MISO_PORT->PIO_PUER |= SPI_0_MISO_PIN;
|
|
SPI_0_MOSI_PORT->PIO_PUDR &= ~SPI_0_MOSI_PIN;
|
|
SPI_0_MOSI_PORT->PIO_PUER |= SPI_0_MOSI_PIN;
|
|
SPI_0_SCK_PORT->PIO_PUDR &= ~SPI_0_SCK_PIN;
|
|
SPI_0_SCK_PORT->PIO_PUER |= SPI_0_SCK_PIN;
|
|
|
|
/* Clear output */
|
|
SPI_0_MISO_PORT->PIO_SODR &= ~SPI_0_MISO_PIN;
|
|
SPI_0_MISO_PORT->PIO_CODR |= SPI_0_MISO_PIN;
|
|
SPI_0_MOSI_PORT->PIO_SODR &= ~SPI_0_MOSI_PIN;
|
|
SPI_0_MOSI_PORT->PIO_CODR |= SPI_0_MOSI_PIN;
|
|
SPI_0_SCK_PORT->PIO_SODR &= ~SPI_0_SCK_PIN;
|
|
SPI_0_SCK_PORT->PIO_CODR |= SPI_0_SCK_PIN;
|
|
|
|
/* Peripheral Function Selection */
|
|
SPI_0_MISO_PORT->PIO_PER &= ~SPI_0_MISO_PIN;
|
|
SPI_0_MISO_PORT->PIO_PDR |= SPI_0_MISO_PIN;
|
|
SPI_0_MOSI_PORT->PIO_PER &= ~SPI_0_MOSI_PIN;
|
|
SPI_0_MOSI_PORT->PIO_PDR |= SPI_0_MOSI_PIN;
|
|
SPI_0_SCK_PORT->PIO_PER &= ~SPI_0_SCK_PIN;
|
|
SPI_0_SCK_PORT->PIO_PDR |= SPI_0_SCK_PIN;
|
|
|
|
/* Peripheral A */
|
|
SPI_0_MISO_PORT->PIO_ABSR &= ~SPI_0_MISO_PIN;
|
|
SPI_0_MOSI_PORT->PIO_ABSR &= ~SPI_0_MOSI_PIN;
|
|
SPI_0_SCK_PORT->PIO_ABSR &= ~SPI_0_SCK_PIN;
|
|
|
|
break;
|
|
#endif /* SPI_0_EN */
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int spi_transfer_byte(spi_t dev, char out, char *in)
|
|
{
|
|
Spi *spi_port;
|
|
|
|
switch (dev) {
|
|
#if SPI_0_EN
|
|
case SPI_0:
|
|
spi_port = SPI_0_DEV;
|
|
break;
|
|
#endif /* SPI_0_EN */
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
while (!(spi_port->SPI_SR & SPI_SR_TDRE));
|
|
|
|
spi_port->SPI_TDR = SPI_TDR_TD(out);
|
|
|
|
while (!(spi_port->SPI_SR & SPI_SR_RDRF));
|
|
|
|
*in = spi_port->SPI_RDR & SPI_RDR_RD_Msk;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
int spi_transfer_bytes(spi_t dev, char *out, char *in, unsigned int length)
|
|
{
|
|
int trans_ret, trans_bytes = 0;
|
|
char in_temp;
|
|
|
|
for (int i = 0; i < length; i++) {
|
|
|
|
if (out) {
|
|
trans_ret = spi_transfer_byte(dev, out[i], &in_temp);
|
|
}
|
|
else {
|
|
trans_ret = spi_transfer_byte(dev, 0, &in_temp);
|
|
}
|
|
|
|
if (trans_ret < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (in) {
|
|
in[i] = in_temp;
|
|
}
|
|
|
|
trans_bytes++;
|
|
}
|
|
|
|
return trans_bytes++;
|
|
}
|
|
|
|
|
|
int spi_transfer_reg(spi_t dev, uint8_t reg, char out, char *in)
|
|
{
|
|
int trans_ret;
|
|
|
|
trans_ret = spi_transfer_byte(dev, reg, in);
|
|
|
|
if (trans_ret < 0) {
|
|
return -1;
|
|
}
|
|
|
|
trans_ret = spi_transfer_byte(dev, out, in);
|
|
|
|
if (trans_ret < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
int spi_transfer_regs(spi_t dev, uint8_t reg, char *out, char *in, unsigned int length)
|
|
{
|
|
int trans_ret;
|
|
|
|
trans_ret = spi_transfer_byte(dev, reg, in);
|
|
|
|
if (trans_ret < 0) {
|
|
return -1;
|
|
}
|
|
|
|
trans_ret = spi_transfer_bytes(dev, out, in, length);
|
|
|
|
if (trans_ret < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return trans_ret;
|
|
}
|
|
|
|
|
|
void spi_transmission_begin(spi_t dev, char reset_val)
|
|
{
|
|
switch (dev) {
|
|
#if SPI_0_EN
|
|
case SPI_0:
|
|
SPI_0_DEV->SPI_TDR = SPI_TDR_TD(reset_val);
|
|
break;
|
|
#endif /* SPI_0_EN */
|
|
}
|
|
}
|
|
|
|
static inline void irq_handler_transfer(Spi *spi, spi_t dev)
|
|
{
|
|
if (spi->SPI_SR & SPI_SR_RDRF) {
|
|
char data;
|
|
data = spi->SPI_RDR & SPI_RDR_RD_Msk;
|
|
data = spi_config[dev].cb(data);
|
|
spi->SPI_TDR = SPI_TDR_TD(data);
|
|
}
|
|
|
|
/* See if a thread with higher priority wants to run now */
|
|
if (sched_context_switch_request) {
|
|
thread_yield();
|
|
}
|
|
}
|
|
|
|
#if SPI_0_EN
|
|
void SPI_0_IRQ_HANDLER(void)
|
|
{
|
|
if (SPI_0_DEV->SPI_SR & SPI_SR_RDRF) {
|
|
irq_handler_transfer(SPI_0_DEV, SPI_0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#endif /* SPI_NUMOF */
|