| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| #include <string.h> |
| #include <errno.h> |
| #include <assert.h> |
| #include <stdbool.h> |
| #include "os/mynewt.h" |
| #include "hal/hal_spi.h" |
| #include "mcu/hal_apollo2.h" |
| #include "mcu/cmsis_nvic.h" |
| |
| #include "am_mcu_apollo.h" |
| |
| /* Prevent CMSIS from breaking apollo2 macros. */ |
| #undef GPIO |
| #undef IOSLAVE |
| #undef CLKGEN |
| |
| #define SPI_0_ENABLED (MYNEWT_VAL(SPI_0_MASTER) || MYNEWT_VAL(SPI_0_SLAVE)) |
| #define SPI_1_ENABLED (MYNEWT_VAL(SPI_1_MASTER) || MYNEWT_VAL(SPI_1_SLAVE)) |
| #define SPI_2_ENABLED (MYNEWT_VAL(SPI_2_MASTER) || MYNEWT_VAL(SPI_2_SLAVE)) |
| #define SPI_3_ENABLED (MYNEWT_VAL(SPI_3_MASTER) || MYNEWT_VAL(SPI_3_SLAVE)) |
| #define SPI_4_ENABLED (MYNEWT_VAL(SPI_4_MASTER) || MYNEWT_VAL(SPI_4_SLAVE)) |
| #define SPI_5_ENABLED (MYNEWT_VAL(SPI_5_MASTER) || MYNEWT_VAL(SPI_5_SLAVE)) |
| |
| #define SPI_ANY_ENABLED ( \ |
| SPI_0_ENABLED || \ |
| SPI_1_ENABLED || \ |
| SPI_2_ENABLED || \ |
| SPI_3_ENABLED || \ |
| SPI_4_ENABLED || \ |
| SPI_5_ENABLED) |
| |
| #if SPI_ANY_ENABLED |
| |
| #define SPI_N_MASTER ( \ |
| MYNEWT_VAL(SPI_0_MASTER) || \ |
| MYNEWT_VAL(SPI_1_MASTER) || \ |
| MYNEWT_VAL(SPI_2_MASTER) || \ |
| MYNEWT_VAL(SPI_3_MASTER) || \ |
| MYNEWT_VAL(SPI_4_MASTER) || \ |
| MYNEWT_VAL(SPI_5_MASTER)) |
| |
| #define SPI_N_SLAVE ( \ |
| MYNEWT_VAL(SPI_0_SLAVE) || \ |
| MYNEWT_VAL(SPI_1_SLAVE) || \ |
| MYNEWT_VAL(SPI_2_SLAVE) || \ |
| MYNEWT_VAL(SPI_3_SLAVE) || \ |
| MYNEWT_VAL(SPI_4_SLAVE) || \ |
| MYNEWT_VAL(SPI_5_SLAVE)) |
| |
| #define APOLLO2_SPI_MAX_CHUNK_SZ 64 |
| #define APOLLO2_SPI_MAX_CHUNK_WORDS (APOLLO2_SPI_MAX_CHUNK_SZ / 4) |
| #define APOLLO2_SPI_MAX_TXR_SZ 4095 |
| |
| #define APOLLO2_SPI_OP_NONE 0 |
| #define APOLLO2_SPI_OP_BLOCKING 1 |
| #define APOLLO2_SPI_OP_NONBLOCKING 2 |
| |
| /* IRQ handler type */ |
| typedef void apollo2_spi_irq_handler(void); |
| |
| struct apollo2_spi { |
| volatile uint8_t op; |
| |
| const uint8_t *txbuf; |
| uint8_t *rxbuf; |
| int buf_num_bytes; |
| int buf_off; |
| uint32_t interrupts; |
| uint16_t buf_len; |
| uint8_t prev_num_bytes; |
| uint8_t spi_num; |
| uint8_t spi_type; |
| |
| uint8_t enabled:1; |
| |
| hal_spi_txrx_cb txrx_cb_func; |
| void *txrx_cb_arg; |
| }; |
| |
| static void apollo2_spi_service_master(struct apollo2_spi *spi, |
| uint32_t status); |
| |
| static uint32_t apollo2_spi_fifo[APOLLO2_SPI_MAX_CHUNK_WORDS]; |
| |
| #if SPI_0_ENABLED |
| static struct apollo2_spi apollo2_spi0; |
| #endif |
| #if SPI_1_ENABLED |
| static struct apollo2_spi apollo2_spi1; |
| #endif |
| #if SPI_2_ENABLED |
| static struct apollo2_spi apollo2_spi2; |
| #endif |
| #if SPI_3_ENABLED |
| static struct apollo2_spi apollo2_spi3; |
| #endif |
| #if SPI_4_ENABLED |
| static struct apollo2_spi apollo2_spi4; |
| #endif |
| #if SPI_5_ENABLED |
| static struct apollo2_spi apollo2_spi5; |
| #endif |
| |
| static uint32_t |
| apollo2_spi_data_mode_to_ios(int spi_mode) |
| { |
| switch (spi_mode) { |
| case HAL_SPI_MODE0: return AM_HAL_IOS_SPIMODE_0; |
| case HAL_SPI_MODE1: return AM_HAL_IOS_SPIMODE_1; |
| case HAL_SPI_MODE2: return AM_HAL_IOS_SPIMODE_2; |
| case HAL_SPI_MODE3: return AM_HAL_IOS_SPIMODE_3; |
| default: return -1; |
| } |
| } |
| |
| static struct apollo2_spi * |
| apollo2_spi_resolve(int spi_num) |
| { |
| switch (spi_num) { |
| #if SPI_0_ENABLED |
| case 0: |
| return &apollo2_spi0; |
| #endif |
| #if SPI_1_ENABLED |
| case 1: |
| return &apollo2_spi1; |
| #endif |
| #if SPI_2_ENABLED |
| case 2: |
| return &apollo2_spi2; |
| #endif |
| #if SPI_3_ENABLED |
| case 3: |
| return &apollo2_spi3; |
| #endif |
| #if SPI_4_ENABLED |
| case 4: |
| return &apollo2_spi4; |
| #endif |
| #if SPI_5_ENABLED |
| case 5: |
| return &apollo2_spi5; |
| #endif |
| default: |
| return NULL; |
| } |
| } |
| |
| static int |
| apollo2_spi_fifo_count(int spi_num) |
| { |
| return AM_BFRn(IOMSTR, spi_num, FIFOPTR, FIFOSIZ); |
| } |
| |
| static int |
| apollo2_spi_fifo_space(int spi_num) |
| { |
| return APOLLO2_SPI_MAX_CHUNK_SZ - apollo2_spi_fifo_count(spi_num); |
| } |
| |
| static void |
| apollo2_spi_block_until_idle(const struct apollo2_spi *spi) |
| { |
| while (spi->op != APOLLO2_SPI_OP_NONE) { } |
| } |
| |
| static void |
| apollo2_spi_clear_ints(int spi_num) |
| { |
| AM_REGn(IOMSTR, spi_num, INTCLR) = 0xffffffff; |
| } |
| |
| static void |
| apollo2_spi_disable_ints(struct apollo2_spi *spi) |
| { |
| /* Remember currently-enabled interrupts. */ |
| assert(spi->interrupts == 0); |
| spi->interrupts = AM_REGn(IOMSTR, spi->spi_num, INTEN); |
| |
| /* Disable interrupts. */ |
| AM_REGn(IOMSTR, spi->spi_num, INTEN) = 0; |
| } |
| |
| static void |
| apollo2_spi_reenable_ints(struct apollo2_spi *spi) |
| { |
| AM_REGn(IOMSTR, spi->spi_num, INTEN) = spi->interrupts; |
| spi->interrupts = 0; |
| } |
| |
| static uint32_t |
| apollo2_spi_status(int spi_num) |
| { |
| uint32_t status; |
| |
| status = AM_REGn(IOMSTR, spi_num, INTSTAT); |
| apollo2_spi_clear_ints(spi_num); |
| |
| return status; |
| } |
| |
| static void |
| apollo2_spi_irqh_x(int spi_num) |
| { |
| struct apollo2_spi *spi; |
| uint32_t status; |
| |
| status = apollo2_spi_status(spi_num); |
| |
| spi = apollo2_spi_resolve(spi_num); |
| assert(spi != NULL); |
| |
| switch (spi->spi_type) { |
| case HAL_SPI_TYPE_MASTER: |
| apollo2_spi_service_master(spi, status); |
| break; |
| |
| case HAL_SPI_TYPE_SLAVE: |
| /* XXX: Slave unimplemented. */ |
| break; |
| |
| default: |
| assert(0); |
| break; |
| } |
| } |
| |
| #if SPI_0_ENABLED |
| static void apollo2_spi_irqh_0(void) { apollo2_spi_irqh_x(0); } |
| #endif |
| #if SPI_1_ENABLED |
| static void apollo2_spi_irqh_1(void) { apollo2_spi_irqh_x(1); } |
| #endif |
| #if SPI_2_ENABLED |
| static void apollo2_spi_irqh_2(void) { apollo2_spi_irqh_x(2); } |
| #endif |
| #if SPI_3_ENABLED |
| static void apollo2_spi_irqh_3(void) { apollo2_spi_irqh_x(3); } |
| #endif |
| #if SPI_4_ENABLED |
| static void apollo2_spi_irqh_4(void) { apollo2_spi_irqh_x(4); } |
| #endif |
| #if SPI_5_ENABLED |
| static void apollo2_spi_irqh_5(void) { apollo2_spi_irqh_x(5); } |
| #endif |
| |
| static int |
| apollo2_spi_irq_info(int spi_num, int *out_irq_num, |
| apollo2_spi_irq_handler **out_irqh) |
| { |
| switch (spi_num) { |
| #if SPI_0_ENABLED |
| case 0: |
| *out_irq_num = IOMSTR0_IRQn; |
| *out_irqh = apollo2_spi_irqh_0; |
| return 0; |
| #endif |
| #if SPI_1_ENABLED |
| case 1: |
| *out_irq_num = IOMSTR1_IRQn; |
| *out_irqh = apollo2_spi_irqh_1; |
| return 0; |
| #endif |
| #if SPI_2_ENABLED |
| case 2: |
| *out_irq_num = IOMSTR2_IRQn; |
| *out_irqh = apollo2_spi_irqh_2; |
| return 0; |
| #endif |
| #if SPI_3_ENABLED |
| case 3: |
| *out_irq_num = IOMSTR3_IRQn; |
| *out_irqh = apollo2_spi_irqh_3; |
| return 0; |
| #endif |
| #if SPI_4_ENABLED |
| case 4: |
| *out_irq_num = IOMSTR4_IRQn; |
| *out_irqh = apollo2_spi_irqh_4; |
| return 0; |
| #endif |
| #if SPI_5_ENABLED |
| case 5: |
| *out_irq_num = IOMSTR5_IRQn; |
| *out_irqh = apollo2_spi_irqh_5; |
| return 0; |
| #endif |
| default: |
| return SYS_EINVAL; |
| } |
| } |
| |
| static int |
| hal_spi_config_master(int spi_num, const struct hal_spi_settings *settings) |
| { |
| am_hal_iom_config_t sdk_config; |
| int cpol; |
| int cpha; |
| int rc; |
| |
| if (spi_num < 0 || spi_num >= AM_REG_IOMSTR_NUM_MODULES) { |
| return SYS_EINVAL; |
| } |
| |
| rc = hal_spi_data_mode_breakout(settings->data_mode, &cpol, &cpha); |
| if (rc != 0) { |
| return SYS_EINVAL; |
| } |
| |
| am_hal_iom_pwrctrl_enable(spi_num); |
| |
| sdk_config.ui32InterfaceMode = |
| AM_HAL_IOM_SPIMODE | AM_REG_IOMSTR_CFG_FULLDUP_FULLDUP; |
| sdk_config.ui32ClockFrequency = settings->baudrate; |
| sdk_config.bSPHA = cpha; |
| sdk_config.bSPOL = cpol; |
| sdk_config.ui8WriteThreshold = 4; |
| sdk_config.ui8ReadThreshold = 60; |
| am_hal_iom_config(spi_num, &sdk_config); |
| |
| return 0; |
| } |
| |
| static int |
| hal_spi_config_slave(int spi_num, const struct hal_spi_settings *settings) |
| { |
| uint32_t ios_data_mode; |
| uint32_t cfg; |
| |
| cfg = AM_REG_IOSLAVE_FIFOCFG_ROBASE(0x78 >> 3); |
| cfg |= AM_REG_IOSLAVE_FIFOCFG_FIFOBASE(0x80 >> 3); |
| cfg |= AM_REG_IOSLAVE_FIFOCFG_FIFOMAX(0x100 >> 3); |
| |
| ios_data_mode = apollo2_spi_data_mode_to_ios(settings->data_mode); |
| |
| AM_REG(IOSLAVE, CFG) = ios_data_mode; |
| AM_REG(IOSLAVE, FIFOCFG) = cfg; |
| return 0; |
| } |
| |
| /* | spi:cfg | sck | miso | mosi | |
| * |-----------+-------+-------+-------| |
| * | 0:1 | 5 | 6 | 7 | |
| * | 1:1 | 8 | 9 | 10 | |
| * | 2:5 | 0 | 2 | 1 | |
| * | 2:5 | 27 | 28 | 25 | |
| * | 3:5 | 42 | 43 | 38 | |
| * | 4:5 | 39 | 40 | 44 | |
| * | 5:5 | 48 | 49 | 47 | |
| */ |
| static int |
| hal_spi_pin_config_master(int spi_num, const struct apollo2_spi_cfg *pins) |
| { |
| const uint8_t miso = pins->miso_pin; |
| const uint8_t mosi = pins->mosi_pin; |
| const uint8_t sck = pins->sck_pin; |
| |
| switch (spi_num) { |
| #if SPI_0_ENABLED |
| case 0: |
| if (sck == 5 && miso == 6 && mosi == 7) { |
| return 1; |
| } else { |
| return -1; |
| } |
| #endif |
| #if SPI_1_ENABLED |
| case 1: |
| if (sck == 8 && miso == 9 && mosi == 10) { |
| return 1; |
| } else { |
| return -1; |
| } |
| #endif |
| #if SPI_2_ENABLED |
| case 2: |
| if (sck == 0 && miso == 2 && mosi == 1) { |
| return 5; |
| } else if (sck == 27 && miso == 28 && mosi == 25) { |
| return 5; |
| } else { |
| return -1; |
| } |
| #endif |
| #if SPI_3_ENABLED |
| case 3: |
| if (sck == 42 && miso == 43 && mosi == 38) { |
| return 5; |
| } else { |
| return -1; |
| } |
| #endif |
| #if SPI_4_ENABLED |
| case 4: |
| if (sck == 39 && miso == 40 && mosi == 44) { |
| return 5; |
| } else { |
| return -1; |
| } |
| #endif |
| #if SPI_5_ENABLED |
| case 5: |
| if (sck == 48 && miso == 49 && mosi == 47) { |
| return 5; |
| } else { |
| return -1; |
| } |
| #endif |
| default: |
| return -1; |
| } |
| } |
| |
| static int |
| hal_spi_pin_config(int spi_num, int master, const struct apollo2_spi_cfg *pins) |
| { |
| if (master) { |
| return hal_spi_pin_config_master(spi_num, pins); |
| } else { |
| return -1; |
| } |
| } |
| |
| static int |
| hal_spi_init_master(int spi_num, const struct apollo2_spi_cfg *cfg) |
| { |
| apollo2_spi_irq_handler *irqh; |
| struct apollo2_spi *spi; |
| int pin_cfg; |
| int irq_num; |
| int rc; |
| |
| spi = apollo2_spi_resolve(spi_num); |
| if (spi == NULL) { |
| return SYS_EINVAL; |
| } |
| |
| pin_cfg = hal_spi_pin_config(spi_num, 1, cfg); |
| if (pin_cfg == -1) { |
| return SYS_EINVAL; |
| } |
| |
| am_hal_gpio_pin_config( |
| cfg->sck_pin, AM_HAL_GPIO_FUNC(pin_cfg) | AM_HAL_PIN_DIR_INPUT); |
| am_hal_gpio_pin_config( |
| cfg->miso_pin, AM_HAL_GPIO_FUNC(pin_cfg) | AM_HAL_PIN_DIR_INPUT); |
| am_hal_gpio_pin_config( |
| cfg->mosi_pin, AM_HAL_GPIO_FUNC(pin_cfg)); |
| |
| memset(spi, 0, sizeof *spi); |
| spi->spi_num = spi_num; |
| spi->spi_type = HAL_SPI_TYPE_MASTER; |
| |
| rc = apollo2_spi_irq_info(spi_num, &irq_num, &irqh); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| NVIC_SetVector(irq_num, (uint32_t)irqh); |
| NVIC_SetPriority(irq_num, (1 << __NVIC_PRIO_BITS) - 1); |
| NVIC_ClearPendingIRQ(irq_num); |
| NVIC_EnableIRQ(irq_num); |
| |
| return 0; |
| } |
| |
| static int |
| hal_spi_init_slave(int spi_num, struct apollo2_spi_cfg *cfg) |
| { |
| return SYS_ERANGE; |
| } |
| |
| /** |
| * Initialize the SPI, given by spi_num. |
| * |
| * @param spi_num The number of the SPI to initialize |
| * @param cfg HW/MCU specific configuration, |
| * passed to the underlying implementation, providing extra |
| * configuration. |
| * @param spi_type SPI type (master or slave) |
| * |
| * @return int 0 on success, non-zero error code on failure. |
| */ |
| int |
| hal_spi_init(int spi_num, void *cfg, uint8_t spi_type) |
| { |
| int rc; |
| |
| if (cfg == NULL) { |
| return SYS_EINVAL; |
| } |
| |
| switch (spi_type) { |
| case HAL_SPI_TYPE_MASTER: |
| rc = hal_spi_init_master(spi_num, cfg); |
| if (rc != 0) { |
| return rc; |
| } |
| break; |
| |
| case HAL_SPI_TYPE_SLAVE: |
| rc = hal_spi_init_slave(spi_num, cfg); |
| if (rc != 0) { |
| return rc; |
| } |
| break; |
| |
| default: |
| return SYS_EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Configure the spi. Must be called after the spi is initialized (after |
| * hal_spi_init is called) and when the spi is disabled (user must call |
| * hal_spi_disable if the spi has been enabled through hal_spi_enable prior |
| * to calling this function). Can also be used to reconfigure an initialized |
| * SPI (assuming it is disabled as described previously). |
| * |
| * @param spi_num The number of the SPI to configure. |
| * @param psettings The settings to configure this SPI with |
| * |
| * @return int 0 on success, non-zero error code on failure. |
| */ |
| int |
| hal_spi_config(int spi_num, struct hal_spi_settings *settings) |
| { |
| const struct apollo2_spi *spi; |
| int rc; |
| |
| spi = apollo2_spi_resolve(spi_num); |
| if (spi == NULL) { |
| return SYS_EINVAL; |
| } |
| |
| if (spi->spi_type == HAL_SPI_TYPE_MASTER) { |
| rc = hal_spi_config_master(spi_num, settings); |
| } else { |
| rc = hal_spi_config_slave(spi_num, settings); |
| } |
| |
| return rc; |
| } |
| |
| /** |
| * Enables the SPI. This does not start a transmit or receive operation; |
| * it is used for power mgmt. Cannot be called when a SPI transfer is in |
| * progress. |
| * |
| * @param spi_num |
| * |
| * @return int 0 on success, non-zero error code on failure. |
| */ |
| int |
| hal_spi_enable(int spi_num) |
| { |
| struct apollo2_spi *spi; |
| |
| spi = apollo2_spi_resolve(spi_num); |
| if (spi == NULL) { |
| return SYS_EINVAL; |
| } |
| |
| if (spi->enabled) { |
| return SYS_EBUSY; |
| } |
| |
| switch (spi->spi_type) { |
| case HAL_SPI_TYPE_MASTER: |
| AM_REGn(IOMSTR, spi_num, CFG) |= AM_REG_IOMSTR_CFG_IFCEN(1); |
| AM_REGn(IOMSTR, spi_num, INTEN) = 0xffffffff; |
| |
| if (spi_num == 0) { |
| AM_REGn(GPIO, 0, PADKEY) = AM_REG_GPIO_PADKEY_KEYVAL; |
| AM_BFW(GPIO, PADREGB, PAD5INPEN, 1); |
| AM_BFW(GPIO, PADREGB, PAD6INPEN, 1); |
| AM_REGn(GPIO, 0, PADKEY) = 0; |
| } else { |
| AM_REGn(GPIO, 0, PADKEY) = AM_REG_GPIO_PADKEY_KEYVAL; |
| AM_BFW(GPIO, PADREGC, PAD8INPEN, 1); |
| AM_BFW(GPIO, PADREGC, PAD9INPEN, 1); |
| AM_REGn(GPIO, 0, PADKEY) = 0; |
| } |
| break; |
| |
| case HAL_SPI_TYPE_SLAVE: |
| AM_REGn(IOSLAVE, spi_num, CFG) |= AM_REG_IOSLAVE_CFG_IFCEN(1); |
| break; |
| |
| default: |
| return SYS_EINVAL; |
| } |
| |
| spi->enabled = 1; |
| return 0; |
| } |
| |
| /** |
| * Disables the SPI. Used for power mgmt. It will halt any current SPI transfers |
| * in progress. |
| * |
| * @param spi_num |
| * |
| * @return int 0 on success, non-zero error code on failure. |
| */ |
| int |
| hal_spi_disable(int spi_num) |
| { |
| struct apollo2_spi *spi; |
| |
| spi = apollo2_spi_resolve(spi_num); |
| if (spi == NULL) { |
| return SYS_EINVAL; |
| } |
| |
| switch (spi->spi_type) { |
| case HAL_SPI_TYPE_MASTER: |
| apollo2_spi_block_until_idle(spi); |
| AM_REGn(IOMSTR, spi_num, CFG) &= ~AM_REG_IOMSTR_CFG_IFCEN(1); |
| spi->enabled = 0; |
| return 0; |
| |
| case HAL_SPI_TYPE_SLAVE: |
| AM_REGn(IOSLAVE, spi_num, CFG) &= ~AM_REG_IOSLAVE_CFG_IFCEN(1); |
| return 0; |
| |
| default: |
| return SYS_EINVAL; |
| } |
| } |
| |
| static void |
| apollo2_spi_fifo_read(struct apollo2_spi *spi, void *rx_data, int num_bytes) |
| { |
| int num_words; |
| int i; |
| |
| num_words = (num_bytes + 3) / 4; |
| for (i = 0; i < num_words; i++) { |
| apollo2_spi_fifo[i] = AM_REGn(IOMSTR, spi->spi_num, FIFO); |
| } |
| |
| if (rx_data != NULL) { |
| memcpy(rx_data, apollo2_spi_fifo, num_bytes); |
| } |
| } |
| |
| static void |
| apollo2_spi_fifo_write(struct apollo2_spi *spi, |
| const void *tx_data, int num_bytes) |
| { |
| uint32_t word; |
| int num_words; |
| int i; |
| |
| assert(num_bytes != 0); |
| |
| memcpy(apollo2_spi_fifo, tx_data, num_bytes); |
| |
| num_words = (num_bytes + 3) / 4; |
| for (i = 0; i < num_words; i++) { |
| if (tx_data == NULL) { |
| word = 0; |
| } else { |
| word = apollo2_spi_fifo[i]; |
| } |
| AM_REGn(IOMSTR, spi->spi_num, FIFO) = word; |
| } |
| } |
| |
| static int |
| apollo2_spi_next_chunk_sz(int buf_sz, int off, int fifo_space) |
| { |
| int bytes_left; |
| |
| bytes_left = buf_sz - off; |
| if (bytes_left > fifo_space) { |
| return fifo_space; |
| } else { |
| return bytes_left; |
| } |
| } |
| |
| static int |
| apollo2_spi_tx_next_chunk(struct apollo2_spi *spi) |
| { |
| int fifo_space; |
| int chunk_sz; |
| |
| fifo_space = apollo2_spi_fifo_space(spi->spi_num); |
| chunk_sz = apollo2_spi_next_chunk_sz(spi->buf_num_bytes, spi->buf_off, |
| fifo_space); |
| if (chunk_sz <= 0) { |
| return 0; |
| } |
| |
| apollo2_spi_clear_ints(spi->spi_num); |
| |
| apollo2_spi_fifo_write(spi, spi->txbuf + spi->buf_off, chunk_sz); |
| spi->prev_num_bytes = chunk_sz; |
| |
| return SYS_EAGAIN; |
| } |
| |
| static uint32_t |
| apollo2_spi_cmd_build(uint16_t num_bytes, uint8_t channel) |
| { |
| return 0x40000000 /* Raw write. */ | |
| (num_bytes & 0xF00) << 15 | |
| (num_bytes & 0xFF) | |
| channel << 16; |
| } |
| |
| static void |
| apollo2_spi_tx_first_chunk(struct apollo2_spi *spi) |
| { |
| uint32_t cmd; |
| |
| apollo2_spi_tx_next_chunk(spi); |
| |
| cmd = apollo2_spi_cmd_build(spi->buf_num_bytes, 0); |
| apollo2_spi_disable_ints(spi); |
| AM_REGn(IOMSTR, spi->spi_num, CMD) = cmd; |
| apollo2_spi_reenable_ints(spi); |
| } |
| |
| static void |
| apollo2_spi_service_master(struct apollo2_spi *spi, uint32_t status) |
| { |
| uint8_t prev_op; |
| int rc; |
| |
| if (spi->op == APOLLO2_SPI_OP_NONE) { |
| /* Spurious interrupt or programming error. */ |
| return; |
| } |
| |
| /* Copy received data. */ |
| apollo2_spi_fifo_read(spi, spi->rxbuf + spi->buf_off, spi->prev_num_bytes); |
| spi->buf_off += spi->prev_num_bytes; |
| |
| assert(spi->buf_off <= spi->buf_num_bytes); |
| |
| if (!(status & AM_HAL_IOM_INT_THR)) { |
| /* Error or command complete. */ |
| |
| prev_op = spi->op; |
| spi->op = APOLLO2_SPI_OP_NONE; |
| |
| if (prev_op == APOLLO2_SPI_OP_NONBLOCKING) { |
| spi->txrx_cb_func(spi->txrx_cb_arg, spi->buf_off); |
| } |
| |
| return; |
| } |
| |
| /* Transmit next chunk. */ |
| rc = apollo2_spi_tx_next_chunk(spi); |
| assert(rc == 0); |
| } |
| |
| static int |
| apollo2_spi_txrx_begin(struct apollo2_spi *spi, uint8_t op, |
| const void *tx_data, void *rx_data, int num_bytes) |
| { |
| if (spi->op != APOLLO2_SPI_OP_NONE) { |
| return SYS_EBUSY; |
| } |
| |
| if (num_bytes <= 0 || num_bytes > APOLLO2_SPI_MAX_TXR_SZ) { |
| return SYS_EINVAL; |
| } |
| |
| spi->op = op; |
| spi->txbuf = tx_data; |
| spi->rxbuf = rx_data; |
| spi->buf_num_bytes = num_bytes; |
| spi->buf_off = 0; |
| |
| apollo2_spi_tx_first_chunk(spi); |
| return 0; |
| } |
| |
| static int |
| apollo2_spi_txrx_blocking(struct apollo2_spi *spi, |
| const void *tx_data, void *rx_data, int num_bytes) |
| { |
| int rc; |
| |
| rc = apollo2_spi_txrx_begin(spi, APOLLO2_SPI_OP_BLOCKING, |
| tx_data, rx_data, num_bytes); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| apollo2_spi_block_until_idle(spi); |
| |
| return 0; |
| } |
| |
| /** |
| * Blocking call to send a value on the SPI. Returns the value received from |
| * the SPI slave. |
| * |
| * MASTER: Sends the value and returns the received value from the slave. |
| * SLAVE: Invalid API. Returns 0xFFFF |
| * |
| * @param spi_num Spi interface to use |
| * @param val Value to send |
| * |
| * @return uint16_t Value received on SPI interface from slave. Returns 0xFFFF |
| * if called when the SPI is configured to be a slave |
| */ |
| uint16_t |
| hal_spi_tx_val(int spi_num, uint16_t val) |
| { |
| struct apollo2_spi *spi; |
| uint8_t tx_data; |
| uint8_t rx_data; |
| int rc; |
| |
| spi = apollo2_spi_resolve(spi_num); |
| if (spi == NULL) { |
| return 0xffff; |
| } |
| |
| switch (spi->spi_type) { |
| case HAL_SPI_TYPE_MASTER: |
| tx_data = val; |
| rc = apollo2_spi_txrx_blocking(spi, &tx_data, &rx_data, 1); |
| if (rc == 0) { |
| return rx_data; |
| } else { |
| return 0xffff; |
| } |
| |
| case HAL_SPI_TYPE_SLAVE: |
| return 0xffff; |
| |
| default: |
| return 0xffff; |
| } |
| } |
| |
| /** |
| * Sets the txrx callback (executed at interrupt context) when the |
| * buffer is transferred by the master or the slave using the non-blocking API. |
| * Cannot be called when the spi is enabled. This callback will also be called |
| * when chip select is de-asserted on the slave. |
| * |
| * NOTE: This callback is only used for the non-blocking interface and must |
| * be called prior to using the non-blocking API. |
| * |
| * @param spi_num SPI interface on which to set callback |
| * @param txrx Callback function |
| * @param arg Argument to be passed to callback function |
| * |
| * @return int 0 on success, non-zero error code on failure. |
| */ |
| int |
| hal_spi_set_txrx_cb(int spi_num, hal_spi_txrx_cb txrx_cb, void *arg) |
| { |
| struct apollo2_spi *spi; |
| |
| spi = apollo2_spi_resolve(spi_num); |
| if (spi == NULL) { |
| return SYS_EINVAL; |
| } |
| |
| if (spi->enabled) { |
| return SYS_EBUSY; |
| } |
| |
| spi->txrx_cb_func = txrx_cb; |
| spi->txrx_cb_arg = arg; |
| |
| return 0; |
| } |
| |
| /** |
| * Blocking interface to send a buffer and store the received values from the |
| * slave. The transmit and receive buffers are either arrays of 8-bit (uint8_t) |
| * values or 16-bit values depending on whether the spi is configured for 8 bit |
| * data or more than 8 bits per value. The 'cnt' parameter is the number of |
| * 8-bit or 16-bit values. Thus, if 'cnt' is 10, txbuf/rxbuf would point to an |
| * array of size 10 (in bytes) if the SPI is using 8-bit data; otherwise |
| * txbuf/rxbuf would point to an array of size 20 bytes (ten, uint16_t values). |
| * |
| * NOTE: these buffers are in the native endian-ness of the platform. |
| * |
| * MASTER: master sends all the values in the buffer and stores the |
| * stores the values in the receive buffer if rxbuf is not NULL. |
| * The txbuf parameter cannot be NULL. |
| * SLAVE: cannot be called for a slave; returns -1 |
| * |
| * @param spi_num SPI interface to use |
| * @param txbuf Pointer to buffer where values to transmit are stored. |
| * @param rxbuf Pointer to buffer to store values received from peer. |
| * @param cnt Number of 8-bit or 16-bit values to be transferred. |
| * |
| * @return int 0 on success, non-zero error code on failure. |
| */ |
| int |
| hal_spi_txrx(int spi_num, void *txbuf, void *rxbuf, int num_bytes) |
| { |
| struct apollo2_spi *spi; |
| int rc; |
| |
| spi = apollo2_spi_resolve(spi_num); |
| if (spi == NULL) { |
| return SYS_EINVAL; |
| } |
| |
| rc = apollo2_spi_txrx_blocking(spi, txbuf, rxbuf, num_bytes); |
| return rc; |
| } |
| |
| /** |
| * Non-blocking interface to send a buffer and store received values. Can be |
| * used for both master and slave SPI types. The user must configure the |
| * callback (using hal_spi_set_txrx_cb); the txrx callback is executed at |
| * interrupt context when the buffer is sent. |
| * |
| * The transmit and receive buffers are either arrays of 8-bit (uint8_t) |
| * values or 16-bit values depending on whether the spi is configured for 8 bit |
| * data or more than 8 bits per value. The 'cnt' parameter is the number of |
| * 8-bit or 16-bit values. Thus, if 'cnt' is 10, txbuf/rxbuf would point to an |
| * array of size 10 (in bytes) if the SPI is using 8-bit data; otherwise |
| * txbuf/rxbuf would point to an array of size 20 bytes (ten, uint16_t values). |
| * |
| * NOTE: these buffers are in the native endian-ness of the platform. |
| * |
| * MASTER: master sends all the values in the buffer and stores the |
| * stores the values in the receive buffer if rxbuf is not NULL. |
| * The txbuf parameter cannot be NULL |
| * SLAVE: Slave "preloads" the data to be sent to the master (values |
| * stored in txbuf) and places received data from master in rxbuf |
| * (if not NULL). The txrx callback occurs when len values are |
| * transferred or master de-asserts chip select. If txbuf is NULL, |
| * the slave transfers its default byte. Both rxbuf and txbuf cannot |
| * be NULL. |
| * |
| * @param spi_num SPI interface to use |
| * @param txbuf Pointer to buffer where values to transmit are stored. |
| * @param rxbuf Pointer to buffer to store values received from peer. |
| * @param num_bytes Number of 8-bit values to be transferred. |
| * |
| * @return int 0 on success, non-zero error code on failure. |
| */ |
| int |
| hal_spi_txrx_noblock(int spi_num, void *txbuf, void *rxbuf, int num_bytes) |
| { |
| struct apollo2_spi *spi; |
| int rc; |
| |
| spi = apollo2_spi_resolve(spi_num); |
| if (spi == NULL) { |
| return SYS_EINVAL; |
| } |
| |
| if (spi->txrx_cb_func == NULL) { |
| return SYS_ENOENT; |
| } |
| |
| if (spi->op != APOLLO2_SPI_OP_NONE) { |
| return SYS_EBUSY; |
| } |
| |
| switch (spi->spi_type) { |
| case HAL_SPI_TYPE_MASTER: |
| rc = apollo2_spi_txrx_begin(spi, APOLLO2_SPI_OP_NONBLOCKING, |
| txbuf, rxbuf, num_bytes); |
| return rc; |
| |
| case HAL_SPI_TYPE_SLAVE: |
| spi->txbuf = txbuf; |
| spi->rxbuf = rxbuf; |
| spi->op = APOLLO2_SPI_OP_NONBLOCKING; |
| return 0; |
| |
| default: |
| return SYS_EINVAL; |
| } |
| } |
| |
| /** |
| * Sets the default value transferred by the slave. Not valid for master |
| * |
| * @param spi_num SPI interface to use |
| * |
| * @return int 0 on success, non-zero error code on failure. |
| */ |
| int |
| hal_spi_slave_set_def_tx_val(int spi_num, uint16_t val) |
| { |
| return SYS_ERANGE; |
| } |
| |
| /** |
| * This aborts the current transfer but keeps the spi enabled. |
| * |
| * @param spi_num SPI interface on which transfer should be aborted. |
| * |
| * @return int 0 on success, non-zero error code on failure. |
| * |
| * NOTE: does not return an error if no transfer was in progress. |
| */ |
| int |
| hal_spi_abort(int spi_num) |
| { |
| return SYS_ERANGE; |
| } |
| |
| #endif /* SPI_ANY_ENABLED */ |