Improved implementation of the SSP (SPI) Bus handling.
authorNathael Pajani <nathael.pajani@ed3l.fr>
Tue, 22 Apr 2014 21:34:56 +0000 (23:34 +0200)
committerNathael Pajani <nathael.pajani@ed3l.fr>
Tue, 8 Nov 2022 16:03:04 +0000 (17:03 +0100)
drivers/ssp.c
include/drivers/ssp.h

index 601df27..4953a6b 100644 (file)
 #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);
 }
 
index f78aaf7..d5382e2 100644 (file)
 /* 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 */