| /* |
| * Copyright (c) 2018 Aapo Vienamo |
| * Copyright (c) 2018 Peter Bigot Consulting, LLC |
| * Copyright (c) 2019-2020 Nordic Semiconductor ASA |
| * Copyright (c) 2020 ZedBlox Ltd. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT semtech_sx1509b |
| |
| #include <errno.h> |
| |
| #include <kernel.h> |
| #include <device.h> |
| #include <init.h> |
| #include <drivers/gpio.h> |
| #include <drivers/gpio/gpio_sx1509b.h> |
| #include <drivers/i2c.h> |
| #include <sys/byteorder.h> |
| #include <sys/util.h> |
| |
| #include <logging/log.h> |
| LOG_MODULE_REGISTER(sx1509b, CONFIG_GPIO_LOG_LEVEL); |
| |
| #include "gpio_utils.h" |
| |
| /* Number of pins supported by the device */ |
| #define NUM_PINS 16 |
| |
| /* Max to select all pins supported on the device. */ |
| #define ALL_PINS ((uint16_t)BIT_MASK(NUM_PINS)) |
| |
| /* Reset delay is 2.5 ms, round up for Zephyr resolution */ |
| #define RESET_DELAY_MS 3 |
| |
| /** Cache of the output configuration and data of the pins. */ |
| struct sx1509b_pin_state { |
| uint16_t input_disable; /* 0x00 */ |
| uint16_t long_slew; /* 0x02 */ |
| uint16_t low_drive; /* 0x04 */ |
| uint16_t pull_up; /* 0x06 */ |
| uint16_t pull_down; /* 0x08 */ |
| uint16_t open_drain; /* 0x0A */ |
| uint16_t polarity; /* 0x0C */ |
| uint16_t dir; /* 0x0E */ |
| uint16_t data; /* 0x10 */ |
| } __packed; |
| |
| struct sx1509b_irq_state { |
| uint16_t interrupt_mask; /* 0x12 */ |
| uint32_t interrupt_sense; /* 0x14, 0x16 */ |
| } __packed; |
| |
| struct sx1509b_debounce_state { |
| uint8_t debounce_config; /* 0x22 */ |
| uint16_t debounce_enable; /* 0x23 */ |
| } __packed; |
| |
| /** Runtime driver data */ |
| struct sx1509b_drv_data { |
| /* gpio_driver_data needs to be first */ |
| struct gpio_driver_data common; |
| const struct device *i2c_master; |
| struct sx1509b_pin_state pin_state; |
| uint16_t led_drv_enable; |
| struct sx1509b_debounce_state debounce_state; |
| struct k_sem lock; |
| |
| #ifdef CONFIG_GPIO_SX1509B_INTERRUPT |
| const struct device *gpio_int; |
| struct gpio_callback gpio_cb; |
| struct k_work work; |
| struct sx1509b_irq_state irq_state; |
| const struct device *dev; |
| /* user ISR cb */ |
| sys_slist_t cb; |
| #endif /* CONFIG_GPIO_SX1509B_INTERRUPT */ |
| |
| }; |
| |
| /** Configuration data */ |
| struct sx1509b_config { |
| /* gpio_driver_config needs to be first */ |
| struct gpio_driver_config common; |
| const char *i2c_master_dev_name; |
| #ifdef CONFIG_GPIO_SX1509B_INTERRUPT |
| const char *gpio_int_dev_name; |
| gpio_pin_t gpio_pin; |
| gpio_dt_flags_t gpio_flags; |
| #endif /* CONFIG_GPIO_SX1509B_INTERRUPT */ |
| uint16_t i2c_slave_addr; |
| }; |
| |
| /* General configuration register addresses */ |
| enum { |
| /* TODO: Add rest of the regs */ |
| SX1509B_REG_CLOCK = 0x1e, |
| SX1509B_REG_RESET = 0x7d, |
| }; |
| |
| /* Magic values for softreset */ |
| enum { |
| SX1509B_REG_RESET_MAGIC0 = 0x12, |
| SX1509B_REG_RESET_MAGIC1 = 0x34, |
| }; |
| |
| /* Register bits for SX1509B_REG_CLOCK */ |
| enum { |
| SX1509B_REG_CLOCK_FOSC_OFF = 0 << 5, |
| SX1509B_REG_CLOCK_FOSC_EXT = 1 << 5, |
| SX1509B_REG_CLOCK_FOSC_INT_2MHZ = 2 << 5, |
| }; |
| |
| /* Register bits for SX1509B_REG_MISC */ |
| enum { |
| SX1509B_REG_MISC_LOG_A = 1 << 3, |
| SX1509B_REG_MISC_LOG_B = 1 << 7, |
| /* ClkX = fOSC */ |
| SX1509B_REG_MISC_FREQ = 1 << 4, |
| }; |
| |
| /* Pin configuration register addresses */ |
| enum { |
| SX1509B_REG_INPUT_DISABLE = 0x00, |
| SX1509B_REG_PULL_UP = 0x06, |
| SX1509B_REG_PULL_DOWN = 0x08, |
| SX1509B_REG_OPEN_DRAIN = 0x0a, |
| SX1509B_REG_DIR = 0x0e, |
| SX1509B_REG_DATA = 0x10, |
| SX1509B_REG_INTERRUPT_MASK = 0x12, |
| SX1509B_REG_INTERRUPT_SENSE = 0x14, |
| SX1509B_REG_INTERRUPT_SENSE_B = 0x14, |
| SX1509B_REG_INTERRUPT_SENSE_A = 0x16, |
| SX1509B_REG_INTERRUPT_SOURCE = 0x18, |
| SX1509B_REG_MISC = 0x1f, |
| SX1509B_REG_LED_DRV_ENABLE = 0x20, |
| SX1509B_REG_DEBOUNCE_CONFIG = 0x22, |
| SX1509B_REG_DEBOUNCE_ENABLE = 0x23, |
| }; |
| |
| /* Edge sensitivity types */ |
| enum { |
| SX1509B_EDGE_NONE = 0x00, |
| SX1509B_EDGE_RISING = 0x01, |
| SX1509B_EDGE_FALLING = 0x02, |
| SX1509B_EDGE_BOTH = 0x03, |
| }; |
| |
| /* Intensity register addresses for all 16 pins */ |
| static const uint8_t intensity_registers[16] = { 0x2a, 0x2d, 0x30, 0x33, |
| 0x36, 0x3b, 0x40, 0x45, |
| 0x4a, 0x4d, 0x50, 0x53, |
| 0x56, 0x5b, 0x60, 0x65 }; |
| |
| /** |
| * @brief Write a big-endian word to an internal address of an I2C slave. |
| * |
| * @param dev Pointer to the device structure for the driver instance. |
| * @param dev_addr Address of the I2C device for writing. |
| * @param reg_addr Address of the internal register being written. |
| * @param value Value to be written to internal register. |
| * |
| * @retval 0 If successful. |
| * @retval -EIO General input / output error. |
| */ |
| static inline int i2c_reg_write_word_be(const struct device *dev, |
| uint16_t dev_addr, |
| uint8_t reg_addr, uint16_t value) |
| { |
| uint8_t tx_buf[3] = { reg_addr, value >> 8, value & 0xff }; |
| |
| return i2c_write(dev, tx_buf, 3, dev_addr); |
| } |
| |
| /** |
| * @brief Write a big-endian byte to an internal address of an I2C slave. |
| * |
| * @param dev Pointer to the device structure for the driver instance. |
| * @param dev_addr Address of the I2C device for writing. |
| * @param reg_addr Address of the internal register being written. |
| * @param value Value to be written to internal register. |
| * |
| * @retval 0 If successful. |
| * @retval -EIO General input / output error. |
| */ |
| static inline int i2c_reg_write_byte_be(const struct device *dev, |
| uint16_t dev_addr, |
| uint8_t reg_addr, uint8_t value) |
| { |
| uint8_t tx_buf[3] = { reg_addr, value }; |
| |
| return i2c_write(dev, tx_buf, 2, dev_addr); |
| } |
| |
| #ifdef CONFIG_GPIO_SX1509B_INTERRUPT |
| static int sx1509b_handle_interrupt(const struct device *dev) |
| { |
| const struct sx1509b_config *cfg = dev->config; |
| struct sx1509b_drv_data *drv_data = dev->data; |
| int ret = 0; |
| uint16_t int_source; |
| uint8_t cmd = SX1509B_REG_INTERRUPT_SOURCE; |
| |
| k_sem_take(&drv_data->lock, K_FOREVER); |
| |
| ret = i2c_write_read(drv_data->i2c_master, cfg->i2c_slave_addr, |
| &cmd, sizeof(cmd), |
| (uint8_t *)&int_source, sizeof(int_source)); |
| if (ret != 0) { |
| goto out; |
| } |
| |
| int_source = sys_be16_to_cpu(int_source); |
| |
| /* reset interrupts before invoking callbacks */ |
| ret = i2c_reg_write_word_be(drv_data->i2c_master, cfg->i2c_slave_addr, |
| SX1509B_REG_INTERRUPT_SOURCE, int_source); |
| |
| out: |
| k_sem_give(&drv_data->lock); |
| |
| if (ret == 0) { |
| gpio_fire_callbacks(&drv_data->cb, dev, int_source); |
| } |
| |
| return ret; |
| } |
| |
| static void sx1509b_work_handler(struct k_work *work) |
| { |
| struct sx1509b_drv_data *drv_data = |
| CONTAINER_OF(work, struct sx1509b_drv_data, work); |
| |
| sx1509b_handle_interrupt(drv_data->dev); |
| } |
| |
| static void sx1509_int_cb(const struct device *dev, |
| struct gpio_callback *gpio_cb, |
| uint32_t pins) |
| { |
| struct sx1509b_drv_data *drv_data = CONTAINER_OF(gpio_cb, |
| struct sx1509b_drv_data, gpio_cb); |
| |
| ARG_UNUSED(pins); |
| |
| k_work_submit(&drv_data->work); |
| } |
| #endif |
| |
| static int write_pin_state(const struct sx1509b_config *cfg, |
| struct sx1509b_drv_data *drv_data, |
| struct sx1509b_pin_state *pins, bool data_first) |
| { |
| struct { |
| uint8_t reg; |
| struct sx1509b_pin_state pins; |
| } __packed pin_buf; |
| int rc; |
| |
| pin_buf.reg = SX1509B_REG_INPUT_DISABLE; |
| pin_buf.pins.input_disable = sys_cpu_to_be16(pins->input_disable); |
| pin_buf.pins.long_slew = sys_cpu_to_be16(pins->long_slew); |
| pin_buf.pins.low_drive = sys_cpu_to_be16(pins->low_drive); |
| pin_buf.pins.pull_up = sys_cpu_to_be16(pins->pull_up); |
| pin_buf.pins.pull_down = sys_cpu_to_be16(pins->pull_down); |
| pin_buf.pins.open_drain = sys_cpu_to_be16(pins->open_drain); |
| pin_buf.pins.polarity = sys_cpu_to_be16(pins->polarity); |
| pin_buf.pins.dir = sys_cpu_to_be16(pins->dir); |
| pin_buf.pins.data = sys_cpu_to_be16(pins->data); |
| |
| if (data_first) { |
| rc = i2c_reg_write_word_be(drv_data->i2c_master, |
| cfg->i2c_slave_addr, |
| SX1509B_REG_DATA, pins->data); |
| if (rc == 0) { |
| rc = i2c_write(drv_data->i2c_master, &pin_buf.reg, |
| sizeof(pin_buf) - sizeof(pins->data), |
| cfg->i2c_slave_addr); |
| } |
| } else { |
| rc = i2c_write(drv_data->i2c_master, &pin_buf.reg, |
| sizeof(pin_buf), cfg->i2c_slave_addr); |
| } |
| |
| return rc; |
| } |
| |
| static int sx1509b_config(const struct device *dev, |
| gpio_pin_t pin, |
| gpio_flags_t flags) |
| { |
| const struct sx1509b_config *cfg = dev->config; |
| struct sx1509b_drv_data *drv_data = dev->data; |
| struct sx1509b_pin_state *pins = &drv_data->pin_state; |
| struct sx1509b_debounce_state *debounce = &drv_data->debounce_state; |
| int rc = 0; |
| bool data_first = false; |
| |
| /* Can't do I2C bus operations from an ISR */ |
| if (k_is_in_isr()) { |
| return -EWOULDBLOCK; |
| } |
| |
| /* Zephyr currently defines drive strength support based on |
| * the behavior and capabilities of the Nordic GPIO |
| * peripheral: strength defaults to low but can be set high, |
| * and is controlled independently for output levels. |
| * |
| * SX150x defaults to high strength, and does not support |
| * different strengths for different levels. |
| * |
| * Until something more general is available reject any |
| * attempt to set a non-default drive strength. |
| */ |
| if ((flags & (GPIO_DS_ALT_LOW | GPIO_DS_ALT_HIGH)) != 0) { |
| return -ENOTSUP; |
| } |
| |
| k_sem_take(&drv_data->lock, K_FOREVER); |
| |
| if (drv_data->led_drv_enable & BIT(pin)) { |
| /* Disable LED driver */ |
| drv_data->led_drv_enable &= ~BIT(pin); |
| rc = i2c_reg_write_word_be(drv_data->i2c_master, |
| cfg->i2c_slave_addr, |
| SX1509B_REG_LED_DRV_ENABLE, |
| drv_data->led_drv_enable); |
| |
| if (rc) { |
| goto out; |
| } |
| } |
| |
| pins->open_drain &= ~BIT(pin); |
| if ((flags & GPIO_SINGLE_ENDED) != 0) { |
| if ((flags & GPIO_LINE_OPEN_DRAIN) != 0) { |
| pins->open_drain |= BIT(pin); |
| } else { |
| /* Open source not supported */ |
| rc = -ENOTSUP; |
| goto out; |
| } |
| } |
| |
| if ((flags & GPIO_PULL_UP) != 0) { |
| pins->pull_up |= BIT(pin); |
| } else { |
| pins->pull_up &= ~BIT(pin); |
| } |
| if ((flags & GPIO_PULL_DOWN) != 0) { |
| pins->pull_down |= BIT(pin); |
| } else { |
| pins->pull_down &= ~BIT(pin); |
| } |
| |
| if ((flags & GPIO_INPUT) != 0) { |
| pins->input_disable &= ~BIT(pin); |
| } else { |
| pins->input_disable |= BIT(pin); |
| } |
| |
| if ((flags & GPIO_OUTPUT) != 0) { |
| pins->dir &= ~BIT(pin); |
| if ((flags & GPIO_OUTPUT_INIT_LOW) != 0) { |
| pins->data &= ~BIT(pin); |
| data_first = true; |
| } else if ((flags & GPIO_OUTPUT_INIT_HIGH) != 0) { |
| pins->data |= BIT(pin); |
| data_first = true; |
| } |
| } else { |
| pins->dir |= BIT(pin); |
| } |
| |
| if ((flags & GPIO_INT_DEBOUNCE) != 0) { |
| debounce->debounce_enable |= BIT(pin); |
| } else { |
| debounce->debounce_enable &= ~BIT(pin); |
| } |
| |
| LOG_DBG("CFG %u %x : ID %04x ; PU %04x ; PD %04x ; DIR %04x ; DAT %04x", |
| pin, flags, |
| pins->input_disable, pins->pull_up, pins->pull_down, |
| pins->dir, pins->data); |
| |
| rc = write_pin_state(cfg, drv_data, pins, data_first); |
| |
| if (rc == 0) { |
| struct { |
| uint8_t reg; |
| struct sx1509b_debounce_state debounce; |
| } __packed debounce_buf; |
| |
| debounce_buf.reg = SX1509B_REG_DEBOUNCE_CONFIG; |
| debounce_buf.debounce.debounce_config |
| = debounce->debounce_config; |
| debounce_buf.debounce.debounce_enable |
| = sys_cpu_to_be16(debounce->debounce_enable); |
| |
| rc = i2c_write(drv_data->i2c_master, |
| &debounce_buf.reg, |
| sizeof(debounce_buf), |
| cfg->i2c_slave_addr); |
| } |
| |
| out: |
| k_sem_give(&drv_data->lock); |
| return rc; |
| } |
| |
| static int port_get(const struct device *dev, |
| gpio_port_value_t *value) |
| { |
| const struct sx1509b_config *cfg = dev->config; |
| struct sx1509b_drv_data *drv_data = dev->data; |
| uint16_t pin_data; |
| int rc = 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); |
| |
| uint8_t cmd = SX1509B_REG_DATA; |
| |
| rc = i2c_write_read(drv_data->i2c_master, cfg->i2c_slave_addr, |
| &cmd, sizeof(cmd), |
| &pin_data, sizeof(pin_data)); |
| LOG_DBG("read %04x got %d", sys_be16_to_cpu(pin_data), rc); |
| if (rc != 0) { |
| goto out; |
| } |
| |
| *value = sys_be16_to_cpu(pin_data); |
| |
| out: |
| k_sem_give(&drv_data->lock); |
| return rc; |
| } |
| |
| static int port_write(const struct device *dev, |
| gpio_port_pins_t mask, |
| gpio_port_value_t value, |
| gpio_port_value_t toggle) |
| { |
| /* Can't do I2C bus operations from an ISR */ |
| if (k_is_in_isr()) { |
| return -EWOULDBLOCK; |
| } |
| |
| const struct sx1509b_config *cfg = dev->config; |
| struct sx1509b_drv_data *drv_data = dev->data; |
| uint16_t *outp = &drv_data->pin_state.data; |
| |
| k_sem_take(&drv_data->lock, K_FOREVER); |
| |
| uint16_t orig_out = *outp; |
| uint16_t out = ((orig_out & ~mask) | (value & mask)) ^ toggle; |
| int rc = i2c_reg_write_word_be(drv_data->i2c_master, cfg->i2c_slave_addr, |
| SX1509B_REG_DATA, out); |
| if (rc == 0) { |
| *outp = out; |
| } |
| |
| k_sem_give(&drv_data->lock); |
| |
| LOG_DBG("write %04x msk %04x val %04x => %04x: %d", orig_out, mask, value, out, rc); |
| |
| return rc; |
| } |
| |
| static int port_set_masked(const struct device *dev, |
| gpio_port_pins_t mask, |
| gpio_port_value_t value) |
| { |
| return port_write(dev, mask, value, 0); |
| } |
| |
| static int port_set_bits(const struct device *dev, |
| gpio_port_pins_t pins) |
| { |
| return port_write(dev, pins, pins, 0); |
| } |
| |
| static int port_clear_bits(const struct device *dev, |
| gpio_port_pins_t pins) |
| { |
| return port_write(dev, pins, 0, 0); |
| } |
| |
| static int port_toggle_bits(const struct device *dev, |
| gpio_port_pins_t pins) |
| { |
| return port_write(dev, 0, 0, pins); |
| } |
| |
| static int pin_interrupt_configure(const struct device *dev, |
| gpio_pin_t pin, |
| enum gpio_int_mode mode, |
| enum gpio_int_trig trig) |
| { |
| int rc = 0; |
| |
| if (!IS_ENABLED(CONFIG_GPIO_SX1509B_INTERRUPT) |
| && (mode != GPIO_INT_MODE_DISABLED)) { |
| return -ENOTSUP; |
| } |
| |
| #ifdef CONFIG_GPIO_SX1509B_INTERRUPT |
| /* Device does not support level-triggered interrupts. */ |
| if (mode == GPIO_INT_MODE_LEVEL) { |
| return -ENOTSUP; |
| } |
| |
| const struct sx1509b_config *cfg = dev->config; |
| struct sx1509b_drv_data *drv_data = dev->data; |
| struct sx1509b_irq_state *irq = &drv_data->irq_state; |
| struct { |
| uint8_t reg; |
| struct sx1509b_irq_state irq; |
| } __packed irq_buf; |
| |
| /* Only level triggered interrupts are supported, and those |
| * only if interrupt support is enabled. |
| */ |
| if (IS_ENABLED(CONFIG_GPIO_SX1509B_INTERRUPT)) { |
| if (mode == GPIO_INT_MODE_LEVEL) { |
| return -ENOTSUP; |
| } |
| } else if (mode != GPIO_INT_MODE_DISABLED) { |
| return -ENOTSUP; |
| } |
| |
| k_sem_take(&drv_data->lock, K_FOREVER); |
| |
| irq->interrupt_sense &= ~(SX1509B_EDGE_BOTH << (pin * 2)); |
| if (mode == GPIO_INT_MODE_DISABLED) { |
| irq->interrupt_mask |= BIT(pin); |
| } else { /* GPIO_INT_MODE_EDGE */ |
| irq->interrupt_mask &= ~BIT(pin); |
| if (trig == GPIO_INT_TRIG_BOTH) { |
| irq->interrupt_sense |= (SX1509B_EDGE_BOTH << |
| (pin * 2)); |
| } else if (trig == GPIO_INT_TRIG_LOW) { |
| irq->interrupt_sense |= (SX1509B_EDGE_FALLING << |
| (pin * 2)); |
| } else if (trig == GPIO_INT_TRIG_HIGH) { |
| irq->interrupt_sense |= (SX1509B_EDGE_RISING << |
| (pin * 2)); |
| } |
| } |
| |
| irq_buf.reg = SX1509B_REG_INTERRUPT_MASK; |
| irq_buf.irq.interrupt_mask = sys_cpu_to_be16(irq->interrupt_mask); |
| irq_buf.irq.interrupt_sense = sys_cpu_to_be32(irq->interrupt_sense); |
| |
| rc = i2c_write(drv_data->i2c_master, &irq_buf.reg, sizeof(irq_buf), |
| cfg->i2c_slave_addr); |
| |
| k_sem_give(&drv_data->lock); |
| #endif /* CONFIG_GPIO_SX1509B_INTERRUPT */ |
| |
| return rc; |
| } |
| |
| /** |
| * @brief Initialization function of SX1509B |
| * |
| * @param dev Device struct |
| * @return 0 if successful, failed otherwise. |
| */ |
| static int sx1509b_init(const struct device *dev) |
| { |
| const struct sx1509b_config *cfg = dev->config; |
| struct sx1509b_drv_data *drv_data = dev->data; |
| int rc; |
| |
| drv_data->i2c_master = device_get_binding(cfg->i2c_master_dev_name); |
| if (!drv_data->i2c_master) { |
| LOG_ERR("%s: no bus %s", dev->name, |
| cfg->i2c_master_dev_name); |
| rc = -EINVAL; |
| goto out; |
| } |
| |
| #ifdef CONFIG_GPIO_SX1509B_INTERRUPT |
| drv_data->dev = dev; |
| |
| drv_data->gpio_int = device_get_binding(cfg->gpio_int_dev_name); |
| if (!drv_data->gpio_int) { |
| rc = -ENOTSUP; |
| goto out; |
| } |
| k_work_init(&drv_data->work, sx1509b_work_handler); |
| |
| gpio_pin_configure(drv_data->gpio_int, cfg->gpio_pin, |
| GPIO_INPUT | cfg->gpio_flags); |
| gpio_pin_interrupt_configure(drv_data->gpio_int, cfg->gpio_pin, |
| GPIO_INT_EDGE_TO_ACTIVE); |
| |
| gpio_init_callback(&drv_data->gpio_cb, sx1509_int_cb, |
| BIT(cfg->gpio_pin)); |
| gpio_add_callback(drv_data->gpio_int, &drv_data->gpio_cb); |
| |
| drv_data->irq_state = (struct sx1509b_irq_state) { |
| .interrupt_mask = ALL_PINS, |
| }; |
| #endif |
| |
| rc = i2c_reg_write_byte(drv_data->i2c_master, cfg->i2c_slave_addr, |
| SX1509B_REG_RESET, SX1509B_REG_RESET_MAGIC0); |
| if (rc != 0) { |
| LOG_ERR("%s: reset m0 failed: %d\n", dev->name, rc); |
| goto out; |
| } |
| rc = i2c_reg_write_byte(drv_data->i2c_master, cfg->i2c_slave_addr, |
| SX1509B_REG_RESET, SX1509B_REG_RESET_MAGIC1); |
| if (rc != 0) { |
| goto out; |
| } |
| |
| k_sleep(K_MSEC(RESET_DELAY_MS)); |
| |
| /* Reset state mediated by initial configuration */ |
| drv_data->pin_state = (struct sx1509b_pin_state) { |
| .dir = (ALL_PINS |
| & ~(DT_INST_PROP(0, init_out_low) |
| | DT_INST_PROP(0, init_out_high))), |
| .data = (ALL_PINS |
| & ~DT_INST_PROP(0, init_out_low)), |
| }; |
| drv_data->debounce_state = (struct sx1509b_debounce_state) { |
| .debounce_config = CONFIG_GPIO_SX1509B_DEBOUNCE_TIME, |
| }; |
| |
| rc = i2c_reg_write_byte(drv_data->i2c_master, cfg->i2c_slave_addr, |
| SX1509B_REG_CLOCK, |
| SX1509B_REG_CLOCK_FOSC_INT_2MHZ); |
| if (rc == 0) { |
| rc = i2c_reg_write_word_be(drv_data->i2c_master, |
| cfg->i2c_slave_addr, |
| SX1509B_REG_DATA, |
| drv_data->pin_state.data); |
| } |
| if (rc == 0) { |
| rc = i2c_reg_write_word_be(drv_data->i2c_master, |
| cfg->i2c_slave_addr, |
| SX1509B_REG_DIR, |
| drv_data->pin_state.dir); |
| } |
| if (rc == 0) { |
| rc = i2c_reg_write_byte_be( |
| drv_data->i2c_master, cfg->i2c_slave_addr, |
| SX1509B_REG_MISC, |
| SX1509B_REG_MISC_LOG_A | SX1509B_REG_MISC_LOG_B | |
| SX1509B_REG_MISC_FREQ); |
| } |
| |
| out: |
| if (rc != 0) { |
| LOG_ERR("%s init failed: %d", dev->name, rc); |
| } else { |
| LOG_INF("%s init ok", dev->name); |
| } |
| k_sem_give(&drv_data->lock); |
| return rc; |
| } |
| |
| #ifdef CONFIG_GPIO_SX1509B_INTERRUPT |
| static int gpio_sx1509b_manage_callback(const struct device *dev, |
| struct gpio_callback *callback, |
| bool set) |
| { |
| struct sx1509b_drv_data *data = dev->data; |
| |
| return gpio_manage_callback(&data->cb, callback, set); |
| } |
| #endif |
| |
| static const struct gpio_driver_api api_table = { |
| .pin_configure = sx1509b_config, |
| .port_get_raw = port_get, |
| .port_set_masked_raw = port_set_masked, |
| .port_set_bits_raw = port_set_bits, |
| .port_clear_bits_raw = port_clear_bits, |
| .port_toggle_bits = port_toggle_bits, |
| .pin_interrupt_configure = pin_interrupt_configure, |
| #ifdef CONFIG_GPIO_SX1509B_INTERRUPT |
| .manage_callback = gpio_sx1509b_manage_callback, |
| #endif |
| }; |
| |
| int sx1509b_led_intensity_pin_configure(const struct device *dev, |
| gpio_pin_t pin) |
| { |
| const struct sx1509b_config *cfg = dev->config; |
| struct sx1509b_drv_data *drv_data = dev->data; |
| struct sx1509b_pin_state *pins = &drv_data->pin_state; |
| int rc; |
| |
| /* Can't do I2C bus operations from an ISR */ |
| if (k_is_in_isr()) { |
| return -EWOULDBLOCK; |
| } |
| |
| if (pin >= ARRAY_SIZE(intensity_registers)) { |
| return -ERANGE; |
| } |
| |
| k_sem_take(&drv_data->lock, K_FOREVER); |
| |
| /* Enable LED driver */ |
| drv_data->led_drv_enable |= BIT(pin); |
| rc = i2c_reg_write_word_be(drv_data->i2c_master, cfg->i2c_slave_addr, |
| SX1509B_REG_LED_DRV_ENABLE, |
| drv_data->led_drv_enable); |
| |
| /* Set intensity to 0 */ |
| if (rc == 0) { |
| rc = i2c_reg_write_byte_be(drv_data->i2c_master, |
| cfg->i2c_slave_addr, |
| intensity_registers[pin], 0); |
| } else { |
| goto out; |
| } |
| |
| pins->input_disable |= BIT(pin); |
| pins->pull_up &= ~BIT(pin); |
| pins->dir &= ~BIT(pin); |
| pins->data &= ~BIT(pin); |
| |
| if (rc == 0) { |
| rc = write_pin_state(cfg, drv_data, pins, false); |
| } |
| |
| out: |
| k_sem_give(&drv_data->lock); |
| return rc; |
| } |
| |
| int sx1509b_led_intensity_pin_set(const struct device *dev, gpio_pin_t pin, |
| uint8_t intensity_val) |
| { |
| const struct sx1509b_config *cfg = dev->config; |
| struct sx1509b_drv_data *drv_data = dev->data; |
| int rc; |
| |
| /* Can't do I2C bus operations from an ISR */ |
| if (k_is_in_isr()) { |
| return -EWOULDBLOCK; |
| } |
| |
| if (pin >= ARRAY_SIZE(intensity_registers)) { |
| return -ERANGE; |
| } |
| |
| k_sem_take(&drv_data->lock, K_FOREVER); |
| |
| rc = i2c_reg_write_byte_be(drv_data->i2c_master, cfg->i2c_slave_addr, |
| intensity_registers[pin], intensity_val); |
| |
| k_sem_give(&drv_data->lock); |
| |
| return rc; |
| } |
| |
| static const struct sx1509b_config sx1509b_cfg = { |
| .common = { |
| .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(0), |
| }, |
| .i2c_master_dev_name = DT_INST_BUS_LABEL(0), |
| #ifdef CONFIG_GPIO_SX1509B_INTERRUPT |
| .gpio_int_dev_name = DT_INST_GPIO_LABEL(0, nint_gpios), |
| .gpio_pin = DT_INST_GPIO_PIN(0, nint_gpios), |
| .gpio_flags = DT_INST_GPIO_FLAGS(0, nint_gpios), |
| #endif |
| .i2c_slave_addr = DT_INST_REG_ADDR(0), |
| }; |
| |
| static struct sx1509b_drv_data sx1509b_drvdata = { |
| .lock = Z_SEM_INITIALIZER(sx1509b_drvdata.lock, 1, 1), |
| }; |
| |
| DEVICE_DT_INST_DEFINE(0, sx1509b_init, NULL, |
| &sx1509b_drvdata, &sx1509b_cfg, |
| POST_KERNEL, CONFIG_GPIO_SX1509B_INIT_PRIORITY, |
| &api_table); |