From 37e0482e352f8e59dcfaa65b2ec76f11cd27b30c Mon Sep 17 00:00:00 2001 From: Hauke Petersen Date: Fri, 4 Nov 2016 18:29:27 +0100 Subject: [PATCH] drivers/periph: remodeled SPI driver interface --- drivers/include/periph/spi.h | 412 ++++++++++++++++++++--------------- 1 file changed, 235 insertions(+), 177 deletions(-) diff --git a/drivers/include/periph/spi.h b/drivers/include/periph/spi.h index 64888141ed..adbc2bf85e 100644 --- a/drivers/include/periph/spi.h +++ b/drivers/include/periph/spi.h @@ -1,9 +1,9 @@ /* - * Copyright (C) 2014 Freie Universität Berlin + * Copyright (C) 2014-2016 Freie Universität Berlin * - * This file is subject to the terms and conditions of the GNU Lesser General - * Public License v2.1. See the file LICENSE in the top level directory for more - * details. + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. */ /** @@ -11,258 +11,316 @@ * @ingroup drivers_periph * @brief Low-level SPI peripheral driver * - * The current design of this interface targets implementations that use the SPI in blocking mode. + * This interface defines an abstraction for using a CPU's hardware SPI units. + * The interface only supports SPI master mode. * - * TODO: add means for asynchronous SPI usage + * As SPI buses can have multiple devices connected to them they are to be + * considered as shared resources. To reflect this, the SPI interface is based + * on a transaction model. This requires that the bus needs to be acquired + * before usage and released afterwards, using the `spi_acquire()` and the + * `spi_release()` functions. + * + * This interface supports both software and hardware chip select lines. This is + * reflected by the cpi_cs_t type, which overloads the gpio_t type with platform + * specific values for defining platform dependent hardware chip select lines. + * + * Some devices have however very uncommon requirements on the usage and the + * timings of their chip select line. For those cases this interface allows to + * manage the chip select line manually from the user code (e.g. by calling + * gpio_set/clear explicitly) while deactivating the SPI driver internal chip + * select handling by passing @ref GPIO_UNDEF as CS parameter. + * + * In the time, when the SPI bus is not used, the SPI unit should be in + * low-power mode to save energy. + * + * The SPI unit's initialization is split into 3 parts: + * 1. `spi_init()` should be called once for each SPI unit defined by a board + * during system initialization. + * 2. `spi_init_cs()` should be called during device driver initialization, as + * each chip select pin/line is used uniquely by a specific device, i.e. chip + * select lines are no shared resource. + * 3. `spi_aquire()` needs to be called for each new transaction. This function + * configures the bus with specific parameters (clock, mode) for the duration + * of that transaction. * * @{ * @file - * @brief Low-level SPI peripheral driver interface definitions + * @brief Low-level SPI peripheral driver interface definition * * @author Hauke Petersen */ -#ifndef SPI_H -#define SPI_H +#ifndef PERIPH_SPI_H +#define PERIPH_SPI_H +#include +#include #include +#include #include "periph_cpu.h" #include "periph_conf.h" +#include "periph/gpio.h" #ifdef __cplusplus extern "C" { #endif -/* add guard for the case that no SPI device is defined */ -#if SPI_NUMOF +/** + * @brief Default SPI device access macro + */ +#ifndef SPI_DEV +#define SPI_DEV(x) (x) +#endif /** - * @brief Definition available SPI devices + * @brief Define global value for undefined SPI device */ +#ifndef SPI_UNDEF +#define SPI_UNDEF (UINT_MAX) +#endif + +/** + * @brief Define value for unused CS line + */ +#ifndef SPI_CS_UNDEF +#define SPI_CS_UNDEF (GPIO_UNDEF) +#endif + +/** + * @brief Default SPI hardware chip select access macro + * + * Per default, we map all hardware chip select lines to be not defined. If an + * implementation makes use of HW chip select lines, this value needs to be + * overridden by the corresponding CPU. + */ +#ifndef SPI_HWCS +#define SPI_HWCS(x) (SPI_CS_UNDEF) +#endif + +/** + * @brief Default type for SPI devices + */ +#ifndef HAVE_SPI_T +typedef unsigned int spi_t; +#endif + +/** + * @brief Chip select pin type overlaps with gpio_t so it can be casted to + * this + */ +#ifndef HAVE_SPI_CS_T +typedef gpio_t spi_cs_t; +#endif + +/** + * @brief Status codes used by the SPI driver interface + */ +enum { + SPI_OK = 0, /**< everything went as planned */ + SPI_NODEV = -1, /**< invalid SPI bus specified */ + SPI_NOCS = -2, /**< invalid chip select line specified */ + SPI_NOMODE = -3, /**< selected mode is not supported */ + SPI_NOCLK = -4 /**< selected clock value is not supported */ +}; + +/** + * @brief Available SPI modes, defining the configuration of clock polarity + * and clock phase + * + * RIOT is using the mode numbers as commonly defined by most vendors + * (https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus#Mode_numbers): + * + * - MODE_0: CPOL=0, CPHA=0 - The first data bit is sampled by the receiver on + * the first SCK rising SCK edge (this mode is used most often). + * - MODE_1: CPOL=0, CPHA=1 - The first data bit is sampled by the receiver on + * the second rising SCK edge. + * - MODE_2: CPOL=1, CPHA=0 - The first data bit is sampled by the receiver on + * the first falling SCK edge. + * - MODE_3: CPOL=1, CPHA=1 - The first data bit is sampled by the receiver on + * the second falling SCK edge. + */ +#ifndef HAVE_SPI_MODE_T typedef enum { -#if SPI_0_EN - SPI_0 = 0, /**< SPI device 0 */ + SPI_MODE_0 = 0, /**< CPOL=0, CPHA=0 */ + SPI_MODE_1, /**< CPOL=0, CPHA=1 */ + SPI_MODE_2, /**< CPOL=1, CPHA=0 */ + SPI_MODE_3 /**< CPOL=1, CPHA=1 */ +} spi_mode_t; #endif -#if SPI_1_EN - SPI_1, /**< SPI device 1 */ -#endif -#if SPI_2_EN - SPI_2, /**< SPI device 2 */ -#endif -#if SPI_3_EN - SPI_3, /**< SPI device 3 */ -#endif -} spi_t; /** - * @brief The SPI mode is defined by the four possible combinations of clock polarity and - * clock phase. - * @{ + * @brief Available SPI clock speeds + * + * The actual speed of the bus can vary to some extend, as the combination of + * CPU clock and available prescaler values on certain platforms may not make + * the exact values possible. */ -#ifndef HAVE_SPI_CONF_T +#ifndef HAVE_SPI_CLK_T typedef enum { - /** - * The first data bit is sampled by the receiver on the first SCK edge. The - * first edge of SCK is rising. This is sometimes also referred to as SPI - * mode 0, or (CPOL=0, CPHA=0). - */ - SPI_CONF_FIRST_RISING = 0, - /** - * The first data bit is sampled by the receiver on the second SCK edge. The - * first edge of SCK is rising, i.e. the sampling edge is falling. This is - * sometimes also referred to as SPI mode 1, or (CPOL=0, CPHA=1). - */ - SPI_CONF_SECOND_RISING = 1, - /** - * The first data bit is sampled by the receiver on the first SCK edge. The - * first edge of SCK is falling. This is sometimes also referred to as SPI - * mode 2, or (CPOL=1, CPHA=0). - */ - SPI_CONF_FIRST_FALLING = 2, - /** - * The first data bit is sampled by the receiver on the second SCK edge. The - * first edge of SCK is falling, i.e. the sampling edge is rising. This is - * sometimes also referred to as SPI mode 3, or (CPOL=1, CPHA=1). - */ - SPI_CONF_SECOND_FALLING = 3 -} spi_conf_t; + SPI_CLK_100KHZ = 0, /**< drive the SPI bus with 100KHz */ + SPI_CLK_400KHZ, /**< drive the SPI bus with 400KHz */ + SPI_CLK_1MHZ, /**< drive the SPI bus with 1MHz */ + SPI_CLK_5MHZ, /**< drive the SPI bus with 5MHz */ + SPI_CLK_10MHZ /**< drive the SPI bus with 10MHz */ +} spi_clk_t; #endif -/** @} */ /** - * @brief Define a set of pre-defined SPI clock speeds. + * @brief Basic initialization of the given SPI bus * - * The actual speed of the bus can vary to some extend, as the combination of CPU clock and - * available prescale values on certain platforms may not make the exact values possible. + * This function does the basic initialization including pin configuration for + * MISO, MOSI, and CLK pins. After initialization, the given device should be + * in power down state. * - * @{ + * This function is intended to be called by the board initialization code + * during system startup to prepare the (shared) SPI device for further usage. + * It uses the board specific initialization parameters as defined in the + * board's `periph_conf.h`. + * + * Errors (e.g. invalid @p bus parameter) are not signaled through a return + * value, but should be signaled using the assert() function internally. + * + * @note This function MUST not be called more than once per bus! + * + * @param[in] bus SPI device to initialize */ -#ifndef HAVE_SPI_SPEED_T -typedef enum { - SPI_SPEED_100KHZ = 0, /**< drive the SPI bus with 100KHz */ - SPI_SPEED_400KHZ, /**< drive the SPI bus with 400KHz */ - SPI_SPEED_1MHZ, /**< drive the SPI bus with 1MHz */ - SPI_SPEED_5MHZ, /**< drive the SPI bus with 5MHz */ - SPI_SPEED_10MHZ /**< drive the SPI bus with 10MHz */ -} spi_speed_t; -#endif -/** @} */ +void spi_init(spi_t bus); /** - * @brief Initialize the given SPI device to work in master mode + * @brief Initialize the used SPI bus pins, i.e. MISO, MOSI, and CLK * - * In master mode the SPI device is configured to control the SPI bus. This means the device - * will start and end all communication on the bus and control the CLK line. For transferring - * data on the bus the below defined transfer functions should be used. * - * @param[in] dev SPI device to initialize - * @param[in] conf Mode of clock phase and clock polarity - * @param[in] speed desired clock speed for driving the SPI bus + * After calling spi_init, the pins must be initialized (i.e. spi_init is + * calling) this function internally. In normal cases, this function will not be + * used. But there are some devices (e.g. CC110x), that use SPI bus lines also + * for other purposes and need the option to dynamically re-configure one or + * more of the used pins. So they can take control over certain pins and return + * control back to the SPI driver using this function. * - * @return 0 on success - * @return -1 on unavailable speed value - * @return -2 on other errors + * The pins used are configured in the board's periph_conf.h. + * + * @param[in] bus SPI device the pins are configure for */ -int spi_init_master(spi_t dev, spi_conf_t conf, spi_speed_t speed); +void spi_init_pins(spi_t bus); /** - * @brief Initialize the given SPI device to work in slave mode + * @brief Initialize the given chip select pin * - * In slave mode the SPI device is purely reacting to the bus. Transaction will be started and - * ended by a connected SPI master. When a byte is received, the callback is called in interrupt - * context with this byte as argument. The return byte of the callback is transferred to the - * master in the next transmission cycle. This interface enables easy implementation of a register - * based access paradigm for the SPI slave. + * The chip select can be any generic GPIO pin (e.g. GPIO_PIN(x,y)), or it can + * be a hardware chip select line. The existence and number of of hardware chip + * select lines depends on the underlying platform and the actual pins used for + * hardware chip select lines are defined in the board's `periph_conf.h`. * - * @param[in] dev The SPI device to initialize as SPI slave - * @param[in] conf Mode of clock phase and polarity - * @param[in] cb callback called every time a byte was received + * Define the used chip select line using the @ref SPI_HWCS(x) macro for + * hardware chip select line `x` or the GPIO_PIN(x,y) macro for using any + * GPIO pin for manual chip select. * - * @return 0 on success - * @return -1 on error + * @param[in] bus SPI device that is used with the given CS line + * @param[in] cs chip select pin to initialize + * + * @return SPI_OK on success + * @return SPI_NODEV on invalid device + * @return SPI_NOCS on invalid CS pin/line */ -int spi_init_slave(spi_t dev, spi_conf_t conf, char (*cb)(char data)); +int spi_init_cs(spi_t bus, spi_cs_t cs); /** - * @brief Configure SCK, MISO and MOSI pins for the given SPI device + * @brief Start a new SPI transaction * - * @param[in] dev SPI device to use + * Starting a new SPI transaction will get exclusive access to the SPI bus + * and configure it according to the given values. If another SPI transaction + * is active when this function is called, this function will block until the + * other transaction is complete (spi_relase was called). * - * @return 0 on success - * @return -1 on error + * @note This function expects the @p bus and the @p cs parameters to be + * valid (they are checked in spi_init and spi_init_cs before) + * + * @param[in] bus SPI device to access + * @param[in] cs chip select pin/line to use, set to SPI_CS_UNDEF if chip + * select should not be handled by the SPI driver + * @param[in] mode mode to use for the new transaction + * @param[in] clk bus clock speed to use for the transaction + * + * @return SPI_OK on success + * @return SPI_NOMODE if given mode is not supported + * @return SPI_NOCLK if given clock speed is not supported */ -int spi_conf_pins(spi_t dev); +int spi_acquire(spi_t bus, spi_cs_t cs, spi_mode_t mode, spi_clk_t clk); /** - * @brief Get mutually exclusive access to the given SPI bus + * @brief Finish an ongoing SPI transaction by releasing the given SPI bus * - * In case the SPI device is busy, this function will block until the bus is free again. + * After release, the given SPI bus should be fully powered down until acquired + * again. * - * @param[in] dev SPI device to access - * - * @return 0 on success - * @return -1 on error + * @param[in] bus SPI device to release */ -int spi_acquire(spi_t dev); - -/** - * @brief Release the given SPI device to be used by others - * - * @param[in] dev SPI device to release - * - * @return 0 on success - * @return -1 on error - */ -int spi_release(spi_t dev); +void spi_release(spi_t bus); /** * @brief Transfer one byte on the given SPI bus * - * @param[in] dev SPI device to use - * @param[in] out Byte to send out, set NULL if only receiving - * @param[out] in Byte to read, set NULL if only sending + * @param[in] bus SPI device to use + * @param[in] cs chip select pin/line to use, set to SPI_CS_UNDEF if chip + * select should not be handled by the SPI driver + * @param[in] cont if true, keep device selected after transfer + * @param[in] out byte to send out, set NULL if only receiving * - * @return Number of bytes that were transfered - * @return -1 on error + * @return the received byte */ -int spi_transfer_byte(spi_t dev, char out, char *in); +uint8_t spi_transfer_byte(spi_t bus, spi_cs_t cs, bool cont, uint8_t out); /** - * @brief Transfer a number bytes on the given SPI bus + * @brief Transfer a number bytes using the given SPI bus * - * @param[in] dev SPI device to use - * @param[in] out Array of bytes to send, set NULL if only receiving - * @param[out] in Buffer to receive bytes to, set NULL if only sending - * @param[in] length Number of bytes to transfer - * - * @return Number of bytes that were transfered - * @return -1 on error + * @param[in] bus SPI device to use + * @param[in] cs chip select pin/line to use, set to SPI_CS_UNDEF if chip + * select should not be handled by the SPI driver + * @param[in] cont if true, keep device selected after transfer + * @param[in] out buffer to send data from, set NULL if only receiving + * @param[out] in buffer to read into, set NULL if only sending + * @param[in] len number of bytes to transfer */ -int spi_transfer_bytes(spi_t dev, char *out, char *in, unsigned int length); +void spi_transfer_bytes(spi_t bus, spi_cs_t cs, bool cont, + const void *out, void *in, size_t len); /** - * @brief Transfer one byte to/from a given register address + * @brief Transfer one byte to/from a given register address * - * This function is a shortcut function for easier handling of register based SPI devices. As - * many SPI devices use a register based addressing scheme, this function is a convenient short- - * cut for interfacing with such devices. + * This function is a shortcut function for easier handling of SPI devices that + * implement a register based access scheme. * - * @param[in] dev SPI device to use - * @param[in] reg Register address to transfer data to/from - * @param[in] out Byte to send, set NULL if only receiving data - * @param[out] in Byte to read, set NULL if only sending + * @param[in] bus SPI device to use + * @param[in] cs chip select pin/line to use, set to SPI_CS_UNDEF if chip + * select should not be handled by the SPI driver + * @param[in] reg register address to transfer data to/from + * @param[in] out byte to send, set NULL if only receiving data * - * @return Number of bytes that were transfered - * @return -1 on error + * @return value that was read from the given register address */ -int spi_transfer_reg(spi_t dev, uint8_t reg, char out, char *in); +uint8_t spi_transfer_reg(spi_t bus, spi_cs_t cs, uint8_t reg, uint8_t out); /** - * @brief Transfer a number of bytes from/to a given register address + * @brief Transfer a number of bytes to/from a given register address * - * This function is a shortcut function for easier handling of register based SPI devices. As - * many SPI devices use a register based addressing scheme, this function is a convenient short- - * cut for interfacing with such devices. + * This function is a shortcut function for easier handling of SPI devices that + * implement a register based access scheme. * - * @param[in] dev SPI device to use - * @param[in] reg Register address to transfer data to/from - * @param[in] out Byte array to send data from, set NULL if only receiving - * @param[out] in Byte buffer to read into, set NULL if only sending - * @param[in] length Number of bytes to transfer - * - * @return Number of bytes that were transfered - * @return -1 on error + * @param[in] bus SPI device to use + * @param[in] cs chip select pin/line to use, set to SPI_CS_UNDEF if chip + * select should not be handled by the SPI driver + * @param[in] reg register address to transfer data to/from + * @param[in] out buffer to send data from, set NULL if only receiving + * @param[out] in buffer to read into, set NULL if only sending + * @param[in] len number of bytes to transfer */ -int spi_transfer_regs(spi_t dev, uint8_t reg, char *out, char *in, unsigned int length); - -/** - * @brief Tell the SPI driver that a new transaction was started. Call only when SPI in slave mode! - * - * @param[in] dev SPI device that is active - * @param[in] reset_val The byte that is send to the master as first byte - */ -void spi_transmission_begin(spi_t dev, char reset_val); - -/** - * @brief Power on the given SPI device - * - * @param[in] dev SPI device to power on - */ -void spi_poweron(spi_t dev); - -/** - * @brief Power off the given SPI device - * - * @param[in] dev SPI device to power off - */ -void spi_poweroff(spi_t dev); - -#endif /* SPI_NUMOF */ +void spi_transfer_regs(spi_t bus, spi_cs_t cs, uint8_t reg, + const void *out, void *in, size_t len); #ifdef __cplusplus } #endif -#endif /* SPI_H */ +#endif /* PERIPH_SPI_H */ /** @} */