| /* spis_nrf5.c - SPI slave driver for Nordic nRF5x SoCs */ |
| /* |
| * Copyright (c) 2016, 2017 Linaro Limited. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define SYS_LOG_LEVEL CONFIG_SYS_LOG_SPI_LEVEL |
| |
| #include <device.h> |
| #include <errno.h> |
| |
| #include <logging/sys_log.h> |
| #include <sys_io.h> |
| #include <board.h> |
| #include <init.h> |
| #include <gpio.h> |
| #include <spi.h> |
| #include <nrf.h> |
| #include <toolchain.h> |
| |
| typedef void (*spis_nrf5_config_t)(void); |
| |
| struct spis_nrf5_config { |
| NRF_SPIS_Type *regs; /* Registers */ |
| spis_nrf5_config_t config_func; /* IRQ config func pointer */ |
| u8_t sck_pin; /* SCK GPIO pin number */ |
| u8_t mosi_pin; /* MOSI GPIO pin number */ |
| u8_t miso_pin; /* MISO GPIO pin number */ |
| u8_t csn_pin; /* CSN GPIO pin number */ |
| u8_t def; /* Default character */ |
| }; |
| |
| struct spis_nrf5_data { |
| u8_t error; |
| struct k_sem device_sync_sem; /* synchronisation semaphore */ |
| }; |
| |
| #define DEV_CFG(dev) \ |
| ((const struct spis_nrf5_config * const)(dev)->config->config_info) |
| #define DEV_DATA(dev) \ |
| ((struct spis_nrf5_data * const)(dev)->driver_data) |
| #define SPI_REGS(dev) \ |
| ((DEV_CFG(dev))->regs) |
| |
| /* Register fields */ |
| |
| #define NRF5_SPIS_SHORTCUT_END_ACQUIRE \ |
| (SPIS_SHORTS_END_ACQUIRE_Enabled << SPIS_SHORTS_END_ACQUIRE_Pos) |
| |
| #define NRF5_SPIS_ORDER_MSB \ |
| (SPIS_CONFIG_ORDER_MsbFirst << SPIS_CONFIG_ORDER_Pos) |
| #define NRF5_SPIS_ORDER_LSB \ |
| (SPIS_CONFIG_ORDER_LsbFirst << SPIS_CONFIG_ORDER_Pos) |
| |
| #define NRF5_SPIS_CPHA_LEADING \ |
| (SPIS_CONFIG_CPHA_Leading << SPIS_CONFIG_CPHA_Pos) |
| #define NRF5_SPIS_CPHA_TRAILING \ |
| (SPIS_CONFIG_CPHA_Trailing << SPIS_CONFIG_CPHA_Pos) |
| |
| #define NRF5_SPIS_CPOL_HIGH \ |
| (SPIS_CONFIG_CPOL_ActiveHigh << SPIS_CONFIG_CPOL_Pos) |
| #define NRF5_SPIS_CPOL_LOW \ |
| (SPIS_CONFIG_CPOL_ActiveLow << SPIS_CONFIG_CPOL_Pos) |
| |
| #define NRF5_SPIS_ENABLED \ |
| (SPIS_ENABLE_ENABLE_Enabled << SPIS_ENABLE_ENABLE_Pos) |
| |
| #define NRF5_SPIS_CSN_DISABLED_CFG 0xff /* CS disabled value from Kconfig */ |
| #if defined(CONFIG_SOC_SERIES_NRF51X) |
| #define NRF5_SPIS_CSN_DISABLED (~0U) /* CS disabled register value */ |
| #elif defined(CONFIG_SOC_SERIES_NRF52X) |
| #define NRF5_SPIS_CSN_DISABLED (1U << 31) /* CS disabled register value */ |
| #endif |
| |
| static inline bool is_buf_in_ram(const void *buf) |
| { |
| return ((((uintptr_t) buf) & 0xE0000000) == 0x20000000); |
| } |
| |
| static void spis_nrf5_print_cfg_registers(struct device *dev) |
| { |
| __unused NRF_SPIS_Type *regs = SPI_REGS(dev); |
| __unused u32_t sck, miso, mosi, csn; |
| __unused u32_t rxd_ptr, rxd_max, rxd_amount; |
| __unused u32_t txd_ptr, txd_max, txd_amount; |
| |
| #if defined(CONFIG_SOC_SERIES_NRF51X) |
| sck = regs->PSELSCK; |
| miso = regs->PSELMISO; |
| mosi = regs->PSELMOSI; |
| csn = regs->PSELCSN; |
| rxd_ptr = regs->RXDPTR; |
| rxd_max = regs->MAXRX; |
| rxd_amount = regs->AMOUNTRX; |
| txd_ptr = regs->TXDPTR; |
| txd_max = regs->MAXTX; |
| txd_amount = regs->AMOUNTTX; |
| #elif defined(CONFIG_SOC_SERIES_NRF52X) |
| sck = regs->PSEL.SCK; |
| miso = regs->PSEL.MISO; |
| mosi = regs->PSEL.MOSI; |
| csn = regs->PSEL.CSN; |
| rxd_ptr = regs->RXD.PTR; |
| rxd_max = regs->RXD.MAXCNT; |
| rxd_amount = regs->RXD.AMOUNT; |
| txd_ptr = regs->TXD.PTR; |
| txd_max = regs->TXD.MAXCNT; |
| txd_amount = regs->TXD.AMOUNT; |
| #endif /* defined(CONFIG_SOC_SERIES_NRF51X) */ |
| |
| SYS_LOG_DBG("\n" |
| "SHORTS: %x, IRQ: %x, SEMSTAT: %x\n" |
| "CONFIG: %x, STATUS: %x, ENABLE: %x\n" |
| "SCKPIN: %x, MISOPIN: %x, MOSIPIN: %x, CSNPIN: %x\n" |
| "RXD (PTR: %x, MAX: %x, AMOUNT: %x)\n" |
| "TXD (PTR: %x, MAX: %x, AMOUNT: %x)", |
| regs->SHORTS, regs->INTENSET, regs->SEMSTAT, |
| regs->CONFIG, regs->STATUS, regs->ENABLE, |
| sck, miso, mosi, csn, |
| rxd_ptr, rxd_max, rxd_amount, |
| txd_ptr, txd_max, txd_amount); |
| } |
| |
| /** |
| * @brief Configure the SPI host controller for operating against slaves |
| * @param dev Pointer to the device structure for the driver instance |
| * @param config Pointer to the application provided configuration |
| * |
| * @return 0 if successful, another DEV_* code otherwise. |
| */ |
| static int spis_nrf5_configure(struct device *dev, struct spi_config *config) |
| { |
| NRF_SPIS_Type *spi_regs = SPI_REGS(dev); |
| u32_t flags; |
| |
| /* make sure module is disabled */ |
| spi_regs->ENABLE = 0; |
| |
| /* TODO: Muck with IRQ priority if needed */ |
| |
| /* Clear any pending events */ |
| spi_regs->EVENTS_ACQUIRED = 0; |
| spi_regs->EVENTS_ENDRX = 0; |
| spi_regs->EVENTS_END = 0; |
| spi_regs->INTENCLR = 0xFFFFFFFF; /* do we need to clear INT ?*/ |
| spi_regs->SHORTS = NRF5_SPIS_SHORTCUT_END_ACQUIRE; |
| spi_regs->INTENSET |= (SPIS_INTENSET_ACQUIRED_Msk | |
| SPIS_INTENSET_END_Msk); |
| |
| /* default transmit and over-read characters */ |
| spi_regs->DEF = DEV_CFG(dev)->def; |
| spi_regs->ORC = 0x000000AA; |
| |
| /* user configuration */ |
| flags = config->config; |
| if (flags & SPI_TRANSFER_LSB) { |
| spi_regs->CONFIG = NRF5_SPIS_ORDER_LSB; |
| } else { |
| spi_regs->CONFIG = NRF5_SPIS_ORDER_MSB; |
| } |
| if (flags & SPI_MODE_CPHA) { |
| spi_regs->CONFIG |= NRF5_SPIS_CPHA_TRAILING; |
| } else { |
| spi_regs->CONFIG |= NRF5_SPIS_CPHA_LEADING; |
| } |
| if (flags & SPI_MODE_CPOL) { |
| spi_regs->CONFIG |= NRF5_SPIS_CPOL_LOW; |
| } else { |
| spi_regs->CONFIG |= NRF5_SPIS_CPOL_HIGH; |
| } |
| |
| /* Enable the SPIS - peripherals sharing same ID will be disabled */ |
| spi_regs->ENABLE = NRF5_SPIS_ENABLED; |
| |
| spis_nrf5_print_cfg_registers(dev); |
| |
| SYS_LOG_DBG("SPI Slave Driver configured"); |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Read and/or write a defined amount of data through an SPI driver |
| * |
| * @param dev Pointer to the device structure for the driver instance |
| * @param tx_buf Memory buffer that data should be transferred from |
| * @param tx_buf_len Size of the memory buffer available for reading from |
| * @param rx_buf Memory buffer that data should be transferred to |
| * @param rx_buf_len Size of the memory buffer available for writing to |
| * |
| * @return 0 if successful, another DEV_* code otherwise. |
| */ |
| static int spis_nrf5_transceive(struct device *dev, const void *tx_buf, |
| u32_t tx_buf_len, void *rx_buf, u32_t rx_buf_len) |
| { |
| NRF_SPIS_Type *spi_regs = SPI_REGS(dev); |
| struct spis_nrf5_data *priv_data = DEV_DATA(dev); |
| |
| __ASSERT(!((tx_buf_len && !tx_buf) || (rx_buf_len && !rx_buf)), |
| "spis_nrf5_transceive: ERROR - NULL buffers"); |
| |
| /* Buffer needs to be in RAM for EasyDMA to work */ |
| if (!tx_buf || !is_buf_in_ram(tx_buf)) { |
| SYS_LOG_ERR("Invalid TX buf %p", tx_buf); |
| return -EINVAL; |
| } |
| if (!rx_buf || !is_buf_in_ram(rx_buf)) { |
| SYS_LOG_ERR("Invalid RX buf %p", rx_buf); |
| return -EINVAL; |
| } |
| |
| priv_data->error = 0; |
| |
| if (spi_regs->SEMSTAT == 1) { |
| #if defined(CONFIG_SOC_SERIES_NRF51X) |
| spi_regs->TXDPTR = (u32_t) tx_buf; |
| spi_regs->RXDPTR = (u32_t) rx_buf; |
| spi_regs->MAXTX = tx_buf_len; |
| spi_regs->MAXRX = rx_buf_len; |
| #elif defined(CONFIG_SOC_SERIES_NRF52X) |
| spi_regs->TXD.PTR = (u32_t) tx_buf; |
| spi_regs->RXD.PTR = (u32_t) rx_buf; |
| spi_regs->TXD.MAXCNT = tx_buf_len; |
| spi_regs->RXD.MAXCNT = rx_buf_len; |
| #endif |
| spi_regs->TASKS_RELEASE = 1; |
| } else { |
| SYS_LOG_ERR("Can't get SEM; unfinished transfer?"); |
| return -EIO; |
| } |
| |
| /* wait for transfer to complete */ |
| k_sem_take(&priv_data->device_sync_sem, K_FOREVER); |
| |
| if (priv_data->error) { |
| priv_data->error = 0; |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Complete SPI module data transfer operations. |
| * @param dev Pointer to the device structure for the driver instance |
| * @param error Error condition (0 = no error, otherwise an error occurred) |
| * @return None. |
| */ |
| static void spis_nrf5_complete(struct device *dev, u32_t error) |
| { |
| __unused NRF_SPIS_Type *spi_regs = SPI_REGS(dev); |
| __unused u32_t txd_amount, rxd_amount; |
| struct spis_nrf5_data *priv_data = DEV_DATA(dev); |
| |
| #if defined(CONFIG_SOC_SERIES_NRF51X) |
| txd_amount = spi_regs->AMOUNTTX; |
| rxd_amount = spi_regs->AMOUNTRX; |
| #elif defined(CONFIG_SOC_SERIES_NRF52X) |
| txd_amount = spi_regs->RXD.AMOUNT; |
| rxd_amount = spi_regs->TXD.AMOUNT; |
| #endif |
| |
| SYS_LOG_DBG("bytes transferred: TX: %u, RX: %u [err %u (%s)]", |
| txd_amount, rxd_amount, |
| error, error == 0 ? "OK" : "ERR"); |
| |
| priv_data->error = error ? 1 : 0; |
| |
| k_sem_give(&priv_data->device_sync_sem); |
| } |
| |
| /** |
| * @brief SPI module interrupt handler. |
| * @param arg Pointer to the device structure for the driver instance |
| * @return None. |
| */ |
| static void spis_nrf5_isr(void *arg) |
| { |
| struct device *dev = arg; |
| NRF_SPIS_Type *spi_regs = SPI_REGS(dev); |
| u32_t error; |
| u32_t tmp; |
| |
| /* We get an interrupt for the following reasons: |
| * 1. Semaphore ACQUIRED: |
| * Semaphore is assigned to the CPU again (always happens |
| * after END if the END_ACQUIRE SHORTS is set) |
| * 2. End of Granted SPI transaction: |
| * Used to unblocked the caller, finishing the transaction |
| */ |
| |
| /* NOTE: |
| * Section 15.8.1 of nrf52 manual suggests reading back the register |
| * to cause a 4-cycle delay to prevent the interrupt from |
| * re-occurring |
| */ |
| |
| if (spi_regs->EVENTS_END) { |
| spi_regs->EVENTS_END = 0; |
| /* force register flush (per spec) */ |
| tmp = spi_regs->EVENTS_END; |
| |
| /* Read and clear error flags. */ |
| error = spi_regs->STATUS; |
| spi_regs->STATUS = error; |
| |
| spis_nrf5_complete(dev, error); |
| } |
| if (spi_regs->EVENTS_ACQUIRED) { |
| spi_regs->EVENTS_ACQUIRED = 0; |
| /* force registesr flush (per spec) */ |
| tmp = spi_regs->EVENTS_ACQUIRED; |
| } |
| } |
| |
| static const struct spi_driver_api nrf5_spis_api = { |
| .transceive = spis_nrf5_transceive, |
| .configure = spis_nrf5_configure, |
| .slave_select = NULL, |
| }; |
| |
| #if defined(CONFIG_SOC_SERIES_NRF51X) |
| static void spis_configure_psel(NRF_SPIS_Type *spi_regs, |
| const struct spis_nrf5_config *cfg) |
| { |
| spi_regs->PSELMOSI = cfg->mosi_pin; |
| spi_regs->PSELMISO = cfg->miso_pin; |
| spi_regs->PSELSCK = cfg->sck_pin; |
| if (cfg->csn_pin == NRF5_SPIS_CSN_DISABLED_CFG) { |
| spi_regs->PSELCSN = NRF5_SPIS_CSN_DISABLED; |
| } else { |
| spi_regs->PSELCSN = cfg->csn_pin; |
| } |
| } |
| #elif defined(CONFIG_SOC_SERIES_NRF52X) |
| static void spis_configure_psel(NRF_SPIS_Type *spi_regs, |
| const struct spis_nrf5_config *cfg) |
| { |
| spi_regs->PSEL.MOSI = cfg->mosi_pin; |
| spi_regs->PSEL.MISO = cfg->miso_pin; |
| spi_regs->PSEL.SCK = cfg->sck_pin; |
| if (cfg->csn_pin == NRF5_SPIS_CSN_DISABLED_CFG) { |
| spi_regs->PSEL.CSN = NRF5_SPIS_CSN_DISABLED; |
| } else { |
| spi_regs->PSEL.CSN = cfg->csn_pin; |
| } |
| } |
| #else |
| #error "Unsupported NRF5 SoC" |
| #endif |
| |
| static int spis_nrf5_init(struct device *dev) |
| { |
| NRF_SPIS_Type *spi_regs = SPI_REGS(dev); |
| struct spis_nrf5_data *priv_data = DEV_DATA(dev); |
| const struct spis_nrf5_config *cfg = DEV_CFG(dev); |
| struct device *gpio_dev; |
| int ret; |
| |
| SYS_LOG_DBG("SPI Slave driver init: %p", dev); |
| |
| /* Enable constant latency for faster SPIS response */ |
| NRF_POWER->TASKS_CONSTLAT = 1; |
| |
| spi_regs->ENABLE = 0; |
| |
| gpio_dev = device_get_binding(CONFIG_GPIO_NRF5_P0_DEV_NAME); |
| |
| ret = gpio_pin_configure(gpio_dev, cfg->miso_pin, |
| GPIO_DIR_IN | GPIO_PUD_NORMAL); |
| __ASSERT_NO_MSG(!ret); |
| |
| ret = gpio_pin_configure(gpio_dev, cfg->mosi_pin, |
| GPIO_DIR_IN | GPIO_PUD_NORMAL); |
| __ASSERT_NO_MSG(!ret); |
| |
| ret = gpio_pin_configure(gpio_dev, cfg->sck_pin, |
| GPIO_DIR_IN | GPIO_PUD_NORMAL); |
| __ASSERT_NO_MSG(!ret); |
| |
| if (cfg->csn_pin != 0xff) { |
| ret = gpio_pin_configure(gpio_dev, cfg->csn_pin, |
| GPIO_DIR_IN | GPIO_PUD_PULL_UP); |
| __ASSERT_NO_MSG(!ret); |
| } |
| |
| spis_configure_psel(spi_regs, cfg); |
| |
| cfg->config_func(); |
| |
| k_sem_init(&priv_data->device_sync_sem, 0, 1); |
| |
| SYS_LOG_DBG("SPI Slave driver initialized on device: %p", dev); |
| |
| return 0; |
| } |
| |
| /* system bindings */ |
| #ifdef CONFIG_SPIS0_NRF52 |
| |
| static void spis_config_irq_0(void); |
| |
| static struct spis_nrf5_data spis_nrf5_data_0; |
| |
| static const struct spis_nrf5_config spis_nrf5_config_0 = { |
| .regs = NRF_SPIS0, |
| .config_func = spis_config_irq_0, |
| .sck_pin = CONFIG_SPIS0_NRF52_GPIO_SCK_PIN, |
| .mosi_pin = CONFIG_SPIS0_NRF52_GPIO_MOSI_PIN, |
| .miso_pin = CONFIG_SPIS0_NRF52_GPIO_MISO_PIN, |
| .csn_pin = CONFIG_SPIS0_NRF52_GPIO_CSN_PIN, |
| .def = CONFIG_SPIS0_NRF52_DEF, |
| }; |
| |
| DEVICE_AND_API_INIT(spis_nrf5_port_0, CONFIG_SPI_0_NAME, spis_nrf5_init, |
| &spis_nrf5_data_0, &spis_nrf5_config_0, PRE_KERNEL_1, |
| CONFIG_SPI_INIT_PRIORITY, &nrf5_spis_api); |
| |
| static void spis_config_irq_0(void) |
| { |
| IRQ_CONNECT(NRF5_IRQ_SPI0_TWI0_IRQn, CONFIG_SPI_0_IRQ_PRI, |
| spis_nrf5_isr, DEVICE_GET(spis_nrf5_port_0), 0); |
| irq_enable(NRF5_IRQ_SPI0_TWI0_IRQn); |
| } |
| |
| #endif /* CONFIG_SPIS0_NRF52 */ |
| |
| #ifdef CONFIG_SPIS1_NRF5 |
| |
| static void spis_config_irq_1(void); |
| |
| static struct spis_nrf5_data spis_nrf5_data_1; |
| |
| static const struct spis_nrf5_config spis_nrf5_config_1 = { |
| .regs = NRF_SPIS1, |
| .config_func = spis_config_irq_1, |
| .sck_pin = CONFIG_SPIS1_NRF5_GPIO_SCK_PIN, |
| .mosi_pin = CONFIG_SPIS1_NRF5_GPIO_MOSI_PIN, |
| .miso_pin = CONFIG_SPIS1_NRF5_GPIO_MISO_PIN, |
| .csn_pin = CONFIG_SPIS1_NRF5_GPIO_CSN_PIN, |
| .def = CONFIG_SPIS1_NRF5_DEF, |
| }; |
| |
| DEVICE_AND_API_INIT(spis_nrf5_port_1, CONFIG_SPI_1_NAME, spis_nrf5_init, |
| &spis_nrf5_data_1, &spis_nrf5_config_1, PRE_KERNEL_1, |
| CONFIG_SPI_INIT_PRIORITY, &nrf5_spis_api); |
| |
| static void spis_config_irq_1(void) |
| { |
| IRQ_CONNECT(NRF5_IRQ_SPI1_TWI1_IRQn, CONFIG_SPI_1_IRQ_PRI, |
| spis_nrf5_isr, DEVICE_GET(spis_nrf5_port_1), 0); |
| irq_enable(NRF5_IRQ_SPI1_TWI1_IRQn); |
| } |
| |
| #endif /* CONFIG_SPIS1_NRF5 */ |