bsps: Add a GPIO based SPI driver

The driver uses simple pin set or get functions to emulate an SPI. It
doesn't care much for speed settings and just tries it's best.
This commit is contained in:
Christian Mauderer
2024-04-24 08:51:45 +02:00
committed by Sebastian Huber
parent 7179f23340
commit 0e7567bc2a
3 changed files with 379 additions and 0 deletions

View File

@@ -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 <rtems.h>
#include <stdbool.h>
#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 */

View File

@@ -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 <bsp.h>
#include <dev/spi/spi.h>
#include <dev/spi/spi-gpio.h>
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;
}

View File

@@ -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