From d920c7c92f8d6bfd610ba5adda6b454d7330b56a Mon Sep 17 00:00:00 2001 From: Nathael Pajani Date: Wed, 13 Nov 2013 16:17:07 +0100 Subject: [PATCH] Add SPI support --- README | 5 +- core/system.c | 8 +- drivers/ssp.c | 367 +++++++++++++++++++++++++++++++++++ include/core/lpc_regs_12xx.h | 34 ++++ include/drivers/ssp.h | 83 ++++++++ 5 files changed, 491 insertions(+), 6 deletions(-) create mode 100644 drivers/ssp.c create mode 100644 include/drivers/ssp.h diff --git a/README b/README index bbfed75..961adf5 100644 --- a/README +++ b/README @@ -77,6 +77,7 @@ SUPPORTED FEATURES and INTERFACES - TMP101 temperature sensor - ADC - GPIO + - SPI (SSP) ******************** @@ -84,6 +85,6 @@ TODO : - Deep sleep mode support. - Test all the GPIO in different modes -- Add support for SPI - Add support for SDCard over SPI - +- Add transfer function for full duplex SPI when using fifo + actually implemented in the transfer functions but not in the read/write functions diff --git a/core/system.c b/core/system.c index 3e53f32..2c65624 100644 --- a/core/system.c +++ b/core/system.c @@ -242,14 +242,14 @@ void Dummy_Clk_Updater(void) { void uart_clk_update(void) __attribute__ ((weak, alias ("Dummy_Clk_Updater"))); void i2c_clock_update(void) __attribute__ ((weak, alias ("Dummy_Clk_Updater"))); -void spi_clk_update(void) __attribute__ ((weak, alias ("Dummy_Clk_Updater"))); +void ssp_clk_update(void) __attribute__ ((weak, alias ("Dummy_Clk_Updater"))); void adc_clk_update(void) __attribute__ ((weak, alias ("Dummy_Clk_Updater"))); static void propagate_main_clock(void) { uart_clk_update(); i2c_clock_update(); - spi_clk_update(); + ssp_clk_update(); adc_clk_update(); } @@ -285,7 +285,7 @@ void Dummy_Pin_Config(void) { } void set_i2c_pins(void) __attribute__ ((weak, alias ("Dummy_Pin_Config"))); -void set_spi_pins(void) __attribute__ ((weak, alias ("Dummy_Pin_Config"))); +void set_ssp_pins(void) __attribute__ ((weak, alias ("Dummy_Pin_Config"))); void set_uarts_pins(void) __attribute__ ((weak, alias ("Dummy_Pin_Config"))); void set_gpio_pins(void) __attribute__ ((weak, alias ("Dummy_Pin_Config"))); void set_adc_pins(void) __attribute__ ((weak, alias ("Dummy_Pin_Config"))); @@ -297,6 +297,6 @@ void system_set_default_pins(void) set_gpio_pins(); set_uarts_pins(); set_i2c_pins(); - set_spi_pins(); + set_ssp_pins(); set_adc_pins(); } diff --git a/drivers/ssp.c b/drivers/ssp.c new file mode 100644 index 0000000..574f6d2 --- /dev/null +++ b/drivers/ssp.c @@ -0,0 +1,367 @@ +/**************************************************************************** + * drivers/ssp.c + * + * Copyright 2012 Nathael Pajani + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + *************************************************************************** */ + + + +/***************************************************************************** */ +/* SSP */ +/***************************************************************************** */ + +#include +#include "core/lpc_regs_12xx.h" +#include "core/lpc_core_cm0.h" +#include "core/system.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; +/* 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 */ +void SSP_0_Handler(void) +{ + struct lpc_ssp* ssp = LPC_SSP0; + uint32_t intr_flags = ssp->masked_int_status; + + /* 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)); +} + + + +/***************************************************************************** */ +/* SPI SSEL */ + +/* SPI ssel mutex for SPI bus */ +static uint32_t spi_cs_mutex = 0; +static void spi_get_cs_mutex(void) +{ + do {} while (sync_lock_test_and_set(&spi_cs_mutex, 1) == 1); +} +static void spi_release_cs_mutex(void) +{ + sync_lock_release(&spi_cs_mutex); +} + +/* SSEL Alternative: + * Set SPI Chip Select Low using GPIO (for I2C or to prevent CS going high between frames + */ +#define SPI_CS_PIN 15 +void spi_cs_pull_low(void) +{ + struct lpc_gpio* gpio0 = LPC_GPIO_0; + + /* Get SPI chip select mutex */ + spi_get_cs_mutex(); + + /* Configure pin as GPIO */ + io_config_clk_on(); + config_gpio(0, SPI_CS_PIN, (LPC_IO_FUNC_ALT(0) | LPC_IO_MODE_PULL_UP | LPC_IO_DIGITAL)); + io_config_clk_off(); + /* Configure pin as output and set it low */ + gpio0->data_dir |= (1 << SPI_CS_PIN); + gpio0->clear = (1 << SPI_CS_PIN); +} +void spi_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(); +} + + +/***************************************************************************** */ +/* This function is used to transfer a byte 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. + */ +uint16_t spi_transfer_single_frame(uint16_t data) +{ + struct lpc_ssp* ssp = LPC_SSP0; + ssp->data = data; + /* Wait until the busy bit is cleared */ + do {} while (ssp->status & LPC_SSP_ST_BUSY); + return ssp->data; +} + +/* 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 */ + + 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 = *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))) { + if (data_in != NULL) { + /* Store them ... */ + *data_in = ssp->data; + 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; +} + +/* 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; +} + + +/***************************************************************************** */ +uint32_t ssp_clk_on(uint32_t rate) +{ + struct lpc_sys_control* sys_ctrl = LPC_SYS_CONTROL; + struct lpc_ssp* ssp = LPC_SSP0; + uint32_t prescale = 0, pclk_div = 0; + uint32_t pclk = 0, div = 0; + + /* Do not divide by 0 */ + if (rate == 0) { + return 0; + } + + pclk = get_main_clock(); + + /* 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 ? + */ + if ((pclk / rate) > 0xFFF0) { + /* What should we do ? */ + div = 0xFFF0; + } else { + div = pclk / rate; + } + + do { + prescale += 2; /* Minimum value is 2, and must be even */ + pclk_div = (div / prescale); + } while ((prescale > 0xFF) || (pclk_div > 0xFF)); + + /* Activate the SSP clock (maybe divide main clock) */ + sys_ctrl->ssp0_clk_div = pclk_div; + + /* Set the prescaler */ + ssp->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); + } +} + +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 */ + 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(); +} + + + + +/***************************************************************************** */ +/* 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. + */ +int ssp_master_on(uint8_t frame_type, uint8_t data_width, uint8_t spi_cs_spi, uint32_t rate ) +{ + struct lpc_ssp* ssp = LPC_SSP0; + + NVIC_DisableIRQ(SSP0_IRQ); + /* Power up the ssp block */ + subsystem_power(LPC_SYS_ABH_CLK_CTRL_SSP0, 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; + + /* 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; + + NVIC_EnableIRQ(SSP0_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) +{ + struct lpc_ssp* ssp = LPC_SSP0; + + NVIC_DisableIRQ(SSP0_IRQ); + /* Power up the ssp block */ + subsystem_power(LPC_SYS_ABH_CLK_CTRL_SSP0, 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; + if (!out_en) { + ssp->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); + + /* Enable SSP */ + ssp->ctrl_1 |= LPC_SSP_ENABLE; + + NVIC_EnableIRQ(SSP0_IRQ); + return 0; /* Config OK */ + +} + +/* Turn off the SSP block */ +void ssp_off(void) +{ + ssp_current_clk = 0; + NVIC_DisableIRQ(SSP0_IRQ); + subsystem_power(LPC_SYS_ABH_CLK_CTRL_SSP0, 0); +} diff --git a/include/core/lpc_regs_12xx.h b/include/core/lpc_regs_12xx.h index 5ae6797..1cdd322 100644 --- a/include/core/lpc_regs_12xx.h +++ b/include/core/lpc_regs_12xx.h @@ -578,6 +578,40 @@ struct lpc_ssp }; #define LPC_SSP0 ((struct lpc_ssp *) LPC_SSP0_BASE) +/* SSP Control 0 register */ +#define LPC_SSP_DATA_WIDTH(x) (((x) - 1) & 0x0F) /* Use 4 for 4 bits, 5 for 5 bits, .... */ +#define LPC_SSP_FRAME_SPI (0x00 << 4) +#define LPC_SSP_FRAME_TI (0x01 << 4) +#define LPC_SSP_FRAME_MICROWIRE (0x02 << 4) +#define LPC_SSP_SPI_CLK_LOW (0x00 << 6) +#define LPC_SSP_SPI_CLK_HIGH (0x01 << 6) +#define LPC_SSP_SPI_CLK_FIRST (0x00 << 7) +#define LPC_SSP_SPI_CLK_LAST (0x01 << 7) +#define LPC_SSP_SPI_CLK_RATE_DIV(x) (((x) & 0xFF) << 8) +/* SSP Control 1 register */ +#define LPC_SSP_LOOPBACK_MODE (0x01 << 0) +#define LPC_SSP_ENABLE (0x01 << 1) +#define LPC_SSP_MASTER_MODE (0x00 << 2) +#define LPC_SSP_SLAVE_MODE (0x01 << 2) +#define LPC_SSP_SLAVE_OUT_DISABLE (0x01 << 3) + +/* SSP Status register */ +#define LPC_SSP_ST_TX_EMPTY (0x01 << 0) +#define LPC_SSP_ST_TX_NOT_FULL (0x01 << 1) +#define LPC_SSP_ST_RX_NOT_EMPTY (0x01 << 2) +#define LPC_SSP_ST_RX_FULL (0x01 << 3) +#define LPC_SSP_ST_BUSY (0x01 << 4) + +/* SSP Interrupt Mask, Raw, Status, Clear */ +#define LPC_SSP_INTR_RX_OVERRUN (0x01 << 0) +#define LPC_SSP_INTR_RX_TIMEOUT (0x01 << 1) +#define LPC_SSP_INTR_RX_HALF_FULL (0x01 << 2) +#define LPC_SSP_INTR_TX_HALF_EMPTY (0x01 << 3) + +/* SSP DMA Control */ +#define LPC_SSP_RX_DMA_EN (0x01 << 0) +#define LPC_SSP_TX_DMA_EN (0x01 << 1) + /***************************************************************************** */ diff --git a/include/drivers/ssp.h b/include/drivers/ssp.h new file mode 100644 index 0000000..cfc7e2e --- /dev/null +++ b/include/drivers/ssp.h @@ -0,0 +1,83 @@ +/**************************************************************************** + * drivers/ssp.h + * + * Copyright 2012 Nathael Pajani + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + *************************************************************************** */ + + + +/***************************************************************************** */ +/* SSP */ +/***************************************************************************** */ + +#include +#include "drivers/gpio.h" + +#define SPI_USE_FIFO 1 + + +/***************************************************************************** */ +/* SPI slave select / activate + * 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); + +/***************************************************************************** */ +/* 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. + */ +int spi_read(uint16_t *data, int size, int use_fifo); + + +/***************************************************************************** */ +uint32_t ssp_clk_on(uint32_t rate); +void ssp_clk_update(void); +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. + */ +int ssp_master_on(uint8_t frame_type, uint8_t data_width, uint8_t spi_cs_spi, uint32_t rate); + +int ssp_slave_on(uint8_t frame_type, uint8_t data_width, uint8_t out_en, uint32_t max_rate); + +/* Turn off the SSP block */ +void ssp_off(void); -- 2.43.0