/* * 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 LOG_MODULE_REGISTER(spi_bitbang); #include #include #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 inline bool spi_bitbang_has_miso(const struct device *dev) { const struct spi_bitbang_config *info = dev->config; return info->miso_gpio.port != NULL; } static inline bool spi_bitbang_has_mosi(const struct device *dev) { const struct spi_bitbang_config *info = dev->config; return info->mosi_gpio.port != NULL; } 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_TRANSFER_LSB | SPI_LINES_DUAL | SPI_LINES_QUAD)) { LOG_ERR("Unsupported configuration"); return -ENOTSUP; } const int bits = SPI_WORD_SIZE_GET(config->operation); if (bits > 16) { LOG_ERR("Word sizes > 16 bits not supported"); return -ENOTSUP; } data->bits = bits; data->dfs = ((data->bits - 1) / 8) + 1; 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; spi_context_cs_configure_all(&data->ctx); 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; spi_bitbang_configure(info, data, spi_cfg); spi_context_buffers_setup(ctx, tx_bufs, rx_bufs, data->dfs); int clock_state = 0; int cpha = 0; bool loop = 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; } /* 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; const bool has_miso = spi_bitbang_has_miso(dev); const bool has_mosi = spi_bitbang_has_mosi(dev); while (spi_context_tx_buf_on(ctx) || spi_context_rx_buf_on(ctx)) { uint16_t w = 0; if (ctx->tx_len) { switch (data->dfs) { case 2: w = *(uint16_t *)(ctx->tx_buf); break; case 1: w = *(uint8_t *)(ctx->tx_buf); break; } } int shift = data->bits - 1; uint16_t r = 0; int b = 0; bool do_read = false; if (has_miso && spi_context_rx_buf_on(ctx)) { do_read = true; } while (shift >= 0) { const int d = (w >> shift) & 0x1; b = 0; /* setup data out first thing */ if (has_mosi) { gpio_pin_set_dt(&info->mosi_gpio, d); } k_busy_wait(wait_us); /* first clock edge */ gpio_pin_set_dt(&info->clk_gpio, !clock_state); if (!loop && do_read && !cpha) { b = gpio_pin_get_dt(&info->miso_gpio); } k_busy_wait(wait_us); /* second clock edge */ gpio_pin_set_dt(&info->clk_gpio, clock_state); if (!loop && do_read && cpha) { b = gpio_pin_get_dt(&info->miso_gpio); } if (loop) { b = d; } r = (r << 1) | (b ? 0x1 : 0x0); --shift; } if (spi_context_rx_buf_on(ctx)) { switch (data->dfs) { 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, 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, struct k_poll_signal *async) { 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 struct spi_driver_api 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 */ }; int spi_bitbang_init(const struct device *dev) { const struct spi_bitbang_config *config = dev->config; int rc; if (!device_is_ready(config->clk_gpio.port)) { 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 (!device_is_ready(config->mosi_gpio.port)) { 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 (!device_is_ready(config->miso_gpio.port)) { 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; } } 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), \ }; \ \ 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)