blob: d899f4f6b709c01e4e5948fe320c65661508717c [file] [log] [blame]
/*
* 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;
const struct spi_config *owner;
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,
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->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)) {
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 int spi_context_wait_for_completion(struct spi_context *ctx)
{
int status = 0;
uint32_t timeout_ms;
timeout_ms = MAX(ctx->tx_len, ctx->rx_len) * 8 * 1000 /
ctx->config->frequency;
timeout_ms += CONFIG_SPI_COMPLETION_TIMEOUT_TOLERANCE;
#ifdef CONFIG_SPI_ASYNC
if (!ctx->asynchronous) {
if (k_sem_take(&ctx->sync, K_MSEC(timeout_ms))) {
LOG_ERR("Timeout waiting for transfer complete");
return -ETIMEDOUT;
}
status = ctx->sync_status;
}
#else
if (k_sem_take(&ctx->sync, K_MSEC(timeout_ms))) {
LOG_ERR("Timeout waiting for transfer complete");
return -ETIMEDOUT;
}
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)) {
ctx->owner = NULL;
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)) {
ctx->owner = NULL;
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_ */