/*
 * Copyright 2024，2025 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/dt-bindings/gpio/pca-series-gpio.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_PCA6408,
	PCA_PART_NO_PCA6416,
	PCA_PART_NO_PCAL9538,
	PCA_PART_NO_PCAL9539,
	PCA_PART_NO_PCAL6408,
	PCA_PART_NO_PCAL6416,
	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",
	"pcal9538",
	"pcal9539",
	"pcal6408",
	"pcal6416",
	"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,
};

/**
 * @brief drive strength for output_drive_strength register
 *
 * @note Only applies to part no with PCA_HAS_LATCH capability.
 */
enum gpio_pca_series_drive_strength {
	PCA_SERIES_DRIVE_STRENGTH_X1 = 0U, /** Lowest strength */
	PCA_SERIES_DRIVE_STRENGTH_X2,
	PCA_SERIES_DRIVE_STRENGTH_X3,
	PCA_SERIES_DRIVE_STRENGTH_X4,      /** Highest strength, reset default */
};

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
 *
 * @note the buffer must be little-endian, the same as I2C register layout,
 *       regardless of host machine endianness.
 */
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
 * {
 */

static inline int gpio_pca_series_reset_pin_toggle(const struct device *dev)
{
	const struct gpio_pca_series_config *cfg = dev->config;
	int ret = 0;

	/** Reset pin connected, do hardware reset */
	if (!device_is_ready(cfg->gpio_rst.port)) {
		return -ENODEV;
	}

	/* 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) {
		return ret;
	}
	k_busy_wait(1);

	ret = gpio_pin_set_dt(&cfg->gpio_rst, 0U);
	if (ret) {
		return ret;
	}
	k_busy_wait(1);

	return ret;
}

static inline int gpio_pca_series_reset_write_reg(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;

	/**
	 * write reset value to registers
	 */
	ret = gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_OUTPUT_PORT, reset_value_1);
	if (ret) {
		return ret;
	}
	ret = gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_CONFIGURATION, reset_value_1);
	if (ret) {
		return ret;
	}
	if (cfg->part_cfg->flags & PCA_HAS_LATCH) {
		ret = gpio_pca_series_reg_write(dev, PCA_REG_TYPE_2B_OUTPUT_DRIVE_STRENGTH,
			reset_value_1);
		if (ret) {
			return ret;
		}
		ret = gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_INPUT_LATCH, reset_value_0);
		if (ret) {
			return ret;
		}
	}
	if (cfg->part_cfg->flags & PCA_HAS_PULL) {
		ret = gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_PULL_ENABLE, reset_value_0);
		if (ret) {
			return ret;
		}
		ret = gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_PULL_SELECT, reset_value_1);
		if (ret) {
			return ret;
		}
	}
	if (cfg->part_cfg->flags & PCA_HAS_OUT_CONFIG) {
		ret = gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_OUTPUT_CONFIG, reset_value_0);
		if (ret) {
			return ret;
		}
	}
#ifdef CONFIG_GPIO_PCA_SERIES_INTERRUPT
	if (cfg->part_cfg->flags & PCA_HAS_INT_MASK) {
		ret = gpio_pca_series_reg_write(dev, PCA_REG_TYPE_1B_INTERRUPT_MASK, reset_value_1);
		if (ret) {
			return ret;
		}
	}
	if (cfg->part_cfg->flags & PCA_HAS_INT_EXTEND) {
		ret = gpio_pca_series_reg_write(dev, PCA_REG_TYPE_2B_INTERRUPT_EDGE, reset_value_0);
		if (ret) {
			return ret;
		}
	}
#endif /* CONFIG_GPIO_PCA_SERIES_INTERRUPT */

	return ret;
}

/**
 * @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 int gpio_pca_series_reset(const struct device *dev)
{
	const struct gpio_pca_series_config *cfg = dev->config;
	int ret = 0;

	if (cfg->gpio_rst.port != NULL) {
		ret = gpio_pca_series_reset_pin_toggle(dev);
		if (ret) {
			LOG_WRN("gpio reset failed (%d), fallback to soft reset", ret);
		} else {
			return ret;
		}
	}

	ret = gpio_pca_series_reset_write_reg(dev);
	if (ret) {
		LOG_ERR("soft reset failed (%d)", ret);
		return ret;
	}

	return ret;
}

#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 *)&reg_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 */

/**
 * @brief Configure the output drive strength for a specific GPIO pin.
 *
 * This function sets the drive strength of the specified pin on the device.
 *
 * @param dev Pointer to the device structure.
 * @param pin GPIO pin number to configure.
 * @param strength Desired drive strength setting.
 * @return int 0 on success,
 *             -EIO if an I2C communication error occurs.
 */
static int gpio_pca_series_pin_drive_strength_configure(
		const struct device *dev, gpio_pin_t pin,
		enum gpio_pca_series_drive_strength strength)
{
	const struct gpio_pca_series_config *cfg = dev->config;
	int ret;
	uint32_t reg_cfg_shift = pin << 1U;
	uint64_t reg_cfg_mask = ((uint64_t)0x3) << reg_cfg_shift;
	uint64_t reg_value;

	if ((cfg->part_cfg->flags & PCA_HAS_LATCH) == 0U) {
		return -ENOTSUP;
	}

	ret = gpio_pca_series_reg_cache_read(dev, PCA_REG_TYPE_2B_OUTPUT_DRIVE_STRENGTH,
						 (uint8_t *)&reg_value);
	if (ret) {
		goto out;
	}
	reg_value = sys_le64_to_cpu(reg_value);
	reg_value = (reg_value & (~reg_cfg_mask)) | ((uint64_t)strength << reg_cfg_shift);
	reg_value = sys_cpu_to_le64(reg_value);
	ret = gpio_pca_series_reg_write(dev, PCA_REG_TYPE_2B_OUTPUT_DRIVE_STRENGTH,
					(uint8_t *)&reg_value);

out:
	return ret;
}

/**
 * }
 * 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 *)&reg_value);
		if (ret != 0) {
			goto out;
		}
		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 *)&reg_value);
		if (ret != 0) {
			goto out;
		}
	}

	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 *)&reg_value);
			if (ret != 0) {
				goto out;
			}
			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 *)&reg_value);
			if (ret != 0) {
				goto out;
			}
		}
		/* configure PCA_REG_TYPE_1B_PULL_ENABLE */
		ret = gpio_pca_series_reg_cache_read(dev,
			PCA_REG_TYPE_1B_PULL_ENABLE, (uint8_t *)&reg_value);
		if (ret != 0) {
			goto out;
		}
		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 *)&reg_value);
		if (ret != 0) {
			goto out;
		}
	}

	if (PCA_SERIES_GPIO_DRIVE_STRENGTH_ENABLE(flags)) {
		ret = gpio_pca_series_pin_drive_strength_configure(dev, pin,
			(enum gpio_pca_series_drive_strength)
				PCA_SERIES_GPIO_DRIVE_STRENGTH_CONFIG(flags));
		if (ret) {
			if (ret == -ENOTSUP) {
				LOG_WRN("Drive strength not supported for device %s, "
					"ignoring drive strength setting for pin %d",
					dev->name, pin);
			} else {
				goto out;
			}
		}
	}

	/* 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 *)&reg_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 *)&reg_value);
	if (ret != 0) {
		goto out;
	}

	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 *)&reg_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 = 3ULL << 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 */

	/** 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);

	/** 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 */

	/** check if any interrupt enabled */
	if ((!int_rise) && (!int_fall)) {
		goto out;
	}

	/** 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));

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;

	/** With deferred initialization, context checking is also required here */
	if (k_is_in_isr()) {
		LOG_ERR("Cannot initialize from ISR context");
		return -EWOULDBLOCK;
	}

	if (!device_is_ready(cfg->i2c.bus)) {
		LOG_ERR("i2c bus device not found");
		goto out_bus;
	}

	/** device reset */
	ret = gpio_pca_series_reset(dev);
	if (ret) {
		LOG_ERR("device reset error %d", ret);
		goto out_bus;
	} else {
		LOG_DBG("device reset done");
	}

#ifdef GPIO_NXP_PCA_SERIES_DEBUG
# ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL
	gpio_pca_series_cache_test(dev);
	/** Device needs to be reset again after test */
	ret = gpio_pca_series_reset(dev);
	if (ret) {
		LOG_ERR("device reset error %d", ret);
		goto out_bus;
	} else {
		LOG_DBG("device reset done");
	}
# 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 */
};

/**
 * pca9554 share the same register layout with pca9538, 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 */
};

/**
 * pca6408 share the same register layout with pca9538, with
 * RESET pin repurposed to another address strapping pin.
 * no difference from driver perspective.
 */

#define GPIO_PCA_PORT_NO_PCA_PART_NO_PCA6408 GPIO_PCA_PORT_NO_PCA_PART_NO_PCA9538
#define GPIO_PCA_FLAG_PCA_PART_NO_PCA6408 GPIO_PCA_FLAG_PCA_PART_NO_PCA9538
#define GPIO_PCA_PART_CFG_PCA_PART_NO_PCA6408 (&gpio_pca_series_part_cfg_pca6408)

const struct gpio_pca_series_part_config gpio_pca_series_part_cfg_pca6408 = {
	.port_no = GPIO_PCA_PORT_NO_PCA_PART_NO_PCA6408,
	.flags = GPIO_PCA_FLAG_PCA_PART_NO_PCA6408,
	.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_PCA6408),
# 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 */
};

/**
 * pca6416 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_PCA6416 GPIO_PCA_PORT_NO_PCA_PART_NO_PCA9539
#define GPIO_PCA_FLAG_PCA_PART_NO_PCA6416 GPIO_PCA_FLAG_PCA_PART_NO_PCA9539
#define GPIO_PCA_PART_CFG_PCA_PART_NO_PCA6416 (&gpio_pca_series_part_cfg_pca6416)

const struct gpio_pca_series_part_config gpio_pca_series_part_cfg_pca6416 = {
	.port_no = GPIO_PCA_PORT_NO_PCA_PART_NO_PCA6416,
	.flags = GPIO_PCA_FLAG_PCA_PART_NO_PCA6416,
	.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_PCA6416),
# endif /* GPIO_NXP_PCA_SERIES_DEBUG */
	.cache_map = gpio_pca_series_cache_map_pca953x,
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */
};

/**
 * @brief implement pcal953x and pcal64xxa driver
 *
 * @note flags = PCA_HAS_LATCH
 *             | PCA_HAS_PULL
 *             | PCA_HAS_INT_MASK
 *
 *       api set    :   standard
 *
 *       ngpios     :   8, 16;
 *       part_no    :   pcal9534 pcal9538 pcal6408
 *                      pcal9535 pcal9539 pcal6416
 */
#define GPIO_PCA_SERIES_FLAG_TYPE_2 (PCA_HAS_LATCH | PCA_HAS_PULL | PCA_HAS_INT_MASK)

#ifdef CONFIG_GPIO_PCA_SERIES_CACHE_ALL
/**
 * cache map for flag = PCA_HAS_LATCH
 *                    | PCA_HAS_PULL
 *                    | PCA_HAS_INT_MASK
 */
static const uint8_t gpio_pca_series_cache_map_pcal953x[] = {
	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 */
	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
	0x07, /** 1b_input_history if PCA_HAS_LATCH and not PCA_HAS_INT_EXTEND */
	0x08, /** 1b_interrupt_rise if PCA_HAS_LATCH and not PCA_HAS_INT_EXTEND */
	0x09, /** 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_pcal9538[] = {
	0x00, /** input_port if not PCA_HAS_OUT_CONFIG, non-cacheable */
	0x01, /** output_port */
/*	0x02,     polarity_inversion  (unused, omitted) */
	0x03, /** configuration */
	0x40, /** 2b_output_drive_strength if PCA_HAS_LATCH*/
	0x42, /** input_latch if PCA_HAS_LATCH*/
	0x43, /** pull_enable if PCA_HAS_PULL */
	0x44, /** 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
	0x45, /** interrupt_mask if PCA_HAS_INT_MASK,
		* non-cacheable if not PCA_HAS_INT_EXTEND
		*/
	0x46, /** 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_PCAL9538 (1U)
#define GPIO_PCA_FLAG_PCA_PART_NO_PCAL9538 GPIO_PCA_SERIES_FLAG_TYPE_2
#define GPIO_PCA_PART_CFG_PCA_PART_NO_PCAL9538 (&gpio_pca_series_part_cfg_pcal9538)

const struct gpio_pca_series_part_config gpio_pca_series_part_cfg_pcal9538 = {
	.port_no = GPIO_PCA_PORT_NO_PCA_PART_NO_PCAL9538,
	.flags = GPIO_PCA_FLAG_PCA_PART_NO_PCAL9538,
	.regs = gpio_pca_series_reg_pcal9538,
#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_PCAL9538),
# endif /* GPIO_NXP_PCA_SERIES_DEBUG */
	.cache_map = gpio_pca_series_cache_map_pcal953x,
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */
};

/**
 * pcal6408 share the same register layout with pcal9538, with
 * additional voltage level translation capability.
 * no difference from driver perspective.
 */

#define GPIO_PCA_PORT_NO_PCA_PART_NO_PCAL6408 GPIO_PCA_PORT_NO_PCA_PART_NO_PCAL9538
#define GPIO_PCA_FLAG_PCA_PART_NO_PCAL6408 GPIO_PCA_FLAG_PCA_PART_NO_PCAL9538
#define GPIO_PCA_PART_CFG_PCA_PART_NO_PCAL6408 (&gpio_pca_series_part_cfg_pcal6408)

const struct gpio_pca_series_part_config gpio_pca_series_part_cfg_pcal6408 = {
	.port_no = GPIO_PCA_PORT_NO_PCA_PART_NO_PCAL6408,
	.flags = GPIO_PCA_FLAG_PCA_PART_NO_PCAL6408,
	.regs = gpio_pca_series_reg_pcal9538,
#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_PCAL6408),
# endif /* GPIO_NXP_PCA_SERIES_DEBUG */
	.cache_map = gpio_pca_series_cache_map_pcal953x,
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */
};

static const uint8_t gpio_pca_series_reg_pcal9539[] = {
	0x00, /** input_port if not PCA_HAS_OUT_CONFIG, non-cacheable */
	0x02, /** output_port */
/*	0x04,     polarity_inversion  (unused, omitted) */
	0x06, /** configuration */
	0x40, /** 2b_output_drive_strength if PCA_HAS_LATCH*/
	0x44, /** input_latch if PCA_HAS_LATCH*/
	0x46, /** pull_enable if PCA_HAS_PULL */
	0x48, /** 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
	0x4a, /** interrupt_mask if PCA_HAS_INT_MASK,
		* non-cacheable if not PCA_HAS_INT_EXTEND
		*/
	0x4c, /** 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_PCAL9539 (2U)
#define GPIO_PCA_FLAG_PCA_PART_NO_PCAL9539 GPIO_PCA_SERIES_FLAG_TYPE_2
#define GPIO_PCA_PART_CFG_PCA_PART_NO_PCAL9539 (&gpio_pca_series_part_cfg_pcal9539)

const struct gpio_pca_series_part_config gpio_pca_series_part_cfg_pcal9539 = {
	.port_no = GPIO_PCA_PORT_NO_PCA_PART_NO_PCAL9539,
	.flags = GPIO_PCA_FLAG_PCA_PART_NO_PCAL9539,
	.regs = gpio_pca_series_reg_pcal9539,
#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_PCAL9539),
# endif /* GPIO_NXP_PCA_SERIES_DEBUG */
	.cache_map = gpio_pca_series_cache_map_pcal953x,
#endif /* CONFIG_GPIO_PCA_SERIES_CACHE_ALL */
};

/**
 * pcal6416 share the same register layout with pcal9539, with
 * additional voltage level translation capability.
 * no difference from driver perspective.
 */

#define GPIO_PCA_PORT_NO_PCA_PART_NO_PCAL6416 GPIO_PCA_PORT_NO_PCA_PART_NO_PCAL9539
#define GPIO_PCA_FLAG_PCA_PART_NO_PCAL6416 GPIO_PCA_FLAG_PCA_PART_NO_PCAL9539
#define GPIO_PCA_PART_CFG_PCA_PART_NO_PCAL6416 (&gpio_pca_series_part_cfg_pcal6416)

const struct gpio_pca_series_part_config gpio_pca_series_part_cfg_pcal6416 = {
	.port_no = GPIO_PCA_PORT_NO_PCA_PART_NO_PCAL6416,
	.flags = GPIO_PCA_FLAG_PCA_PART_NO_PCAL6416,
	.regs = gpio_pca_series_reg_pcal9539,
#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_PCAL6416),
# endif /* GPIO_NXP_PCA_SERIES_DEBUG */
	.cache_map = gpio_pca_series_cache_map_pcal953x,
#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_pca6408
DT_INST_FOREACH_STATUS_OKAY_VARGS(GPIO_PCA_SERIES_DEVICE_INSTANCE, PCA_PART_NO_PCA6408)

#undef DT_DRV_COMPAT
#define DT_DRV_COMPAT nxp_pca6416
DT_INST_FOREACH_STATUS_OKAY_VARGS(GPIO_PCA_SERIES_DEVICE_INSTANCE, PCA_PART_NO_PCA6416)

#undef DT_DRV_COMPAT
#define DT_DRV_COMPAT nxp_pcal9538
DT_INST_FOREACH_STATUS_OKAY_VARGS(GPIO_PCA_SERIES_DEVICE_INSTANCE, PCA_PART_NO_PCAL9538)

#undef DT_DRV_COMPAT
#define DT_DRV_COMPAT nxp_pcal9539
DT_INST_FOREACH_STATUS_OKAY_VARGS(GPIO_PCA_SERIES_DEVICE_INSTANCE, PCA_PART_NO_PCAL9539)

#undef DT_DRV_COMPAT
#define DT_DRV_COMPAT nxp_pcal6408
DT_INST_FOREACH_STATUS_OKAY_VARGS(GPIO_PCA_SERIES_DEVICE_INSTANCE, PCA_PART_NO_PCAL6408)

#undef DT_DRV_COMPAT
#define DT_DRV_COMPAT nxp_pcal6416
DT_INST_FOREACH_STATUS_OKAY_VARGS(GPIO_PCA_SERIES_DEVICE_INSTANCE, PCA_PART_NO_PCAL6416)

#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)
