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,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;
}