From bd40f75d370cca1933fdae164751c71e197d363b Mon Sep 17 00:00:00 2001 From: UncleRus Date: Fri, 4 Mar 2016 01:10:06 +0500 Subject: [PATCH] Hardware SPI driver --- core/esp_spi.c | 206 ++++++++++++++++++++++++++++++++++++ core/include/esp/spi.h | 197 ++++++++++++++++++++++++++++++++++ core/include/esp/spi_regs.h | 2 + 3 files changed, 405 insertions(+) create mode 100644 core/esp_spi.c create mode 100644 core/include/esp/spi.h diff --git a/core/esp_spi.c b/core/esp_spi.c new file mode 100644 index 0000000..ac9b0b1 --- /dev/null +++ b/core/esp_spi.c @@ -0,0 +1,206 @@ +/* + * ESP hardware SPI master driver + * + * Part of esp-open-rtos + * Copyright (c) Ruslan V. Uss, 2016 + * BSD Licensed as described in the file LICENSE + */ +#include "esp/spi.h" + +#include "esp/iomux.h" +#include "esp/gpio.h" +#include + +//#include + +#define _SPI0_SCK_GPIO 6 +#define _SPI0_MISO_GPIO 7 +#define _SPI0_MOSI_GPIO 8 +#define _SPI0_HD_GPIO 9 +#define _SPI0_WP_GPIO 10 +#define _SPI0_CS0_GPIO 11 + +#define _SPI1_MISO_GPIO 12 +#define _SPI1_MOSI_GPIO 13 +#define _SPI1_SCK_GPIO 14 +#define _SPI1_CS0_GPIO 15 + +#define _SPI0_FUNC 1 +#define _SPI1_FUNC 2 + +#define _SPI_BUF_SIZE 64 + +inline static void _set_pin_function(uint8_t pin, uint32_t function) +{ + iomux_set_function(gpio_to_iomux(pin), function); +} + +bool spi_init(uint8_t bus, spi_mode_t mode, uint32_t freq_divider, bool msb, spi_endianness_t endianness, bool minimal_pins) +{ + switch (bus) + { + case 0: + _set_pin_function(_SPI0_MISO_GPIO, _SPI0_FUNC); + _set_pin_function(_SPI0_MOSI_GPIO, _SPI0_FUNC); + _set_pin_function(_SPI0_SCK_GPIO, _SPI0_FUNC); + if (!minimal_pins) + { + _set_pin_function(_SPI0_HD_GPIO, _SPI0_FUNC); + _set_pin_function(_SPI0_WP_GPIO, _SPI0_FUNC); + _set_pin_function(_SPI0_CS0_GPIO, _SPI0_FUNC); + } + break; + case 1: + _set_pin_function(_SPI1_MISO_GPIO, _SPI1_FUNC); + _set_pin_function(_SPI1_MOSI_GPIO, _SPI1_FUNC); + _set_pin_function(_SPI1_SCK_GPIO, _SPI1_FUNC); + if (!minimal_pins) + _set_pin_function(_SPI1_CS0_GPIO, _SPI1_FUNC); + break; + default: + return false; + } + + SPI(bus).USER0 = SPI_USER0_MOSI | SPI_USER0_CLOCK_IN_EDGE | SPI_USER0_DUPLEX | + (minimal_pins ? 0 : (SPI_USER0_CS_HOLD | SPI_USER0_CS_SETUP)); + + spi_set_frequency_div(bus, freq_divider); + spi_set_mode(bus, mode); + spi_set_msb(bus, msb); + spi_set_endianness(bus, endianness); + + return true; +} + +void spi_set_mode(uint8_t bus, spi_mode_t mode) +{ + // CPHA + if ((uint8_t)mode & 1) + SPI(bus).USER0 |= SPI_USER0_CLOCK_OUT_EDGE; + else + SPI(bus).USER0 &= ~SPI_USER0_CLOCK_OUT_EDGE; + + // CPOL - see http://bbs.espressif.com/viewtopic.php?t=342#p5384 + if ((uint8_t)mode & 2) + SPI(bus).PIN |= SPI_PIN_IDLE_EDGE; + else + SPI(bus).PIN &= ~SPI_PIN_IDLE_EDGE; +} + +void spi_set_msb(uint8_t bus, bool msb) +{ + if (msb) + SPI(bus).CTRL0 &= ~(SPI_CTRL0_WR_BIT_ORDER | SPI_CTRL0_RD_BIT_ORDER); + else + SPI(bus).CTRL0 |= (SPI_CTRL0_WR_BIT_ORDER | SPI_CTRL0_RD_BIT_ORDER); +} + +void spi_set_endianness(uint8_t bus, spi_endianness_t endianness) +{ + if (endianness == SPI_BIG_ENDIAN) + SPI(bus).USER0 |= (SPI_USER0_WR_BYTE_ORDER | SPI_USER0_RD_BYTE_ORDER); + else + SPI(bus).USER0 &= ~(SPI_USER0_WR_BYTE_ORDER | SPI_USER0_RD_BYTE_ORDER); +} + +void spi_set_frequency_div(uint8_t bus, uint32_t divider) +{ + uint32_t predivider = divider & 0xffff; + uint32_t count = divider >> 16; + if (count > 1 && divider > 1) + { + predivider = predivider > SPI_CLOCK_DIV_PRE_M + 1 ? SPI_CLOCK_DIV_PRE_M + 1 : predivider; + count = count > SPI_CLOCK_COUNT_NUM_M + 1 ? SPI_CLOCK_COUNT_NUM_M + 1 : count; + IOMUX.CONF &= ~(bus == 0 ? IOMUX_CONF_SPI0_CLOCK_EQU_SYS_CLOCK : IOMUX_CONF_SPI1_CLOCK_EQU_SYS_CLOCK); + SPI(bus).CLOCK = (((predivider - 1) & SPI_CLOCK_DIV_PRE_M) << SPI_CLOCK_DIV_PRE_S) | + (((count - 1) & SPI_CLOCK_COUNT_NUM_M) << SPI_CLOCK_COUNT_NUM_S) | + (((count / 2 - 1) & SPI_CLOCK_COUNT_HIGH_M) << SPI_CLOCK_COUNT_HIGH_S) | + (((count - 1) & SPI_CLOCK_COUNT_LOW_M) << SPI_CLOCK_COUNT_LOW_S); + } + else + { + IOMUX.CONF |= bus == 0 ? IOMUX_CONF_SPI0_CLOCK_EQU_SYS_CLOCK : IOMUX_CONF_SPI1_CLOCK_EQU_SYS_CLOCK; + SPI(bus).CLOCK = SPI_CLOCK_EQU_SYS_CLOCK; + } +} + +inline static void _set_size(uint8_t bus, uint8_t bytes) +{ + uint16_t bits = ((uint16_t)bytes << 3) - 1; + const uint32_t mask = ~((SPI_USER1_MOSI_BITLEN_M << SPI_USER1_MOSI_BITLEN_S) | + (SPI_USER1_MISO_BITLEN_M << SPI_USER1_MISO_BITLEN_S)); + SPI(bus).USER1 = (SPI(bus).USER1 & mask) | (bits << SPI_USER1_MOSI_BITLEN_S) | + (bits << SPI_USER1_MISO_BITLEN_S); +} + +inline static void _wait(uint8_t bus) +{ + while (SPI(bus).CMD & SPI_CMD_USR) + ; +} + +inline static void _start(uint8_t bus) +{ + SPI(bus).CMD |= SPI_CMD_USR; +} + +inline static uint32_t _reverse_bytes(uint32_t value) +{ + return (value << 24) | ((value << 8) & 0x00ff0000) | ((value >> 8) & 0x0000ff00) | (value >> 24); +} + +static uint32_t _spi_single_transfer (uint8_t bus, uint32_t data, uint8_t len) +{ + _wait(bus); + _set_size(bus, len); + spi_endianness_t e = spi_get_endianness(bus); + SPI(bus).W0 = e == SPI_BIG_ENDIAN ? _reverse_bytes(data) : data; + _start(bus); + _wait(bus); + return e == SPI_BIG_ENDIAN ? _reverse_bytes(SPI(bus).W0) : SPI(bus).W0; +} + +// works properly only with little endian byte order +static void _spi_buf_transfer (uint8_t bus, uint8_t *data, size_t len) +{ + _wait(bus); + _set_size(bus, len); + memcpy((void *)&SPI(bus).W0, data, len); + _start(bus); + _wait(bus); + memcpy(data, (void *)&SPI(bus).W0, len); +} + +uint8_t spi_transfer_8(uint8_t bus, uint8_t data) +{ + return _spi_single_transfer(bus, data, sizeof(data)); +} + +uint16_t spi_transfer_16(uint8_t bus, uint16_t data) +{ + return _spi_single_transfer(bus, data, sizeof(data)); +} + +uint32_t spi_transfer_32(uint8_t bus, uint32_t data) +{ + return _spi_single_transfer(bus, data, sizeof(data)); +} + +void spi_transfer(uint8_t bus, void *data, size_t len) +{ + if (!data || !len) return; + + _wait(bus); + spi_endianness_t e = spi_get_endianness(bus); + spi_set_endianness(bus, SPI_LITTLE_ENDIAN); + + size_t counts = len / _SPI_BUF_SIZE; + for (uint8_t i = 0; i < counts; i++) + _spi_buf_transfer(bus, data + i * _SPI_BUF_SIZE, _SPI_BUF_SIZE); + + uint8_t tail = len % _SPI_BUF_SIZE; + if (tail) + _spi_buf_transfer(bus, data + counts * _SPI_BUF_SIZE, tail); + + spi_set_endianness(bus, e); +} diff --git a/core/include/esp/spi.h b/core/include/esp/spi.h new file mode 100644 index 0000000..6b2b0e2 --- /dev/null +++ b/core/include/esp/spi.h @@ -0,0 +1,197 @@ +/** + * \file Hardware SPI master driver + * + * Part of esp-open-rtos + * + * \copyright Ruslan V. Uss, 2016 + * BSD Licensed as described in the file LICENSE + */ +#ifndef _ESP_SPI_H_ +#define _ESP_SPI_H_ + +#include +#include +#include "esp/spi_regs.h" + +// FIXME Better to define it somewhere else. This is not the CPU frequency! +#define SYSTEM_BUS_FREQ 80000000UL + +/** + * Macro for use with spi_init and spi_set_frequency_div. + * SPI frequency = 80000000 / divider / count + * dvider must be in 1..8192 and count in 1..64 + */ +#define SPI_GET_FREQ_DIV(divider, count) (((count) << 16) | (divider)) + +/** + * Predefinded SPI frequency dividers + */ +#define SPI_FREQ_DIV_125K SPI_GET_FREQ_DIV(64, 10) ///< 125kHz +#define SPI_FREQ_DIV_250K SPI_GET_FREQ_DIV(32, 10) ///< 250kHz +#define SPI_FREQ_DIV_500K SPI_GET_FREQ_DIV(16, 10) ///< 500kHz +#define SPI_FREQ_DIV_1M SPI_GET_FREQ_DIV(8, 10) ///< 1MHz +#define SPI_FREQ_DIV_2M SPI_GET_FREQ_DIV(4, 10) ///< 2MHz +#define SPI_FREQ_DIV_4M SPI_GET_FREQ_DIV(2, 10) ///< 4MHz +#define SPI_FREQ_DIV_8M SPI_GET_FREQ_DIV(5, 2) ///< 8MHz +#define SPI_FREQ_DIV_10M SPI_GET_FREQ_DIV(4, 2) ///< 10MHz +#define SPI_FREQ_DIV_20M SPI_GET_FREQ_DIV(2, 2) ///< 20MHz +#define SPI_FREQ_DIV_40M SPI_GET_FREQ_DIV(1, 2) ///< 40MHz +#define SPI_FREQ_DIV_80M SPI_GET_FREQ_DIV(1, 1) ///< 80MHz + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef enum _spi_mode_t { + SPI_MODE0 = 0, ///< CPOL = 0, CPHA = 0 + SPI_MODE1, ///< CPOL = 0, CPHA = 1 + SPI_MODE2, ///< CPOL = 1, CPHA = 0 + SPI_MODE3 ///< CPOL = 1, CPHA = 1 +} spi_mode_t; + +typedef enum _spi_endianness_t { + SPI_LITTLE_ENDIAN, + SPI_BIG_ENDIAN +} spi_endianness_t; + +/** + * \brief Initalize SPI bus + * Initalize specified SPI bus and setup appropriate pins: + * Bus 0: + * - MISO = GPIO 7 + * - MOSI = GPIO 8 + * - SCK = GPIO 6 + * - CS0 = GPIO 11 (if minimal_pins is false) + * - HD = GPIO 9 (if minimal_pins is false) + * - WP = GPIO 10 (if minimal_pins is false) + * Bus 1: + * - MISO = GPIO 12 + * - MOSI = GPIO 13 + * - SCK = GPIO 14 + * - CS0 = GPIO 15 (if minimal_pins is false) + * Note that system flash memory is on the bus 0! + * \param bus Bus ID: 0 - system, 1 - user + * \param mode Bus mode + * \param freq_divider SPI bus frequency divider, use SPI_GET_FREQ_DIV() or predefined value + * \param msb Bit order, MSB first if true + * \param endianness Byte order + * \param minimal_pins If true use the minimal set of pins: MISO, MOSI and SCK. + * \return false when error + */ +bool spi_init(uint8_t bus, spi_mode_t mode, uint32_t freq_divider, bool msb, spi_endianness_t endianness, bool minimal_pins); + +/** + * \brief Set SPI bus mode + * \param bus Bus ID: 0 - system, 1 - user + * \param mode Bus mode. + */ +void spi_set_mode(uint8_t bus, spi_mode_t mode); +/** + * \brief Get mode of the SPI bus + * \param bus Bus ID: 0 - system, 1 - user + * \return Bus mode + */ +inline spi_mode_t spi_get_mode(uint8_t bus) +{ + return (spi_mode_t)((SPI(bus).PIN & SPI_PIN_IDLE_EDGE ? 2 : 0) | (SPI(bus).USER0 & SPI_USER0_CLOCK_OUT_EDGE ? 1 : 0)); +} + +/** + * \brief Set SPI bus frequency + * Examples: + * + * spi_set_frequency_div(1, SPI_FREQ_DIV_8M); // 8 MHz, predefined value + * ... + * spi_set_frequency_div(1, SPI_GET_FREQ_DIV(8, 10)); // divider = 8, count = 10, + * // frequency = 80000000 Hz / 8 / 10 = 1000000 Hz + * + * \param bus Bus ID: 0 - system, 1 - user + * \param divider Predivider of the system bus frequency (80MHz) in the 2 low + * bytes and period pulses count in the third byte. Please note that + * divider must be be in range 1..8192 and count in range 2..64. Use the + * macro SPI_GET_FREQ_DIV(divider, count) to get the correct parameter value. + */ +void spi_set_frequency_div(uint8_t bus, uint32_t divider); +/** + * \brief Get SPI bus frequency + * Note the result is in Hz. + * \param bus Bus ID: 0 - system, 1 - user + * \return SPI frequency, Hz + */ +inline uint32_t spi_get_frequency_hz(uint8_t bus) +{ + return SYSTEM_BUS_FREQ / + (((SPI(bus).CLOCK >> SPI_CLOCK_DIV_PRE_S) & SPI_CLOCK_DIV_PRE_M) + 1) / + (((SPI(bus).CLOCK >> SPI_CLOCK_COUNT_NUM_S) & SPI_CLOCK_COUNT_NUM_M) + 1); +} + +/** + * \brief Set SPI bus bit order + * \param bus Bus ID: 0 - system, 1 - user + * \param msb Bit order, MSB first if true + */ +void spi_set_msb(uint8_t bus, bool msb); +/** + * \brief Get SPI bus bit order + * \param bus Bus ID: 0 - system, 1 - user + * \return msb Bit order, MSB first if true + */ +inline bool spi_get_msb(uint8_t bus) +{ + return !(SPI(bus).CTRL0 & (SPI_CTRL0_WR_BIT_ORDER | SPI_CTRL0_RD_BIT_ORDER)); +} + +/** + * \brief Set SPI bus byte order + * This value is ignored when transferring buffer with spi_transfer() + * \param bus Bus ID: 0 - system, 1 - user + * \param endianness Byte order + */ +void spi_set_endianness(uint8_t bus, spi_endianness_t endianness); +/** + * \brief Get SPI bus byte order + * \param bus Bus ID: 0 - system, 1 - user + * \return endianness Byte order + */ +inline spi_endianness_t spi_get_endianness(uint8_t bus) +{ + return SPI(bus).USER0 & (SPI_USER0_WR_BYTE_ORDER | SPI_USER0_RD_BYTE_ORDER) + ? SPI_BIG_ENDIAN + : SPI_LITTLE_ENDIAN; +} + +/** + * \brief Transfer byte over SPI + * \param bus Bus ID: 0 - system, 1 - user + * \param data Byte to send + * \return Received byte + */ +uint8_t spi_transfer_8(uint8_t bus, uint8_t data); +/** + * \brief Transfer word over SPI + * \param bus Bus ID: 0 - system, 1 - user + * \param data Word to send + * \return Received word + */ +uint16_t spi_transfer_16(uint8_t bus, uint16_t data); +/** + * \brief Transfer dword over SPI + * \param bus Bus ID: 0 - system, 1 - user + * \param data dword to send + * \return Received dword + */ +uint32_t spi_transfer_32(uint8_t bus, uint32_t data); +/** + * \brief Transfer buffer over SPI + * \param bus Bus ID: 0 - system, 1 - user + * \param data Data to send. Buffer contents will be replaced with received data + * \param len Buffer size + */ +void spi_transfer(uint8_t bus, void *data, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* _ESP_SPI_H_ */ diff --git a/core/include/esp/spi_regs.h b/core/include/esp/spi_regs.h index dfcf998..37eb113 100644 --- a/core/include/esp/spi_regs.h +++ b/core/include/esp/spi_regs.h @@ -152,6 +152,7 @@ _Static_assert(sizeof(struct SPI_REGS) == 0x100, "SPI_REGS is the wrong size"); #define SPI_USER0_CS_SETUP BIT(5) #define SPI_USER0_CS_HOLD BIT(4) #define SPI_USER0_FLASH_MODE BIT(2) +#define SPI_USER0_DUPLEX BIT(0) /* Details for USER1 register */ @@ -173,6 +174,7 @@ _Static_assert(sizeof(struct SPI_REGS) == 0x100, "SPI_REGS is the wrong size"); /* Details for PIN register */ +#define SPI_PIN_IDLE_EDGE BIT(29) ///< CPOL #define SPI_PIN_CS2_DISABLE BIT(2) #define SPI_PIN_CS1_DISABLE BIT(1) #define SPI_PIN_CS0_DISABLE BIT(0)