/* * Copyright (c) 2025 Croxel Inc. * Copyright (c) 2025 CogniPilot Foundation * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include /** Used to get instance data from slave index */ #include <../drivers/sensor/broadcom/afbr_s50/platform.h> #include 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);