| /* |
| * 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 <os/mynewt.h> |
| #include <assert.h> |
| |
| #include <hal/hal_spi.h> |
| #include <hal/hal_gpio.h> |
| |
| #include <defs/error.h> |
| #include <syscfg/syscfg.h> |
| |
| #include <mcu/fe310_hal.h> |
| #include <mcu/sys_clock.h> |
| #include <mcu/plic.h> |
| #include <sifive/devices/spi.h> |
| #include <env/freedom-e300-hifive1/platform.h> |
| |
| struct fe310_hal_spi { |
| struct hal_spi_settings spi_cfg; |
| |
| uint32_t spi_base; |
| |
| int len; |
| int txleft; |
| int rxleft; |
| |
| /* Pointers to tx/rx buffers */ |
| uint8_t *txbuf; |
| uint8_t *rxbuf; |
| |
| /* Callback and arguments */ |
| hal_spi_txrx_cb txrx_cb_func; |
| void *txrx_cb_arg; |
| }; |
| |
| #if MYNEWT_VAL(SPI_1) |
| struct fe310_hal_spi fe310_hal_spi1 = { |
| .spi_base = SPI1_CTRL_ADDR |
| }; |
| #endif |
| |
| #if MYNEWT_VAL(SPI_2) |
| struct fe310_hal_spi fe310_hal_spi2 = { |
| .spi_base = SPI2_CTRL_ADDR |
| }; |
| #endif |
| |
| static struct fe310_hal_spi *fe310_hal_spis[3] = { |
| NULL, /* SPI 0 used to access flash */ |
| #if MYNEWT_VAL(SPI_1) |
| &fe310_hal_spi1, |
| #else |
| NULL, |
| #endif |
| #if MYNEWT_VAL(SPI_2) |
| &fe310_hal_spi2, |
| #else |
| NULL, |
| #endif |
| }; |
| |
| #define FE310_HAL_SPI_RESOLVE(__n, __v) \ |
| if ((__n) > 2) { \ |
| rc = SYS_EINVAL; \ |
| goto err; \ |
| } \ |
| (__v) = (struct fe310_hal_spi *) fe310_hal_spis[(__n)]; \ |
| if ((__v) == NULL) { \ |
| rc = SYS_EINVAL; \ |
| goto err; \ |
| } |
| |
| static void |
| spi_interrupt_handler(int int_num) |
| { |
| struct fe310_hal_spi *spi = fe310_hal_spis[int_num - INT_SPI0_BASE]; |
| uint32_t val; |
| int i; |
| |
| /* For sanity reason finish interrupt even if transmission is faster |
| * then the FIFO feeding code. |
| * FIFO is 8 bytes long, 10 should be enough to fill up FIFO. |
| */ |
| for (i = 10; i != 0; --i) { |
| val = _REG32(spi->spi_base, SPI_REG_RXFIFO); |
| if (spi->rxleft && (val & SPI_RXFIFO_EMPTY) == 0) { |
| if (spi->rxbuf) { |
| *spi->rxbuf++ = (uint8_t)val; |
| } |
| spi->rxleft--; |
| } |
| if (spi->txleft) { |
| if (_REG32(spi->spi_base, SPI_REG_TXFIFO) & SPI_TXFIFO_FULL) { |
| break; |
| } |
| _REG32(spi->spi_base, SPI_REG_TXFIFO) = *spi->txbuf++; |
| spi->txleft--; |
| } else { |
| /* Set watermark to 0, interrupt from TX FIFO will be blocked */ |
| _REG32(spi->spi_base, SPI_REG_TXCTRL) = 0; |
| if (!spi->rxleft) { |
| plic_disable_interrupt(int_num); |
| _REG32(spi->spi_base, SPI_REG_IE) = 0; |
| spi->txrx_cb_func(spi->txrx_cb_arg, spi->len); |
| break; |
| } |
| } |
| } |
| } |
| |
| int |
| hal_spi_init(int spi_num, void *usercfg, uint8_t spi_type) |
| { |
| int rc; |
| struct fe310_hal_spi *spi; |
| FE310_HAL_SPI_RESOLVE(spi_num, spi); |
| |
| /* Check for valid arguments */ |
| rc = SYS_EINVAL; |
| if ((spi_type != HAL_SPI_TYPE_MASTER)) { |
| goto err; |
| } |
| |
| plic_set_handler(INT_SPI0_BASE + spi_num, spi_interrupt_handler, 3); |
| if (spi_num == 1 && MYNEWT_VAL(SPI_1)) { |
| /* Set GPIO alternative function IOF0 for SPI1 pins */ |
| _REG32(GPIO_CTRL_ADDR, GPIO_IOF_SEL) &= |
| ~(_BITUL(IOF_SPI1_MOSI) | |
| _BITUL(IOF_SPI1_MISO) | |
| _BITUL(IOF_SPI1_SCK)); |
| /* Enable alternate function for SPI1 pins */ |
| _REG32(GPIO_CTRL_ADDR, GPIO_IOF_EN) |= |
| (_BITUL(IOF_SPI1_MOSI) | |
| _BITUL(IOF_SPI1_MISO) | |
| _BITUL(IOF_SPI1_SCK)); |
| } else if (spi_num == 2 && MYNEWT_VAL(SPI_2)) { |
| /* Set GPIO alternative function IOF0 for SPI2 pins */ |
| _REG32(GPIO_CTRL_ADDR, GPIO_IOF_SEL) &= |
| ~(_BITUL(IOF_SPI2_MOSI) | |
| _BITUL(IOF_SPI2_MISO) | |
| _BITUL(IOF_SPI2_SCK)); |
| /* Enable alternate function for SPI2 pins */ |
| _REG32(GPIO_CTRL_ADDR, GPIO_IOF_EN) |= |
| (_BITUL(IOF_SPI2_MOSI) | |
| _BITUL(IOF_SPI2_MISO) | |
| _BITUL(IOF_SPI2_SCK)); |
| } else { |
| rc = SYS_EINVAL; |
| goto err; |
| } |
| |
| rc = 0; |
| err: |
| return (rc); |
| } |
| |
| /** |
| * 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) |
| { |
| int rc = 0; |
| struct fe310_hal_spi *spi; |
| FE310_HAL_SPI_RESOLVE(spi_num, spi); |
| |
| spi->txrx_cb_func = txrx_cb; |
| spi->txrx_cb_arg = arg; |
| err: |
| 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) |
| { |
| 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) |
| { |
| return 0; |
| } |
| |
| int |
| hal_spi_config(int spi_num, struct hal_spi_settings *settings) |
| { |
| int rc = 0; |
| uint32_t fmt = SPI_FMT_LEN(8); |
| uint32_t div; |
| struct fe310_hal_spi *spi; |
| FE310_HAL_SPI_RESOLVE(spi_num, spi); |
| |
| if (settings->data_mode > HAL_SPI_MODE3 || |
| settings->word_size != HAL_SPI_WORD_SIZE_8BIT || |
| settings->data_order > HAL_SPI_LSB_FIRST) { |
| rc = SYS_EINVAL; |
| goto err; |
| } |
| if (settings->data_order == HAL_SPI_LSB_FIRST) { |
| fmt |= SPI_FMT_ENDIAN(1); |
| } |
| div = get_cpu_freq() / (2 * settings->baudrate); |
| if (div) { |
| div--; |
| } |
| if (div >= (1 << 12)) { |
| div = 0xFFF; |
| } |
| spi->spi_cfg = *settings; |
| _REG32(spi->spi_base, SPI_REG_SCKDIV) = div; |
| _REG32(spi->spi_base, SPI_REG_SCKMODE) = settings->data_mode; |
| _REG32(spi->spi_base, SPI_REG_FMT) = fmt; |
| _REG32(spi->spi_base, SPI_REG_CSID) = 0; |
| /* HAL does not support hardware CS */ |
| _REG32(spi->spi_base, SPI_REG_CSMODE) = SPI_CSMODE_OFF; |
| /* TX FIFO will not raise interrupts */ |
| _REG32(spi->spi_base, SPI_REG_TXCTRL) = 0; |
| _REG32(spi->spi_base, SPI_REG_RXCTRL) = 0; |
| _REG32(spi->spi_base, SPI_REG_IE) = 0; |
| err: |
| return (rc); |
| } |
| |
| int |
| hal_spi_txrx_noblock(int spi_num, void *txbuf, void *rxbuf, int len) |
| { |
| int sr; |
| int rc = 0; |
| struct fe310_hal_spi *spi; |
| FE310_HAL_SPI_RESOLVE(spi_num, spi); |
| |
| __HAL_DISABLE_INTERRUPTS(sr); |
| spi->rxbuf = (uint8_t *)rxbuf; |
| spi->txbuf = (uint8_t *)txbuf; |
| spi->rxleft = len; |
| spi->txleft = len; |
| spi->len = len; |
| _REG32(spi->spi_base, SPI_REG_TXCTRL) = 4; |
| _REG32(spi->spi_base, SPI_REG_IE) = 1; |
| plic_enable_interrupt(INT_SPI0_BASE + spi_num); |
| __HAL_ENABLE_INTERRUPTS(sr); |
| err: |
| return rc; |
| } |
| |
| /** |
| * 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) |
| { |
| uint32_t retval; |
| struct fe310_hal_spi *spi; |
| int rc = 0; |
| FE310_HAL_SPI_RESOLVE(spi_num, spi); |
| |
| while (_REG32(spi->spi_base, SPI_REG_TXFIFO) & SPI_TXFIFO_FULL) { |
| } |
| _REG32(spi->spi_base, SPI_REG_TXFIFO) = val; |
| do { |
| retval = _REG32(spi->spi_base, SPI_REG_RXFIFO); |
| } while (retval & SPI_RXFIFO_EMPTY); |
| |
| err: |
| return rc ? 0xFFFF : (uint8_t)retval; |
| } |
| |
| /** |
| * 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 len) |
| { |
| int rc = 0; |
| struct fe310_hal_spi *spi; |
| uint32_t val; |
| int sent = 0; |
| int received = 0; |
| FE310_HAL_SPI_RESOLVE(spi_num, spi); |
| |
| if (!len) { |
| rc = SYS_EINVAL; |
| goto err; |
| } |
| while (!(_REG32(spi->spi_base, SPI_REG_RXFIFO) & SPI_RXFIFO_EMPTY)) { |
| } |
| |
| while (received < len) { |
| if (sent < len && (sent - received) < FE310_SPI_FIFO_LENGHT && |
| !(_REG32(spi->spi_base, SPI_REG_TXFIFO) & SPI_TXFIFO_FULL)) { |
| _REG32(spi->spi_base, SPI_REG_TXFIFO) = ((uint8_t *)txbuf)[sent++]; |
| } |
| val = _REG32(spi->spi_base, SPI_REG_RXFIFO); |
| if (!(val & SPI_RXFIFO_EMPTY)) { |
| if (rxbuf) { |
| ((uint8_t *)rxbuf)[received] = (uint8_t)val; |
| } |
| received++; |
| } |
| } |
| |
| rc = 0; |
| err: |
| return rc; |
| } |
| |
| int |
| hal_spi_abort(int spi_num) |
| { |
| int rc = 0; |
| struct fe310_hal_spi *spi; |
| int sr; |
| |
| __HAL_DISABLE_INTERRUPTS(sr); |
| FE310_HAL_SPI_RESOLVE(spi_num, spi); |
| |
| _REG32(spi->spi_base, SPI_REG_IE) = 0; |
| _REG32(spi->spi_base, SPI_REG_TXCTRL) = 0; |
| while ((_REG32(spi->spi_base, SPI_REG_RXFIFO) & SPI_RXFIFO_EMPTY) == 0) { |
| } |
| plic_disable_interrupt(spi_num + INT_SPI0_BASE); |
| spi->txbuf = NULL; |
| spi->rxbuf = NULL; |
| spi->txleft = 0; |
| spi->rxleft = 0; |
| err: |
| __HAL_ENABLE_INTERRUPTS(sr); |
| return rc; |
| } |