From: Nathael Pajani Date: Tue, 22 Apr 2014 21:34:56 +0000 (+0200) Subject: Improved implementation of the SSP (SPI) Bus handling. X-Git-Url: http://git.techno-innov.fr/?a=commitdiff_plain;h=43bd0e2c4eb750ffab6a753fc30cb4c1c7a20f6a;p=soft%2Flpc122x%2Fcore Improved implementation of the SSP (SPI) Bus handling. --- diff --git a/drivers/ssp.c b/drivers/ssp.c index 601df27..4953a6b 100644 --- a/drivers/ssp.c +++ b/drivers/ssp.c @@ -31,11 +31,37 @@ #include "core/pio.h" #include "lib/string.h" #include "drivers/ssp.h" -#include "drivers/gpio.h" -/* Store our current SPI clock rate */ -static uint32_t ssp_current_clk = 0; +struct ssp_device +{ + uint32_t num; + struct lpc_ssp* regs; + uint32_t current_clk; + uint32_t mutex; + volatile uint32_t int_rx_stats; + volatile uint32_t int_overrun_stats; + volatile uint32_t int_rx_timeout_stats; + + uint32_t irq; + uint32_t clk_ctrl_bit; +}; + +#define NUM_SSPS 1 +static struct ssp_device ssps[NUM_SSPS] = { + { + .num = 0, + .regs = LPC_SSP0, + .current_clk = 0, + .mutex = 0, + .int_rx_stats = 0, + .int_overrun_stats = 0, + .int_rx_timeout_stats = 0, + .irq = SSP0_IRQ, + .clk_ctrl_bit = LPC_SYS_ABH_CLK_CTRL_SSP0, + }, +}; + /* Handlers */ @@ -46,6 +72,12 @@ void SSP_0_Handler(void) /* Clear the interrupts. Other bits are cleared by fifo access */ ssp->int_clear = (intr_flags & (LPC_SSP_INTR_RX_OVERRUN | LPC_SSP_INTR_RX_TIMEOUT)); + if (intr_flags & LPC_SSP_INTR_RX_OVERRUN) { + ssps[0].int_overrun_stats += 1; + } + if (intr_flags & LPC_SSP_INTR_RX_TIMEOUT) { + ssps[0].int_rx_timeout_stats += 1; + } } @@ -53,77 +85,30 @@ void SSP_0_Handler(void) /***************************************************************************** */ /* SPI Bus mutex */ -/* SPI global mutex for SPI bus */ -static uint32_t spi_mutex = 0; - #if MULTITASKING == 1 -int spi_get_mutex(void) +int spi_get_mutex(uint8_t ssp_num) { /* Note : a multitasking OS should call the scheduler here. Any other system * will get frozen, unless some interrupt routine has been set to release * the mutex. */ - do {} while (sync_lock_test_and_set(&spi_mutex, 1) == 1); + do {} while (sync_lock_test_and_set(&(ssps[ssp_num].mutex), 1) == 1); return 1; } #else -int spi_get_mutex(void) +int spi_get_mutex(uint8_t ssp_num) { - if (sync_lock_test_and_set(&spi_mutex, 1) == 1) { + if (sync_lock_test_and_set(&(ssps[ssp_num].mutex), 1) == 1) { return -EBUSY; } return 1; } #endif -void spi_release_mutex(void) +void spi_release_mutex(uint8_t ssp_num) { - sync_lock_release(&spi_mutex); + sync_lock_release(&(ssps[ssp_num].mutex)); } -/***************************************************************************** */ -/* SPI device SSEL: - * Set SPI Chip Select Low using GPIO mode when SPI driver is in use - */ - -/* SPI slave select mutex for SPI bus */ -static uint32_t spi_cs_mutex = 0; - -/* Use this function when you need to enable access to the module EEPROM on the - * GPIO Demo Module from Techno-Innov but the micro-controller acts as a slave on the - * SPI bus. - * Any data sent by the master on the SPI bus while the spi_cs_mutex is held will be lost. - */ -#define SPI_CS_PIN LPC_GPIO_0_15 -int spi_device_cs_pull_low(void) -{ - struct lpc_gpio* gpio0 = LPC_GPIO_0; - struct pio spi_cs = SPI_CS_PIN; - - if (sync_lock_test_and_set(&spi_cs_mutex, 1) == 1) { - return -EBUSY; - } - - /* Configure pin as GPIO */ - config_pio(&spi_cs, (LPC_IO_MODE_PULL_UP | LPC_IO_DIGITAL)); - /* Configure pin as output and set it low */ - gpio0->data_dir |= (1 << spi_cs.pin); - gpio0->clear = (1 << spi_cs.pin); - - return 0; -} -void spi_device_cs_release(void) -{ - struct lpc_gpio* gpio0 = LPC_GPIO_0; - struct pio spi_cs = SPI_CS_PIN; - - /* Release mutex */ - sync_lock_release(&spi_cs_mutex); - - /* Set pin high */ - gpio0->set = (1 << spi_cs.pin); -} - - /***************************************************************************** */ /* This function is used to transfer a word (4 to 16 bits) to AND from a device @@ -131,49 +116,37 @@ void spi_device_cs_release(void) * always provided by the master. The SPI clock cannont be activated without sending * data, which means we must send data to the device when we want to read data from * the device. + * Note : the SSP device number is not checked, thus a value above the number of SSP + * devices present on the micro-controller may break your programm. + * Double check the value of the ssp_num parameter. The check is made on the call to + * ssp_master_on() or ssp_slave_on(). + * This function does not take care of the SPI chip select. */ -uint16_t spi_transfer_single_frame(uint16_t data) +uint16_t spi_transfer_single_frame(uint8_t ssp_num, uint16_t data) { - struct lpc_ssp* ssp = LPC_SSP0; - ssp->data = data; + struct lpc_ssp* ssp_regs = ssps[ssp_num].regs; + ssp_regs->data = data; /* Wait until the busy bit is cleared */ - while (ssp->status & LPC_SSP_ST_BUSY); - return ssp->data; + while (ssp_regs->status & LPC_SSP_ST_BUSY); + return ssp_regs->data; } -/* Multiple words (4 to 16 bits) transfer function on the SPI bus. - * The SSP fifo is used to leave as little time between frames as possible. - * Parameters : - * size is the number of frames, each one having the configured data width (4 to 16 bits). - * data_out : data to be sent. Data will be read in the lower bits of the 16 bits values - * pointed by "data_out" for each frame. If NULL, then the content of data_in - will be used. - * data_in : buffer for read data. If NULL, read data will be discarded. - * As the SPI bus is full duplex, data can flow in both directions, but the clock is - * always provided by the master. The SPI clock cannont be activated without sending - * data, which means we must send data to the device when we want to read data from - * the device. - * This function does not take care of the SPI chip select. - * Note: there's no need to count Rx data as it is equal to Tx data - */ -int spi_transfer_multiple_frames(uint16_t* data_out, int size, uint16_t* data_in) + + +/***************************************************************************** */ +/* Multiple words (4 to 16 bits) transfer function on the SPI bus. */ + +/* Internal function used to send 9 to 16 bits wide data */ +static int spi_transfer_words(struct lpc_ssp* ssp, uint16_t* data_out, uint16_t* data_in, int size) { - struct lpc_ssp* ssp = LPC_SSP0; int count = 0; uint16_t data_read = 0; /* Used to store SPI Rx data */ - /* Did the user provide a buffer to send data ? */ - if (data_out == NULL) { - if (data_in == NULL) { - return 0; - } - data_out = data_in; - } /* Transfer */ do { /* Fill Tx fifo with available data, but stop if rx fifo is full */ while ((count < size) && - ((ssp->status & (LPC_SSP_ST_TX_NOT_FULL | LPC_SSP_ST_RX_FULL)) == LPC_SSP_ST_TX_NOT_FULL)) { + ((ssp->status & (LPC_SSP_ST_TX_NOT_FULL | LPC_SSP_ST_RX_FULL)) == LPC_SSP_ST_TX_NOT_FULL)) { ssp->data = *data_out; count++; data_out++; @@ -196,14 +169,86 @@ int spi_transfer_multiple_frames(uint16_t* data_out, int size, uint16_t* data_in return count; } +/* Internal function used to send 4 to 8 bits wide data */ +static int spi_transfer_bytes(struct lpc_ssp* ssp, uint8_t* data_out, uint8_t* data_in, int size) +{ + int count = 0; + uint8_t data_read = 0; /* Used to store SPI Rx data */ + + /* Transfer */ + do { + /* Fill Tx fifo with available data, but stop if rx fifo is full */ + while ((count < size) && + ((ssp->status & (LPC_SSP_ST_TX_NOT_FULL | LPC_SSP_ST_RX_FULL)) == LPC_SSP_ST_TX_NOT_FULL)) { + ssp->data = (uint16_t)(*data_out); + count++; + data_out++; + } + + /* Read some of the replies, but stop if there's still data to send and the fifo + * is running short */ + while ((ssp->status & LPC_SSP_ST_RX_NOT_EMPTY) && + !((count < size) && (ssp->raw_int_status & LPC_SSP_INTR_TX_HALF_EMPTY))) { + /* Read the data (mandatory) */ + data_read = (uint8_t)ssp->data; + if (data_in != NULL) { + /* And store when requested */ + *data_in = data_read; + data_in++; + } + } + /* Go on till both all data is sent and all data is received */ + } while ((count < size) || (ssp->status & (LPC_SSP_ST_BUSY | LPC_SSP_ST_RX_NOT_EMPTY))); + + return count; +} + + +/* Multiple words (4 to 16 bits) transfer function on the SPI bus. + * The SSP fifo is used to leave as little time between frames as possible. + * Parameters : + * size is the number of frames, each one having the configured data width (4 to 16 bits). + * data_out : data to be sent. Data will be read in the lower bits of the 16 bits values + * pointed by "data_out" for each frame. If NULL, then the content of data_in + * will be used. + * data_in : buffer for read data. If NULL, read data will be discarded. + * As the SPI bus is full duplex, data can flow in both directions, but the clock is + * always provided by the master. The SPI clock cannont be activated without sending + * data, which means we must send data to the device when we want to read data from + * the device. + * This function does not take care of the SPI chip select. + * Note: there's no need to count Rx data as it is equal to Tx data + * Note : the SSP device number is not checked, thus a value above the number of SSP + * devices present on the micro-controller may break your programm. + * Double check the value of the ssp_num parameter. The check is made on the call to + * ssp_master_on() or ssp_slave_on(). + */ +int spi_transfer_multiple_frames(uint8_t ssp_num, void* data_out, void* data_in, int size, int width) +{ + struct lpc_ssp* ssp_regs = ssps[ssp_num].regs; + + /* Did the user provide a buffer to send data ? */ + if (data_out == NULL) { + if (data_in == NULL) { + return -EINVAL; + } + data_out = data_in; + } + /* Transfer using either byte or word transfer functions */ + if (width > 8) { + return spi_transfer_words(ssp_regs, (uint16_t*)data_out, (uint16_t*)data_in, size); + } else { + return spi_transfer_bytes(ssp_regs, (uint8_t*)data_out, (uint8_t*)data_in, size); + } +} /***************************************************************************** */ -uint32_t ssp_clk_on(uint32_t rate) +uint32_t ssp_clk_on(uint8_t ssp_num, uint32_t rate) { struct lpc_sys_control* sys_ctrl = LPC_SYS_CONTROL; - struct lpc_ssp* ssp = LPC_SSP0; + struct lpc_ssp* ssp_regs = ssps[ssp_num].regs; uint32_t prescale = 0, pclk_div = 0; uint32_t pclk = 0, div = 0; @@ -216,7 +261,7 @@ uint32_t ssp_clk_on(uint32_t rate) /* Make sure we can get this clock rate */ /* NOTE: We use only to divisors, so we could achieve lower clock rates by using - * the third one. Any need for this though ? + * the third one. Any need for this though ? */ if ((pclk / rate) > 0xFFF0) { /* What should we do ? */ @@ -234,7 +279,7 @@ uint32_t ssp_clk_on(uint32_t rate) sys_ctrl->ssp0_clk_div = pclk_div; /* Set the prescaler */ - ssp->clk_prescale = prescale; + ssp_regs->clk_prescale = prescale; /* And return the achieved clock */ return (pclk / (prescale * pclk_div)); @@ -242,8 +287,11 @@ uint32_t ssp_clk_on(uint32_t rate) void ssp_clk_update(void) { - if (ssp_current_clk) { - ssp_clk_on(ssp_current_clk); + int i = 0; + for (i = 0; i < NUM_SSPS; i++) { + if (ssps[i].current_clk) { + ssp_clk_on(i, ssps[i].current_clk); + } } } @@ -275,75 +323,87 @@ static void ssp_set_pin_func(uint32_t ssp_num) * handling highly depends on the device on the other end of the wires. Use a GPIO * to activate the device's chip select (usually active low) */ -int ssp_master_on(uint8_t frame_type, uint8_t data_width, uint32_t rate ) +int ssp_master_on(uint8_t ssp_num, uint8_t frame_type, uint8_t data_width, uint32_t rate ) { - struct lpc_ssp* ssp = LPC_SSP0; + struct ssp_device* ssp = NULL; + struct lpc_ssp* ssp_regs = NULL; - NVIC_DisableIRQ(SSP0_IRQ); + if (ssp_num >= NUM_SSPS) + return -EINVAL; + ssp = &ssps[ssp_num]; + ssp_regs = ssp->regs; + + NVIC_DisableIRQ(ssp->irq); /* Configure pins first */ - ssp_set_pin_func(0); /* Only one SPI on the LPC1224 */ + ssp_set_pin_func(ssp_num); /* Only one SPI on the LPC1224 */ /* Power up the ssp block */ - subsystem_power(LPC_SYS_ABH_CLK_CTRL_SSP0, 1); + subsystem_power(ssp->clk_ctrl_bit, 1); /* Configure the SSP mode */ - ssp->ctrl_0 = LPC_SSP_DATA_WIDTH(data_width); - ssp->ctrl_0 |= (frame_type | LPC_SSP_SPI_CLK_LOW | LPC_SSP_SPI_CLK_FIRST); - ssp->ctrl_1 = LPC_SSP_MASTER_MODE; + ssp_regs->ctrl_0 = (LPC_SSP_DATA_WIDTH(data_width) | frame_type | LPC_SSP_SPI_CLK_LOW | LPC_SSP_SPI_CLK_FIRST); + ssp_regs->ctrl_1 = LPC_SSP_MASTER_MODE; /* Configure the clock : done after basic configuration */ - ssp_current_clk = ssp_clk_on(rate); + ssp->current_clk = ssp_clk_on(ssp_num, rate); /* Enable SSP */ - ssp->ctrl_1 |= LPC_SSP_ENABLE; + ssp_regs->ctrl_1 |= LPC_SSP_ENABLE; - NVIC_EnableIRQ(SSP0_IRQ); + NVIC_EnableIRQ(ssp->irq); return 0; /* Config OK */ } -int ssp_slave_on(uint8_t frame_type, uint8_t data_width, uint8_t out_en, uint32_t max_rate) +int ssp_slave_on(uint8_t ssp_num, uint8_t frame_type, uint8_t data_width, uint8_t out_en, uint32_t max_rate) { - struct lpc_ssp* ssp = LPC_SSP0; + struct ssp_device* ssp = NULL; + struct lpc_ssp* ssp_regs = NULL; - /* Is the slave select pin available ? - * We need this as we share the chip-select with the I2C bus. */ - if (sync_lock_test_and_set(&spi_cs_mutex, 1) == 1) { - return -EBUSY; - } + if (ssp_num >= NUM_SSPS) + return -EINVAL; + ssp = &ssps[ssp_num]; + ssp_regs = ssp->regs; - NVIC_DisableIRQ(SSP0_IRQ); + NVIC_DisableIRQ(ssp->irq); /* Power up the ssp block */ - subsystem_power(LPC_SYS_ABH_CLK_CTRL_SSP0, 1); + subsystem_power(ssp->clk_ctrl_bit, 1); /* Configure the SSP mode */ - ssp->ctrl_0 = LPC_SSP_DATA_WIDTH(data_width); - ssp->ctrl_0 |= (frame_type | LPC_SSP_SPI_CLK_LOW | LPC_SSP_SPI_CLK_FIRST); - ssp->ctrl_1 = LPC_SSP_SLAVE_MODE; + ssp_regs->ctrl_0 = LPC_SSP_DATA_WIDTH(data_width); + ssp_regs->ctrl_0 |= (frame_type | LPC_SSP_SPI_CLK_LOW | LPC_SSP_SPI_CLK_FIRST); + ssp_regs->ctrl_1 = LPC_SSP_SLAVE_MODE; if (!out_en) { - ssp->ctrl_1 |= LPC_SSP_SLAVE_OUT_DISABLE; + ssp_regs->ctrl_1 |= LPC_SSP_SLAVE_OUT_DISABLE; } /* Configure the clock : done after basic configuration. * Our clock must be at least 12 times the master clock */ - ssp_current_clk = ssp_clk_on(max_rate * 16); + ssp->current_clk = ssp_clk_on(ssp_num, max_rate * 16); /* Enable SSP */ - ssp->ctrl_1 |= LPC_SSP_ENABLE; + ssp_regs->ctrl_1 |= LPC_SSP_ENABLE; - ssp_set_pin_func(0); + ssp_set_pin_func(ssp_num); - NVIC_EnableIRQ(SSP0_IRQ); + NVIC_EnableIRQ(ssp->irq); return 0; /* Config OK */ } /* Turn off the SSP block */ -void ssp_off(void) +void ssp_off(uint8_t ssp_num) { - ssp_current_clk = 0; - NVIC_DisableIRQ(SSP0_IRQ); - subsystem_power(LPC_SYS_ABH_CLK_CTRL_SSP0, 0); + struct ssp_device* ssp = NULL; + + if (ssp_num >= NUM_SSPS) + return; + ssp = &ssps[ssp_num]; + + ssp->current_clk = 0; + NVIC_DisableIRQ(ssp->irq); + subsystem_power(ssp->clk_ctrl_bit, 0); + /* Can be done even if we don't hold the mutex */ - sync_lock_release(&spi_cs_mutex); + sync_lock_release(&ssp->mutex); } diff --git a/include/drivers/ssp.h b/include/drivers/ssp.h index f78aaf7..d5382e2 100644 --- a/include/drivers/ssp.h +++ b/include/drivers/ssp.h @@ -32,50 +32,48 @@ /* Set this to 1 for use of this driver in a multitasking OS, it will activate the SPI Mutex */ #define MULTITASKING 0 -enum spi_fifo_or_pooling { - SPI_NO_FIFO = 0, - SPI_USE_FIFO = 1, -}; -/***************************************************************************** */ -/* SPI device mode slave select sharing. - * Note: Used only by non SPI functions to request the SSEL pin when SPI driver is used. - */ -int spi_device_cs_pull_low(void); -void spi_device_cs_release(void); - /***************************************************************************** */ -/* SPI Bus mutex */ +/* SPI mutex for SPI bus */ /* In multitasking environment spi_get_mutex will block until mutex is available and always * return 1. In non multitasking environments spi_get_mutex returns either 1 (got mutex) * or -EBUSY (SPI bus in use) */ -int spi_get_mutex(void); -void spi_release_mutex(void); +int spi_get_mutex(uint8_t ssp_num); +void spi_release_mutex(uint8_t ssp_num); /***************************************************************************** */ -/* This function is used to transfer a byte to AND from a device +/* This function is used to transfer a word (4 to 16 bits) to AND from a device * As the SPI bus is full duplex, data can flow in both directions, but the clock is * always provided by the master. The SPI clock cannont be activated without sending * data, which means we must send data to the device when we want to read data from * the device. + * Note : the SSP device number is not checked, thus a value above the number of SSP + * devices present on the micro-controller may break your programm. + * Double check the value of the ssp_num parameter. The check is made on the call to + * ssp_master_on() or ssp_slave_on(). * This function does not take care of the SPI chip select. */ -uint16_t spi_transfer_single_frame(uint16_t data); +uint16_t spi_transfer_single_frame(uint8_t ssp_num, uint16_t data); /***************************************************************************** */ /* Multiple words (4 to 16 bits) transfer function on the SPI bus. * The SSP fifo is used to leave as little time between frames as possible. * Parameters : - * size is the number of frames, each one having the configured data width (4 to 16 bits). + * ssp_num : ssp device number. The SSP device number is not checked, thus a value above + * the number of SSP devices present on the micro-controller may break your + * programm. Double check the value of the ssp_num parameter. The check is made + * on the call to ssp_master_on() or ssp_slave_on(). + * size : the number of frames, each one having the configured data width (4 to 16 bits). * data_out : data to be sent. Data will be read in the lower bits of the 16 bits values * pointed by "data_out" for each frame. If NULL, then the content of data_in will be used. * data_in : buffer for read data. If NULL, read data will be discarded. + * Returns the number of data words transfered. * As the SPI bus is full duplex, data can flow in both directions, but the clock is * always provided by the master. The SPI clock cannont be activated without sending * data, which means we must send data to the device when we want to read data from @@ -83,13 +81,14 @@ uint16_t spi_transfer_single_frame(uint16_t data); * This function does not take care of the SPI chip select. * Note: there's no need to count Rx data as it is equal to Tx data */ -int spi_transfer_multiple_frames(uint16_t* data_out, int size, uint16_t* data_in); +int spi_transfer_multiple_frames(uint8_t ssp_num, void* data_out, void* data_in, int size, int width); + /***************************************************************************** */ -uint32_t ssp_clk_on(uint32_t rate); void ssp_clk_update(void); + /***************************************************************************** */ /* SSP Setup as master */ /* Returns 0 on success @@ -101,12 +100,13 @@ void ssp_clk_update(void); * handling highly depends on the device on the other end of the wires. Use a GPIO * to activate the device's chip select (usually active low) */ -int ssp_master_on(uint8_t frame_type, uint8_t data_width, uint32_t rate); +int ssp_master_on(uint8_t ssp_num, uint8_t frame_type, uint8_t data_width, uint32_t rate); + +int ssp_slave_on(uint8_t ssp_num, uint8_t frame_type, uint8_t data_width, uint8_t out_en, uint32_t max_rate); -int ssp_slave_on(uint8_t frame_type, uint8_t data_width, uint8_t out_en, uint32_t max_rate); +/* Turn off given SSP block */ +void ssp_off(uint8_t ssp_num); -/* Turn off the SSP block */ -void ssp_off(void); #endif /* DRIVERS_SSP_H */