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.
460 lines
10 KiB
460 lines
10 KiB
/* |
|
* Copyright (c) 2017 Intel Corporation |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
/** |
|
* @file |
|
* @brief Private API for SPI drivers |
|
*/ |
|
|
|
#ifndef ZEPHYR_DRIVERS_SPI_SPI_CONTEXT_H_ |
|
#define ZEPHYR_DRIVERS_SPI_SPI_CONTEXT_H_ |
|
|
|
#include <zephyr/drivers/gpio.h> |
|
#include <zephyr/drivers/spi.h> |
|
#include <zephyr/kernel.h> |
|
|
|
#ifdef __cplusplus |
|
extern "C" { |
|
#endif |
|
|
|
enum spi_ctx_runtime_op_mode { |
|
SPI_CTX_RUNTIME_OP_MODE_MASTER = BIT(0), |
|
SPI_CTX_RUNTIME_OP_MODE_SLAVE = BIT(1), |
|
}; |
|
|
|
struct spi_context { |
|
const struct spi_config *config; |
|
const struct spi_config *owner; |
|
const struct gpio_dt_spec *cs_gpios; |
|
size_t num_cs_gpios; |
|
|
|
struct k_sem lock; |
|
struct k_sem sync; |
|
int sync_status; |
|
|
|
#ifdef CONFIG_SPI_ASYNC |
|
spi_callback_t callback; |
|
void *callback_data; |
|
bool asynchronous; |
|
#endif /* CONFIG_SPI_ASYNC */ |
|
const struct spi_buf *current_tx; |
|
size_t tx_count; |
|
const struct spi_buf *current_rx; |
|
size_t rx_count; |
|
|
|
const uint8_t *tx_buf; |
|
size_t tx_len; |
|
uint8_t *rx_buf; |
|
size_t rx_len; |
|
|
|
#ifdef CONFIG_SPI_SLAVE |
|
int recv_frames; |
|
#endif /* CONFIG_SPI_SLAVE */ |
|
}; |
|
|
|
#define SPI_CONTEXT_INIT_LOCK(_data, _ctx_name) \ |
|
._ctx_name.lock = Z_SEM_INITIALIZER(_data._ctx_name.lock, 0, 1) |
|
|
|
#define SPI_CONTEXT_INIT_SYNC(_data, _ctx_name) \ |
|
._ctx_name.sync = Z_SEM_INITIALIZER(_data._ctx_name.sync, 0, 1) |
|
|
|
#define SPI_CONTEXT_CS_GPIO_SPEC_ELEM(_node_id, _prop, _idx) \ |
|
GPIO_DT_SPEC_GET_BY_IDX(_node_id, _prop, _idx), |
|
|
|
#define SPI_CONTEXT_CS_GPIOS_FOREACH_ELEM(_node_id) \ |
|
DT_FOREACH_PROP_ELEM(_node_id, cs_gpios, \ |
|
SPI_CONTEXT_CS_GPIO_SPEC_ELEM) |
|
|
|
#define SPI_CONTEXT_CS_GPIOS_INITIALIZE(_node_id, _ctx_name) \ |
|
._ctx_name.cs_gpios = (const struct gpio_dt_spec []) { \ |
|
COND_CODE_1(DT_SPI_HAS_CS_GPIOS(_node_id), \ |
|
(SPI_CONTEXT_CS_GPIOS_FOREACH_ELEM(_node_id)), ({0})) \ |
|
}, \ |
|
._ctx_name.num_cs_gpios = DT_PROP_LEN_OR(_node_id, cs_gpios, 0), |
|
|
|
static inline bool spi_context_configured(struct spi_context *ctx, |
|
const struct spi_config *config) |
|
{ |
|
return !!(ctx->config == config); |
|
} |
|
|
|
static inline bool spi_context_is_slave(struct spi_context *ctx) |
|
{ |
|
return (ctx->config->operation & SPI_OP_MODE_SLAVE); |
|
} |
|
|
|
static inline void spi_context_lock(struct spi_context *ctx, |
|
bool asynchronous, |
|
spi_callback_t callback, |
|
void *callback_data, |
|
const struct spi_config *spi_cfg) |
|
{ |
|
if ((spi_cfg->operation & SPI_LOCK_ON) && |
|
(k_sem_count_get(&ctx->lock) == 0) && |
|
(ctx->owner == spi_cfg)) { |
|
return; |
|
} |
|
|
|
k_sem_take(&ctx->lock, K_FOREVER); |
|
ctx->owner = spi_cfg; |
|
|
|
#ifdef CONFIG_SPI_ASYNC |
|
ctx->asynchronous = asynchronous; |
|
ctx->callback = callback; |
|
ctx->callback_data = callback_data; |
|
#endif /* CONFIG_SPI_ASYNC */ |
|
} |
|
|
|
static inline void spi_context_release(struct spi_context *ctx, int status) |
|
{ |
|
#ifdef CONFIG_SPI_SLAVE |
|
if (status >= 0 && (ctx->config->operation & SPI_LOCK_ON)) { |
|
return; |
|
} |
|
#endif /* CONFIG_SPI_SLAVE */ |
|
|
|
#ifdef CONFIG_SPI_ASYNC |
|
if (!ctx->asynchronous || (status < 0)) { |
|
ctx->owner = NULL; |
|
k_sem_give(&ctx->lock); |
|
} |
|
#else |
|
if (!(ctx->config->operation & SPI_LOCK_ON)) { |
|
ctx->owner = NULL; |
|
k_sem_give(&ctx->lock); |
|
} |
|
#endif /* CONFIG_SPI_ASYNC */ |
|
} |
|
|
|
static inline size_t spi_context_total_tx_len(struct spi_context *ctx); |
|
static inline size_t spi_context_total_rx_len(struct spi_context *ctx); |
|
|
|
static inline int spi_context_wait_for_completion(struct spi_context *ctx) |
|
{ |
|
int status = 0; |
|
bool wait; |
|
|
|
#ifdef CONFIG_SPI_ASYNC |
|
wait = !ctx->asynchronous; |
|
#else |
|
wait = true; |
|
#endif |
|
|
|
if (wait) { |
|
k_timeout_t timeout; |
|
|
|
/* Do not use any timeout in the slave mode, as in this case |
|
* it is not known when the transfer will actually start and |
|
* what the frequency will be. |
|
*/ |
|
if (IS_ENABLED(CONFIG_SPI_SLAVE) && spi_context_is_slave(ctx)) { |
|
timeout = K_FOREVER; |
|
} else { |
|
uint32_t tx_len = spi_context_total_tx_len(ctx); |
|
uint32_t rx_len = spi_context_total_rx_len(ctx); |
|
uint32_t timeout_ms; |
|
|
|
timeout_ms = MAX(tx_len, rx_len) * 8 * 1000 / |
|
ctx->config->frequency; |
|
timeout_ms += CONFIG_SPI_COMPLETION_TIMEOUT_TOLERANCE; |
|
|
|
timeout = K_MSEC(timeout_ms); |
|
} |
|
|
|
if (k_sem_take(&ctx->sync, timeout)) { |
|
LOG_ERR("Timeout waiting for transfer complete"); |
|
return -ETIMEDOUT; |
|
} |
|
status = ctx->sync_status; |
|
} |
|
|
|
#ifdef CONFIG_SPI_SLAVE |
|
if (spi_context_is_slave(ctx) && !status) { |
|
return ctx->recv_frames; |
|
} |
|
#endif /* CONFIG_SPI_SLAVE */ |
|
|
|
return status; |
|
} |
|
|
|
static inline void spi_context_complete(struct spi_context *ctx, |
|
const struct device *dev, |
|
int status) |
|
{ |
|
#ifdef CONFIG_SPI_ASYNC |
|
if (!ctx->asynchronous) { |
|
ctx->sync_status = status; |
|
k_sem_give(&ctx->sync); |
|
} else { |
|
if (ctx->callback) { |
|
#ifdef CONFIG_SPI_SLAVE |
|
if (spi_context_is_slave(ctx) && !status) { |
|
/* Let's update the status so it tells |
|
* about number of received frames. |
|
*/ |
|
status = ctx->recv_frames; |
|
} |
|
#endif /* CONFIG_SPI_SLAVE */ |
|
ctx->callback(dev, status, ctx->callback_data); |
|
} |
|
|
|
if (!(ctx->config->operation & SPI_LOCK_ON)) { |
|
ctx->owner = NULL; |
|
k_sem_give(&ctx->lock); |
|
} |
|
} |
|
#else |
|
ctx->sync_status = status; |
|
k_sem_give(&ctx->sync); |
|
#endif /* CONFIG_SPI_ASYNC */ |
|
} |
|
|
|
static inline int spi_context_cs_configure_all(struct spi_context *ctx) |
|
{ |
|
int ret; |
|
const struct gpio_dt_spec *cs_gpio; |
|
|
|
for (cs_gpio = ctx->cs_gpios; cs_gpio < &ctx->cs_gpios[ctx->num_cs_gpios]; cs_gpio++) { |
|
if (!device_is_ready(cs_gpio->port)) { |
|
LOG_ERR("CS GPIO port %s pin %d is not ready", |
|
cs_gpio->port->name, cs_gpio->pin); |
|
return -ENODEV; |
|
} |
|
|
|
ret = gpio_pin_configure_dt(cs_gpio, GPIO_OUTPUT_INACTIVE); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static inline void _spi_context_cs_control(struct spi_context *ctx, |
|
bool on, bool force_off) |
|
{ |
|
if (ctx->config && spi_cs_is_gpio(ctx->config)) { |
|
if (on) { |
|
gpio_pin_set_dt(&ctx->config->cs.gpio, 1); |
|
k_busy_wait(ctx->config->cs.delay); |
|
} else { |
|
if (!force_off && |
|
ctx->config->operation & SPI_HOLD_ON_CS) { |
|
return; |
|
} |
|
|
|
k_busy_wait(ctx->config->cs.delay); |
|
gpio_pin_set_dt(&ctx->config->cs.gpio, 0); |
|
} |
|
} |
|
} |
|
|
|
static inline void spi_context_cs_control(struct spi_context *ctx, bool on) |
|
{ |
|
_spi_context_cs_control(ctx, on, false); |
|
} |
|
|
|
static inline void spi_context_unlock_unconditionally(struct spi_context *ctx) |
|
{ |
|
/* Forcing CS to go to inactive status */ |
|
_spi_context_cs_control(ctx, false, true); |
|
|
|
if (!k_sem_count_get(&ctx->lock)) { |
|
ctx->owner = NULL; |
|
k_sem_give(&ctx->lock); |
|
} |
|
} |
|
|
|
static inline void *spi_context_get_next_buf(const struct spi_buf **current, |
|
size_t *count, |
|
size_t *buf_len, |
|
uint8_t dfs) |
|
{ |
|
/* This loop skips zero-length buffers in the set, if any. */ |
|
while (*count) { |
|
if (((*current)->len / dfs) != 0) { |
|
*buf_len = (*current)->len / dfs; |
|
return (*current)->buf; |
|
} |
|
++(*current); |
|
--(*count); |
|
} |
|
|
|
*buf_len = 0; |
|
return NULL; |
|
} |
|
|
|
static inline |
|
void spi_context_buffers_setup(struct spi_context *ctx, |
|
const struct spi_buf_set *tx_bufs, |
|
const struct spi_buf_set *rx_bufs, |
|
uint8_t dfs) |
|
{ |
|
LOG_DBG("tx_bufs %p - rx_bufs %p - %u", tx_bufs, rx_bufs, dfs); |
|
|
|
ctx->current_tx = tx_bufs ? tx_bufs->buffers : NULL; |
|
ctx->tx_count = ctx->current_tx ? tx_bufs->count : 0; |
|
ctx->tx_buf = (const uint8_t *) |
|
spi_context_get_next_buf(&ctx->current_tx, &ctx->tx_count, |
|
&ctx->tx_len, dfs); |
|
|
|
ctx->current_rx = rx_bufs ? rx_bufs->buffers : NULL; |
|
ctx->rx_count = ctx->current_rx ? rx_bufs->count : 0; |
|
ctx->rx_buf = (uint8_t *) |
|
spi_context_get_next_buf(&ctx->current_rx, &ctx->rx_count, |
|
&ctx->rx_len, dfs); |
|
|
|
ctx->sync_status = 0; |
|
|
|
#ifdef CONFIG_SPI_SLAVE |
|
ctx->recv_frames = 0; |
|
#endif /* CONFIG_SPI_SLAVE */ |
|
|
|
LOG_DBG("current_tx %p (%zu), current_rx %p (%zu)," |
|
" tx buf/len %p/%zu, rx buf/len %p/%zu", |
|
ctx->current_tx, ctx->tx_count, |
|
ctx->current_rx, ctx->rx_count, |
|
(void *)ctx->tx_buf, ctx->tx_len, |
|
(void *)ctx->rx_buf, ctx->rx_len); |
|
} |
|
|
|
static ALWAYS_INLINE |
|
void spi_context_update_tx(struct spi_context *ctx, uint8_t dfs, uint32_t len) |
|
{ |
|
if (!ctx->tx_len) { |
|
return; |
|
} |
|
|
|
if (len > ctx->tx_len) { |
|
LOG_ERR("Update exceeds current buffer"); |
|
return; |
|
} |
|
|
|
ctx->tx_len -= len; |
|
if (!ctx->tx_len) { |
|
/* Current buffer is done. Get the next one to be processed. */ |
|
++ctx->current_tx; |
|
--ctx->tx_count; |
|
ctx->tx_buf = (const uint8_t *) |
|
spi_context_get_next_buf(&ctx->current_tx, |
|
&ctx->tx_count, |
|
&ctx->tx_len, dfs); |
|
} else if (ctx->tx_buf) { |
|
ctx->tx_buf += dfs * len; |
|
} |
|
|
|
LOG_DBG("tx buf/len %p/%zu", (void *)ctx->tx_buf, ctx->tx_len); |
|
} |
|
|
|
static ALWAYS_INLINE |
|
bool spi_context_tx_on(struct spi_context *ctx) |
|
{ |
|
return !!(ctx->tx_len); |
|
} |
|
|
|
static ALWAYS_INLINE |
|
bool spi_context_tx_buf_on(struct spi_context *ctx) |
|
{ |
|
return !!(ctx->tx_buf && ctx->tx_len); |
|
} |
|
|
|
static ALWAYS_INLINE |
|
void spi_context_update_rx(struct spi_context *ctx, uint8_t dfs, uint32_t len) |
|
{ |
|
#ifdef CONFIG_SPI_SLAVE |
|
if (spi_context_is_slave(ctx)) { |
|
ctx->recv_frames += len; |
|
} |
|
|
|
#endif /* CONFIG_SPI_SLAVE */ |
|
|
|
if (!ctx->rx_len) { |
|
return; |
|
} |
|
|
|
if (len > ctx->rx_len) { |
|
LOG_ERR("Update exceeds current buffer"); |
|
return; |
|
} |
|
|
|
ctx->rx_len -= len; |
|
if (!ctx->rx_len) { |
|
/* Current buffer is done. Get the next one to be processed. */ |
|
++ctx->current_rx; |
|
--ctx->rx_count; |
|
ctx->rx_buf = (uint8_t *) |
|
spi_context_get_next_buf(&ctx->current_rx, |
|
&ctx->rx_count, |
|
&ctx->rx_len, dfs); |
|
} else if (ctx->rx_buf) { |
|
ctx->rx_buf += dfs * len; |
|
} |
|
|
|
LOG_DBG("rx buf/len %p/%zu", (void *)ctx->rx_buf, ctx->rx_len); |
|
} |
|
|
|
static ALWAYS_INLINE |
|
bool spi_context_rx_on(struct spi_context *ctx) |
|
{ |
|
return !!(ctx->rx_len); |
|
} |
|
|
|
static ALWAYS_INLINE |
|
bool spi_context_rx_buf_on(struct spi_context *ctx) |
|
{ |
|
return !!(ctx->rx_buf && ctx->rx_len); |
|
} |
|
|
|
/* |
|
* Returns the maximum length of a transfer for which all currently active |
|
* directions have a continuous buffer, i.e. the maximum SPI transfer that |
|
* can be done with DMA that handles only non-scattered buffers. |
|
*/ |
|
static inline size_t spi_context_max_continuous_chunk(struct spi_context *ctx) |
|
{ |
|
if (!ctx->tx_len) { |
|
return ctx->rx_len; |
|
} else if (!ctx->rx_len) { |
|
return ctx->tx_len; |
|
} |
|
|
|
return MIN(ctx->tx_len, ctx->rx_len); |
|
} |
|
|
|
static inline size_t spi_context_longest_current_buf(struct spi_context *ctx) |
|
{ |
|
return ctx->tx_len > ctx->rx_len ? ctx->tx_len : ctx->rx_len; |
|
} |
|
|
|
static inline size_t spi_context_total_tx_len(struct spi_context *ctx) |
|
{ |
|
size_t n; |
|
size_t total_len = 0; |
|
|
|
for (n = 0; n < ctx->tx_count; ++n) { |
|
total_len += ctx->current_tx[n].len; |
|
} |
|
|
|
return total_len; |
|
} |
|
|
|
static inline size_t spi_context_total_rx_len(struct spi_context *ctx) |
|
{ |
|
size_t n; |
|
size_t total_len = 0; |
|
|
|
for (n = 0; n < ctx->rx_count; ++n) { |
|
total_len += ctx->current_rx[n].len; |
|
} |
|
|
|
return total_len; |
|
} |
|
|
|
#ifdef __cplusplus |
|
} |
|
#endif |
|
|
|
#endif /* ZEPHYR_DRIVERS_SPI_SPI_CONTEXT_H_ */
|
|
|