Files
rtems/bsps/shared/dev/spi/spi-gpio.c
Christian Mauderer 0e7567bc2a 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.
2024-06-07 06:46:00 +00:00

226 lines
5.6 KiB
C

/* 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;
}