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.
KED/peripherals/gpio/gpio.c

174 lines
5.0 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* @file gpio_linux.c
* @brief GPIO implementation for Linux using libgpiod and KED function pointer injection.
*/
#include "../../ked/peripherals/gpio/gpio.h"
#include <gpiod.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/**
* @brief Internal Linux GPIO context.
*
* Holds the active chip and line handles.
* This implementation assumes one active GPIO at a time (can be extended to multiple if needed).
*/
typedef struct {
struct gpiod_chip* chip;
struct gpiod_line* line;
} gpio_linux_ctx_t;
static gpio_linux_ctx_t gpio_ctx;
/* === Centralized Error Handling === */
/**
* @brief Centralized error handler for GPIO-related failures.
*
* Logs the error to stderr and can be extended to trigger hooks or diagnostics.
*
* @param err The GPIO error code encountered.
*/
static void gpio_linux_handle_error(gpio_error_t err) {
fprintf(stderr, "[GPIO/Linux] ERROR: %d\n", err);
// Future: report to diagnostic logger or panic handler
}
/* === GPIO Operation Implementations === */
/**
* @brief Set the output level of the GPIO line.
*
* @param value 1 = HIGH, 0 = LOW
*/
static void gpio_linux_set(uint8_t value) {
if (gpio_ctx.line) {
gpiod_line_set_value(gpio_ctx.line, value);
} else {
gpio_linux_handle_error(T_GPIO_ERR_NOT_AVAILABLE);
}
}
/**
* @brief Read the current logic level of the GPIO line.
*
* @return Logic level (1 = HIGH, 0 = LOW)
*/
static uint8_t gpio_linux_read(void) {
if (!gpio_ctx.line) {
gpio_linux_handle_error(T_GPIO_ERR_NOT_AVAILABLE);
return 0;
}
return (uint8_t)gpiod_line_get_value(gpio_ctx.line);
}
/**
* @brief Toggle the current logic level of the GPIO line.
*/
static void gpio_linux_toggle(void) {
if (!gpio_ctx.line) {
gpio_linux_handle_error(T_GPIO_ERR_NOT_AVAILABLE);
return;
}
uint8_t current = (uint8_t)gpiod_line_get_value(gpio_ctx.line);
gpiod_line_set_value(gpio_ctx.line, !current);
}
/**
* @brief Update GPIO configuration dynamically.
*
* Currently not supported on Linux placeholder only.
*/
static gpio_error_t gpio_linux_update(void) {
// TODO: implement runtime reconfiguration if needed
return T_GPIO_ERR_UNSUPPORTED_MODE;
}
/* === Static GPIO Function Table Instance === */
/**
* @brief Shared Linux GPIO function table for all gpio_t instances.
*
* Each GPIO object holds a pointer to this table via its `vtable` field.
* This avoids redundant copies of function pointers per object.
*/
static const gpio_fn_ptr_t gpio_linux_fn = {
.set = gpio_linux_set,
.read = gpio_linux_read,
.toggle = gpio_linux_toggle,
.update = gpio_linux_update,
.on_error = gpio_linux_handle_error
};
/* === Public API Implementation === */
/**
* @brief Initialize the GPIO using the given Linux gpio_t config and callback struct.
*
* This sets direction, bias, and binds callbacks to the gpio_fn_ptr_t table.
*
* @param gpio Pointer to GPIO configuration structure
* @param callbacks Optional pointer to gpio_fn_ptr_t to receive platform-specific handlers
* @return T_GPIO_ERR_OK on success, or appropriate error code
*/
gpio_error_t ked_gpio_init(gpio_t* gpio) {
gpio_ctx.chip = gpiod_chip_open(gpio->chipname);
if (!gpio_ctx.chip) {
gpio_linux_handle_error(T_GPIO_ERR_NOT_AVAILABLE);
return T_GPIO_ERR_NOT_AVAILABLE;
}
gpio_ctx.line = gpiod_chip_get_line(gpio_ctx.chip, gpio->line_offset);
if (!gpio_ctx.line) {
gpiod_chip_close(gpio_ctx.chip);
gpio_linux_handle_error(T_GPIO_ERR_NOT_AVAILABLE);
return T_GPIO_ERR_NOT_AVAILABLE;
}
struct gpiod_line_request_config req_cfg = {
.consumer = "ked_gpio",
.request_type = (gpio->direction == T_GPIO_DIRECTION_OUTPUT)
? GPIOD_LINE_REQUEST_DIRECTION_OUTPUT
: GPIOD_LINE_REQUEST_DIRECTION_INPUT,
.flags = 0
};
if (gpio->bias == T_GPIO_BIAS_PULL_UP)
req_cfg.flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP;
else if (gpio->bias == T_GPIO_BIAS_PULL_DOWN)
req_cfg.flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN;
else if (gpio->bias == T_GPIO_BIAS_DISABLE)
req_cfg.flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLE;
else if (gpio->bias == T_GPIO_BIAS_DEFAULT)
req_cfg.request_type = GPIOD_LINE_REQUEST_DIRECTION_AS_IS;
if (gpiod_line_request(gpio_ctx.line, &req_cfg, 0) < 0) {
gpiod_chip_close(gpio_ctx.chip);
gpio_linux_handle_error(T_GPIO_ERR_BUS_BUSY);
return T_GPIO_ERR_BUS_BUSY;
}
gpio->func = &gpio_linux_fn;
return T_GPIO_ERR_OK;
}
/**
* @brief Deinitialize GPIO from platform and free resources.
*
* @param gpio Unused in this implementation
* @param callbacks Pointer to callback table that includes `deinit`
*/
void ked_gpio_deinit(gpio_t* gpio) {
if (gpio_ctx.line) {
gpiod_line_release(gpio_ctx.line);
gpio_ctx.line = NULL;
}
if (gpio_ctx.chip) {
gpiod_chip_close(gpio_ctx.chip);
gpio_ctx.chip = NULL;
}
}