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.
2238 lines
72 KiB
2238 lines
72 KiB
/* |
|
* Copyright 2024 NXP |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
/** |
|
* @file Driver for PCA(L)xxxx SERIES I2C-based GPIO expander. |
|
*/ |
|
|
|
#include <errno.h> |
|
|
|
#include <zephyr/kernel.h> |
|
#include <zephyr/device.h> |
|
#include <zephyr/init.h> |
|
#include <zephyr/sys/byteorder.h> |
|
#include <zephyr/sys/util.h> |
|
#include <zephyr/drivers/gpio.h> |
|
#include <zephyr/drivers/gpio/gpio_utils.h> |
|
#include <zephyr/drivers/i2c.h> |
|
|
|
#define LOG_LEVEL CONFIG_GPIO_LOG_LEVEL |
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(gpio_pca_series); |
|
|
|
/** Private debug macro, enable more error checking and print more log. */ |
|
/* #define GPIO_NXP_PCA_SERIES_DEBUG */ |
|
|
|
/* Feature flags */ |
|
#define PCA_HAS_LATCH BIT(0) /** + output_drive_strength, + input_latch */ |
|
#define PCA_HAS_PULL BIT(1) /** + pull_enable, + pull_select */ |
|
#define PCA_HAS_INT_MASK BIT(2) /** + interrupt_mask, + int_status */ |
|
#define PCA_HAS_INT_EXTEND BIT(3) /** + interrupt_edge, + interrupt_clear */ |
|
#define PCA_HAS_OUT_CONFIG BIT(4) /** + input_status, + output_config */ |
|
|
|
/* get port and pin from gpio_pin_t */ |
|
#define PCA_PORT(gpio_pin) (gpio_pin >> 3) |
|
#define PCA_PIN(gpio_pin) (gpio_pin & GENMASK(2, 0)) |
|
|
|
#define PCA_REG_INVALID (0xff) |
|
|
|
/** |
|
* @brief part number definition. |
|
*/ |
|
enum gpio_pca_series_part_no { |
|
PCA_PART_NO_PCA9538, |
|
PCA_PART_NO_PCA9539, |
|
PCA_PART_NO_PCA9554, |
|
PCA_PART_NO_PCA9555, |
|
PCA_PART_NO_PCAL6524, |
|
PCA_PART_NO_PCAL6534, |
|
}; |
|
|
|
/** |
|
* @brief part name definition for debug. |
|
* |
|
* @note must be consistent in order with @ref enum gpio_pca_series_part_no |
|
*/ |
|
const char *const gpio_pca_series_part_name[] = { |
|
"pca9538", |
|
"pca9539", |
|
"pca9554", |
|
"pca9555", |
|
"pcal6524", |
|
"pcal6534", |
|
}; |
|
|
|
/** |
|
* Device reg layout types: |
|
* - Type 0: PCA953X, PCA955X |
|
* - Type 1: PCAL953X, PCAL955X, PCAL64XXA |
|
* - Type 2: PCA957X |
|
* - Type 3: PCAL65XX |
|
*/ |
|
|
|
enum gpio_pca_series_reg_type { /** Type0 Type1 Type2 Type3 */ |
|
PCA_REG_TYPE_1B_INPUT_PORT = 0U, /** x x x x */ |
|
PCA_REG_TYPE_1B_OUTPUT_PORT, /** x x x x */ |
|
/* PCA_REG_TYPE_1B_POLARITY_INVERSION, x x x x * (unused, omitted) */ |
|
PCA_REG_TYPE_1B_CONFIGURATION, /** x x x x */ |
|
PCA_REG_TYPE_2B_OUTPUT_DRIVE_STRENGTH, /** x x */ |
|
PCA_REG_TYPE_1B_INPUT_LATCH, /** x x */ |
|
PCA_REG_TYPE_1B_PULL_ENABLE, /** x x*1 x */ |
|
PCA_REG_TYPE_1B_PULL_SELECT, /** x x x */ |
|
PCA_REG_TYPE_1B_INPUT_STATUS, /** x */ |
|
PCA_REG_TYPE_1B_OUTPUT_CONFIG, /** x*2 */ |
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT /** */ |
|
PCA_REG_TYPE_1B_INTERRUPT_MASK, /** x x x */ |
|
PCA_REG_TYPE_1B_INTERRUPT_STATUS, /** x x x */ |
|
PCA_REG_TYPE_2B_INTERRUPT_EDGE, /** x */ |
|
PCA_REG_TYPE_1B_INTERRUPT_CLEAR, /** x */ |
|
# ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
PCA_REG_TYPE_1B_INPUT_HISTORY, /** x x x * (cache registry) */ |
|
PCA_REG_TYPE_1B_INTERRUPT_RISE, /** x x x * (cache registry) */ |
|
PCA_REG_TYPE_1B_INTERRUPT_FALL, /** x x x * (cache registry) */ |
|
# endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
PCA_REG_TYPE_COUNT, /** not a register */ |
|
}; |
|
/** |
|
* #1: "pull_enable" register is named "bus_hold" in PCA957x datasheet. |
|
* #2: this is for "individual pin output configuration register". We do not use |
|
* port-level "pin output configuration" register. |
|
*/ |
|
|
|
const char *const gpio_pca_series_reg_name[] = { |
|
"1b_input_port", |
|
"1b_output_port", |
|
/* "1b_polarity_inversion," */ |
|
"1b_configuration", |
|
"2b_output_drive_strength", |
|
"1b_input_latch", |
|
"1b_pull_enable", |
|
"1b_pull_select", |
|
"1b_input_status", |
|
"1b_output_config", |
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
"1b_interrupt_mask", |
|
"1b_interrupt_status", |
|
"2b_interrupt_edge", |
|
"1b_interrupt_clear", |
|
# ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
"1b_input_history", |
|
"1b_interrupt_rise", |
|
"ib_interrupt_fall", |
|
# endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
"reg_end", |
|
}; |
|
|
|
/** |
|
* @brief interrupt config for interrupt_edge register |
|
* |
|
* @note Only applies to part no with PCA_HAS_INT_EXTEND capability. |
|
*/ |
|
enum PCA_INTERRUPT_config_extended { |
|
PCA_INTERRUPT_LEVEL_CHANGE = 0U, /* default */ |
|
PCA_INTERRUPT_RISING_EDGE, |
|
PCA_INTERRUPT_FALLING_EDGE, |
|
PCA_INTERRUPT_EITHER_EDGE, |
|
}; |
|
|
|
struct gpio_pca_series_part_config { |
|
uint8_t port_no; /* number of 8-pin ports on device */ |
|
uint8_t flags; /* capability flags */ |
|
const uint8_t *regs; /* pointer to register map */ |
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
# ifdef GPIO_NXP_PCA_SERIES_DEBUG |
|
uint8_t cache_size; |
|
# endif /* GPIO_NXP_PCA_SERIES_DEBUG */ |
|
const uint8_t *cache_map; |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
}; |
|
|
|
/** Configuration data */ |
|
struct gpio_pca_series_config { |
|
struct gpio_driver_config common; /* gpio_driver_config needs to be first */ |
|
struct i2c_dt_spec i2c; /* i2c bus dt spec */ |
|
const struct gpio_pca_series_part_config *part_cfg; /* config of part unmber */ |
|
struct gpio_dt_spec gpio_rst; /* device reset gpio */ |
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
struct gpio_dt_spec gpio_int; /** device interrupt gpio */ |
|
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
}; |
|
|
|
/** Runtime driver data */ |
|
struct gpio_pca_series_data { |
|
struct gpio_driver_data common; /** gpio_driver_data needs to be first */ |
|
struct k_sem lock; |
|
void *cache; /** device spicific reg cache |
|
* - if CONFIG_GPIO_PCA_SERIES_CACHE_ALL is set, |
|
* it points to device specific cache memory. |
|
* - if CONFIG_GPIO_PCA_SERIES_CACHE_ALL is not set, |
|
* it point to a struct gpio_pca_series_reg_cache_mini |
|
* instance. |
|
*/ |
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
const struct device *self; /** Self-reference to the driver instance */ |
|
struct gpio_callback gpio_cb; /** gpio_int ISR callback */ |
|
sys_slist_t callbacks; /** port pin callbacks list */ |
|
struct k_work int_work; /** worker that fire callbacks */ |
|
#endif |
|
}; |
|
|
|
/** |
|
* gpio_pca_reg_access_api |
|
* { |
|
*/ |
|
|
|
/** |
|
* @brief get internal address of register from register type |
|
* |
|
* @param dev device struct |
|
* @param reg_type value from enum gpio_pca_series_reg_type |
|
* @return uint8_t PCA_REG_INVALID if reg is not used |
|
* other internal address of register |
|
*/ |
|
static inline uint8_t gpio_pca_series_reg_get_addr(const struct device *dev, |
|
enum gpio_pca_series_reg_type reg_type) |
|
{ |
|
const struct gpio_pca_series_config *cfg = dev->config; |
|
|
|
#ifdef GPIO_NXP_PCA_SERIES_DEBUG |
|
if (reg_type >= PCA_REG_TYPE_COUNT) { |
|
LOG_ERR("reg_type %d out of range", reg_type); |
|
return 0; |
|
} |
|
#endif /* GPIO_NXP_PCA_SERIES_DEBUG */ |
|
|
|
return cfg->part_cfg->regs[reg_type]; |
|
} |
|
|
|
/** |
|
* @brief get per-port size for register |
|
* |
|
* @param dev device struct |
|
* @param reg_type value from enum gpio_pca_series_reg_type |
|
* @return uint32_t size in bytes |
|
* 0 if fail |
|
*/ |
|
static inline uint32_t gpio_pca_series_reg_size_per_port(const struct device *dev, |
|
enum gpio_pca_series_reg_type reg_type) |
|
{ |
|
#ifdef GPIO_NXP_PCA_SERIES_DEBUG |
|
if (reg_type >= PCA_REG_TYPE_COUNT) { |
|
LOG_ERR("reg_type %d out of range", reg_type); |
|
return 0; |
|
} |
|
#endif /* GPIO_NXP_PCA_SERIES_DEBUG */ |
|
|
|
switch (reg_type) { |
|
case PCA_REG_TYPE_1B_INPUT_PORT: |
|
case PCA_REG_TYPE_1B_OUTPUT_PORT: |
|
/* case PCA_REG_TYPE_1B_POLARITY_INVERSION: */ |
|
case PCA_REG_TYPE_1B_CONFIGURATION: |
|
case PCA_REG_TYPE_1B_INPUT_LATCH: |
|
case PCA_REG_TYPE_1B_PULL_ENABLE: |
|
case PCA_REG_TYPE_1B_PULL_SELECT: |
|
case PCA_REG_TYPE_1B_INPUT_STATUS: |
|
case PCA_REG_TYPE_1B_OUTPUT_CONFIG: |
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
case PCA_REG_TYPE_1B_INTERRUPT_MASK: |
|
case PCA_REG_TYPE_1B_INTERRUPT_STATUS: |
|
case PCA_REG_TYPE_1B_INTERRUPT_CLEAR: |
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
case PCA_REG_TYPE_1B_INPUT_HISTORY: |
|
case PCA_REG_TYPE_1B_INTERRUPT_RISE: |
|
case PCA_REG_TYPE_1B_INTERRUPT_FALL: |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
return 1; |
|
case PCA_REG_TYPE_2B_OUTPUT_DRIVE_STRENGTH: |
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
case PCA_REG_TYPE_2B_INTERRUPT_EDGE: |
|
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
return 2; |
|
default: |
|
LOG_ERR("unsupported reg type %d", reg_type); |
|
return 0; /** should never happen */ |
|
} |
|
} |
|
|
|
/** |
|
* @brief get read size for register |
|
* |
|
* @param dev device struct |
|
* @param reg_type value from enum gpio_pca_series_reg_type |
|
* @return uint32_t size in bytes |
|
* 0 if fail |
|
*/ |
|
static inline uint32_t gpio_pca_series_reg_size(const struct device *dev, |
|
enum gpio_pca_series_reg_type reg_type) |
|
{ |
|
const struct gpio_pca_series_config *cfg = dev->config; |
|
|
|
return gpio_pca_series_reg_size_per_port(dev, reg_type) * cfg->part_cfg->port_no; |
|
} |
|
|
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
/** forward declarations */ |
|
static inline uint8_t gpio_pca_series_reg_cache_offset(const struct device *dev, |
|
enum gpio_pca_series_reg_type reg_type); |
|
static inline int gpio_pca_series_reg_cache_update(const struct device *dev, |
|
enum gpio_pca_series_reg_type reg_type, |
|
const uint8_t *buf); |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
|
|
/** |
|
* @brief read register with i2c interface. |
|
* |
|
* @note if CONFIG_GPIO_PCA_SERIES_CACHE_ALL is enabled, this will |
|
* not update reg cache. Cache must be updated with |
|
* @ref gpio_pca_series_reg_cache_update. |
|
* |
|
* @param dev device struct |
|
* @param reg_type value from enum gpio_pca_series_reg_type |
|
* @param buf pointer to data in little-endian byteorder |
|
* @return int 0 if success |
|
* -EFAULT if register is not supported |
|
* -EIO if i2c failure |
|
*/ |
|
static inline int gpio_pca_series_reg_read(const struct device *dev, |
|
enum gpio_pca_series_reg_type reg_type, uint8_t *buf) |
|
{ |
|
const struct gpio_pca_series_config *cfg = dev->config; |
|
int ret = 0; |
|
uint32_t size = gpio_pca_series_reg_size(dev, reg_type); |
|
uint8_t addr = gpio_pca_series_reg_get_addr(dev, reg_type); |
|
|
|
LOG_DBG("device read type %d addr 0x%x len %d", reg_type, addr, size); |
|
|
|
#ifdef GPIO_NXP_PCA_SERIES_DEBUG |
|
if (!buf) { |
|
return -EFAULT; |
|
} |
|
|
|
if (addr == PCA_REG_INVALID) { |
|
LOG_ERR("trying to read unsupported reg, reg type %d", reg_type); |
|
return -EFAULT; |
|
} |
|
#endif /* GPIO_NXP_PCA_SERIES_DEBUG */ |
|
|
|
ret = i2c_write_read_dt(&cfg->i2c, (uint8_t *)&addr, 1, (uint8_t *)buf, size); |
|
if (ret) { |
|
LOG_ERR("i2c read error [%d]", ret); |
|
} |
|
return ret; |
|
} |
|
|
|
/** |
|
* @brief write register with i2c interface. |
|
* |
|
* @note if CONFIG_GPIO_PCA_SERIES_CACHE_ALL is enabled, this will |
|
* also update reg cache. |
|
* |
|
* @param dev device struct |
|
* @param reg_type value from enum gpio_pca_series_reg_type |
|
* @param buf pointer to data in little-endian byteorder |
|
* @return int 0 if success |
|
* -EFAULT if register is not supported |
|
* -EIO if i2c failure |
|
*/ |
|
static inline int gpio_pca_series_reg_write(const struct device *dev, |
|
enum gpio_pca_series_reg_type reg_type, const uint8_t *buf) |
|
{ |
|
const struct gpio_pca_series_config *cfg = dev->config; |
|
int ret = 0; |
|
struct i2c_msg msg[2]; |
|
uint32_t size = gpio_pca_series_reg_size(dev, reg_type); |
|
uint8_t addr = gpio_pca_series_reg_get_addr(dev, reg_type); |
|
|
|
#ifdef GPIO_NXP_PCA_SERIES_DEBUG |
|
if (!buf) { |
|
return -EFAULT; |
|
} |
|
|
|
if (addr == PCA_REG_INVALID) { |
|
LOG_ERR("trying to write unsupported reg type %d", reg_type); |
|
return -EFAULT; |
|
} |
|
#endif /* GPIO_NXP_PCA_SERIES_DEBUG */ |
|
|
|
LOG_DBG("device write type %d addr 0x%x len %d", reg_type, addr, size); |
|
|
|
msg[0].buf = (uint8_t *)&addr; |
|
msg[0].len = 1; |
|
msg[0].flags = I2C_MSG_WRITE; |
|
msg[1].buf = (uint8_t *)buf; |
|
msg[1].len = size; |
|
msg[1].flags = I2C_MSG_WRITE | I2C_MSG_STOP; |
|
ret = i2c_transfer_dt(&cfg->i2c, msg, 2); |
|
if (ret) { |
|
LOG_ERR("i2c write error [%d]", ret); |
|
return ret; |
|
} |
|
|
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
if (gpio_pca_series_reg_cache_offset(dev, reg_type) != PCA_REG_INVALID) { |
|
(void)gpio_pca_series_reg_cache_update(dev, reg_type, buf); |
|
} |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
|
|
return ret; |
|
} |
|
|
|
/** |
|
* } |
|
* gpio_pca_reg_access_api |
|
*/ |
|
|
|
/** |
|
* gpio_pca_reg_cache_api |
|
* { |
|
* @note full cache is stored in le byteorder, consistent with reg layout. |
|
* mini cache is stored in cpu byteorder |
|
*/ |
|
|
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
|
|
/** |
|
* @brief get memory offset of register cache from register type |
|
* |
|
* @param dev device struct |
|
* @param reg_type value from enum gpio_pca_series_reg_type |
|
* @return uint8_t PCA_REG_INVALID if reg is not used or uncacheable |
|
* other offset in bytes related to cache pointer |
|
*/ |
|
static inline uint8_t gpio_pca_series_reg_cache_offset(const struct device *dev, |
|
enum gpio_pca_series_reg_type reg_type) |
|
{ |
|
const struct gpio_pca_series_config *cfg = dev->config; |
|
|
|
if (cfg->part_cfg->cache_map[reg_type] == PCA_REG_INVALID) { |
|
return PCA_REG_INVALID; |
|
} else { |
|
return cfg->part_cfg->cache_map[reg_type] * cfg->part_cfg->port_no; |
|
} |
|
} |
|
|
|
/** |
|
* @brief read all cacheable physical registers from device and update them |
|
* in cache |
|
* |
|
* @param dev device struct |
|
* @param reg_type value from enum gpio_pca_series_reg_type |
|
* @return uint8_t PCA_REG_INVALID if reg is not used or uncacheable |
|
* other offset in bytes related to cache pointer |
|
*/ |
|
static inline int gpio_pca_series_reg_cache_reset(const struct device *dev) |
|
{ |
|
struct gpio_pca_series_data *data = dev->data; |
|
int ret = 0; |
|
|
|
for (int reg_type = 0; reg_type < PCA_REG_TYPE_COUNT; ++reg_type) { |
|
uint8_t cache_offset = gpio_pca_series_reg_cache_offset(dev, reg_type); |
|
|
|
if (cache_offset == PCA_REG_INVALID) { |
|
continue; |
|
} |
|
|
|
LOG_DBG("cache init type %d", reg_type); |
|
|
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
/** |
|
* On devices without PCA_HAS_INT_EXTEND calabilitiy, |
|
* PCA_REG_TYPE_2B_INTERRUPT_EDGE caches mask of rising and falling pins, |
|
* while the actual register is not present. Account for that here: |
|
*/ |
|
uint8_t reg_addr = gpio_pca_series_reg_get_addr(dev, reg_type); |
|
|
|
if (reg_addr == PCA_REG_INVALID) { |
|
const uint8_t reset_value_0[] = { |
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; |
|
|
|
switch (reg_type) { |
|
case PCA_REG_TYPE_1B_INPUT_HISTORY: |
|
ret = gpio_pca_series_reg_read(dev, PCA_REG_TYPE_1B_INPUT_PORT, |
|
((uint8_t *)data->cache) + cache_offset); |
|
if (ret) { |
|
LOG_ERR("cache initial input read failed %d", ret); |
|
} |
|
break; |
|
case PCA_REG_TYPE_1B_INTERRUPT_RISE: |
|
case PCA_REG_TYPE_1B_INTERRUPT_FALL: |
|
ret = gpio_pca_series_reg_cache_update(dev, reg_type, |
|
reset_value_0); |
|
if (ret) { |
|
LOG_ERR("init initial interrupt config failed %d", ret); |
|
} |
|
break; |
|
default: |
|
LOG_ERR("trying to cache reg that is not present"); |
|
break; |
|
} |
|
if (ret) { |
|
break; |
|
} |
|
continue; |
|
} |
|
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
|
|
ret = gpio_pca_series_reg_read(dev, reg_type, |
|
((uint8_t *)data->cache) + cache_offset); |
|
if (ret) { |
|
LOG_ERR("reg type %d cache init fail %d", reg_type, ret); |
|
break; |
|
} |
|
} |
|
return ret; |
|
} |
|
|
|
/** |
|
* @brief read register value from reg cache |
|
* |
|
* @param dev device struct |
|
* @param reg_type value from enum gpio_pca_series_reg_type |
|
* @param buf pointer to read data in little-endian byteorder |
|
* @return int 0 if success |
|
* -EINVAL if invalid arguments |
|
* -EACCES if register is uncacheable |
|
*/ |
|
static inline int gpio_pca_series_reg_cache_read(const struct device *dev, |
|
enum gpio_pca_series_reg_type reg_type, |
|
uint8_t *buf) |
|
{ |
|
struct gpio_pca_series_data *data = dev->data; |
|
int ret = 0; |
|
uint8_t offset = gpio_pca_series_reg_cache_offset(dev, reg_type); |
|
uint32_t size = gpio_pca_series_reg_size(dev, reg_type); |
|
uint8_t *src; |
|
|
|
#ifdef GPIO_NXP_PCA_SERIES_DEBUG |
|
if (!buf) { |
|
return -EINVAL; |
|
} |
|
|
|
if (offset == PCA_REG_INVALID) { |
|
LOG_ERR("can not get noncacheable reg %d"); |
|
return -EFAULT; |
|
} |
|
#endif /* GPIO_NXP_PCA_SERIES_DEBUG */ |
|
|
|
src = ((uint8_t *)data->cache) + offset; |
|
LOG_DBG("cache read type %d len %d mem addr 0x%p", reg_type, size, (void *)src); |
|
memcpy(buf, src, size); |
|
return ret; |
|
} |
|
|
|
/** |
|
* @brief update register cache from device or existing value. |
|
* |
|
* @param dev device struct |
|
* @param reg_type value from enum gpio_pca_series_reg_type |
|
* @param buf pointer to new data to update from, in little-endian byteorder |
|
* @return int 0 if success |
|
* -EINVAL if invalid arguments |
|
* -EACCES if register is uncacheable |
|
*/ |
|
static inline int gpio_pca_series_reg_cache_update(const struct device *dev, |
|
enum gpio_pca_series_reg_type reg_type, |
|
const uint8_t *buf) |
|
{ |
|
struct gpio_pca_series_data *data = dev->data; |
|
uint8_t offset = gpio_pca_series_reg_cache_offset(dev, reg_type); |
|
uint32_t size = gpio_pca_series_reg_size(dev, reg_type); |
|
uint8_t *dst; |
|
|
|
#ifdef GPIO_NXP_PCA_SERIES_DEBUG |
|
if (!buf) { |
|
return -EINVAL; |
|
} |
|
|
|
if (offset == PCA_REG_INVALID) { |
|
LOG_ERR("can not update non-cacheable reg type %d", reg_type); |
|
return -EACCES; |
|
} |
|
#endif /* GPIO_NXP_PCA_SERIES_DEBUG */ |
|
|
|
LOG_DBG("cache update type %d len %d from %s", reg_type, size, |
|
(buf ? "buffer" : "device")); |
|
|
|
dst = ((uint8_t *)data->cache) + offset; |
|
LOG_DBG("cache write mem addr 0x%p len %d", (void *)dst, size); |
|
|
|
/** update cache from buf */ |
|
memcpy(dst, buf, size); |
|
|
|
return 0; |
|
} |
|
|
|
#else /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
|
|
#define gpio_pca_series_reg_cache_read gpio_pca_series_reg_read |
|
|
|
struct gpio_pca_series_reg_cache_mini { |
|
uint32_t output; /** cache output value for faster output */ |
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
uint32_t input_old; /** only used when interrupt mask & edge config is not present */ |
|
uint32_t int_rise; /** only used if interrupt edge is software-compared */ |
|
uint32_t int_fall; /** only used if interrupt edge is software-compared */ |
|
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
}; |
|
|
|
static inline struct gpio_pca_series_reg_cache_mini *gpio_pca_series_reg_cache_mini_get( |
|
const struct device *dev) |
|
{ |
|
struct gpio_pca_series_data *data = dev->data; |
|
struct gpio_pca_series_reg_cache_mini *cache = |
|
(struct gpio_pca_series_reg_cache_mini *)(&data->cache); |
|
LOG_DBG("mini cache addr 0x%p", (void *)cache); |
|
return cache; |
|
} |
|
|
|
static inline int gpio_pca_series_reg_cache_mini_reset(const struct device *dev) |
|
{ |
|
const struct gpio_pca_series_config *cfg = dev->config; |
|
struct gpio_pca_series_reg_cache_mini *cache = |
|
gpio_pca_series_reg_cache_mini_get(dev); |
|
int ret = 0; |
|
|
|
ret = gpio_pca_series_reg_read(dev, PCA_REG_TYPE_1B_OUTPUT_PORT, |
|
(uint8_t *)&cache->output); |
|
if (ret) { |
|
LOG_ERR("minimum cache failed to read initial output: %d", ret); |
|
goto out; |
|
} |
|
|
|
cache->output = sys_le32_to_cpu(cache->output); |
|
|
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
cache->int_rise = 0; |
|
cache->int_fall = 0; |
|
|
|
/* Read initial input value */ |
|
enum gpio_pca_series_reg_type input_reg = |
|
cfg->part_cfg->flags & PCA_HAS_OUT_CONFIG ? |
|
PCA_REG_TYPE_1B_INPUT_STATUS : PCA_REG_TYPE_1B_INPUT_PORT; |
|
|
|
ret = gpio_pca_series_reg_read(dev, input_reg, (uint8_t *)&cache->input_old); |
|
if (ret) { |
|
LOG_ERR("minimum cache failed to read initial input: %d", ret); |
|
} |
|
|
|
cache->input_old = sys_le32_to_cpu(cache->input_old); |
|
#else |
|
ARG_UNUSED(cfg); |
|
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
|
|
out: |
|
return ret; |
|
} |
|
|
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
|
|
/** |
|
* } |
|
* gpio_pca_cache_api |
|
*/ |
|
|
|
/** |
|
* gpio_pca_custom_api |
|
* { |
|
*/ |
|
|
|
/** |
|
* @brief Reset function of pca_series |
|
* |
|
* This function pulls reset pin to reset a pca_series |
|
* device if reset_pin is present. Otherwise it write |
|
* reset value to device registers. |
|
*/ |
|
static inline void gpio_pca_series_reset(const struct device *dev) |
|
{ |
|
const struct gpio_pca_series_config *cfg = dev->config; |
|
const uint8_t reset_value_0[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; |
|
const uint8_t reset_value_1[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; |
|
int ret = 0; |
|
|
|
/** Reset pin connected, do hardware reset */ |
|
if (cfg->gpio_rst.port != NULL) { |
|
if (!device_is_ready(cfg->gpio_rst.port)) { |
|
goto sw_rst; |
|
} |
|
/* Reset gpio should be set to active LOW in dts */ |
|
ret = gpio_pin_configure_dt(&cfg->gpio_rst, |
|
GPIO_OUTPUT_HIGH | GPIO_OUTPUT_INIT_LOGICAL); |
|
if (ret) { |
|
goto sw_rst; |
|
} |
|
k_sleep(K_USEC(1)); |
|
ret = gpio_pin_set_dt(&cfg->gpio_rst, 0U); |
|
if (ret) { |
|
goto sw_rst; |
|
} |
|
k_sleep(K_USEC(1)); |
|
return; |
|
} |
|
|
|
sw_rst: |
|
/** Warn that gpio configured but failed */ |
|
if (cfg->gpio_rst.port != NULL) { |
|
LOG_WRN("gpio reset failed, fallback to soft reset"); |
|
} |
|
/** |
|
* Reset pin not connected, write reset value to registers |
|
* No need to check return, as unsupported reg will return early with error |
|
*/ |
|
gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_OUTPUT_PORT, reset_value_1); |
|
gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_CONFIGURATION, reset_value_1); |
|
if (cfg->part_cfg->flags & PCA_HAS_LATCH) { |
|
gpio_pca_series_reg_write(dev, PCA_REG_TYPE_2B_OUTPUT_DRIVE_STRENGTH, |
|
reset_value_1); |
|
gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_INPUT_LATCH, reset_value_0); |
|
} |
|
if (cfg->part_cfg->flags & PCA_HAS_PULL) { |
|
gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_PULL_ENABLE, reset_value_0); |
|
gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_PULL_SELECT, reset_value_1); |
|
} |
|
if (cfg->part_cfg->flags & PCA_HAS_OUT_CONFIG) { |
|
gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_OUTPUT_CONFIG, reset_value_0); |
|
} |
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
if (cfg->part_cfg->flags & PCA_HAS_INT_MASK) { |
|
gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_INTERRUPT_MASK, reset_value_1); |
|
} |
|
if (cfg->part_cfg->flags & PCA_HAS_INT_EXTEND) { |
|
gpio_pca_series_reg_write(dev, PCA_REG_TYPE_2B_INTERRUPT_EDGE, reset_value_0); |
|
} |
|
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
} |
|
|
|
#ifdef GPIO_NXP_PCA_SERIES_DEBUG |
|
|
|
/** |
|
* @brief Dump all available register and cache for debug purpose. |
|
* |
|
* @note This function does not consider cpu byteorder. |
|
*/ |
|
void gpio_pca_series_debug_dump(const struct device *dev) |
|
{ |
|
const struct gpio_pca_series_config *cfg = dev->config; |
|
struct gpio_pca_series_data *data = dev->data; |
|
int ret = 0; |
|
|
|
LOG_WRN("**** debug dump ****"); |
|
LOG_WRN("device: %s", dev->name); |
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
LOG_WRN("cache base addr: 0x%p size: 0x%2.2x", (void *)data->cache, |
|
cfg->part_cfg->cache_size); |
|
#else |
|
LOG_WRN("cache base addr: 0x%p", (void *)data->cache); |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
|
|
LOG_WRN("register profile:"); |
|
LOG_WRN("type\t" |
|
"name\t\t\t" |
|
"addr\t" |
|
"reg_value\t\t\t" |
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
"cache\t" |
|
"cache_value\t\t" |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
); |
|
for (int reg_type = 0; reg_type < PCA_REG_TYPE_COUNT; ++reg_type) { |
|
uint8_t reg = cfg->part_cfg->regs[reg_type]; |
|
uint8_t reg_val[8]; |
|
uint64_t *reg_val_p = (uint64_t *)®_val; |
|
uint32_t reg_size = gpio_pca_series_reg_size(dev, reg_type); |
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
uint8_t cache = gpio_pca_series_reg_cache_offset(dev, reg_type); |
|
uint8_t cache_val[8]; |
|
uint64_t *cache_val_p = (uint64_t *)&cache_val; |
|
|
|
if (reg == PCA_REG_INVALID && cache == PCA_REG_INVALID) |
|
#else |
|
if (reg == PCA_REG_INVALID) |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
{ |
|
continue; |
|
} |
|
|
|
if (reg != PCA_REG_INVALID) { |
|
*reg_val_p = 0; |
|
ret = gpio_pca_series_reg_read(dev, reg_type, reg_val); |
|
if (ret) { |
|
LOG_ERR("read reg error from reg type %d, invalidate this reg", |
|
reg_type); |
|
reg = PCA_REG_INVALID; |
|
} |
|
} |
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
if (cache != PCA_REG_INVALID) { |
|
*cache_val_p = 0; |
|
ret = gpio_pca_series_reg_cache_read(dev, reg_type, cache_val); |
|
if (ret) { |
|
LOG_ERR("read reg cache error from reg type %d, invalidate this " |
|
"reg cache", |
|
reg_type); |
|
reg = PCA_REG_INVALID; |
|
} |
|
} |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
|
|
/** do_print */ |
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
if (reg != PCA_REG_INVALID && cache != PCA_REG_INVALID) { |
|
LOG_WRN("%.2d\t%-24s\t0x%2.2x\t0x%16.16x\t0x%2.2x\t0x%16.16x\t" |
|
, reg_type, gpio_pca_series_reg_name[reg_type] |
|
, reg, *reg_val_p |
|
, cache, *cache_val_p |
|
); |
|
if (memcmp(reg_val, cache_val, reg_size)) { |
|
LOG_ERR("reg %d cache mismatch", reg_type); |
|
} |
|
} else if (reg == PCA_REG_INVALID && cache != PCA_REG_INVALID) { |
|
/** |
|
* On devices without PCA_HAS_INT_EXTEND calabilitiy, |
|
* PCA_REG_TYPE_2B_INTERRUPT_EDGE caches mask of rising and falling pins, |
|
* while the actual register is not present. Account for that here: |
|
*/ |
|
LOG_WRN("%.2d\t%-24s\tNone\tNone\t\t\t0x%2.2x\t0x%16.16x\t" |
|
, reg_type, gpio_pca_series_reg_name[reg_type] |
|
, cache, *cache_val_p |
|
); |
|
} else { |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
LOG_WRN("%.2d\t%-24s\t0x%2.2x\t0x%16.16x\t" |
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
"None\tNone\t\t\t" |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
, reg_type, gpio_pca_series_reg_name[reg_type] |
|
, reg, *reg_val_p |
|
); |
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
} |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
} |
|
LOG_WRN("**** dump finish ****"); |
|
} |
|
|
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
|
|
/** |
|
* @brief Validate the cache api by filling data to the cache. |
|
*/ |
|
void gpio_pca_series_cache_test(const struct device *dev) |
|
{ |
|
const struct gpio_pca_series_config *cfg = dev->config; |
|
const uint8_t reset_value_0[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; |
|
const uint8_t reset_value_1[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; |
|
uint8_t buffer[8]; |
|
uint8_t expected_offset = 0U; |
|
uint64_t *buffer_p = (uint64_t *)buffer; |
|
|
|
LOG_WRN("**** cache test ****"); |
|
LOG_WRN("device: %s", dev->name); |
|
|
|
for (int reg_type = 0; reg_type < PCA_REG_TYPE_COUNT; ++reg_type) { |
|
uint8_t cache_offset = gpio_pca_series_reg_cache_offset(dev, reg_type); |
|
uint32_t cache_size = gpio_pca_series_reg_size(dev, reg_type); |
|
|
|
if (cache_offset == PCA_REG_INVALID) { |
|
LOG_WRN("skip reg %d: not present or non-cacheable", reg_type); |
|
continue; |
|
} |
|
|
|
if (cache_offset != expected_offset) { |
|
LOG_ERR("reg %d cache offset 0x%2.2x error, expected 0x%2.2x", |
|
reg_type, cache_offset, expected_offset); |
|
break; |
|
} |
|
|
|
expected_offset += cache_size; |
|
|
|
LOG_WRN("testing reg %d size %d", reg_type, cache_size); |
|
(void)gpio_pca_series_reg_cache_update(dev, reg_type, reset_value_0); |
|
*buffer_p = 0; |
|
gpio_pca_series_reg_cache_read(dev, reg_type, buffer); |
|
LOG_WRN("fill 00, result: 0x%16.16x", *buffer_p); |
|
(void)gpio_pca_series_reg_cache_update(dev, reg_type, reset_value_1); |
|
*buffer_p = 0; |
|
gpio_pca_series_reg_cache_read(dev, reg_type, buffer); |
|
LOG_WRN("fill ff, result: 0x%16.16x", *buffer_p); |
|
} |
|
LOG_WRN("**** test finish ****"); |
|
} |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
|
|
#endif /* GPIO_NXP_PCA_SERIES_DEBUG */ |
|
|
|
/** |
|
* } |
|
* gpio_pca_custom_api |
|
*/ |
|
|
|
/** |
|
* gpio_pca_zephyr_gpio_api |
|
* { |
|
*/ |
|
|
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
/** Forward declaration */ |
|
static inline void gpio_pca_series_interrupt_handler_standard( |
|
const struct device *dev, gpio_port_value_t *input_value); |
|
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
|
|
/** |
|
* @brief configure gpio port. |
|
* |
|
* @note This API applies to all supported part no. Capabilities ( |
|
* PCA_HAS_PULL and PCA_HAS_OUT_CONFIG) are evaluated and handled. |
|
* |
|
* @return int 0 if success |
|
* -ENOTSUP if unsupported config flags are provided |
|
*/ |
|
static int gpio_pca_series_pin_configure(const struct device *dev, |
|
gpio_pin_t pin, gpio_flags_t flags) |
|
{ |
|
const struct gpio_pca_series_config *cfg = dev->config; |
|
struct gpio_pca_series_data *data = dev->data; |
|
uint32_t reg_value = 0; |
|
int ret = 0; |
|
|
|
if ((flags & GPIO_INPUT) && (flags & GPIO_OUTPUT)) { |
|
return -ENOTSUP; |
|
} |
|
|
|
if ((flags & GPIO_SINGLE_ENDED) && |
|
(cfg->part_cfg->flags & PCA_HAS_OUT_CONFIG) == 0U) { |
|
return -ENOTSUP; |
|
} |
|
|
|
if ((flags & GPIO_PULL_UP) || (flags & GPIO_PULL_DOWN)) { |
|
if ((cfg->part_cfg->flags & PCA_HAS_PULL) == 0U) { |
|
return -ENOTSUP; |
|
} |
|
} /* Can't do I2C bus operations from an ISR */ |
|
if (k_is_in_isr()) { |
|
return -EWOULDBLOCK; |
|
} |
|
|
|
LOG_DBG("dev %s configure pin %d flag 0x%x", dev->name, pin, flags); |
|
|
|
k_sem_take(&data->lock, K_FOREVER); |
|
|
|
/** |
|
* TODO: Only write 1 byte. |
|
* |
|
* The config api only configures 1 pin, so only need to write the |
|
* modified byte. Need to create new register & cache api to provide |
|
* single byte access. |
|
* This applies to: pin_configure, pin_interrupt_configure |
|
*/ |
|
if (cfg->part_cfg->flags & PCA_HAS_OUT_CONFIG) { |
|
/* configure PCA_REG_TYPE_1B_OUTPUT_CONFIG */ |
|
ret = gpio_pca_series_reg_cache_read(dev, |
|
PCA_REG_TYPE_1B_OUTPUT_CONFIG, (uint8_t *)®_value); |
|
reg_value = sys_le32_to_cpu(reg_value); |
|
if (flags & GPIO_SINGLE_ENDED) { |
|
reg_value |= (BIT(pin)); /* set bit to set open-drain */ |
|
} else { |
|
reg_value &= (~BIT(pin)); /* clear bit to set push-pull */ |
|
} |
|
reg_value = sys_cpu_to_le32(reg_value); |
|
ret = gpio_pca_series_reg_write(dev, |
|
PCA_REG_TYPE_1B_OUTPUT_CONFIG, (uint8_t *)®_value); |
|
} |
|
|
|
if ((cfg->part_cfg->flags & PCA_HAS_PULL)) { |
|
if ((flags & GPIO_PULL_UP) || (flags & GPIO_PULL_DOWN)) { |
|
/* configure PCA_REG_TYPE_1B_PULL_SELECT */ |
|
ret = gpio_pca_series_reg_cache_read(dev, |
|
PCA_REG_TYPE_1B_PULL_SELECT, (uint8_t *)®_value); |
|
reg_value = sys_le32_to_cpu(reg_value); |
|
if (flags & GPIO_PULL_UP) { |
|
reg_value |= (BIT(pin)); |
|
} else { |
|
reg_value &= (~BIT(pin)); |
|
} |
|
reg_value = sys_cpu_to_le32(reg_value); |
|
ret = gpio_pca_series_reg_write(dev, |
|
PCA_REG_TYPE_1B_PULL_SELECT, (uint8_t *)®_value); |
|
} |
|
/* configure PCA_REG_TYPE_1B_PULL_ENABLE */ |
|
ret = gpio_pca_series_reg_cache_read(dev, |
|
PCA_REG_TYPE_1B_PULL_ENABLE, (uint8_t *)®_value); |
|
reg_value = sys_le32_to_cpu(reg_value); |
|
if ((flags & GPIO_PULL_UP) || (flags & GPIO_PULL_DOWN)) { |
|
reg_value |= (BIT(pin)); /* set bit to enable pull */ |
|
} else { |
|
reg_value &= (~BIT(pin)); /* clear bit to disable pull */ |
|
} |
|
reg_value = sys_cpu_to_le32(reg_value); |
|
ret = gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_PULL_ENABLE, |
|
(uint8_t *)®_value); |
|
} |
|
|
|
/* configure PCA_REG_TYPE_1B_OUTPUT */ |
|
if ((flags & GPIO_OUTPUT_INIT_HIGH) || (flags & GPIO_OUTPUT_INIT_LOW)) { |
|
uint32_t out_old; |
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
/* get output register old value from reg cache */ |
|
ret = gpio_pca_series_reg_cache_read(dev, PCA_REG_TYPE_1B_OUTPUT_PORT, |
|
(uint8_t *)&out_old); |
|
if (ret) { |
|
ret = -EINVAL; /* should never fail */ |
|
goto out; |
|
} |
|
out_old = sys_le32_to_cpu(out_old); |
|
#else /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
out_old = gpio_pca_series_reg_cache_mini_get(dev)->output; |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
|
|
if (flags & GPIO_OUTPUT_INIT_HIGH) { |
|
reg_value = out_old | (BIT(pin)); |
|
} |
|
if (flags & GPIO_OUTPUT_INIT_LOW) { |
|
reg_value = out_old & (~BIT(pin)); |
|
} |
|
reg_value = sys_cpu_to_le32(reg_value); |
|
ret = gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_OUTPUT_PORT, |
|
(uint8_t *)®_value); |
|
if (ret != 0) { |
|
goto out; |
|
} |
|
#ifndef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
/** update output register old value to void* cache raw value */ |
|
gpio_pca_series_reg_cache_mini_get(dev)->output = |
|
sys_le32_to_cpu(reg_value); |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
} |
|
|
|
/* configure PCA_REG_TYPE_1B_CONFIGURATION */ |
|
ret = gpio_pca_series_reg_cache_read(dev, |
|
PCA_REG_TYPE_1B_CONFIGURATION, (uint8_t *)®_value); |
|
reg_value = sys_le32_to_cpu(reg_value); |
|
if (flags & GPIO_INPUT) { |
|
reg_value |= (BIT(pin)); /* set bit to set input */ |
|
} else { |
|
reg_value &= (~BIT(pin)); /* clear bit to set output */ |
|
} |
|
reg_value = sys_cpu_to_le32(reg_value); |
|
ret = gpio_pca_series_reg_write(dev, |
|
PCA_REG_TYPE_1B_CONFIGURATION, (uint8_t *)®_value); |
|
|
|
out: |
|
k_sem_give(&data->lock); |
|
LOG_DBG("dev %s configure return %d", dev->name, ret); |
|
return ret; |
|
} |
|
|
|
/** |
|
* @brief read gpio port |
|
* |
|
* @note read input_port register will clear interrupt masks |
|
* on supported devices. This API is used for part no |
|
* without PCA_HAS_INT_EXTEND capability. |
|
* |
|
* @param dev |
|
* @param value |
|
* @return int 0 if success |
|
* -EWOULDBLOCK if called from ISR context |
|
*/ |
|
static int gpio_pca_series_port_read_standard( |
|
const struct device *dev, gpio_port_value_t *value) |
|
{ |
|
struct gpio_pca_series_data *data = dev->data; |
|
uint32_t input_data; |
|
int ret = 0; |
|
|
|
/* Can't do I2C bus operations from an ISR */ |
|
if (k_is_in_isr()) { |
|
return -EWOULDBLOCK; |
|
} |
|
|
|
LOG_DBG("dev %s standard_read", dev->name); |
|
|
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
gpio_pca_series_interrupt_handler_standard(dev, value); |
|
ARG_UNUSED(data); |
|
ARG_UNUSED(input_data); |
|
#else |
|
k_sem_take(&data->lock, K_FOREVER); |
|
|
|
/* Read Input Register */ |
|
ret = gpio_pca_series_reg_read(dev, |
|
PCA_REG_TYPE_1B_INPUT_PORT, (uint8_t *)&input_data); |
|
if (ret) { |
|
LOG_ERR("port read error %d", ret); |
|
} else { |
|
*value = (gpio_port_value_t)sys_le32_to_cpu(input_data); |
|
} |
|
k_sem_give(&data->lock); |
|
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
|
|
LOG_DBG("dev %s standard_read return %d result 0x%8.8x", |
|
dev->name, ret, (uint32_t) *value); |
|
return ret; |
|
} |
|
|
|
/** |
|
* @brief read gpio port |
|
* |
|
* @note This API is used for part no with PCA_HAS_INT_EXTEND capability. |
|
* It read input_status register, which will NOT clear interrupt masks. |
|
* |
|
* @return int 0 if success |
|
* -EWOULDBLOCK if called from ISR context |
|
*/ |
|
static int gpio_pca_series_port_read_extended( |
|
const struct device *dev, gpio_port_value_t *value) |
|
{ |
|
const struct gpio_pca_series_config *cfg = dev->config; |
|
struct gpio_pca_series_data *data = dev->data; |
|
uint32_t input_data; |
|
int ret = 0; |
|
|
|
#ifdef GPIO_NXP_PCA_SERIES_DEBUG |
|
/** |
|
* Check the flags during runtime. |
|
* |
|
* The purpose is to check api assignment for developer who is adding |
|
* new device support to this driver. |
|
*/ |
|
const uint8_t check_flags = (PCA_HAS_LATCH | PCA_HAS_INT_MASK | PCA_HAS_INT_EXTEND); |
|
|
|
if ((cfg->part_cfg->flags & check_flags) != check_flags) { |
|
LOG_ERR("unsupported device trying to read gpio with extended api"); |
|
return -ENOTSUP; |
|
} |
|
#else |
|
ARG_UNUSED(cfg); |
|
#endif /* GPIO_NXP_PCA_SERIES_DEBUG */ |
|
|
|
/* Can't do I2C bus operations from an ISR */ |
|
if (k_is_in_isr()) { |
|
return -EWOULDBLOCK; |
|
} |
|
|
|
LOG_DBG("dev %s extended_read", dev->name); |
|
k_sem_take(&data->lock, K_FOREVER); |
|
|
|
/* Read Input Status Register */ |
|
ret = gpio_pca_series_reg_read(dev, PCA_REG_TYPE_1B_INPUT_STATUS, |
|
(uint8_t *)&input_data); |
|
if (ret) { |
|
LOG_ERR("port read error %d", ret); |
|
} else { |
|
*value = sys_le32_to_cpu(input_data); |
|
} |
|
|
|
k_sem_give(&data->lock); |
|
LOG_DBG("dev %s extended_read return %d result 0x%8.8x", |
|
dev->name, ret, (uint32_t) *value); |
|
return ret; |
|
} |
|
|
|
static int gpio_pca_series_port_write(const struct device *dev, |
|
gpio_port_pins_t mask, gpio_port_value_t value, gpio_port_value_t toggle) |
|
{ |
|
struct gpio_pca_series_data *data = dev->data; |
|
uint32_t out_old; |
|
uint32_t out; |
|
int ret = 0; |
|
|
|
/* Can't do I2C bus operations from an ISR */ |
|
if (k_is_in_isr()) { |
|
return -EWOULDBLOCK; |
|
} |
|
|
|
LOG_DBG("dev %s write mask 0x%8.8x value 0x%8.8x toggle 0x%8.8x", |
|
dev->name, (uint32_t)mask, (uint32_t)value, (uint32_t)toggle); |
|
k_sem_take(&data->lock, K_FOREVER); |
|
|
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
/** get output register old value from reg cache */ |
|
ret = gpio_pca_series_reg_cache_read(dev, PCA_REG_TYPE_1B_OUTPUT_PORT, |
|
(uint8_t *)&out_old); |
|
if (ret) { |
|
return -EINVAL; /** should never fail */ |
|
} |
|
out_old = sys_le32_to_cpu(out_old); |
|
#else /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
LOG_DBG("access address 0x%8.8x", (uint32_t)data->cache); |
|
out_old = gpio_pca_series_reg_cache_mini_get(dev)->output; |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
|
|
out = ((out_old & ~mask) | (value & mask)) ^ toggle; |
|
out = sys_cpu_to_le32(out); |
|
|
|
ret = gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_OUTPUT_PORT, (uint8_t *)&out); |
|
if (ret == 0) { |
|
#ifndef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
/** update output register old value to void* cache raw value */ |
|
gpio_pca_series_reg_cache_mini_get(dev)->output = sys_le32_to_cpu(out); |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
} |
|
|
|
k_sem_give(&data->lock); |
|
|
|
LOG_DBG("dev %s write return %d result 0x%8.8x", dev->name, ret, out); |
|
return ret; |
|
} |
|
|
|
static int gpio_pca_series_port_set_masked(const struct device *dev, gpio_port_pins_t mask, |
|
gpio_port_value_t value) |
|
{ |
|
return gpio_pca_series_port_write(dev, mask, value, 0); |
|
} |
|
|
|
static int gpio_pca_series_port_set_bits(const struct device *dev, gpio_port_pins_t pins) |
|
{ |
|
return gpio_pca_series_port_write(dev, pins, pins, 0); |
|
} |
|
|
|
static int gpio_pca_series_port_clear_bits(const struct device *dev, gpio_port_pins_t pins) |
|
{ |
|
return gpio_pca_series_port_write(dev, pins, 0, 0); |
|
} |
|
|
|
static int gpio_pca_series_port_toggle_bits(const struct device *dev, gpio_port_pins_t pins) |
|
{ |
|
return gpio_pca_series_port_write(dev, 0, 0, pins); |
|
} |
|
|
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
/** |
|
* @brief Configure interrupt for device with software-compared interrupt edge |
|
* |
|
* @note This version is used by devices that does not have interrupt edge config |
|
* (aka PCA_HAS_INT_EXTEND), and relies on software to check the edge. |
|
* This applies to all pca(l)9xxx and pcal64xxa devices. |
|
* This will also configure interrupt mask register if the device has it. |
|
* |
|
* @param dev |
|
* @param pin |
|
* @param mode |
|
* @param trig |
|
* @return int |
|
*/ |
|
static int gpio_pca_series_pin_interrupt_configure_standard( |
|
const struct device *dev, gpio_pin_t pin, enum gpio_int_mode mode, |
|
enum gpio_int_trig trig) |
|
{ |
|
const struct gpio_pca_series_config *cfg = dev->config; |
|
struct gpio_pca_series_data *data = dev->data; |
|
uint32_t int_rise, int_fall; |
|
uint32_t int_mask, input_latch; |
|
int ret = 0; |
|
|
|
if (cfg->gpio_int.port == NULL) { |
|
return -ENOTSUP; |
|
} |
|
/* Device does not support level-triggered interrupts. */ |
|
if (mode == GPIO_INT_MODE_LEVEL) { |
|
return -ENOTSUP; |
|
} |
|
if (k_is_in_isr()) { |
|
return -EWOULDBLOCK; |
|
} |
|
|
|
k_sem_take(&data->lock, K_FOREVER); |
|
|
|
/** |
|
* TODO: Only write 1 byte. |
|
* |
|
* The config api only configures 1 pin, so only need to write the |
|
* modified byte. Need to create new register & cache api to provide |
|
* single byte access. |
|
* This applies to: pin_configure, pin_interrupt_configure |
|
*/ |
|
|
|
/** get current interrupt config */ |
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
/** read from cache even if this register is not present on device */ |
|
ret = gpio_pca_series_reg_cache_read(dev, PCA_REG_TYPE_1B_INTERRUPT_RISE, |
|
(uint8_t *)&int_rise); |
|
if (ret) { |
|
goto out; |
|
} |
|
int_rise = sys_le32_to_cpu(int_rise); |
|
/** read from cache even if this register is not present on device */ |
|
ret = gpio_pca_series_reg_cache_read(dev, PCA_REG_TYPE_1B_INTERRUPT_FALL, |
|
(uint8_t *)&int_fall); |
|
if (ret) { |
|
goto out; |
|
} |
|
int_fall = sys_le32_to_cpu(int_fall); |
|
#else |
|
int_rise = gpio_pca_series_reg_cache_mini_get(dev)->int_rise; |
|
int_fall = gpio_pca_series_reg_cache_mini_get(dev)->int_fall; |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
|
|
if (mode == GPIO_INT_MODE_DISABLED) { |
|
int_fall &= ~BIT(pin); |
|
int_rise &= ~BIT(pin); |
|
} else { |
|
if (trig == GPIO_INT_TRIG_BOTH) { |
|
int_fall |= BIT(pin); |
|
int_rise |= BIT(pin); |
|
} else if (trig == GPIO_INT_TRIG_LOW) { |
|
int_fall |= BIT(pin); |
|
int_rise &= ~BIT(pin); |
|
} else if (trig == GPIO_INT_TRIG_HIGH) { |
|
int_fall &= ~BIT(pin); |
|
int_rise |= BIT(pin); |
|
} |
|
} |
|
|
|
int_mask = int_fall | int_rise; |
|
input_latch = ~int_mask; |
|
|
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
/** read from cache even if this register is not present on device */ |
|
int_rise = sys_cpu_to_le32(int_rise); |
|
ret = gpio_pca_series_reg_cache_update(dev, PCA_REG_TYPE_1B_INTERRUPT_RISE, |
|
(uint8_t *)&int_rise); |
|
if (ret) { |
|
goto out; |
|
} |
|
/** read from cache even if this register is not present on device */ |
|
int_fall = sys_cpu_to_le32(int_fall); |
|
ret = gpio_pca_series_reg_cache_update(dev, PCA_REG_TYPE_1B_INTERRUPT_FALL, |
|
(uint8_t *)&int_fall); |
|
if (ret) { |
|
goto out; |
|
} |
|
#else |
|
gpio_pca_series_reg_cache_mini_get(dev)->int_rise = int_rise; |
|
gpio_pca_series_reg_cache_mini_get(dev)->int_fall = int_fall; |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
|
|
/** enable latch if available, so we do not lost interrupt */ |
|
if (cfg->part_cfg->flags & PCA_HAS_LATCH) { |
|
input_latch = sys_cpu_to_le32(input_latch); |
|
ret = gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_INPUT_LATCH, |
|
(uint8_t *)&input_latch); |
|
if (ret) { |
|
goto out; |
|
} |
|
} |
|
/** update interrupt mask register if available */ |
|
if (cfg->part_cfg->flags & PCA_HAS_INT_MASK) { |
|
int_mask = sys_cpu_to_le32(int_mask); |
|
ret = gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_INTERRUPT_MASK, |
|
(uint8_t *)&int_mask); |
|
if (ret) { |
|
goto out; |
|
} |
|
} |
|
|
|
out: |
|
k_sem_give(&data->lock); |
|
|
|
if (ret) { |
|
LOG_ERR("int config(s) error %d", ret); |
|
} |
|
return ret; |
|
} |
|
|
|
/** |
|
* @brief Configure interrupt for device with hardware-selected interrupt edge |
|
* |
|
* @note This version is used by devices that have interrupt edge config |
|
* (aka PCA_HAS_INT_EXTEND), so interrupt only triggers on selected edge. |
|
* This applies to all pcal65xx devices. |
|
* This will configure interrupt mask register and interrupt edge register. |
|
* (All devices that have PCA_HAS_INT_EXTEND flag should have PCA_HAS_INT_MASK |
|
* flag. Otherwise, throw out error.) |
|
*/ |
|
static int gpio_pca_series_pin_interrupt_configure_extended( |
|
const struct device *dev, |
|
gpio_pin_t pin, enum gpio_int_mode mode, |
|
enum gpio_int_trig trig) |
|
{ |
|
const struct gpio_pca_series_config *cfg = dev->config; |
|
struct gpio_pca_series_data *data = dev->data; |
|
uint64_t int_edge; |
|
uint32_t int_mask, input_latch; |
|
int ret = 0; |
|
uint32_t edge_cfg_shift = pin << 1U; |
|
uint64_t edge_cfg_mask = 0b11 << edge_cfg_shift; |
|
|
|
if (cfg->gpio_int.port == NULL) { |
|
return -ENOTSUP; |
|
} |
|
/* Device does not support level-triggered interrupts. */ |
|
if (mode == GPIO_INT_MODE_LEVEL) { |
|
return -ENOTSUP; |
|
} |
|
|
|
#ifdef GPIO_NXP_PCA_SERIES_DEBUG |
|
/** |
|
* Check the flags during runtime. |
|
* |
|
* The purpose is to check api assignment for developer who is adding |
|
* new device support to this driver. |
|
*/ |
|
const uint8_t check_flags = (PCA_HAS_LATCH | PCA_HAS_INT_MASK | PCA_HAS_INT_EXTEND); |
|
|
|
if ((cfg->part_cfg->flags & check_flags) != check_flags) { |
|
LOG_ERR("unsupported device trying to configure interrupt with extended api"); |
|
return -ENOTSUP; |
|
} |
|
#endif /* GPIO_NXP_PCA_SERIES_DEBUG */ |
|
|
|
if (k_is_in_isr()) { |
|
return -EWOULDBLOCK; |
|
} |
|
|
|
LOG_DBG("int cfg(e) pin %d mode %d trig %d", pin, mode, trig); |
|
|
|
k_sem_take(&data->lock, K_FOREVER); |
|
|
|
/** |
|
* TODO: Only write 1 byte. |
|
* |
|
* The config api only configures 1 pin, so only need to write the |
|
* modified byte. Need to create new register & cache api to provide |
|
* single byte access. |
|
* This applies to: pin_configure, pin_interrupt_configure |
|
*/ |
|
|
|
/** get current interrupt edge config */ |
|
ret = gpio_pca_series_reg_cache_read(dev, PCA_REG_TYPE_2B_INTERRUPT_EDGE, |
|
(uint8_t *)&int_edge); |
|
if (ret) { |
|
LOG_ERR("get current interrupt edge config fail [%d]", ret); |
|
goto out; |
|
} |
|
int_edge = sys_le64_to_cpu(int_edge); |
|
|
|
ret = gpio_pca_series_reg_cache_read(dev, PCA_REG_TYPE_1B_INTERRUPT_MASK, |
|
(uint8_t *)&int_mask); |
|
if (ret) { |
|
goto out; |
|
} |
|
int_mask = sys_le32_to_cpu(int_mask); |
|
|
|
if (mode == GPIO_INT_MODE_DISABLED) { |
|
int_mask |= BIT(pin); /** set 1 to disable interrupt */ |
|
} else { |
|
if (trig == GPIO_INT_TRIG_BOTH) { |
|
int_edge = (int_edge & (~edge_cfg_mask)) | |
|
(PCA_INTERRUPT_EITHER_EDGE << edge_cfg_shift); |
|
} else if (trig == GPIO_INT_TRIG_LOW) { |
|
int_edge = (int_edge & (~edge_cfg_mask)) | |
|
(PCA_INTERRUPT_FALLING_EDGE << edge_cfg_shift); |
|
} else if (trig == GPIO_INT_TRIG_HIGH) { |
|
int_edge = (int_edge & (~edge_cfg_mask)) | |
|
(PCA_INTERRUPT_RISING_EDGE << edge_cfg_shift); |
|
} |
|
int_mask &= ~BIT(pin); /** set 0 to enable interrupt */ |
|
} |
|
|
|
/** update interrupt edge config */ |
|
int_edge = sys_cpu_to_le64(int_edge); |
|
ret = gpio_pca_series_reg_write(dev, PCA_REG_TYPE_2B_INTERRUPT_EDGE, |
|
(uint8_t *)&int_edge); |
|
if (ret) { |
|
goto out; |
|
} |
|
/** enable latch, so we do not lost interrupt */ |
|
input_latch = ~int_mask; |
|
input_latch = sys_cpu_to_le32(input_latch); |
|
ret = gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_INPUT_LATCH, |
|
(uint8_t *)&input_latch); |
|
if (ret) { |
|
goto out; |
|
} |
|
/** update interrupt mask register */ |
|
int_mask = sys_cpu_to_le32(int_mask); |
|
ret = gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_INTERRUPT_MASK, |
|
(uint8_t *)&int_mask); |
|
if (ret) { |
|
goto out; |
|
} |
|
|
|
out: |
|
k_sem_give(&data->lock); |
|
return ret; |
|
} |
|
|
|
static int gpio_pca_series_manage_callback(const struct device *dev, |
|
struct gpio_callback *callback, bool set) |
|
{ |
|
struct gpio_pca_series_data *data = dev->data; |
|
|
|
return gpio_manage_callback(&data->callbacks, callback, set); |
|
} |
|
|
|
static void gpio_pca_series_interrupt_handler_standard(const struct device *dev, |
|
gpio_port_value_t *input_value) |
|
{ |
|
struct gpio_pca_series_data *data = dev->data; |
|
int ret = 0; |
|
uint32_t input_old = 0, int_rise = 0, int_fall = 0; |
|
uint32_t input = 0; |
|
uint32_t transitioned_pins = 0; |
|
uint32_t int_status = 0; |
|
|
|
k_sem_take(&data->lock, K_FOREVER); |
|
|
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
/** read from cache even if this register is not present on device */ |
|
ret = gpio_pca_series_reg_cache_read(dev, PCA_REG_TYPE_1B_INPUT_HISTORY, |
|
(uint8_t *)&input_old); |
|
if (ret) { |
|
goto out; |
|
} |
|
input_old = sys_le32_to_cpu(input_old); |
|
/** read from cache even if this register is not present on device */ |
|
ret = gpio_pca_series_reg_cache_read(dev, PCA_REG_TYPE_1B_INTERRUPT_RISE, |
|
(uint8_t *)&int_rise); |
|
if (ret) { |
|
goto out; |
|
} |
|
int_rise = sys_le32_to_cpu(int_rise); |
|
/** read from cache even if this register is not present on device */ |
|
ret = gpio_pca_series_reg_cache_read(dev, PCA_REG_TYPE_1B_INTERRUPT_FALL, |
|
(uint8_t *)&int_fall); |
|
if (ret) { |
|
goto out; |
|
} |
|
int_fall = sys_le32_to_cpu(int_fall); |
|
#else |
|
input_old = gpio_pca_series_reg_cache_mini_get(dev)->input_old; |
|
int_rise = gpio_pca_series_reg_cache_mini_get(dev)->int_rise; |
|
int_fall = gpio_pca_series_reg_cache_mini_get(dev)->int_fall; |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
|
|
/** check if any interrupt enabled */ |
|
if ((!int_rise) && (!int_fall)) { |
|
goto out; |
|
} |
|
|
|
/** read current input value, and clear status if reg is present */ |
|
ret = gpio_pca_series_reg_read(dev, PCA_REG_TYPE_1B_INPUT_PORT, (uint8_t *)&input); |
|
if (ret) { |
|
goto out; |
|
} |
|
input = sys_le32_to_cpu(input); |
|
/** compare input to input_old to get transitioned_pins */ |
|
transitioned_pins = input_old ^ input; |
|
|
|
/** Mask gpio transactions with rising/falling edge interrupt config */ |
|
int_status = (int_rise & transitioned_pins & input) |
|
| (int_fall & transitioned_pins & (~input)); |
|
|
|
/** update current input to cache */ |
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
uint32_t input_le = sys_cpu_to_le32(input); |
|
|
|
ret = gpio_pca_series_reg_cache_update(dev, PCA_REG_TYPE_1B_INPUT_HISTORY, |
|
(uint8_t *)&input_le); |
|
#else |
|
gpio_pca_series_reg_cache_mini_get(dev)->input_old = input; |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
|
|
out: |
|
k_sem_give(&data->lock); |
|
|
|
if (input_value) { |
|
*input_value = input; |
|
} |
|
if ((ret == 0) && (int_status)) { |
|
gpio_fire_callbacks(&data->callbacks, dev, int_status); |
|
} |
|
} |
|
|
|
static void gpio_pca_series_interrupt_handler_extended(const struct device *dev) |
|
{ |
|
const struct gpio_pca_series_config *cfg = dev->config; |
|
struct gpio_pca_series_data *data = dev->data; |
|
|
|
int ret = 0; |
|
uint32_t int_status = 0; |
|
|
|
#ifdef GPIO_NXP_PCA_SERIES_DEBUG |
|
/** |
|
* Check the flags during runtime. |
|
* |
|
* The purpose is to check api assignment for developer who is adding |
|
* new device support to this driver. |
|
*/ |
|
const uint8_t check_flags = (PCA_HAS_LATCH | PCA_HAS_INT_MASK | PCA_HAS_INT_EXTEND); |
|
|
|
if ((cfg->part_cfg->flags & check_flags) != check_flags) { |
|
LOG_ERR("unsupported device trying to read gpio with extended api"); |
|
return; |
|
} |
|
#else |
|
ARG_UNUSED(cfg); |
|
#endif /* GPIO_NXP_PCA_SERIES_DEBUG */ |
|
|
|
LOG_DBG("enter int handler(e)"); |
|
|
|
k_sem_take(&data->lock, K_FOREVER); |
|
|
|
/** get transitioned_pins from interrupt_status register */ |
|
ret = gpio_pca_series_reg_read(dev, PCA_REG_TYPE_1B_INTERRUPT_STATUS, |
|
(uint8_t *)&int_status); |
|
if (ret) { |
|
goto out; |
|
} |
|
|
|
/** clear status */ |
|
ret = gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_INTERRUPT_CLEAR, |
|
(uint8_t *)&int_status); |
|
if (ret) { |
|
goto out; |
|
} |
|
|
|
out: |
|
k_sem_give(&data->lock); |
|
|
|
if ((ret == 0) && (int_status)) { |
|
int_status = sys_le32_to_cpu(int_status); |
|
gpio_fire_callbacks(&data->callbacks, dev, int_status); |
|
} |
|
} |
|
|
|
static void gpio_pca_series_interrupt_worker_standard(struct k_work *work) |
|
{ |
|
struct gpio_pca_series_data *data = |
|
CONTAINER_OF(work, struct gpio_pca_series_data, int_work); |
|
const struct device *dev = data->self; |
|
|
|
gpio_pca_series_interrupt_handler_standard(dev, NULL); |
|
} |
|
|
|
static void gpio_pca_series_interrupt_worker_extended(struct k_work *work) |
|
{ |
|
struct gpio_pca_series_data *data = |
|
CONTAINER_OF(work, struct gpio_pca_series_data, int_work); |
|
const struct device *dev = data->self; |
|
|
|
gpio_pca_series_interrupt_handler_extended(dev); |
|
} |
|
|
|
static void gpio_pca_series_gpio_int_handler(const struct device *dev, |
|
struct gpio_callback *gpio_cb, uint32_t pins) |
|
{ |
|
ARG_UNUSED(dev); |
|
ARG_UNUSED(pins); |
|
|
|
LOG_DBG("gpio_int trigger"); |
|
|
|
struct gpio_pca_series_data *data = |
|
CONTAINER_OF(gpio_cb, struct gpio_pca_series_data, gpio_cb); |
|
|
|
k_work_submit(&data->int_work); |
|
} |
|
|
|
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
|
|
/** |
|
* } |
|
* gpio_pca_zephyr_gpio_api |
|
*/ |
|
|
|
static DEVICE_API(gpio, gpio_pca_series_api_funcs_standard) = { |
|
.pin_configure = gpio_pca_series_pin_configure, |
|
.port_get_raw = gpio_pca_series_port_read_standard, |
|
.port_set_masked_raw = gpio_pca_series_port_set_masked, |
|
.port_set_bits_raw = gpio_pca_series_port_set_bits, |
|
.port_clear_bits_raw = gpio_pca_series_port_clear_bits, |
|
.port_toggle_bits = gpio_pca_series_port_toggle_bits, |
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
.pin_interrupt_configure = gpio_pca_series_pin_interrupt_configure_standard, |
|
.manage_callback = gpio_pca_series_manage_callback, |
|
#endif |
|
}; |
|
|
|
static DEVICE_API(gpio, gpio_pca_series_api_funcs_extended) = { |
|
.pin_configure = gpio_pca_series_pin_configure, |
|
.port_get_raw = gpio_pca_series_port_read_extended, /* special version used */ |
|
.port_set_masked_raw = gpio_pca_series_port_set_masked, |
|
.port_set_bits_raw = gpio_pca_series_port_set_bits, |
|
.port_clear_bits_raw = gpio_pca_series_port_clear_bits, |
|
.port_toggle_bits = gpio_pca_series_port_toggle_bits, |
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
.pin_interrupt_configure = gpio_pca_series_pin_interrupt_configure_extended, |
|
.manage_callback = gpio_pca_series_manage_callback, |
|
#endif |
|
}; |
|
|
|
/** |
|
* @brief Initialization function of pca_series |
|
* |
|
* This sets initial input/ output configuration and output states. |
|
* The interrupt is configured if this is enabled. |
|
* |
|
* @param dev Device struct |
|
* @return 0 if successful, failed otherwise. |
|
*/ |
|
static int gpio_pca_series_init(const struct device *dev) |
|
{ |
|
const struct gpio_pca_series_config *cfg = dev->config; |
|
struct gpio_pca_series_data *data = dev->data; |
|
int ret = 0; |
|
|
|
if (!device_is_ready(cfg->i2c.bus)) { |
|
LOG_ERR("i2c bus device not found"); |
|
goto out_bus; |
|
} |
|
|
|
/** device reset */ |
|
gpio_pca_series_reset(dev); |
|
LOG_DBG("device reset done"); |
|
|
|
#ifdef GPIO_NXP_PCA_SERIES_DEBUG |
|
# ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
gpio_pca_series_cache_test(dev); |
|
# endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
#endif /* GPIO_NXP_PCA_SERIES_DEBUG */ |
|
|
|
/** Set cache to initial state */ |
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
ret = gpio_pca_series_reg_cache_reset(dev); |
|
#else |
|
ret = gpio_pca_series_reg_cache_mini_reset(dev); |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
if (ret) { |
|
LOG_ERR("cache init error %d", ret); |
|
goto out; |
|
} |
|
LOG_DBG("cache init done"); |
|
|
|
/** configure interrupt */ |
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
/** save dev pointer */ |
|
data->self = dev; |
|
|
|
/** check the flags and init work obj */ |
|
const uint8_t check_flags = (PCA_HAS_LATCH | PCA_HAS_INT_MASK | PCA_HAS_INT_EXTEND); |
|
|
|
if ((cfg->part_cfg->flags & check_flags) == check_flags) { |
|
k_work_init(&data->int_work, gpio_pca_series_interrupt_worker_extended); |
|
} else { |
|
k_work_init(&data->int_work, gpio_pca_series_interrupt_worker_standard); |
|
} |
|
|
|
/** Interrupt pin connected, enable interrupt */ |
|
if (cfg->gpio_int.port != NULL) { |
|
if (!device_is_ready(cfg->gpio_int.port)) { |
|
LOG_ERR("Cannot get pointer to gpio interrupt device"); |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
ret = gpio_pin_configure_dt(&cfg->gpio_int, GPIO_INPUT); |
|
if (ret) { |
|
goto out; |
|
} |
|
|
|
ret = gpio_pin_interrupt_configure_dt(&cfg->gpio_int, GPIO_INT_EDGE_TO_ACTIVE); |
|
if (ret) { |
|
goto out; |
|
} |
|
|
|
gpio_init_callback(&data->gpio_cb, gpio_pca_series_gpio_int_handler, |
|
BIT(cfg->gpio_int.pin)); |
|
|
|
ret = gpio_add_callback(cfg->gpio_int.port, &data->gpio_cb); |
|
} else { |
|
LOG_WRN("pca interrupt enabled w/o int-gpios configured in dts"); |
|
} |
|
#else |
|
ARG_UNUSED(data); |
|
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
|
|
out: |
|
#ifdef GPIO_NXP_PCA_SERIES_DEBUG |
|
gpio_pca_series_debug_dump(dev); |
|
#endif /* GPIO_NXP_PCA_SERIES_DEBUG */ |
|
|
|
out_bus: |
|
if (ret) { |
|
LOG_ERR("%s init failed: %d", dev->name, ret); |
|
} else { |
|
LOG_INF("%s init ok", dev->name); |
|
} |
|
return ret; |
|
} |
|
|
|
/** |
|
* @brief get device description by part_no |
|
*/ |
|
#define GPIO_PCA_GET_API_BY_PART_NO(part_no) ( \ |
|
(part_no == PCA_PART_NO_PCAL6524) ? &gpio_pca_series_api_funcs_extended : \ |
|
(part_no == PCA_PART_NO_PCAL6534) ? &gpio_pca_series_api_funcs_extended : \ |
|
&gpio_pca_series_api_funcs_standard \ |
|
) |
|
#define GPIO_PCA_GET_PORT_NO_CFG_BY_PART_NO(part_no) (GPIO_PCA_PORT_NO_##part_no) |
|
#define GPIO_PCA_GET_PART_FLAG_BY_PART_NO(part_no) (GPIO_PCA_FLAG_##part_no) |
|
#define GPIO_PCA_GET_PART_CFG_BY_PART_NO(part_no) (GPIO_PCA_PART_CFG_##part_no) |
|
|
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
|
|
/** Cache size increment by feature flags */ |
|
#define PCA_REG_HAS_LATCH (3U) /* +2b_drive_strength, +1b_input_latch */ |
|
#define PCA_REG_HAS_PULL (2U) /* +1b_pull_enable, +1b_pull_select */ |
|
#define PCA_REG_HAS_OUT_CONFIG (1U) /* +1b_output_config */ |
|
|
|
#define GPIO_PCA_GET_CACHE_SIZE_BY_PART_NO_NO_INT(part_no) (( \ |
|
2U /* basic: +output_port, +configuration */ \ |
|
+ ((GPIO_PCA_GET_PART_FLAG_BY_PART_NO(part_no) & PCA_HAS_LATCH) ? \ |
|
PCA_REG_HAS_LATCH : 0U) \ |
|
+ ((GPIO_PCA_GET_PART_FLAG_BY_PART_NO(part_no) & PCA_HAS_PULL) ? \ |
|
PCA_REG_HAS_PULL : 0U) \ |
|
+ ((GPIO_PCA_GET_PART_FLAG_BY_PART_NO(part_no) & PCA_HAS_OUT_CONFIG) ? \ |
|
PCA_REG_HAS_OUT_CONFIG : 0U) \ |
|
) * GPIO_PCA_GET_PORT_NO_CFG_BY_PART_NO(part_no) \ |
|
) |
|
|
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
|
|
/** Cache size increment by feature flags (continued) */ |
|
#define PCA_REG_HAS_INT_EXTEND (3U) /* true: +2b_interrupt_edge, +1b_interrupt_mask */ |
|
#define PCA_REG_NO_INT_EXTEND (3U) /* false: +1b_input_history, +1b_interrupt_rise, |
|
* +1b_interrupt_fall |
|
*/ |
|
|
|
/** |
|
* registers: |
|
* 1b_input_port |
|
* - present on all devices |
|
* - not used if PCA_HAS_OUT_CONFIG |
|
* - non-cacheable |
|
* 1b_output_port |
|
* - present on all devices |
|
* - cacheable |
|
* 1b_configuration |
|
* - present on all devices |
|
* - cacheable |
|
* 2b_output_drive_strength |
|
* - present if PCA_HAS_LATCH |
|
* - cacheable if present |
|
* 1b_input_latch |
|
* - present if PCA_HAS_LATCH |
|
* - non-cacheable |
|
* 1b_pull_enable |
|
* - present if PCA_HAS_PULL |
|
* - cacheable if present |
|
* 1b_pull_select |
|
* - present if PCA_HAS_PULL |
|
* - cacheable if present |
|
* 1b_input_status |
|
* - present if PCA_HAS_OUT_CONFIG |
|
* - replaces 1b_input_port if present |
|
* - non-cacheable |
|
* 1b_output_config |
|
* - present if PCA_HAS_OUT_CONFIG |
|
* - cacheable if present |
|
* 1b_interrupt_mask |
|
* - present if PCA_HAS_INT_MASK |
|
* - not present by default |
|
* - cacheable if PCA_HAS_INT_EXTEND |
|
* 1b_interrupt_status |
|
* - present if PCA_HAS_INT_MASK |
|
* - not used if not PCA_HAS_INT_EXTEND |
|
* - read only |
|
* - non-cacheable |
|
* 2b_interrupt_edge |
|
* - present if PCA_HAS_INT_EXTEND |
|
* - cacheable if present |
|
* 1b_interrupt_clear |
|
* - present if PCA_HAS_INT_EXTEND |
|
* - write only |
|
* - non-cacheable |
|
* 1b_input_history |
|
* - not present on all devices (fake cache) |
|
* - store last input value |
|
* - cacheable (present) if not PCA_HAS_INT_EXTEND |
|
* 1b_interrupt_rise |
|
* - not present on all devices (fake cache) |
|
* - store pins interrupt on rising edge |
|
* - cacheable (present) if not PCA_HAS_INT_EXTEND |
|
* 1b_interrupt_fall |
|
* - not present on all devices (fake cache) |
|
* - store pins interrupt on falling edge |
|
* - cacheable (present) if not PCA_HAS_INT_EXTEND |
|
*/ |
|
|
|
#define GPIO_PCA_GET_CACHE_SIZE_BY_PART_NO(part_no) ( \ |
|
GPIO_PCA_GET_CACHE_SIZE_BY_PART_NO_NO_INT(part_no) \ |
|
+ (((GPIO_PCA_GET_PART_FLAG_BY_PART_NO(part_no) & PCA_HAS_INT_EXTEND) ? \ |
|
PCA_REG_HAS_INT_EXTEND : PCA_REG_NO_INT_EXTEND) \ |
|
) * GPIO_PCA_GET_PORT_NO_CFG_BY_PART_NO(part_no) \ |
|
) |
|
#else /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
#define GPIO_PCA_GET_CACHE_SIZE_BY_PART_NO(part_no) ( \ |
|
GPIO_PCA_GET_CACHE_SIZE_BY_PART_NO_NO_INT(part_no) \ |
|
) |
|
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
|
|
/** |
|
* @brief implement pca953x driver |
|
* |
|
* @note flags = 0U; |
|
* |
|
* api set : standard |
|
* ngpios : 8, 16 |
|
* part_no : pca9534 pca9538 pca9535 pca9539 |
|
*/ |
|
#define GPIO_PCA_SERIES_FLAG_TYPE_0 (0U) |
|
|
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
/** |
|
* cache map for flag = 0U |
|
*/ |
|
static const uint8_t gpio_pca_series_cache_map_pca953x[] = { |
|
PCA_REG_INVALID, /** input_port if not PCA_HAS_OUT_CONFIG, non-cacheable */ |
|
0x00, /** output_port */ |
|
/* 0x02, polarity_inversion (unused, omitted) */ |
|
0x01, /** configuration */ |
|
PCA_REG_INVALID, /** 2b_output_drive_strength if PCA_HAS_LATCH*/ |
|
PCA_REG_INVALID, /** input_latch if PCA_HAS_LATCH*/ |
|
PCA_REG_INVALID, /** pull_enable if PCA_HAS_PULL */ |
|
PCA_REG_INVALID, /** pull_select if PCA_HAS_PULL */ |
|
PCA_REG_INVALID, /** input_status if PCA_HAS_OUT_CONFIG, non-cacheable */ |
|
PCA_REG_INVALID, /** output_config if PCA_HAS_OUT_CONFIG */ |
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
PCA_REG_INVALID, /** interrupt_mask if PCA_HAS_INT_MASK, |
|
* non-cacheable if not PCA_HAS_INT_EXTEND |
|
*/ |
|
PCA_REG_INVALID, /** int_status if PCA_HAS_INT_MASK, non-cacheable */ |
|
PCA_REG_INVALID, /** 2b_interrupt_edge if PCA_HAS_INT_EXTEND */ |
|
PCA_REG_INVALID, /** interrupt_clear if PCA_HAS_INT_EXTEND, non-cacheable */ |
|
# ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
0x02, /** 1b_input_history if PCA_HAS_LATCH and not PCA_HAS_INT_EXTEND */ |
|
0x03, /** 1b_interrupt_rise if PCA_HAS_LATCH and not PCA_HAS_INT_EXTEND */ |
|
0x04, /** 1b_interrupt_fall if PCA_HAS_LATCH and not PCA_HAS_INT_EXTEND */ |
|
# endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
}; |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
|
|
static const uint8_t gpio_pca_series_reg_pca9538[] = { |
|
0x00, /** input_port if not PCA_HAS_OUT_CONFIG, non-cacheable */ |
|
0x01, /** output_port */ |
|
/* 0x02, polarity_inversion (unused, omitted) */ |
|
0x03, /** configuration */ |
|
PCA_REG_INVALID, /** 2b_output_drive_strength if PCA_HAS_LATCH*/ |
|
PCA_REG_INVALID, /** input_latch if PCA_HAS_LATCH*/ |
|
PCA_REG_INVALID, /** pull_enable if PCA_HAS_PULL */ |
|
PCA_REG_INVALID, /** pull_select if PCA_HAS_PULL */ |
|
PCA_REG_INVALID, /** input_status if PCA_HAS_OUT_CONFIG, non-cacheable */ |
|
PCA_REG_INVALID, /** output_config if PCA_HAS_OUT_CONFIG */ |
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
PCA_REG_INVALID, /** interrupt_mask if PCA_HAS_INT_MASK, |
|
* non-cacheable if not PCA_HAS_INT_EXTEND |
|
*/ |
|
PCA_REG_INVALID, /** int_status if PCA_HAS_INT_MASK */ |
|
PCA_REG_INVALID, /** 2b_interrupt_edge if PCA_HAS_INT_EXTEND */ |
|
PCA_REG_INVALID, /** interrupt_clear if PCA_HAS_INT_EXTEND, non-cacheable */ |
|
# ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
PCA_REG_INVALID, /** 1b_input_history if PCA_HAS_LATCH and not PCA_HAS_INT_EXTEND */ |
|
PCA_REG_INVALID, /** 1b_interrupt_rise if PCA_HAS_LATCH and not PCA_HAS_INT_EXTEND */ |
|
PCA_REG_INVALID, /** 1b_interrupt_fall if PCA_HAS_LATCH and not PCA_HAS_INT_EXTEND */ |
|
# endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
}; |
|
|
|
#define GPIO_PCA_PORT_NO_PCA_PART_NO_PCA9538 (1U) |
|
#define GPIO_PCA_FLAG_PCA_PART_NO_PCA9538 GPIO_PCA_SERIES_FLAG_TYPE_0 |
|
#define GPIO_PCA_PART_CFG_PCA_PART_NO_PCA9538 (&gpio_pca_series_part_cfg_pca9538) |
|
|
|
const struct gpio_pca_series_part_config gpio_pca_series_part_cfg_pca9538 = { |
|
.port_no = GPIO_PCA_PORT_NO_PCA_PART_NO_PCA9538, |
|
.flags = GPIO_PCA_FLAG_PCA_PART_NO_PCA9538, |
|
.regs = gpio_pca_series_reg_pca9538, |
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
# ifdef GPIO_NXP_PCA_SERIES_DEBUG |
|
.cache_size = GPIO_PCA_GET_CACHE_SIZE_BY_PART_NO(PCA_PART_NO_PCA9538), |
|
# endif /* GPIO_NXP_PCA_SERIES_DEBUG */ |
|
.cache_map = gpio_pca_series_cache_map_pca953x, |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
}; |
|
|
|
/** |
|
* pca9555 share the same register layout with pca9539, with |
|
* RESET pin repurposed to another address strapping pin. |
|
* no difference from driver perspective. |
|
*/ |
|
|
|
#define GPIO_PCA_PORT_NO_PCA_PART_NO_PCA9554 GPIO_PCA_PORT_NO_PCA_PART_NO_PCA9538 |
|
#define GPIO_PCA_FLAG_PCA_PART_NO_PCA9554 GPIO_PCA_FLAG_PCA_PART_NO_PCA9538 |
|
#define GPIO_PCA_PART_CFG_PCA_PART_NO_PCA9554 (&gpio_pca_series_part_cfg_pca9554) |
|
|
|
const struct gpio_pca_series_part_config gpio_pca_series_part_cfg_pca9554 = { |
|
.port_no = GPIO_PCA_PORT_NO_PCA_PART_NO_PCA9554, |
|
.flags = GPIO_PCA_FLAG_PCA_PART_NO_PCA9554, |
|
.regs = gpio_pca_series_reg_pca9538, |
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
# ifdef GPIO_NXP_PCA_SERIES_DEBUG |
|
.cache_size = GPIO_PCA_GET_CACHE_SIZE_BY_PART_NO(PCA_PART_NO_PCA9554), |
|
# endif /* GPIO_NXP_PCA_SERIES_DEBUG */ |
|
.cache_map = gpio_pca_series_cache_map_pca953x, |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
}; |
|
|
|
static const uint8_t gpio_pca_series_reg_pca9539[] = { |
|
0x00, /** input_port if not PCA_HAS_OUT_CONFIG, non-cacheable */ |
|
0x02, /** output_port */ |
|
/* 0x04, polarity_inversion (unused, omitted) */ |
|
0x06, /** configuration */ |
|
PCA_REG_INVALID, /** 2b_output_drive_strength if PCA_HAS_LATCH*/ |
|
PCA_REG_INVALID, /** input_latch if PCA_HAS_LATCH*/ |
|
PCA_REG_INVALID, /** pull_enable if PCA_HAS_PULL */ |
|
PCA_REG_INVALID, /** pull_select if PCA_HAS_PULL */ |
|
PCA_REG_INVALID, /** input_status if PCA_HAS_OUT_CONFIG, non-cacheable */ |
|
PCA_REG_INVALID, /** output_config if PCA_HAS_OUT_CONFIG */ |
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
PCA_REG_INVALID, /** interrupt_mask if PCA_HAS_INT_MASK, |
|
* non-cacheable if not PCA_HAS_INT_EXTEND |
|
*/ |
|
PCA_REG_INVALID, /** int_status if PCA_HAS_INT_MASK */ |
|
PCA_REG_INVALID, /** 2b_interrupt_edge if PCA_HAS_INT_EXTEND */ |
|
PCA_REG_INVALID, /** interrupt_clear if PCA_HAS_INT_EXTEND, non-cacheable */ |
|
# ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
PCA_REG_INVALID, /** 1b_input_history if PCA_HAS_LATCH and not PCA_HAS_INT_EXTEND */ |
|
PCA_REG_INVALID, /** 1b_interrupt_rise if PCA_HAS_LATCH and not PCA_HAS_INT_EXTEND */ |
|
PCA_REG_INVALID, /** 1b_interrupt_fall if PCA_HAS_LATCH and not PCA_HAS_INT_EXTEND */ |
|
# endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
}; |
|
|
|
#define GPIO_PCA_PORT_NO_PCA_PART_NO_PCA9539 (2U) |
|
#define GPIO_PCA_FLAG_PCA_PART_NO_PCA9539 GPIO_PCA_SERIES_FLAG_TYPE_0 |
|
#define GPIO_PCA_PART_CFG_PCA_PART_NO_PCA9539 (&gpio_pca_series_part_cfg_pca9539) |
|
|
|
const struct gpio_pca_series_part_config gpio_pca_series_part_cfg_pca9539 = { |
|
.port_no = GPIO_PCA_PORT_NO_PCA_PART_NO_PCA9539, |
|
.flags = GPIO_PCA_FLAG_PCA_PART_NO_PCA9539, |
|
.regs = gpio_pca_series_reg_pca9539, |
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
# ifdef GPIO_NXP_PCA_SERIES_DEBUG |
|
.cache_size = GPIO_PCA_GET_CACHE_SIZE_BY_PART_NO(PCA_PART_NO_PCA9539), |
|
# endif /* GPIO_NXP_PCA_SERIES_DEBUG */ |
|
.cache_map = gpio_pca_series_cache_map_pca953x, |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
}; |
|
|
|
/** |
|
* pca9555 share the same register layout with pca9539, with |
|
* RESET pin repurposed to another address strapping pin. |
|
* no difference from driver perspective. |
|
*/ |
|
|
|
#define GPIO_PCA_PORT_NO_PCA_PART_NO_PCA9555 GPIO_PCA_PORT_NO_PCA_PART_NO_PCA9539 |
|
#define GPIO_PCA_FLAG_PCA_PART_NO_PCA9555 GPIO_PCA_FLAG_PCA_PART_NO_PCA9539 |
|
#define GPIO_PCA_PART_CFG_PCA_PART_NO_PCA9555 (&gpio_pca_series_part_cfg_pca9555) |
|
|
|
const struct gpio_pca_series_part_config gpio_pca_series_part_cfg_pca9555 = { |
|
.port_no = GPIO_PCA_PORT_NO_PCA_PART_NO_PCA9555, |
|
.flags = GPIO_PCA_FLAG_PCA_PART_NO_PCA9555, |
|
.regs = gpio_pca_series_reg_pca9539, |
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
# ifdef GPIO_NXP_PCA_SERIES_DEBUG |
|
.cache_size = GPIO_PCA_GET_CACHE_SIZE_BY_PART_NO(PCA_PART_NO_PCA9555), |
|
# endif /* GPIO_NXP_PCA_SERIES_DEBUG */ |
|
.cache_map = gpio_pca_series_cache_map_pca953x, |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
}; |
|
|
|
/** |
|
* @brief implement pcal65xx driver |
|
* |
|
* @note flags = PCA_HAS_LATCH |
|
* | PCA_HAS_PULL |
|
* | PCA_HAS_INT_MASK |
|
* | PCA_HAS_INT_EXTEND |
|
* | PCA_HAS_OUT_CONFIG |
|
* |
|
* api set : pcal65xx |
|
* ngpios : 24, 32 |
|
* part_no : pcal6524 pcal6534 |
|
*/ |
|
#define GPIO_PCA_SERIES_FLAG_TYPE_3 (PCA_HAS_LATCH | PCA_HAS_PULL | PCA_HAS_INT_MASK \ |
|
| PCA_HAS_INT_EXTEND | PCA_HAS_OUT_CONFIG) |
|
|
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
/** |
|
* cache map for flag = PCA_HAS_LATCH |
|
* | PCA_HAS_PULL |
|
* | PCA_HAS_INT_MASK |
|
* | PCA_HAS_INT_EXTEND |
|
* | PCA_HAS_OUT_CONFIG |
|
*/ |
|
static const uint8_t gpio_pca_series_cache_map_pcal65xx[] = { |
|
PCA_REG_INVALID, /** input_port if not PCA_HAS_OUT_CONFIG, non-cacheable */ |
|
0x00, /** output_port */ |
|
/* 0x02, polarity_inversion (unused, omitted) */ |
|
0x01, /** configuration */ |
|
0x02, /** 2b_output_drive_strength if PCA_HAS_LATCH*/ |
|
0x04, /** input_latch if PCA_HAS_LATCH*/ |
|
0x05, /** pull_enable if PCA_HAS_PULL */ |
|
0x06, /** pull_select if PCA_HAS_PULL */ |
|
PCA_REG_INVALID, /** input_status if PCA_HAS_OUT_CONFIG, non-cacheable */ |
|
0x07, /** output_config if PCA_HAS_OUT_CONFIG */ |
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
0x08, /** interrupt_mask if PCA_HAS_INT_MASK, |
|
* non-cacheable if not PCA_HAS_INT_EXTEND |
|
*/ |
|
PCA_REG_INVALID, /** int_status if PCA_HAS_INT_MASK, non-cacheable */ |
|
0x09, /** 2b_interrupt_edge if PCA_HAS_INT_EXTEND */ |
|
PCA_REG_INVALID, /** interrupt_clear if PCA_HAS_INT_EXTEND, non-cacheable */ |
|
# ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
PCA_REG_INVALID, /** 1b_input_history if PCA_HAS_LATCH and not PCA_HAS_INT_EXTEND */ |
|
PCA_REG_INVALID, /** 1b_interrupt_rise if PCA_HAS_LATCH and not PCA_HAS_INT_EXTEND */ |
|
PCA_REG_INVALID, /** 1b_interrupt_fall if PCA_HAS_LATCH and not PCA_HAS_INT_EXTEND */ |
|
# endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
}; |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
|
|
static const uint8_t gpio_pca_series_reg_pcal6524[] = { |
|
PCA_REG_INVALID, /** input_port if not PCA_HAS_OUT_CONFIG, non-cacheable */ |
|
0x04, /** output_port */ |
|
/* 0x08, polarity_inversion (unused, omitted) */ |
|
0x0c, /** configuration */ |
|
0x40, /** 2b_output_drive_strength if PCA_HAS_LATCH*/ |
|
0x48, /** input_latch if PCA_HAS_LATCH*/ |
|
0x4c, /** pull_enable if PCA_HAS_PULL */ |
|
0x50, /** pull_select if PCA_HAS_PULL */ |
|
0x6c, /** input_status if PCA_HAS_OUT_CONFIG, non-cacheable */ |
|
0x70, /** output_config if PCA_HAS_OUT_CONFIG */ |
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
0x54, /** interrupt_mask if PCA_HAS_INT_MASK, |
|
* non-cacheable if not PCA_HAS_INT_EXTEND |
|
*/ |
|
0x58, /** int_status if PCA_HAS_INT_MASK */ |
|
0x60, /** 2b_interrupt_edge if PCA_HAS_INT_EXTEND */ |
|
0x68, /** interrupt_clear if PCA_HAS_INT_EXTEND, non-cacheable */ |
|
# ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
PCA_REG_INVALID, /** 1b_input_history if PCA_HAS_LATCH and not PCA_HAS_INT_EXTEND */ |
|
PCA_REG_INVALID, /** 1b_interrupt_rise if PCA_HAS_LATCH and not PCA_HAS_INT_EXTEND */ |
|
PCA_REG_INVALID, /** 1b_interrupt_fall if PCA_HAS_LATCH and not PCA_HAS_INT_EXTEND */ |
|
# endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
}; |
|
|
|
#define GPIO_PCA_PORT_NO_PCA_PART_NO_PCAL6524 (3U) |
|
#define GPIO_PCA_FLAG_PCA_PART_NO_PCAL6524 GPIO_PCA_SERIES_FLAG_TYPE_3 |
|
#define GPIO_PCA_PART_CFG_PCA_PART_NO_PCAL6524 (&gpio_pca_series_part_cfg_pcal6524) |
|
|
|
const struct gpio_pca_series_part_config gpio_pca_series_part_cfg_pcal6524 = { |
|
.port_no = GPIO_PCA_PORT_NO_PCA_PART_NO_PCAL6524, |
|
.flags = GPIO_PCA_FLAG_PCA_PART_NO_PCAL6524, |
|
.regs = gpio_pca_series_reg_pcal6524, |
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
# ifdef GPIO_NXP_PCA_SERIES_DEBUG |
|
.cache_size = GPIO_PCA_GET_CACHE_SIZE_BY_PART_NO(PCA_PART_NO_PCAL6524), |
|
# endif /* GPIO_NXP_PCA_SERIES_DEBUG */ |
|
.cache_map = gpio_pca_series_cache_map_pcal65xx, |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
}; |
|
|
|
static const uint8_t gpio_pca_series_reg_pcal6534[] = { |
|
PCA_REG_INVALID, /** input_port if not PCA_HAS_OUT_CONFIG, non-cacheable */ |
|
0x05, /** output_port */ |
|
/* 0x0a, polarity_inversion (unused, omitted) */ |
|
0x0f, /** configuration */ |
|
0x30, /** 2b_output_drive_strength if PCA_HAS_LATCH*/ |
|
0x3a, /** input_latch if PCA_HAS_LATCH*/ |
|
0x3f, /** pull_enable if PCA_HAS_PULL */ |
|
0x44, /** pull_select if PCA_HAS_PULL */ |
|
0x63, /** input_status if PCA_HAS_OUT_CONFIG, non-cacheable */ |
|
0x68, /** output_config if PCA_HAS_OUT_CONFIG */ |
|
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT |
|
0x49, /** interrupt_mask if PCA_HAS_INT_MASK, |
|
* non-cacheable if not PCA_HAS_INT_EXTEND |
|
*/ |
|
0x4e, /** int_status if PCA_HAS_INT_MASK */ |
|
0x54, /** 2b_interrupt_edge if PCA_HAS_INT_EXTEND */ |
|
0x5e, /** interrupt_clear if PCA_HAS_INT_EXTEND, non-cacheable */ |
|
# ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
PCA_REG_INVALID, /** 1b_input_history if PCA_HAS_LATCH and not PCA_HAS_INT_EXTEND */ |
|
PCA_REG_INVALID, /** 1b_interrupt_rise if PCA_HAS_LATCH and not PCA_HAS_INT_EXTEND */ |
|
PCA_REG_INVALID, /** 1b_interrupt_fall if PCA_HAS_LATCH and not PCA_HAS_INT_EXTEND */ |
|
# endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */ |
|
}; |
|
|
|
#define GPIO_PCA_PORT_NO_PCA_PART_NO_PCAL6534 (4U) |
|
#define GPIO_PCA_FLAG_PCA_PART_NO_PCAL6534 GPIO_PCA_SERIES_FLAG_TYPE_3 |
|
#define GPIO_PCA_PART_CFG_PCA_PART_NO_PCAL6534 (&gpio_pca_series_part_cfg_pcal6534) |
|
|
|
const struct gpio_pca_series_part_config gpio_pca_series_part_cfg_pcal6534 = { |
|
.port_no = GPIO_PCA_PORT_NO_PCA_PART_NO_PCAL6534, |
|
.flags = GPIO_PCA_FLAG_PCA_PART_NO_PCAL6534, |
|
.regs = gpio_pca_series_reg_pcal6534, |
|
#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL |
|
# ifdef GPIO_NXP_PCA_SERIES_DEBUG |
|
.cache_size = GPIO_PCA_GET_CACHE_SIZE_BY_PART_NO(PCA_PART_NO_PCAL6534), |
|
# endif /* GPIO_NXP_PCA_SERIES_DEBUG */ |
|
.cache_map = gpio_pca_series_cache_map_pcal65xx, |
|
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */ |
|
}; |
|
|
|
/** |
|
* @brief common device instance |
|
* |
|
*/ |
|
#define GPIO_PCA_SERIES_DEVICE_INSTANCE(inst, part_no) \ |
|
static const struct gpio_pca_series_config gpio_##part_no##_##inst##_cfg = { \ |
|
.common = { \ |
|
.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(inst), \ |
|
}, \ |
|
.i2c = I2C_DT_SPEC_INST_GET(inst), \ |
|
.part_cfg = GPIO_PCA_GET_PART_CFG_BY_PART_NO(part_no), \ |
|
.gpio_rst = GPIO_DT_SPEC_INST_GET_OR(inst, reset_gpios, {}), \ |
|
IF_ENABLED(CONFIG_GPIO_PCA_SERIES_INTERRUPT, \ |
|
(.gpio_int = GPIO_DT_SPEC_INST_GET_OR(inst, int_gpios, {}),)) \ |
|
}; \ |
|
static uint8_t gpio_##part_no##_##inst##_reg_cache[COND_CODE_1( \ |
|
CONFIG_GPIO_PCA_SERIES_CACHE_ALL, \ |
|
(GPIO_PCA_GET_CACHE_SIZE_BY_PART_NO(part_no) /** true */\ |
|
), \ |
|
(sizeof(struct gpio_pca_series_reg_cache_mini) /** false */ \ |
|
))]; \ |
|
static struct gpio_pca_series_data gpio_##part_no##_##inst##_data = { \ |
|
.lock = Z_SEM_INITIALIZER(gpio_##part_no##_##inst##_data.lock, 1, 1), \ |
|
.cache = (void *)gpio_##part_no##_##inst##_reg_cache, \ |
|
}; \ |
|
DEVICE_DT_INST_DEFINE(inst, gpio_pca_series_init, NULL, \ |
|
&gpio_##part_no##_##inst##_data, \ |
|
&gpio_##part_no##_##inst##_cfg, POST_KERNEL, \ |
|
CONFIG_GPIO_PCA_SERIES_INIT_PRIORITY, \ |
|
GPIO_PCA_GET_API_BY_PART_NO(part_no)); |
|
|
|
|
|
#undef DT_DRV_COMPAT |
|
#define DT_DRV_COMPAT nxp_pca9538 |
|
DT_INST_FOREACH_STATUS_OKAY_VARGS(GPIO_PCA_SERIES_DEVICE_INSTANCE, PCA_PART_NO_PCA9538) |
|
|
|
#undef DT_DRV_COMPAT |
|
#define DT_DRV_COMPAT nxp_pca9539 |
|
DT_INST_FOREACH_STATUS_OKAY_VARGS(GPIO_PCA_SERIES_DEVICE_INSTANCE, PCA_PART_NO_PCA9539) |
|
|
|
#undef DT_DRV_COMPAT |
|
#define DT_DRV_COMPAT nxp_pca9554 |
|
DT_INST_FOREACH_STATUS_OKAY_VARGS(GPIO_PCA_SERIES_DEVICE_INSTANCE, PCA_PART_NO_PCA9554) |
|
|
|
#undef DT_DRV_COMPAT |
|
#define DT_DRV_COMPAT nxp_pca9555 |
|
DT_INST_FOREACH_STATUS_OKAY_VARGS(GPIO_PCA_SERIES_DEVICE_INSTANCE, PCA_PART_NO_PCA9555) |
|
|
|
#undef DT_DRV_COMPAT |
|
#define DT_DRV_COMPAT nxp_pcal6524 |
|
DT_INST_FOREACH_STATUS_OKAY_VARGS(GPIO_PCA_SERIES_DEVICE_INSTANCE, PCA_PART_NO_PCAL6524) |
|
|
|
#undef DT_DRV_COMPAT |
|
#define DT_DRV_COMPAT nxp_pcal6534 |
|
DT_INST_FOREACH_STATUS_OKAY_VARGS(GPIO_PCA_SERIES_DEVICE_INSTANCE, PCA_PART_NO_PCAL6534)
|
|
|