#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 */
/* 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;
+ }
}
/***************************************************************************** */
/* 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
* 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++;
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;
/* 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 ? */
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));
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);
+ }
}
}
* 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);
}
/* 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
* 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
* 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 */