blob: 988ca201ffc6aac65334faccdbd2706de3e6d96d [file] [log] [blame]
/*
* Copyright (c) 2025 Croxel Inc.
* Copyright (c) 2025 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/sys/check.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/rtio/rtio.h>
#include <platform/argus_s2pi.h>
#include <platform/argus_irq.h>
#include <api/argus_status.h>
/** Used to get instance data from slave index */
#include <../drivers/sensor/broadcom/afbr_s50/platform.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(afbr_s2pi, CONFIG_SENSOR_LOG_LEVEL);
status_t S2PI_GetStatus(s2pi_slave_t slave)
{
struct afbr_s50_platform_data *data;
int err;
err = afbr_s50_platform_get_by_id(slave, &data);
CHECKIF(err) {
return ERROR_FAIL;
}
return atomic_get(&data->s2pi.rtio.state);
}
/** This basic implementation assumes the AFBR is alone in the bus, hence no
* need to get a mutex. If more AFBR's are lumped together in a bus, these
* two APIs need an implementation.
*/
status_t S2PI_TryGetMutex(s2pi_slave_t slave)
{
return STATUS_OK;
}
void S2PI_ReleaseMutex(s2pi_slave_t slave)
{
/* See comment above. */
}
static void S2PI_complete_callback(struct rtio *ctx,
const struct rtio_sqe *sqe,
void *arg)
{
struct afbr_s50_platform_data *data = (struct afbr_s50_platform_data *)arg;
struct rtio_cqe *cqe;
int err = 0;
status_t status = STATUS_OK;
do {
cqe = rtio_cqe_consume(ctx);
if (cqe != NULL) {
err = err ? err : cqe->result;
rtio_cqe_release(ctx, cqe);
}
} while (cqe != NULL);
/** If the transfer was aborted, skip notifying users */
if (atomic_cas(&data->s2pi.rtio.state, STATUS_BUSY, STATUS_IDLE)) {
status = STATUS_OK;
} else if (atomic_cas(&data->s2pi.rtio.state, ERROR_ABORTED, STATUS_IDLE)) {
status = ERROR_ABORTED;
} else {
return;
}
if (err) {
status = ERROR_FAIL;
}
if (data->s2pi.rtio.callback.handler) {
(void)data->s2pi.rtio.callback.handler(status, data->s2pi.rtio.callback.data);
}
}
status_t S2PI_TransferFrame(s2pi_slave_t slave,
uint8_t const *txData,
uint8_t *rxData,
size_t frameSize,
s2pi_callback_t callback,
void *callbackData)
{
struct afbr_s50_platform_data *data;
int err;
err = afbr_s50_platform_get_by_id(slave, &data);
CHECKIF(err) {
return ERROR_S2PI_INVALID_SLAVE;
}
CHECKIF(atomic_get(&data->s2pi.mode) == STATUS_S2PI_GPIO_MODE) {
LOG_ERR("S2PI is in GPIO MODE");
return ERROR_S2PI_INVALID_STATE;
}
/** Check if the SPI bus is busy */
if (!atomic_cas(&data->s2pi.rtio.state, STATUS_IDLE, STATUS_BUSY)) {
LOG_WRN("SPI bus is busy");
return STATUS_BUSY;
}
data->s2pi.rtio.callback.handler = callback;
data->s2pi.rtio.callback.data = callbackData;
struct rtio *ctx = data->s2pi.rtio.ctx;
struct rtio_iodev *iodev = data->s2pi.rtio.iodev;
struct rtio_sqe *xfer_sqe = rtio_sqe_acquire(ctx);
struct rtio_sqe *cb_sqe = rtio_sqe_acquire(ctx);
CHECKIF(!xfer_sqe || !cb_sqe) {
LOG_ERR("Failed to allocate SQE, please check RTIO queue "
"configuration on afbr_s50.c");
return ERROR_FAIL;
}
if (rxData) {
rtio_sqe_prep_transceive(xfer_sqe, iodev, RTIO_PRIO_HIGH,
txData, rxData, frameSize, NULL);
} else {
rtio_sqe_prep_write(xfer_sqe, iodev, RTIO_PRIO_HIGH,
txData, frameSize, NULL);
}
xfer_sqe->flags |= RTIO_SQE_CHAINED;
rtio_sqe_prep_callback_no_cqe(cb_sqe, S2PI_complete_callback, data, NULL);
rtio_submit(ctx, 0);
return STATUS_OK;
}
status_t S2PI_Abort(s2pi_slave_t slave)
{
struct afbr_s50_platform_data *data;
int err;
err = afbr_s50_platform_get_by_id(slave, &data);
CHECKIF(err) {
return ERROR_S2PI_INVALID_SLAVE;
}
(void)atomic_set(&data->s2pi.rtio.state, ERROR_ABORTED);
S2PI_complete_callback(data->s2pi.rtio.ctx, NULL, data);
return STATUS_OK;
}
status_t S2PI_SetIrqCallback(s2pi_slave_t slave,
s2pi_irq_callback_t callback,
void *callbackData)
{
struct afbr_s50_platform_data *data;
int err;
err = afbr_s50_platform_get_by_id(slave, &data);
CHECKIF(err) {
return ERROR_S2PI_INVALID_SLAVE;
}
data->s2pi.irq.handler = callback;
data->s2pi.irq.data = callbackData;
if (data->s2pi.irq.handler) {
err = gpio_pin_interrupt_configure_dt(data->s2pi.gpio.irq, GPIO_INT_EDGE_TO_ACTIVE);
CHECKIF(err) {
LOG_ERR("Failed to enable IRQ GPIO: %d", err);
return ERROR_FAIL;
}
} else {
err = gpio_pin_interrupt_configure_dt(data->s2pi.gpio.irq, GPIO_INT_DISABLE);
CHECKIF(err) {
LOG_ERR("Failed to disable IRQ GPIO: %d", err);
return ERROR_FAIL;
}
}
return STATUS_OK;
}
uint32_t S2PI_ReadIrqPin(s2pi_slave_t slave)
{
struct afbr_s50_platform_data *data;
int err;
err = afbr_s50_platform_get_by_id(slave, &data);
CHECKIF(err) {
LOG_ERR("Error getting platform data: %d", err);
return 0;
}
const struct gpio_dt_spec *irq = data->s2pi.gpio.irq;
int value;
value = gpio_pin_get_dt(irq);
CHECKIF(value < 0) {
LOG_ERR("Error reading IRQ pin: %d", value);
return 0;
}
return !value;
}
status_t S2PI_CycleCsPin(s2pi_slave_t slave)
{
struct afbr_s50_platform_data *data;
int err;
err = afbr_s50_platform_get_by_id(slave, &data);
CHECKIF(err) {
LOG_ERR("Error getting platform data: %d", err);
return ERROR_S2PI_INVALID_SLAVE;
}
const struct gpio_dt_spec *cs = data->s2pi.gpio.spi.cs;
err = gpio_pin_set_dt(cs, 1);
CHECKIF(err) {
LOG_ERR("Error setting CS pin logical high: %d", err);
return ERROR_FAIL;
}
err = gpio_pin_set_dt(cs, 0);
CHECKIF(err) {
LOG_ERR("Error setting CS pin logical low: %d", err);
return ERROR_FAIL;
}
return STATUS_OK;
}
status_t S2PI_CaptureGpioControl(s2pi_slave_t slave)
{
struct afbr_s50_platform_data *data;
int err;
err = afbr_s50_platform_get_by_id(slave, &data);
CHECKIF(err) {
LOG_ERR("Error getting platform data: %d", err);
return ERROR_S2PI_INVALID_SLAVE;
}
err = pinctrl_apply_state(data->s2pi.pincfg, PINCTRL_STATE_PRIV_START);
CHECKIF(err) {
LOG_ERR("Error applying gpio pinctrl state: %d", err);
return ERROR_FAIL;
}
err = gpio_pin_configure_dt(data->s2pi.gpio.spi.miso, GPIO_INPUT | GPIO_PULL_UP);
err |= gpio_pin_configure_dt(data->s2pi.gpio.spi.mosi, GPIO_OUTPUT);
err |= gpio_pin_configure_dt(data->s2pi.gpio.spi.clk, GPIO_OUTPUT);
CHECKIF(err) {
LOG_ERR("Error configuring GPIO pins: %d", err);
return ERROR_FAIL;
}
(void)atomic_set(&data->s2pi.mode, STATUS_S2PI_GPIO_MODE);
return STATUS_OK;
}
status_t S2PI_ReleaseGpioControl(s2pi_slave_t slave)
{
struct afbr_s50_platform_data *data;
int err;
err = afbr_s50_platform_get_by_id(slave, &data);
CHECKIF(err) {
LOG_ERR("Error getting platform data: %d", err);
return ERROR_S2PI_INVALID_SLAVE;
}
(void)gpio_pin_configure_dt(data->s2pi.gpio.spi.miso, GPIO_DISCONNECTED);
(void)gpio_pin_configure_dt(data->s2pi.gpio.spi.mosi, GPIO_DISCONNECTED);
(void)gpio_pin_configure_dt(data->s2pi.gpio.spi.clk, GPIO_DISCONNECTED);
err = pinctrl_apply_state(data->s2pi.pincfg, PINCTRL_STATE_DEFAULT);
CHECKIF(err) {
LOG_ERR("Error applying default pinctrl state: %d", err);
return ERROR_FAIL;
}
(void)atomic_set(&data->s2pi.mode, STATUS_IDLE);
return STATUS_OK;
}
status_t S2PI_WriteGpioPin(s2pi_slave_t slave, s2pi_pin_t pin, uint32_t value)
{
struct afbr_s50_platform_data *data;
const struct gpio_dt_spec *gpio_pin;
int err;
err = afbr_s50_platform_get_by_id(slave, &data);
CHECKIF(err) {
LOG_ERR("Error getting platform data: %d", err);
return ERROR_S2PI_INVALID_SLAVE;
}
CHECKIF(!(atomic_get(&data->s2pi.mode) == STATUS_S2PI_GPIO_MODE)) {
LOG_ERR("S2PI is in SPI MODE");
return ERROR_S2PI_INVALID_STATE;
}
switch (pin) {
case S2PI_CS:
gpio_pin = data->s2pi.gpio.spi.cs;
break;
case S2PI_CLK:
gpio_pin = data->s2pi.gpio.spi.clk;
break;
case S2PI_MOSI:
gpio_pin = data->s2pi.gpio.spi.mosi;
break;
case S2PI_MISO:
gpio_pin = data->s2pi.gpio.spi.miso;
break;
default:
CHECKIF(true) {
__ASSERT(false, "Not implemented for pin: %d", pin);
return ERROR_FAIL;
}
}
err = gpio_pin_set_raw(gpio_pin->port, gpio_pin->pin, value);
CHECKIF(err) {
LOG_ERR("Error setting GPIO pin: %d", err);
return ERROR_FAIL;
}
/* Delay suggested by API documentation of S2PI_WriteGpioPin */
k_sleep(K_USEC(10));
return STATUS_OK;
}
status_t S2PI_ReadGpioPin(s2pi_slave_t slave, s2pi_pin_t pin, uint32_t *value)
{
struct afbr_s50_platform_data *data;
const struct gpio_dt_spec *gpio_pin;
int err;
int pin_value;
err = afbr_s50_platform_get_by_id(slave, &data);
CHECKIF(err) {
LOG_ERR("Error getting platform data: %d", err);
return ERROR_S2PI_INVALID_SLAVE;
}
CHECKIF(!(atomic_get(&data->s2pi.mode) == STATUS_S2PI_GPIO_MODE)) {
LOG_ERR("S2PI is in SPI MODE");
return ERROR_S2PI_INVALID_STATE;
}
switch (pin) {
case S2PI_CS:
gpio_pin = data->s2pi.gpio.spi.cs;
break;
case S2PI_CLK:
gpio_pin = data->s2pi.gpio.spi.clk;
break;
case S2PI_MOSI:
gpio_pin = data->s2pi.gpio.spi.mosi;
break;
case S2PI_MISO:
gpio_pin = data->s2pi.gpio.spi.miso;
break;
default:
CHECKIF(true) {
__ASSERT(false, "Not implemented for pin: %d", pin);
return ERROR_FAIL;
}
}
pin_value = gpio_pin_get_raw(gpio_pin->port, gpio_pin->pin);
*value = (uint32_t)pin_value;
return STATUS_OK;
}
static void s2pi_irq_gpio_callback(const struct device *dev,
struct gpio_callback *cb,
uint32_t pins)
{
struct afbr_s50_platform_data *data = CONTAINER_OF(cb,
struct afbr_s50_platform_data,
s2pi.irq.cb);
if (data->s2pi.irq.handler) {
data->s2pi.irq.handler(data->s2pi.irq.data);
}
}
static int s2pi_init(struct afbr_s50_platform_data *data)
{
int err;
(void)atomic_set(&data->s2pi.rtio.state, STATUS_IDLE);
(void)atomic_set(&data->s2pi.mode, STATUS_IDLE);
CHECKIF(!data->s2pi.gpio.irq || !data->s2pi.gpio.spi.cs) {
LOG_ERR("GPIOs not supplied");
return -EIO;
}
if (!gpio_is_ready_dt(data->s2pi.gpio.irq)) {
LOG_ERR("IRQ GPIO not ready");
return -EIO;
}
err = gpio_pin_configure_dt(data->s2pi.gpio.irq, GPIO_INPUT);
CHECKIF(err) {
LOG_ERR("Failed to configure IRQ GPIO");
return -EIO;
}
gpio_init_callback(&data->s2pi.irq.cb, s2pi_irq_gpio_callback,
BIT(data->s2pi.gpio.irq->pin));
err = gpio_add_callback(data->s2pi.gpio.irq->port, &data->s2pi.irq.cb);
CHECKIF(err) {
LOG_ERR("Failed to add IRQ callback");
return -EIO;
}
return 0;
}
static struct afbr_s50_platform_init_node s2pi_init_node = {
.init_fn = s2pi_init,
};
static int s2pi_platform_init_hooks(void)
{
afbr_s50_platform_init_hooks_add(&s2pi_init_node);
return 0;
}
SYS_INIT(s2pi_platform_init_hooks, POST_KERNEL, 0);