You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
355 lines
8.0 KiB
355 lines
8.0 KiB
/* |
|
* Copyright (c) 2021 Marc Reilly - Creative Product Design |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT zephyr_spi_bitbang |
|
|
|
#define LOG_LEVEL CONFIG_SPI_LOG_LEVEL |
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(spi_bitbang); |
|
|
|
#include <zephyr/sys/sys_io.h> |
|
#include <zephyr/drivers/spi.h> |
|
#include <zephyr/drivers/spi/rtio.h> |
|
#include "spi_context.h" |
|
|
|
struct spi_bitbang_data { |
|
struct spi_context ctx; |
|
int bits; |
|
int wait_us; |
|
int dfs; |
|
}; |
|
|
|
struct spi_bitbang_config { |
|
struct gpio_dt_spec clk_gpio; |
|
struct gpio_dt_spec mosi_gpio; |
|
struct gpio_dt_spec miso_gpio; |
|
}; |
|
|
|
static int spi_bitbang_configure(const struct spi_bitbang_config *info, |
|
struct spi_bitbang_data *data, |
|
const struct spi_config *config) |
|
{ |
|
if (config->operation & SPI_OP_MODE_SLAVE) { |
|
LOG_ERR("Slave mode not supported"); |
|
return -ENOTSUP; |
|
} |
|
|
|
if (config->operation & (SPI_LINES_DUAL | SPI_LINES_QUAD | SPI_LINES_OCTAL)) { |
|
LOG_ERR("Unsupported configuration"); |
|
return -ENOTSUP; |
|
} |
|
|
|
const int bits = SPI_WORD_SIZE_GET(config->operation); |
|
|
|
if (bits > 32) { |
|
LOG_ERR("Word sizes > 32 bits not supported"); |
|
return -ENOTSUP; |
|
} |
|
|
|
data->bits = bits; |
|
data->dfs = ((data->bits - 1) / 8) + 1; |
|
|
|
/* As there is no uint24_t, it is assumed uint32_t will be used as the buffer base type. */ |
|
if (data->dfs == 3) { |
|
data->dfs = 4; |
|
} |
|
|
|
if (config->frequency > 0) { |
|
/* convert freq to period, the extra /2 is due to waiting |
|
* twice in each clock cycle. The '2000' is an upscale factor. |
|
*/ |
|
data->wait_us = (1000000ul * 2000ul / config->frequency) / 2000ul; |
|
data->wait_us /= 2; |
|
} else { |
|
data->wait_us = 8 / 2; /* 125 kHz */ |
|
} |
|
|
|
data->ctx.config = config; |
|
|
|
return 0; |
|
} |
|
|
|
static int spi_bitbang_transceive(const struct device *dev, |
|
const struct spi_config *spi_cfg, |
|
const struct spi_buf_set *tx_bufs, |
|
const struct spi_buf_set *rx_bufs) |
|
{ |
|
const struct spi_bitbang_config *info = dev->config; |
|
struct spi_bitbang_data *data = dev->data; |
|
struct spi_context *ctx = &data->ctx; |
|
int rc; |
|
const struct gpio_dt_spec *miso = NULL; |
|
const struct gpio_dt_spec *mosi = NULL; |
|
gpio_flags_t mosi_flags = GPIO_OUTPUT_INACTIVE; |
|
|
|
rc = spi_bitbang_configure(info, data, spi_cfg); |
|
if (rc < 0) { |
|
return rc; |
|
} |
|
|
|
if (spi_cfg->operation & SPI_HALF_DUPLEX) { |
|
if (!info->mosi_gpio.port) { |
|
LOG_ERR("No MOSI pin specified in half duplex mode"); |
|
return -EINVAL; |
|
} |
|
|
|
if (tx_bufs && rx_bufs) { |
|
LOG_ERR("Both RX and TX specified in half duplex mode"); |
|
return -EINVAL; |
|
} else if (tx_bufs && !rx_bufs) { |
|
/* TX mode */ |
|
mosi = &info->mosi_gpio; |
|
} else if (!tx_bufs && rx_bufs) { |
|
/* RX mode */ |
|
mosi_flags = GPIO_INPUT; |
|
miso = &info->mosi_gpio; |
|
} |
|
} else { |
|
if (info->mosi_gpio.port) { |
|
mosi = &info->mosi_gpio; |
|
} |
|
|
|
if (info->miso_gpio.port) { |
|
miso = &info->miso_gpio; |
|
} |
|
} |
|
|
|
if (info->mosi_gpio.port) { |
|
rc = gpio_pin_configure_dt(&info->mosi_gpio, mosi_flags); |
|
if (rc < 0) { |
|
LOG_ERR("Couldn't configure MOSI pin: %d", rc); |
|
return rc; |
|
} |
|
} |
|
|
|
spi_context_buffers_setup(ctx, tx_bufs, rx_bufs, data->dfs); |
|
|
|
int clock_state = 0; |
|
int cpha = 0; |
|
bool loop = false; |
|
bool lsb = false; |
|
|
|
if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPOL) { |
|
clock_state = 1; |
|
} |
|
if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPHA) { |
|
cpha = 1; |
|
} |
|
if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_LOOP) { |
|
loop = true; |
|
} |
|
if (spi_cfg->operation & SPI_TRANSFER_LSB) { |
|
lsb = true; |
|
} |
|
|
|
/* set the initial clock state before CS */ |
|
gpio_pin_set_dt(&info->clk_gpio, clock_state); |
|
|
|
spi_context_cs_control(ctx, true); |
|
|
|
const uint32_t wait_us = data->wait_us; |
|
|
|
while (spi_context_tx_buf_on(ctx) || spi_context_rx_buf_on(ctx)) { |
|
uint32_t w = 0; |
|
|
|
if (ctx->tx_len) { |
|
switch (data->dfs) { |
|
case 4: |
|
case 3: |
|
w = *(uint32_t *)(ctx->tx_buf); |
|
break; |
|
case 2: |
|
w = *(uint16_t *)(ctx->tx_buf); |
|
break; |
|
case 1: |
|
w = *(uint8_t *)(ctx->tx_buf); |
|
break; |
|
} |
|
} |
|
|
|
uint32_t r = 0; |
|
uint8_t i = 0; |
|
int b = 0; |
|
bool do_read = false; |
|
|
|
if (miso && spi_context_rx_buf_on(ctx)) { |
|
do_read = true; |
|
} |
|
|
|
while (i < data->bits) { |
|
const int shift = lsb ? i : (data->bits - 1 - i); |
|
const int d = (w >> shift) & 0x1; |
|
|
|
b = 0; |
|
|
|
/* setup data out first thing */ |
|
if (mosi) { |
|
gpio_pin_set_dt(mosi, d); |
|
} |
|
|
|
k_busy_wait(wait_us); |
|
|
|
if (!loop && do_read && !cpha) { |
|
b = gpio_pin_get_dt(miso); |
|
} |
|
|
|
/* first (leading) clock edge */ |
|
gpio_pin_set_dt(&info->clk_gpio, !clock_state); |
|
|
|
k_busy_wait(wait_us); |
|
|
|
if (!loop && do_read && cpha) { |
|
b = gpio_pin_get_dt(miso); |
|
} |
|
|
|
/* second (trailing) clock edge */ |
|
gpio_pin_set_dt(&info->clk_gpio, clock_state); |
|
|
|
if (loop) { |
|
b = d; |
|
} |
|
|
|
r |= (b ? 0x1 : 0x0) << shift; |
|
|
|
++i; |
|
} |
|
|
|
if (spi_context_rx_buf_on(ctx)) { |
|
switch (data->dfs) { |
|
case 4: |
|
case 3: |
|
*(uint32_t *)(ctx->rx_buf) = r; |
|
break; |
|
case 2: |
|
*(uint16_t *)(ctx->rx_buf) = r; |
|
break; |
|
case 1: |
|
*(uint8_t *)(ctx->rx_buf) = r; |
|
break; |
|
} |
|
} |
|
|
|
LOG_DBG(" w: %04x, r: %04x , do_read: %d", w, r, do_read); |
|
|
|
spi_context_update_tx(ctx, data->dfs, 1); |
|
spi_context_update_rx(ctx, data->dfs, 1); |
|
} |
|
|
|
spi_context_cs_control(ctx, false); |
|
|
|
spi_context_complete(ctx, dev, 0); |
|
|
|
return 0; |
|
} |
|
|
|
#ifdef CONFIG_SPI_ASYNC |
|
static int spi_bitbang_transceive_async(const struct device *dev, |
|
const struct spi_config *spi_cfg, |
|
const struct spi_buf_set *tx_bufs, |
|
const struct spi_buf_set *rx_bufs, |
|
spi_callback_t cb, |
|
void *userdata) |
|
{ |
|
return -ENOTSUP; |
|
} |
|
#endif |
|
|
|
int spi_bitbang_release(const struct device *dev, |
|
const struct spi_config *config) |
|
{ |
|
struct spi_bitbang_data *data = dev->data; |
|
struct spi_context *ctx = &data->ctx; |
|
|
|
spi_context_unlock_unconditionally(ctx); |
|
return 0; |
|
} |
|
|
|
static DEVICE_API(spi, spi_bitbang_api) = { |
|
.transceive = spi_bitbang_transceive, |
|
.release = spi_bitbang_release, |
|
#ifdef CONFIG_SPI_ASYNC |
|
.transceive_async = spi_bitbang_transceive_async, |
|
#endif /* CONFIG_SPI_ASYNC */ |
|
#ifdef CONFIG_SPI_RTIO |
|
.iodev_submit = spi_rtio_iodev_default_submit, |
|
#endif |
|
}; |
|
|
|
int spi_bitbang_init(const struct device *dev) |
|
{ |
|
const struct spi_bitbang_config *config = dev->config; |
|
struct spi_bitbang_data *data = dev->data; |
|
int rc; |
|
|
|
if (!gpio_is_ready_dt(&config->clk_gpio)) { |
|
LOG_ERR("GPIO port for clk pin is not ready"); |
|
return -ENODEV; |
|
} |
|
rc = gpio_pin_configure_dt(&config->clk_gpio, GPIO_OUTPUT_INACTIVE); |
|
if (rc < 0) { |
|
LOG_ERR("Couldn't configure clk pin; (%d)", rc); |
|
return rc; |
|
} |
|
|
|
if (config->mosi_gpio.port != NULL) { |
|
if (!gpio_is_ready_dt(&config->mosi_gpio)) { |
|
LOG_ERR("GPIO port for mosi pin is not ready"); |
|
return -ENODEV; |
|
} |
|
rc = gpio_pin_configure_dt(&config->mosi_gpio, |
|
GPIO_OUTPUT_INACTIVE); |
|
if (rc < 0) { |
|
LOG_ERR("Couldn't configure mosi pin; (%d)", rc); |
|
return rc; |
|
} |
|
} |
|
|
|
if (config->miso_gpio.port != NULL) { |
|
if (!gpio_is_ready_dt(&config->miso_gpio)) { |
|
LOG_ERR("GPIO port for miso pin is not ready"); |
|
return -ENODEV; |
|
} |
|
|
|
|
|
rc = gpio_pin_configure_dt(&config->miso_gpio, GPIO_INPUT); |
|
if (rc < 0) { |
|
LOG_ERR("Couldn't configure miso pin; (%d)", rc); |
|
return rc; |
|
} |
|
} |
|
|
|
rc = spi_context_cs_configure_all(&data->ctx); |
|
if (rc < 0) { |
|
LOG_ERR("Failed to configure CS pins: %d", rc); |
|
return rc; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
#define SPI_BITBANG_INIT(inst) \ |
|
static struct spi_bitbang_config spi_bitbang_config_##inst = { \ |
|
.clk_gpio = GPIO_DT_SPEC_INST_GET(inst, clk_gpios), \ |
|
.mosi_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, mosi_gpios, {0}), \ |
|
.miso_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, miso_gpios, {0}), \ |
|
}; \ |
|
\ |
|
static struct spi_bitbang_data spi_bitbang_data_##inst = { \ |
|
SPI_CONTEXT_INIT_LOCK(spi_bitbang_data_##inst, ctx), \ |
|
SPI_CONTEXT_INIT_SYNC(spi_bitbang_data_##inst, ctx), \ |
|
SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(inst), ctx) \ |
|
}; \ |
|
\ |
|
SPI_DEVICE_DT_INST_DEFINE(inst, \ |
|
spi_bitbang_init, \ |
|
NULL, \ |
|
&spi_bitbang_data_##inst, \ |
|
&spi_bitbang_config_##inst, \ |
|
POST_KERNEL, \ |
|
CONFIG_SPI_INIT_PRIORITY, \ |
|
&spi_bitbang_api); |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(SPI_BITBANG_INIT)
|
|
|