zephyr/drivers/spi/spi_context.h
Andrzej Głąbek a3430c2caf drivers: spi: nrfx: Restore recently changed spi_context function
Function spi_context_longest_current_buf() has been introduced in
commit ddef35c1da for the purpose of
getting the longest possible (potentially partial) SPI transfer
for which all currently active directions have a continuous buffer.
Such transfer can be done with taking advantage of a DMA that cannot
use scattered buffers (and this is the case for nRF SPI drivers with
which this function has been introduced).
Unfortunately, because of its inadequate name, later on this function
has been incorrectly used in other SPI drivers for getting the longer
of TX/RX buffers. And commit afc480f12b
recently "fixed" the implementation of this function, assumably to
adjust it to those incorrect uses, but this way it has also broken
the nRF SPI drivers.
Instead of restoring the original implementation of the function in
question, this commit adds a new one with functionality equivalent
to that original but with a hopefully less misleading name, and this
function is used in the nRF SPI drivers.

Signed-off-by: Andrzej Głąbek <andrzej.glabek@nordicsemi.no>
2020-09-15 15:59:18 +02:00

396 lines
8.6 KiB
C

/*
* 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 <drivers/gpio.h>
#include <drivers/spi.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;
struct k_sem lock;
struct k_sem sync;
int sync_status;
#ifdef CONFIG_SPI_ASYNC
struct k_poll_signal *signal;
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)
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,
struct k_poll_signal *signal)
{
k_sem_take(&ctx->lock, K_FOREVER);
#ifdef CONFIG_SPI_ASYNC
ctx->asynchronous = asynchronous;
ctx->signal = signal;
#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)) {
k_sem_give(&ctx->lock);
}
#else
k_sem_give(&ctx->lock);
#endif /* CONFIG_SPI_ASYNC */
}
static inline int spi_context_wait_for_completion(struct spi_context *ctx)
{
int status = 0;
#ifdef CONFIG_SPI_ASYNC
if (!ctx->asynchronous) {
k_sem_take(&ctx->sync, K_FOREVER);
status = ctx->sync_status;
}
#else
k_sem_take(&ctx->sync, K_FOREVER);
status = ctx->sync_status;
#endif /* CONFIG_SPI_ASYNC */
#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, int status)
{
#ifdef CONFIG_SPI_ASYNC
if (!ctx->asynchronous) {
ctx->sync_status = status;
k_sem_give(&ctx->sync);
} else {
if (ctx->signal) {
#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 */
k_poll_signal_raise(ctx->signal, status);
}
if (!(ctx->config->operation & SPI_LOCK_ON)) {
k_sem_give(&ctx->lock);
}
}
#else
ctx->sync_status = status;
k_sem_give(&ctx->sync);
#endif /* CONFIG_SPI_ASYNC */
}
static inline
gpio_dt_flags_t spi_context_cs_active_level(struct spi_context *ctx)
{
if (ctx->config->operation & SPI_CS_ACTIVE_HIGH) {
return GPIO_ACTIVE_HIGH;
}
return GPIO_ACTIVE_LOW;
}
static inline void spi_context_cs_configure(struct spi_context *ctx)
{
if (ctx->config->cs && ctx->config->cs->gpio_dev) {
/* Validate CS active levels are equivalent */
__ASSERT(spi_context_cs_active_level(ctx) ==
(ctx->config->cs->gpio_dt_flags & GPIO_ACTIVE_LOW),
"Devicetree and spi_context CS levels are not equal");
gpio_pin_configure(ctx->config->cs->gpio_dev,
ctx->config->cs->gpio_pin,
ctx->config->cs->gpio_dt_flags |
GPIO_OUTPUT_INACTIVE);
} else {
LOG_INF("CS control inhibited (no GPIO device)");
}
}
static inline void _spi_context_cs_control(struct spi_context *ctx,
bool on, bool force_off)
{
if (ctx->config && ctx->config->cs && ctx->config->cs->gpio_dev) {
if (on) {
gpio_pin_set(ctx->config->cs->gpio_dev,
ctx->config->cs->gpio_pin, 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(ctx->config->cs->gpio_dev,
ctx->config->cs->gpio_pin, 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)) {
k_sem_give(&ctx->lock);
}
}
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);
if (tx_bufs) {
ctx->current_tx = tx_bufs->buffers;
ctx->tx_count = tx_bufs->count;
ctx->tx_buf = (const uint8_t *)ctx->current_tx->buf;
ctx->tx_len = ctx->current_tx->len / dfs;
} else {
ctx->current_tx = NULL;
ctx->tx_count = 0;
ctx->tx_buf = NULL;
ctx->tx_len = 0;
}
if (rx_bufs) {
ctx->current_rx = rx_bufs->buffers;
ctx->rx_count = rx_bufs->count;
ctx->rx_buf = (uint8_t *)ctx->current_rx->buf;
ctx->rx_len = ctx->current_rx->len / dfs;
} else {
ctx->current_rx = NULL;
ctx->rx_count = 0;
ctx->rx_buf = NULL;
ctx->rx_len = 0;
}
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,
ctx->tx_buf, ctx->tx_len, 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) {
ctx->tx_count--;
if (ctx->tx_count) {
ctx->current_tx++;
ctx->tx_buf = (const uint8_t *)ctx->current_tx->buf;
ctx->tx_len = ctx->current_tx->len / dfs;
} else {
ctx->tx_buf = NULL;
}
} else if (ctx->tx_buf) {
ctx->tx_buf += dfs * len;
}
LOG_DBG("tx buf/len %p/%zu", 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) {
ctx->rx_count--;
if (ctx->rx_count) {
ctx->current_rx++;
ctx->rx_buf = (uint8_t *)ctx->current_rx->buf;
ctx->rx_len = ctx->current_rx->len / dfs;
} else {
ctx->rx_buf = NULL;
}
} else if (ctx->rx_buf) {
ctx->rx_buf += dfs * len;
}
LOG_DBG("rx buf/len %p/%zu", 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_ */