diff --git a/bsps/include/dev/spi/spi-gpio.h b/bsps/include/dev/spi/spi-gpio.h new file mode 100644 index 0000000000..6b74542817 --- /dev/null +++ b/bsps/include/dev/spi/spi-gpio.h @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +/** + * @file + * + * @ingroup RTEMSDeviceSPIGPIO + * + * @brief This header file provides the interfaces of @ref RTEMSDeviceSPIGPIO. + */ + +/* + * Copyright (C) 2024 embedded brains GmbH & Co. KG + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _RTEMS_DEV_SPI_SPI_GPIO_H +#define _RTEMS_DEV_SPI_SPI_GPIO_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * @defgroup RTEMSDeviceSPIGPIO + * + * @ingroup SPI + * + * @brief A simple SPI bus driver that just uses GPIO pins + * + * Implements a SPI that is emulated by toggling GPIO pins. Callbacks are used + * to set or get the pins so that it can be easily adapted to various GPIO + * controllers. + * + * NOTE: This driver is slow! If you need performance: Don't use it. The + * intended use case is (for example) a one time configuration of some SPI + * peripheral. + * + * The driver will just work as fast as it can. Setting a speed limit is + * currently not supported. + * + * @{ + */ + +#define SPI_GPIO_MAX_CS 4 + +/** + * @brief Type of the functions that set pins + * + * Set a GPIO pin to @a level. @a level is false for low or true for high. @a + * arg is an application specific parameter. + */ +typedef void (spi_gpio_set_pin_fn) (void *arg, bool level); + +/** + * @brief Type of the functions that read pins + * + * Get current level of GPIO pin. Should return either 0 for low or anything + * else for high. @a arg is an application specific parameter. + */ +typedef bool (spi_gpio_get_pin_fn) (void *arg); + +/** + * @brief Parameters for the driver + * + * Parameters that should be provided by the application while registering the + * driver. Mainly functions to set single pins. + */ +struct spi_gpio_params { + /** Function for setting the clock pin */ + spi_gpio_set_pin_fn *set_clk; + /** Application specific argument for the clock pin setter function */ + void *set_clk_arg; + /** Function for setting the mosi pin */ + spi_gpio_set_pin_fn *set_mosi; + /** Application specific argument for the mosi pin setter function */ + void *set_mosi_arg; + /** Function for reading the miso pin */ + spi_gpio_get_pin_fn *get_miso; + /** Application specific argument for the miso pin getter function */ + void *get_miso_arg; + /** Functions for setting the cs pins */ + spi_gpio_set_pin_fn *set_cs[SPI_GPIO_MAX_CS]; + /** Application specific arguments for the cs pin getter functions */ + void *set_cs_arg[SPI_GPIO_MAX_CS]; + + /** + * Idle level of the CS pin. + * + * Set the value to 0 for a high active CS or to 1 for a low active CS pin. + */ + bool cs_idle[SPI_GPIO_MAX_CS]; +}; + +/** + * Register a new SPI GPIO instance at the device path @a device. + * + * To save memory, @a params will be used directly. Make sure that the structure + * remains valid during the complete application run time. + * + * @returns RTEMS_SUCCESSFUL if registering the bus worked or an error code if + * it didn't. + */ +rtems_status_code spi_gpio_init( + const char *device, + const struct spi_gpio_params *params + ); + +/** @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _RTEMS_DEV_SPI_SPI_GPIO_H */ diff --git a/bsps/shared/dev/spi/spi-gpio.c b/bsps/shared/dev/spi/spi-gpio.c new file mode 100644 index 0000000000..728aefe029 --- /dev/null +++ b/bsps/shared/dev/spi/spi-gpio.c @@ -0,0 +1,225 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +/** + * @file + * + * @ingroup RTEMSDeviceSPIGPIO + * + * @brief This file provides the implementation of @ref RTEMSDeviceSPIGPIO. + */ + +/* + * Copyright (C) 2024 embedded brains GmbH & Co. KG + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +struct spi_gpio_bus { + spi_bus base; + const struct spi_gpio_params *p; +}; + +static int spi_gpio_check_msg( + struct spi_gpio_bus *bus, + const spi_ioc_transfer *msg +) +{ + /* + * Check that only implemented modes are requested. + * + * Ignore the frequency. This driver doesn't give any guarantees regarding + * that. + */ + if ((msg->mode & ~(SPI_CPHA | SPI_CPOL | SPI_NO_CS)) != 0 || + (msg->bits_per_word != 8) || + (msg->delay_usecs != 0) || + (msg->cs >= SPI_GPIO_MAX_CS) || + (bus->p->set_cs[msg->cs] == NULL)) { + return -EINVAL; + } + + return 0; +} + +static int spi_gpio_transfer_msg( + struct spi_gpio_bus *bus, + const spi_ioc_transfer *msg +) +{ + uint8_t cs = msg->cs; + bool clk_idle = ((msg->mode & SPI_CPOL) != 0); + bool cpha = ((msg->mode & SPI_CPHA) != 0); + const uint8_t *tx_buf = msg->tx_buf; + uint8_t *rx_buf = msg->rx_buf; + size_t len = msg->len; + int i; + int rv; + + rv = spi_gpio_check_msg(bus, msg); + if (rv != 0) { + return rv; + } + + /* + * Make sure that all chip selects but the one in the message are idle. + * Excluding the one in the message is necessary to avoid deselecting the chip + * between messages. + */ + for (i = 0; i < SPI_GPIO_MAX_CS; ++i) { + if (i != cs && bus->p->set_cs[i] != NULL) { + bus->p->set_cs[i](bus->p->set_cs_arg[i], bus->p->cs_idle[i]); + } + } + + /* Set up clock according to CPOL. */ + bus->p->set_clk(bus->p->set_clk_arg, clk_idle); + + /* Now select the chip (if it isn't selected from the previous message) */ + bus->p->set_cs[cs](bus->p->set_cs_arg[cs], !bus->p->cs_idle[cs]); + + while (len > 0) { + uint8_t out = 0xff; + uint8_t in = 0; + size_t ctr; + + --len; + + if (tx_buf != NULL) { + out = *tx_buf; + ++tx_buf; + } + + /* + * Read / Write the data. + * + * If CPHA=0: Set up data. Read data. Clock to active. Clock to inactive. + * If CPHA=1: Clock to active. Set up data. Read data. Clock to inactive. + */ + for (ctr = 0; ctr < sizeof(out) * 8; ++ctr) { + bool d_out; + bool d_in; + + if (cpha) { + bus->p->set_clk(bus->p->set_clk_arg, !clk_idle); + } + + d_out = ((out & 0x80) != 0); + out <<= 1; + bus->p->set_mosi(bus->p->set_mosi_arg, d_out); + + d_in = bus->p->get_miso(bus->p->get_miso_arg); + in = (in << 1) | (d_in ? 1 : 0); + + if (!cpha) { + bus->p->set_clk(bus->p->set_clk_arg, !clk_idle); + } + bus->p->set_clk(bus->p->set_clk_arg, clk_idle); + } + + if (rx_buf != NULL) { + *rx_buf = in; + ++rx_buf; + } + } + + /* Deselect the chip if the message wants that. */ + if (msg->cs_change) { + bus->p->set_cs[cs](bus->p->set_cs_arg[cs], bus->p->cs_idle[cs]); + } + + return 0; +} + +static int spi_gpio_transfer( + spi_bus *base, + const spi_ioc_transfer *msgs, + uint32_t n +) +{ + struct spi_gpio_bus *bus; + bus = (struct spi_gpio_bus *) base; + int rv = 0; + + while (n > 0 && rv == 0) { + rv = spi_gpio_transfer_msg(bus, msgs); + --n; + ++msgs; + }; + + return rv; +} + +static void spi_gpio_destroy(spi_bus *base) +{ + struct spi_gpio_bus *bus; + bus = (struct spi_gpio_bus *) base; + spi_bus_destroy_and_free(&bus->base); +} + +static int spi_gpio_setup(spi_bus *base) +{ + struct spi_gpio_bus *bus; + bus = (struct spi_gpio_bus *) base; + struct spi_ioc_transfer msg = { + .speed_hz = base->speed_hz, + .delay_usecs = base->delay_usecs, + .bits_per_word = base->bits_per_word, + .mode = base->mode, + .cs = base->cs, + }; + + return spi_gpio_check_msg(bus, &msg); +} + +rtems_status_code +spi_gpio_init(const char* device, const struct spi_gpio_params *params) +{ + struct spi_gpio_bus *bus; + int eno; + + if (device == NULL || params == NULL) { + return RTEMS_INVALID_ADDRESS; + } + + bus = (struct spi_gpio_bus*) spi_bus_alloc_and_init(sizeof(*bus)); + if (bus == NULL) { + return RTEMS_UNSATISFIED; + } + + bus->p = params; + + bus->base.transfer = spi_gpio_transfer; + bus->base.destroy = spi_gpio_destroy; + bus->base.setup = spi_gpio_setup; + + eno = spi_bus_register(&bus->base, device); + if (eno != 0) { + spi_bus_destroy_and_free(&bus->base); + return RTEMS_UNSATISFIED; + } + + return RTEMS_SUCCESSFUL; +} diff --git a/spec/build/bsps/objdevspigpio.yml b/spec/build/bsps/objdevspigpio.yml new file mode 100644 index 0000000000..8f2e0dab8f --- /dev/null +++ b/spec/build/bsps/objdevspigpio.yml @@ -0,0 +1,17 @@ +SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause +build-type: objects +cflags: [] +copyrights: +- Copyright (C) 2024 embedded brains GmbH & Co. KG +cppflags: [] +cxxflags: [] +enabled-by: true +includes: [] +install: +- destination: ${BSP_INCLUDEDIR}/dev/spi + source: + - bsps/include/dev/spi/spi-gpio.h +links: [] +source: +- bsps/shared/dev/spi/spi-gpio.c +type: build