/* * Copyright (c) 2021 Abel Sensors * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "gpio_utils.h" #include LOG_MODULE_REGISTER(fxl6408, CONFIG_FXL6408_LOG_LEVEL); #define DT_DRV_COMPAT fcs_fxl6408 /* Register definitions */ #define REG_DEVICE_ID_CTRL 0x01 #define REG_DIRECTION 0x03 #define REG_OUTPUT 0x05 #define REG_OUTPUT_HIGH_Z 0x07 #define REG_INPUT_DEFAULT_STATE 0x09 #define REG_PUD_EN 0x0B #define REG_PUD_SEL 0x0D #define REG_INPUT_VALUE 0x0F #define REG_INT_MASK 0x11 #define REG_INT_STATUS 0x13 #define SUPPORTED_FLAGS (GPIO_INPUT | GPIO_OUTPUT | GPIO_OUTPUT_INIT_LOW |\ GPIO_OUTPUT_INIT_HIGH | GPIO_PULL_DOWN | GPIO_PULL_UP |\ GPIO_ACTIVE_HIGH | GPIO_ACTIVE_LOW | GPIO_INT_DEBOUNCE) /** Configuration data*/ struct gpio_fxl6408_config { /* gpio_driver_config needs to be first */ struct gpio_driver_config common; /** Master I2C device */ const struct device *i2c_master; /** The slave address of the chip */ uint16_t i2c_slave_addr; }; /** Runtime driver data */ struct gpio_fxl6408_drv_data { /* gpio_driver_data needs to be first */ struct gpio_driver_data common; struct { uint8_t input; uint8_t output; uint8_t dir; uint8_t high_z; uint8_t pud_en; uint8_t pud_sel; } reg_cache; struct k_sem lock; }; /** * @brief Read the port of certain register function. * * @param dev Device struct of the FXL6408. * @param reg Register to read. * @param cache Pointer to the cache to be updated after successful read. * * @return 0 if successful, failed otherwise. */ static int read_port_regs(const struct device *dev, uint8_t reg, uint8_t *cache) { const struct gpio_fxl6408_config *const config = dev->config; const struct device *i2c_master = config->i2c_master; uint16_t i2c_addr = config->i2c_slave_addr; uint8_t port_data; int ret; ret = i2c_reg_read_byte(i2c_master, i2c_addr, reg, &port_data); if (ret != 0) { LOG_ERR("Error reading register 0x%X (%d)", reg, ret); return ret; } *cache = port_data; LOG_DBG("Read: REG[0x%X] = 0x%X", reg, *cache); return ret; } /** * @brief Write to the port registers of certain register function. * * @param dev Device struct of the FXL6408. * @param reg Register to write into. Possible values: REG_DEVICE_ID_CTRL, * REG_OUTPUT, REG_DIRECTION, REG_PUD_SEL, REG_PUD_EN, REG_OUTPUT_HIGH_Z and * REG_INPUT_DEFAULT. * @param cache Pointer to the cache to be updated after successful write. * @param value New value to set. * * @return 0 if successful, failed otherwise. */ static int write_port_regs(const struct device *dev, uint8_t reg, uint8_t *cache, uint8_t value) { const struct gpio_fxl6408_config *const config = dev->config; const struct device *i2c_master = config->i2c_master; int ret = 0; if (*cache != value) { ret = i2c_reg_write_byte(i2c_master, config->i2c_slave_addr, reg, value); if (ret != 0) { LOG_ERR("error writing to register 0x%X (%d)", reg, ret); return ret; } *cache = value; LOG_DBG("Write: REG[0x%X] = 0x%X", reg, *cache); } return ret; } static inline int update_input_regs(const struct device *dev, uint8_t *buf) { struct gpio_fxl6408_drv_data *const drv_data = (struct gpio_fxl6408_drv_data *const)dev->data; int ret = read_port_regs(dev, REG_INPUT_VALUE, &drv_data->reg_cache.input); *buf = drv_data->reg_cache.input; return ret; } static inline int update_output_regs(const struct device *dev, uint8_t value) { struct gpio_fxl6408_drv_data *const drv_data = (struct gpio_fxl6408_drv_data *const)dev->data; return write_port_regs(dev, REG_OUTPUT, &drv_data->reg_cache.output, value); } static inline int update_high_z_regs(const struct device *dev, uint8_t value) { struct gpio_fxl6408_drv_data *const drv_data = (struct gpio_fxl6408_drv_data *const)dev->data; return write_port_regs(dev, REG_OUTPUT_HIGH_Z, &drv_data->reg_cache.high_z, value); } static inline int update_direction_regs(const struct device *dev, uint8_t value) { struct gpio_fxl6408_drv_data *const drv_data = (struct gpio_fxl6408_drv_data *const)dev->data; return write_port_regs(dev, REG_DIRECTION, &drv_data->reg_cache.dir, value); } static inline int update_pul_sel_regs(const struct device *dev, uint8_t value) { struct gpio_fxl6408_drv_data *const drv_data = (struct gpio_fxl6408_drv_data *const)dev->data; return write_port_regs(dev, REG_PUD_SEL, &drv_data->reg_cache.pud_sel, value); } static inline int update_pul_en_regs(const struct device *dev, uint8_t value) { struct gpio_fxl6408_drv_data *const drv_data = (struct gpio_fxl6408_drv_data *const)dev->data; return write_port_regs(dev, REG_PUD_EN, &drv_data->reg_cache.pud_en, value); } static int setup_pin_dir(const struct device *dev, uint32_t pin, int flags) { struct gpio_fxl6408_drv_data *const drv_data = (struct gpio_fxl6408_drv_data *const)dev->data; uint8_t reg_dir = drv_data->reg_cache.dir; uint8_t reg_out = drv_data->reg_cache.output; uint8_t reg_high_z = drv_data->reg_cache.high_z; int ret; if (((flags & GPIO_INPUT) != 0) && ((flags & GPIO_OUTPUT) != 0)) { return -ENOTSUP; } /* Update the driver data to the actual situation of the FXL6408 */ if (flags & GPIO_OUTPUT) { if ((flags & GPIO_OUTPUT_INIT_HIGH)) { reg_out |= BIT(pin); } else if ((flags & GPIO_OUTPUT_INIT_LOW)) { reg_out &= ~BIT(pin); } reg_dir |= BIT(pin); reg_high_z &= ~BIT(pin); } else if (flags & GPIO_INPUT) { reg_dir &= ~BIT(pin); reg_high_z &= ~BIT(pin); } else { reg_high_z |= BIT(pin); reg_dir |= BIT(pin); } ret = update_output_regs(dev, reg_out); if (ret != 0) { return ret; } ret = update_high_z_regs(dev, reg_high_z); if (ret != 0) { return ret; } ret = update_direction_regs(dev, reg_dir); return ret; } /** * @brief Setup the pin pull up/pull down status * * @param dev Device struct of the FXL6408 * @param pin The pin number * @param flags Flags of pin or port * * @return 0 if successful, failed otherwise */ static int setup_pin_pullupdown(const struct device *dev, uint32_t pin, int flags) { struct gpio_fxl6408_drv_data *const drv_data = (struct gpio_fxl6408_drv_data *const)dev->data; uint8_t reg_pud; int ret; /* If disabling pull up/down, there is no need to set the selection * register. Just go straight to disabling. */ if ((flags & (GPIO_PULL_UP | GPIO_PULL_DOWN)) != 0U) { /* Setup pin pull up or pull down */ reg_pud = drv_data->reg_cache.pud_sel; /* Pull down == 0, pull up == 1 */ WRITE_BIT(reg_pud, pin, (flags & GPIO_PULL_UP) != 0U); ret = update_pul_sel_regs(dev, reg_pud); if (ret != 0) { return ret; } } /* Enable/disable pull up/down */ reg_pud = drv_data->reg_cache.pud_en; WRITE_BIT(reg_pud, pin, (flags & (GPIO_PULL_UP | GPIO_PULL_DOWN)) != 0U); ret = update_pul_en_regs(dev, reg_pud); return ret; } static int gpio_fxl6408_pin_config(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags) { struct gpio_fxl6408_drv_data *const drv_data = (struct gpio_fxl6408_drv_data *const)dev->data; int ret; /* Check if supported flag is set */ if ((flags & ~SUPPORTED_FLAGS) != 0) { return -ENOTSUP; } /* Can't do I2C bus operations from an ISR */ if (k_is_in_isr()) { return -EWOULDBLOCK; } k_sem_take(&drv_data->lock, K_FOREVER); ret = setup_pin_dir(dev, pin, flags); if (ret != 0) { LOG_ERR("error setting pin direction (%d)", ret); goto done; } ret = setup_pin_pullupdown(dev, pin, flags); if (ret) { LOG_ERR("error setting pin pull up/down (%d)", ret); goto done; } done: k_sem_give(&drv_data->lock); return ret; } static int gpio_fxl6408_port_get_raw(const struct device *dev, uint32_t *value) { struct gpio_fxl6408_drv_data *const drv_data = (struct gpio_fxl6408_drv_data *const)dev->data; uint8_t buf = 0; int ret = 0; /* Can't do I2C bus operations from an ISR */ if (k_is_in_isr()) { return -EWOULDBLOCK; } k_sem_take(&drv_data->lock, K_FOREVER); ret = update_input_regs(dev, &buf); if (ret != 0) { goto done; } *value = buf; done: k_sem_give(&drv_data->lock); return ret; } static int gpio_fxl6408_port_set_masked_raw(const struct device *dev, uint32_t mask, uint32_t value) { struct gpio_fxl6408_drv_data *const drv_data = (struct gpio_fxl6408_drv_data *const)dev->data; uint8_t reg_out; int ret; /* Can't do I2C bus operations from an ISR */ if (k_is_in_isr()) { return -EWOULDBLOCK; } k_sem_take(&drv_data->lock, K_FOREVER); reg_out = drv_data->reg_cache.output; reg_out = (reg_out & ~mask) | (mask & value); ret = update_output_regs(dev, reg_out); k_sem_give(&drv_data->lock); return ret; } static int gpio_fxl6408_port_set_bits_raw(const struct device *dev, uint32_t mask) { return gpio_fxl6408_port_set_masked_raw(dev, mask, mask); } static int gpio_fxl6408_port_clear_bits_raw(const struct device *dev, uint32_t mask) { return gpio_fxl6408_port_set_masked_raw(dev, mask, 0); } static int gpio_fxl6408_port_toggle_bits(const struct device *dev, uint32_t mask) { struct gpio_fxl6408_drv_data *const drv_data = (struct gpio_fxl6408_drv_data *const)dev->data; uint8_t reg_out; int ret; /* Can't do I2C bus operations from an ISR */ if (k_is_in_isr()) { return -EWOULDBLOCK; } k_sem_take(&drv_data->lock, K_FOREVER); reg_out = drv_data->reg_cache.output; reg_out ^= mask; ret = update_output_regs(dev, reg_out); k_sem_give(&drv_data->lock); return ret; } static int gpio_fxl6408_pin_interrupt_configure(const struct device *port, gpio_pin_t pin, enum gpio_int_mode mode, enum gpio_int_trig trig) { LOG_DBG("Pin interrupts not supported."); return -ENOTSUP; } int gpio_fxl6408_init(const struct device *dev) { struct gpio_fxl6408_drv_data *const drv_data = (struct gpio_fxl6408_drv_data *const)dev->data; const struct gpio_fxl6408_config *const config = dev->config; if (!device_is_ready(config->i2c_master)) { LOG_ERR("%s is not ready", config->i2c_master->name); return -ENODEV; } k_sem_init(&drv_data->lock, 1, 1); return 0; } static const struct gpio_driver_api gpio_fxl_driver = { .pin_configure = gpio_fxl6408_pin_config, .port_get_raw = gpio_fxl6408_port_get_raw, .port_set_masked_raw = gpio_fxl6408_port_set_masked_raw, .port_set_bits_raw = gpio_fxl6408_port_set_bits_raw, .port_clear_bits_raw = gpio_fxl6408_port_clear_bits_raw, .port_toggle_bits = gpio_fxl6408_port_toggle_bits, .pin_interrupt_configure = gpio_fxl6408_pin_interrupt_configure }; #define GPIO_FXL6408_DEVICE_INSTANCE(inst) \ static const struct gpio_fxl6408_config gpio_fxl6408_##inst##_cfg = { \ .common = { \ .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(inst),\ }, \ .i2c_slave_addr = DT_INST_REG_ADDR(inst), \ .i2c_master = DEVICE_DT_GET(DT_INST_BUS(inst)) \ }; \ \ static struct gpio_fxl6408_drv_data gpio_fxl6408_##inst##_drvdata = { \ .reg_cache = { \ .input = 0x0, \ .output = 0x00, \ .dir = 0x0, \ .high_z = 0xFF, \ .pud_en = 0xFF, \ .pud_sel = 0x0 \ } \ }; \ \ DEVICE_DT_INST_DEFINE(inst, gpio_fxl6408_init, NULL, \ &gpio_fxl6408_##inst##_drvdata, \ &gpio_fxl6408_##inst##_cfg, POST_KERNEL, \ CONFIG_GPIO_FXL6408_INIT_PRIORITY, \ &gpio_fxl_driver); DT_INST_FOREACH_STATUS_OKAY(GPIO_FXL6408_DEVICE_INSTANCE)