From 1c7b771a10314935ed5c6f5c36c45a76d370f1bf Mon Sep 17 00:00:00 2001 From: Nathael Pajani Date: Sun, 29 Dec 2013 02:15:36 +0100 Subject: [PATCH] SPI code rewrite due to difficulties in handling slave select with previous code when acting as SPI master. Partially tested only. --- drivers/eeprom.c | 21 ++-- drivers/ssp.c | 238 +++++++++++++++++++----------------------- include/drivers/ssp.h | 73 ++++++++----- 3 files changed, 168 insertions(+), 164 deletions(-) diff --git a/drivers/eeprom.c b/drivers/eeprom.c index 3a95ee3..6aa161f 100644 --- a/drivers/eeprom.c +++ b/drivers/eeprom.c @@ -37,13 +37,14 @@ * These dummy functions will be over-ridden by SPI ones if the SPI driver is used. */ #define I2C_CS_PIN 15 -void I2C_CS_Default_Set(void) +int I2C_CS_Default_Set(void) { struct lpc_gpio* gpio0 = LPC_GPIO_0; config_gpio(0, I2C_CS_PIN, (LPC_IO_FUNC_ALT(0) | LPC_IO_MODE_PULL_UP | LPC_IO_DIGITAL)); /* Configure SPI_CS as output and set it low. */ gpio0->data_dir |= (1 << I2C_CS_PIN); gpio0->clear = (1 << I2C_CS_PIN); + return 1; } void I2C_CS_Default_Release(void) { @@ -51,8 +52,8 @@ void I2C_CS_Default_Release(void) gpio0->set = (1 << I2C_CS_PIN); } -void spi_cs_pull_low(void) __attribute__ ((weak, alias ("I2C_CS_Default_Set"))); -void spi_cs_release(void) __attribute__ ((weak, alias ("I2C_CS_Default_Release"))); +int spi_device_cs_pull_low(void) __attribute__ ((weak, alias ("I2C_CS_Default_Set"))); +void spi_device_cs_release(void) __attribute__ ((weak, alias ("I2C_CS_Default_Release"))); /***************************************************************************** */ @@ -92,7 +93,7 @@ int eeprom_detect(void) } if (ret > 0) { - return -1; + return -EIO; } else if (ret == -EREMOTEIO) { return EEPROM_TYPE_NONE; /* No module */ } @@ -137,7 +138,9 @@ int eeprom_read(uint32_t offset, void *buf, size_t count) char ctrl_buf[CMD_BUF_SIZE] = { I2C_CONT, I2C_CONT, I2C_DO_REPEATED_START, I2C_CONT, }; int eeprom_type = 0; - spi_cs_pull_low(); + if (spi_device_cs_pull_low() == 0) { + return -EBUSY; + } eeprom_type = get_eeprom_type(); /* Read the requested data */ @@ -157,7 +160,7 @@ int eeprom_read(uint32_t offset, void *buf, size_t count) ret = -1; break; } - spi_cs_release(); + spi_device_cs_release(); return ret; } @@ -190,7 +193,9 @@ int eeprom_write(uint32_t offset, const void *buf, size_t count) char full_buff[(EEPROM_ID_MAX_PAGE_SIZE + MAX_CMD_SIZE)]; int eeprom_type = 0; - spi_cs_pull_low(); + if (spi_device_cs_pull_low() == 0) { + return -EBUSY; + } eeprom_type = get_eeprom_type(); switch (eeprom_type) { @@ -243,7 +248,7 @@ int eeprom_write(uint32_t offset, const void *buf, size_t count) write_count += size; } - spi_cs_release(); + spi_device_cs_release(); if (write_count != count) return ret; diff --git a/drivers/ssp.c b/drivers/ssp.c index d4dd498..1194291 100644 --- a/drivers/ssp.c +++ b/drivers/ssp.c @@ -35,10 +35,6 @@ /* Store our current SPI clock rate */ static uint32_t ssp_current_clk = 0; -/* Device configured ? */ -static uint32_t spi_device_pins_in_use = 0; -/* Do we use SPI to handle chip-select or a GPIO ? */ -static uint32_t use_spi_cs = 1; /* Handlers */ @@ -54,29 +50,55 @@ void SSP_0_Handler(void) /***************************************************************************** */ -/* SPI SSEL */ +/* SPI Bus mutex */ -/* SPI ssel mutex for SPI bus */ -static uint32_t spi_cs_mutex = 0; -static void spi_get_cs_mutex(void) +/* SPI global mutex for SPI bus */ +static uint32_t spi_mutex = 0; + +#if MULTITASKING == 1 +int spi_get_mutex(void) { - do {} while (sync_lock_test_and_set(&spi_cs_mutex, 1) == 1); + /* 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); + return 1; } -static void spi_release_cs_mutex(void) +#else +int spi_get_mutex(void) { - sync_lock_release(&spi_cs_mutex); + if (sync_lock_test_and_set(&spi_mutex, 1) == 1) { + return -EBUSY; + } + return 1; +} +#endif +void spi_release_mutex(void) +{ + sync_lock_release(&spi_mutex); } -/* SSEL Alternative: - * Set SPI Chip Select Low using GPIO (for I2C or to prevent CS going high between frames +/***************************************************************************** */ +/* SPI device SSEL: + * Set SPI Chip Select Low using GPIO mode when SPI driver is in use */ -#define SPI_CS_PIN 15 -void spi_cs_pull_low(void) + +/* 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. + */ +int spi_device_cs_pull_low(void) { struct lpc_gpio* gpio0 = LPC_GPIO_0; - /* Get SPI chip select mutex */ - spi_get_cs_mutex(); + if (sync_lock_test_and_set(&spi_cs_mutex, 1) == 1) { + return -EBUSY; + } /* Configure pin as GPIO */ io_config_clk_on(); @@ -85,25 +107,24 @@ void spi_cs_pull_low(void) /* 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_cs_release(void) +void spi_device_cs_release(void) { - if (spi_device_pins_in_use == 1) { - io_config_clk_on(); - /* Configure pin as SPI */ - config_gpio(0, SPI_CS_PIN, (LPC_IO_FUNC_ALT(2) | LPC_IO_DIGITAL)); - io_config_clk_off(); - } else { - struct lpc_gpio* gpio0 = LPC_GPIO_0; - /* Set pin high */ - gpio0->set = (1 << SPI_CS_PIN); - } - spi_release_cs_mutex(); + struct lpc_gpio* gpio0 = LPC_GPIO_0; + /* Set pin high */ + gpio0->set = (1 << SPI_CS_PIN); + /* Configure pin as SPI again */ + io_config_clk_on(); + config_gpio(0, SPI_CS_PIN, (LPC_IO_FUNC_ALT(2) | LPC_IO_DIGITAL)); /* SPI Chip Select */ + io_config_clk_off(); } + /***************************************************************************** */ -/* 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 @@ -118,13 +139,35 @@ uint16_t spi_transfer_single_frame(uint16_t data) return ssp->data; } -/* Note: there's no need to count Rx data as it is equal to Tx 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) { struct lpc_ssp* ssp = LPC_SSP0; int count = 0; - uint16_t data_drop = 0; /* Used to store unused SPI Rx data */ + 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) && @@ -134,92 +177,24 @@ int spi_transfer_multiple_frames(uint16_t* data_out, int size, uint16_t* data_in data_out++; } - /* Read some of the replies, but stop if there's still data to send and the fifo is running short */ + /* 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 = ssp->data; if (data_in != NULL) { - /* Store them ... */ - *data_in = ssp->data; + /* And store when requested */ + *data_in = data_read; data_in++; - } else { - /* ... or drop them */ - data_drop = ssp->data; } } /* 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 (data_drop - data_drop); /* 0, but then data_drop is used :) */ -} - - -/* Handle diferent kind of chip-select methods. - * Some devices require that the CS be maintained low between frames, a high on chip select - * meaning a reset on the SPI communication (MAX31855 thermocouple to digital converter for - * example). - */ -static void spi_cs_get(void) -{ - if (use_spi_cs == 1) { - spi_get_cs_mutex(); - } else { - spi_cs_pull_low(); - } -} -void spi_cs_put(void) -{ - if (use_spi_cs == 1) { - spi_release_cs_mutex(); - } else { - spi_cs_release(); - } -} -/* Write data to the SPI bus. - * Any data received while sending is discarded. - * Data will be read in the lower bits of the 16 bits values pointed by "data" for each frame. - * size is the number of frames, each one having the configured data width (4 to 16 bits). - * If use_fifo is 0 frames are received one at a time, otherise the fifo is used to leave - * as little time between frames as possible. - */ -int spi_write(uint16_t *data, int size, int use_fifo) -{ - int i; - spi_cs_get(); - if (use_fifo == 0) { - for (i = 0; i < size; i++) { - spi_transfer_single_frame(*data); - data++; - } - } else { - spi_transfer_multiple_frames(data, size, NULL); - } - spi_cs_put(); - return size; + return count; } -/* Read data from the SPI bus. - * Data will be stored in the lower bits of the 16 bits values pointed by "data" for each frame. - * size is the number of frames, each one having the configured data width (4 to 16 bits). - * In order to activate the bus clock a default "all ones" frame is sent for each frame we - * want to read. - * If use_fifo is 0 frames are received one at a time, otherise the fifo is used to leave - * as little time between frames as possible. - */ -int spi_read(uint16_t *data, int size, int use_fifo) -{ - int i; - spi_cs_get(); - if (use_fifo == 0) { - for (i = 0; i < size; i++) { - *data = spi_transfer_single_frame(0xFFFF); - data++; - } - } else { - spi_transfer_multiple_frames(data, size, data); - } - spi_cs_put(); - return size; -} /***************************************************************************** */ @@ -270,20 +245,18 @@ void ssp_clk_update(void) } } +/* Configure main SPI pins, used in both master and device mode. + * The slave select is not configured here as it's use is implementation dependant in master + * mode. In slave mode it is configured in the ssp_slave_on() function. + */ void set_ssp_pins(void) { /* Make sure IO_Config is clocked */ io_config_clk_on(); - - /* We need this as we share the chip-select with the I2C bus. */ - spi_device_pins_in_use = 1; - - /* SPI pins */ + /* Main SPI pins */ config_gpio(0, 14, (LPC_IO_FUNC_ALT(2) | LPC_IO_DIGITAL)); /* SPI Clock */ - config_gpio(0, 15, (LPC_IO_FUNC_ALT(2) | LPC_IO_DIGITAL)); /* SPI Chip Select */ config_gpio(0, 16, (LPC_IO_FUNC_ALT(2) | LPC_IO_DIGITAL)); /* SPI MISO */ config_gpio(0, 17, (LPC_IO_FUNC_ALT(2) | LPC_IO_DIGITAL)); /* SPI MOSI */ - /* Config done, power off IO_CONFIG block */ io_config_clk_off(); } @@ -294,14 +267,15 @@ void set_ssp_pins(void) /***************************************************************************** */ /* SSP Setup as master */ /* Returns 0 on success - * frame_type is SPI, TI or MICROWIRE (use apropriate defines for this one). - * data_width is a number between 4 and 16. - * spi_cs_spi: set to 0 handle Slave select as a GPIO and keep it loww during the whole - * data transaction (do not pull high between frame). This is required by some slave - * devices. - * rate : The bit rate, in Hz. + * Parameters : + * frame_type is SPI, TI or MICROWIRE (use apropriate defines for this one). + * data_width is a number between 4 and 16. + * rate : The bit rate, in Hz. + * The SPI Chip Select is not handled by the SPI driver in master SPI mode as it's + * 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, uint8_t spi_cs_spi, uint32_t rate ) +int ssp_master_on(uint8_t frame_type, uint8_t data_width, uint32_t rate ) { struct lpc_ssp* ssp = LPC_SSP0; @@ -317,12 +291,6 @@ int ssp_master_on(uint8_t frame_type, uint8_t data_width, uint8_t spi_cs_spi, ui /* Configure the clock : done after basic configuration */ ssp_current_clk = ssp_clk_on(rate); - if (spi_cs_spi == 0) { - use_spi_cs = 0; - } else { - use_spi_cs = 1; - } - /* Enable SSP */ ssp->ctrl_1 |= LPC_SSP_ENABLE; @@ -334,6 +302,12 @@ int ssp_slave_on(uint8_t frame_type, uint8_t data_width, uint8_t out_en, uint32_ { struct lpc_ssp* ssp = LPC_SSP0; + /* 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; + } + NVIC_DisableIRQ(SSP0_IRQ); /* Power up the ssp block */ subsystem_power(LPC_SYS_ABH_CLK_CTRL_SSP0, 1); @@ -346,6 +320,12 @@ int ssp_slave_on(uint8_t frame_type, uint8_t data_width, uint8_t out_en, uint32_ ssp->ctrl_1 |= LPC_SSP_SLAVE_OUT_DISABLE; } + /* Use SPI as Device : configure the SSEL pin */ + /* Make sure IO_Config is clocked */ + io_config_clk_on(); + config_gpio(0, SPI_CS_PIN, (LPC_IO_FUNC_ALT(2) | LPC_IO_DIGITAL)); /* SPI Chip Select */ + io_config_clk_off(); + /* 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); @@ -355,7 +335,6 @@ int ssp_slave_on(uint8_t frame_type, uint8_t data_width, uint8_t out_en, uint32_ NVIC_EnableIRQ(SSP0_IRQ); return 0; /* Config OK */ - } /* Turn off the SSP block */ @@ -364,4 +343,7 @@ void ssp_off(void) ssp_current_clk = 0; NVIC_DisableIRQ(SSP0_IRQ); subsystem_power(LPC_SYS_ABH_CLK_CTRL_SSP0, 0); + /* Can be done even if we don't hold the mutex */ + sync_lock_release(&spi_cs_mutex); } + diff --git a/include/drivers/ssp.h b/include/drivers/ssp.h index d3f15ae..15a2e45 100644 --- a/include/drivers/ssp.h +++ b/include/drivers/ssp.h @@ -29,15 +29,33 @@ #include #include "drivers/gpio.h" -#define SPI_USE_FIFO 1 +/* 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, +}; + +#define SPI_CS_PIN 15 /***************************************************************************** */ -/* SPI slave select / activate +/* SPI device mode slave select sharing. * Note: Used only by non SPI functions to request the SSEL pin when SPI driver is used. */ -void spi_cs_pull_low(void); -void spi_cs_release(void); +int spi_device_cs_pull_low(void); +void spi_device_cs_release(void); + + +/***************************************************************************** */ +/* SPI Bus mutex */ +/* 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); /***************************************************************************** */ @@ -52,24 +70,22 @@ uint16_t spi_transfer_single_frame(uint16_t data); /***************************************************************************** */ -/* Write data to the SPI bus. - * Any data received while sending is discarded. - * Data will be read in the lower bits of the 16 bits values pointed by "data" for each frame. - * size is the number of frames, each one having the configured data width (4 to 16 bits). - * If use_fifo is 0 frames are received one at a time, otherise the fifo is used to leave - * as little time between frames as possible. - */ -int spi_write(uint16_t *data, int size, int use_fifo); - -/* Read data from the SPI bus. - * Data will be stored in the lower bits of the 16 bits values pointed by "data" for each frame. - * size is the number of frames, each one having the configured data width (4 to 16 bits). - * In order to activate the bus clock a default "all ones" frame is sent for each frame we - * want to read. - * If use_fifo is 0 frames are received one at a time, otherise the fifo is used to leave - * as little time between frames as possible. +/* 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_read(uint16_t *data, int size, int use_fifo); +int spi_transfer_multiple_frames(uint16_t* data_out, int size, uint16_t* data_in); /***************************************************************************** */ @@ -82,14 +98,15 @@ void set_ssp_pins(void); /***************************************************************************** */ /* SSP Setup as master */ /* Returns 0 on success - * frame_type is SPI, TI or MICROWIRE (use apropriate defines for this one). - * data_width is a number between 4 and 16. - * spi_cs_spi: set to 0 handle Slave select as a GPIO and keep it loww during the whole - * data transaction (do not pull high between frame). This is required by some slave - * devices. - * rate : The bit rate, in Hz. + * Parameters : + * frame_type is SPI, TI or MICROWIRE (use apropriate defines for this one). + * data_width is a number between 4 and 16. + * rate : The bit rate, in Hz. + * The SPI Chip Select is not handled by the SPI driver in master SPI mode as it's + * 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, uint8_t spi_cs_spi, uint32_t rate); +int ssp_master_on(uint8_t frame_type, uint8_t data_width, uint32_t rate); int ssp_slave_on(uint8_t frame_type, uint8_t data_width, uint8_t out_en, uint32_t max_rate); -- 2.43.0