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.
532 lines
14 KiB
532 lines
14 KiB
/* |
|
* Copyright (c) 2021 Nuvoton Technology Corporation. |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT nuvoton_nct38xx_gpio_port |
|
|
|
#include "gpio_nct38xx.h" |
|
#include <zephyr/drivers/gpio/gpio_utils.h> |
|
#include <zephyr/drivers/gpio.h> |
|
#include <zephyr/drivers/mfd/nct38xx.h> |
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_DECLARE(gpio_ntc38xx, CONFIG_GPIO_LOG_LEVEL); |
|
|
|
/* Driver config */ |
|
struct gpio_nct38xx_port_config { |
|
/* gpio_driver_config needs to be first */ |
|
struct gpio_driver_config common; |
|
/* NCT38XX controller dev */ |
|
const struct device *mfd; |
|
/* GPIO port index */ |
|
uint8_t gpio_port; |
|
/* GPIO port 0 pinmux mask */ |
|
uint8_t pinmux_mask; |
|
}; |
|
|
|
/* Driver data */ |
|
struct gpio_nct38xx_port_data { |
|
/* gpio_driver_data needs to be first */ |
|
struct gpio_driver_data common; |
|
/* GPIO callback list */ |
|
sys_slist_t cb_list_gpio; |
|
/* lock NCT38xx register access */ |
|
struct k_sem *lock; |
|
/* I2C device for the MFD parent */ |
|
const struct i2c_dt_spec *i2c_dev; |
|
}; |
|
|
|
/* GPIO api functions */ |
|
static int gpio_nct38xx_pin_config(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags) |
|
{ |
|
const struct gpio_nct38xx_port_config *const config = dev->config; |
|
struct gpio_nct38xx_port_data *const data = dev->data; |
|
uint32_t mask; |
|
uint8_t new_reg; |
|
int ret; |
|
|
|
/* Don't support simultaneous in/out mode */ |
|
if (((flags & GPIO_INPUT) != 0) && ((flags & GPIO_OUTPUT) != 0)) { |
|
return -ENOTSUP; |
|
} |
|
|
|
/* Don't support "open source" mode */ |
|
if (((flags & GPIO_SINGLE_ENDED) != 0) && ((flags & GPIO_LINE_OPEN_DRAIN) == 0)) { |
|
return -ENOTSUP; |
|
} |
|
|
|
/* Don't support pull-up/pull-down */ |
|
if (((flags & GPIO_PULL_UP) != 0) || ((flags & GPIO_PULL_DOWN) != 0)) { |
|
return -ENOTSUP; |
|
} |
|
|
|
k_sem_take(data->lock, K_FOREVER); |
|
|
|
/* Pin multiplexing */ |
|
if (config->gpio_port == 0) { |
|
/* Set the mux control bit, but ensure the reserved fields |
|
* are cleared. Note that pinmux_mask contains the set |
|
* of non-reserved bits. |
|
*/ |
|
new_reg = BIT(pin) & config->pinmux_mask; |
|
mask = BIT(pin) | ~config->pinmux_mask; |
|
|
|
ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_MUX_CONTROL, mask, new_reg); |
|
if (ret < 0) { |
|
goto done; |
|
} |
|
} |
|
|
|
/* Configure pin as input. */ |
|
if (flags & GPIO_INPUT) { |
|
/* Clear the direction bit to set as an input */ |
|
new_reg = 0; |
|
mask = BIT(pin); |
|
ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_DIR(config->gpio_port), |
|
mask, new_reg); |
|
|
|
goto done; |
|
} |
|
|
|
/* Select open drain 0:push-pull 1:open-drain */ |
|
mask = BIT(pin); |
|
if (flags & GPIO_OPEN_DRAIN) { |
|
new_reg = mask; |
|
} else { |
|
new_reg = 0; |
|
} |
|
ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_OD_SEL(config->gpio_port), |
|
mask, new_reg); |
|
if (ret < 0) { |
|
goto done; |
|
} |
|
|
|
/* Set level 0:low 1:high */ |
|
if (flags & GPIO_OUTPUT_INIT_HIGH) { |
|
new_reg = mask; |
|
} else if (flags & GPIO_OUTPUT_INIT_LOW) { |
|
new_reg = 0; |
|
} |
|
ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port), |
|
mask, new_reg); |
|
if (ret < 0) { |
|
goto done; |
|
} |
|
|
|
/* Configure pin as output, if requested 0:input 1:output */ |
|
if (flags & GPIO_OUTPUT) { |
|
new_reg = BIT(pin); |
|
ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_DIR(config->gpio_port), |
|
mask, new_reg); |
|
} |
|
|
|
done: |
|
k_sem_give(data->lock); |
|
return ret; |
|
} |
|
|
|
#ifdef CONFIG_GPIO_GET_CONFIG |
|
int gpio_nct38xx_pin_get_config(const struct device *dev, gpio_pin_t pin, gpio_flags_t *flags) |
|
{ |
|
const struct gpio_nct38xx_port_config *const config = dev->config; |
|
struct gpio_nct38xx_port_data *const data = dev->data; |
|
uint32_t mask = BIT(pin); |
|
uint8_t reg; |
|
int ret; |
|
|
|
k_sem_take(data->lock, K_FOREVER); |
|
|
|
if (config->gpio_port == 0) { |
|
if (mask & (~config->common.port_pin_mask)) { |
|
ret = -ENOTSUP; |
|
goto done; |
|
} |
|
|
|
ret = i2c_reg_read_byte_dt(data->i2c_dev, NCT38XX_REG_MUX_CONTROL, ®); |
|
if (ret < 0) { |
|
goto done; |
|
} |
|
|
|
if ((mask & config->pinmux_mask) && (mask & (~reg))) { |
|
*flags = GPIO_DISCONNECTED; |
|
goto done; |
|
} |
|
} |
|
|
|
ret = i2c_reg_read_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_DIR(config->gpio_port), ®); |
|
if (ret < 0) { |
|
goto done; |
|
} |
|
|
|
if (reg & mask) { |
|
/* Output */ |
|
*flags = GPIO_OUTPUT; |
|
|
|
/* 0 - push-pull, 1 - open-drain */ |
|
ret = i2c_reg_read_byte_dt(data->i2c_dev, |
|
NCT38XX_REG_GPIO_OD_SEL(config->gpio_port), ®); |
|
if (ret < 0) { |
|
goto done; |
|
} |
|
|
|
if (mask & reg) { |
|
*flags |= GPIO_OPEN_DRAIN; |
|
} |
|
|
|
/* Output value */ |
|
ret = i2c_reg_read_byte_dt(data->i2c_dev, |
|
NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port), ®); |
|
if (ret < 0) { |
|
goto done; |
|
} |
|
|
|
if (mask & reg) { |
|
*flags |= GPIO_OUTPUT_HIGH; |
|
} else { |
|
*flags |= GPIO_OUTPUT_LOW; |
|
} |
|
} else { |
|
/* Input */ |
|
*flags = GPIO_INPUT; |
|
} |
|
|
|
done: |
|
k_sem_give(data->lock); |
|
return ret; |
|
} |
|
#endif /* CONFIG_GPIO_GET_CONFIG */ |
|
|
|
static int gpio_nct38xx_port_get_raw(const struct device *dev, gpio_port_value_t *value) |
|
{ |
|
int ret; |
|
const struct gpio_nct38xx_port_config *const config = dev->config; |
|
struct gpio_nct38xx_port_data *const data = dev->data; |
|
|
|
k_sem_take(data->lock, K_FOREVER); |
|
|
|
ret = i2c_reg_read_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_DATA_IN(config->gpio_port), |
|
(uint8_t *)value); |
|
|
|
k_sem_give(data->lock); |
|
return ret; |
|
} |
|
|
|
static int gpio_nct38xx_port_set_masked_raw(const struct device *dev, gpio_port_pins_t mask, |
|
gpio_port_value_t value) |
|
{ |
|
const struct gpio_nct38xx_port_config *const config = dev->config; |
|
struct gpio_nct38xx_port_data *const data = dev->data; |
|
int ret; |
|
|
|
k_sem_take(data->lock, K_FOREVER); |
|
|
|
ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port), |
|
mask, value); |
|
|
|
k_sem_give(data->lock); |
|
|
|
return ret; |
|
} |
|
|
|
static int gpio_nct38xx_port_set_bits_raw(const struct device *dev, gpio_port_pins_t mask) |
|
{ |
|
const struct gpio_nct38xx_port_config *const config = dev->config; |
|
struct gpio_nct38xx_port_data *const data = dev->data; |
|
int ret; |
|
|
|
k_sem_take(data->lock, K_FOREVER); |
|
|
|
ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port), |
|
mask, mask); |
|
|
|
k_sem_give(data->lock); |
|
|
|
return ret; |
|
} |
|
|
|
static int gpio_nct38xx_port_clear_bits_raw(const struct device *dev, gpio_port_pins_t mask) |
|
{ |
|
const struct gpio_nct38xx_port_config *const config = dev->config; |
|
struct gpio_nct38xx_port_data *const data = dev->data; |
|
int ret; |
|
|
|
k_sem_take(data->lock, K_FOREVER); |
|
|
|
ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port), |
|
mask, 0); |
|
|
|
k_sem_give(data->lock); |
|
|
|
return ret; |
|
} |
|
|
|
static int gpio_nct38xx_port_toggle_bits(const struct device *dev, gpio_port_pins_t mask) |
|
{ |
|
const struct gpio_nct38xx_port_config *const config = dev->config; |
|
struct gpio_nct38xx_port_data *const data = dev->data; |
|
uint8_t reg, new_reg; |
|
int ret; |
|
|
|
k_sem_take(data->lock, K_FOREVER); |
|
|
|
ret = i2c_reg_read_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port), |
|
®); |
|
if (ret < 0) { |
|
goto done; |
|
} |
|
new_reg = reg ^ mask; |
|
if (new_reg != reg) { |
|
ret = i2c_reg_write_byte_dt(data->i2c_dev, |
|
NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port), new_reg); |
|
} |
|
|
|
done: |
|
k_sem_give(data->lock); |
|
|
|
return ret; |
|
} |
|
|
|
static int gpio_nct38xx_pin_interrupt_configure(const struct device *dev, gpio_pin_t pin, |
|
enum gpio_int_mode mode, enum gpio_int_trig trig) |
|
{ |
|
const struct gpio_nct38xx_port_config *const config = dev->config; |
|
struct gpio_nct38xx_port_data *const data = dev->data; |
|
uint8_t new_reg, new_rise, new_fall; |
|
int ret; |
|
uint32_t mask = BIT(pin); |
|
|
|
k_sem_take(data->lock, K_FOREVER); |
|
|
|
/* Disable irq before configuring them */ |
|
new_reg = 0; |
|
ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_ALERT_MASK(config->gpio_port), |
|
mask, new_reg); |
|
|
|
/* Configure and enable interrupt? */ |
|
if (mode == GPIO_INT_MODE_DISABLED) { |
|
goto done; |
|
} |
|
|
|
/* set edge register */ |
|
if (mode == GPIO_INT_MODE_EDGE) { |
|
if (trig == GPIO_INT_TRIG_LOW) { |
|
new_rise = 0; |
|
new_fall = mask; |
|
} else if (trig == GPIO_INT_TRIG_HIGH) { |
|
new_rise = mask; |
|
new_fall = 0; |
|
} else if (trig == GPIO_INT_TRIG_BOTH) { |
|
new_rise = mask; |
|
new_fall = mask; |
|
} else { |
|
LOG_ERR("Invalid interrupt trigger type %d", trig); |
|
return -EINVAL; |
|
} |
|
} else { |
|
/* level mode */ |
|
new_rise = 0; |
|
new_fall = 0; |
|
} |
|
|
|
ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_ALERT_RISE(config->gpio_port), |
|
mask, new_rise); |
|
if (ret < 0) { |
|
goto done; |
|
} |
|
|
|
ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_ALERT_FALL(config->gpio_port), |
|
mask, new_fall); |
|
if (ret < 0) { |
|
goto done; |
|
} |
|
|
|
if (mode == GPIO_INT_MODE_LEVEL) { |
|
/* set active high/low */ |
|
if (trig == GPIO_INT_TRIG_LOW) { |
|
new_reg = 0; |
|
} else if (trig == GPIO_INT_TRIG_HIGH) { |
|
new_reg = mask; |
|
} else { |
|
LOG_ERR("Invalid interrupt trigger type %d", trig); |
|
ret = -EINVAL; |
|
goto done; |
|
} |
|
ret = i2c_reg_update_byte_dt(data->i2c_dev, |
|
NCT38XX_REG_GPIO_ALERT_LEVEL(config->gpio_port), mask, |
|
new_reg); |
|
|
|
if (ret < 0) { |
|
goto done; |
|
} |
|
} |
|
|
|
/* Clear pending bit */ |
|
ret = i2c_reg_write_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_ALERT_STAT(config->gpio_port), |
|
mask); |
|
if (ret < 0) { |
|
goto done; |
|
} |
|
|
|
/* Enable it after configuration is completed */ |
|
new_reg = mask; |
|
ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_ALERT_MASK(config->gpio_port), |
|
mask, new_reg); |
|
|
|
done: |
|
k_sem_give(data->lock); |
|
|
|
return ret; |
|
} |
|
|
|
static int gpio_nct38xx_manage_callback(const struct device *dev, struct gpio_callback *callback, |
|
bool set) |
|
{ |
|
struct gpio_nct38xx_port_data *const data = dev->data; |
|
|
|
return gpio_manage_callback(&data->cb_list_gpio, callback, set); |
|
} |
|
|
|
#ifdef CONFIG_GPIO_GET_DIRECTION |
|
static int gpio_nct38xx_port_get_direction(const struct device *dev, gpio_port_pins_t mask, |
|
gpio_port_pins_t *inputs, gpio_port_pins_t *outputs) |
|
{ |
|
const struct gpio_nct38xx_port_config *const config = dev->config; |
|
struct gpio_nct38xx_port_data *const data = dev->data; |
|
uint8_t dir_reg; |
|
int ret; |
|
|
|
k_sem_take(data->lock, K_FOREVER); |
|
|
|
if (config->gpio_port == 0) { |
|
uint8_t enabled_gpios; |
|
/* Remove the disabled GPIOs from the mask */ |
|
ret = i2c_reg_read_byte_dt(data->i2c_dev, NCT38XX_REG_MUX_CONTROL, &enabled_gpios); |
|
mask &= (enabled_gpios & config->common.port_pin_mask); |
|
|
|
if (ret < 0) { |
|
goto done; |
|
} |
|
} |
|
|
|
/* Read direction register, 0 - input, 1 - output */ |
|
ret = i2c_reg_read_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_DIR(config->gpio_port), |
|
&dir_reg); |
|
if (ret < 0) { |
|
goto done; |
|
} |
|
|
|
if (inputs) { |
|
*inputs = mask & (~dir_reg); |
|
} |
|
|
|
if (outputs) { |
|
*outputs = mask & dir_reg; |
|
} |
|
|
|
done: |
|
k_sem_give(data->lock); |
|
return ret; |
|
} |
|
#endif /* CONFIG_GPIO_GET_DIRECTION */ |
|
|
|
int gpio_nct38xx_dispatch_port_isr(const struct device *dev) |
|
{ |
|
const struct gpio_nct38xx_port_config *const config = dev->config; |
|
struct gpio_nct38xx_port_data *const data = dev->data; |
|
uint8_t alert_pins, mask; |
|
int ret; |
|
|
|
do { |
|
k_sem_take(data->lock, K_FOREVER); |
|
ret = i2c_reg_read_byte_dt( |
|
data->i2c_dev, NCT38XX_REG_GPIO_ALERT_STAT(config->gpio_port), &alert_pins); |
|
if (ret < 0) { |
|
k_sem_give(data->lock); |
|
return ret; |
|
} |
|
|
|
ret = i2c_reg_read_byte_dt(data->i2c_dev, |
|
NCT38XX_REG_GPIO_ALERT_MASK(config->gpio_port), &mask); |
|
if (ret < 0) { |
|
k_sem_give(data->lock); |
|
return ret; |
|
} |
|
alert_pins &= mask; |
|
if (alert_pins) { |
|
ret = i2c_reg_write_byte_dt(data->i2c_dev, |
|
NCT38XX_REG_GPIO_ALERT_STAT(config->gpio_port), |
|
alert_pins); |
|
if (ret < 0) { |
|
k_sem_give(data->lock); |
|
return ret; |
|
} |
|
} |
|
k_sem_give(data->lock); |
|
|
|
gpio_fire_callbacks(&data->cb_list_gpio, dev, alert_pins); |
|
|
|
/* |
|
* Vendor defined alert is generated if at least one STATn bit |
|
* changes from 0 to 1. We should guarantee the STATn bit is |
|
* clear to 0 before leaving isr. |
|
*/ |
|
} while (alert_pins); |
|
|
|
return 0; |
|
} |
|
|
|
static DEVICE_API(gpio, gpio_nct38xx_driver) = { |
|
.pin_configure = gpio_nct38xx_pin_config, |
|
#ifdef CONFIG_GPIO_GET_CONFIG |
|
.pin_get_config = gpio_nct38xx_pin_get_config, |
|
#endif /* CONFIG_GPIO_GET_CONFIG */ |
|
.port_get_raw = gpio_nct38xx_port_get_raw, |
|
.port_set_masked_raw = gpio_nct38xx_port_set_masked_raw, |
|
.port_set_bits_raw = gpio_nct38xx_port_set_bits_raw, |
|
.port_clear_bits_raw = gpio_nct38xx_port_clear_bits_raw, |
|
.port_toggle_bits = gpio_nct38xx_port_toggle_bits, |
|
.pin_interrupt_configure = gpio_nct38xx_pin_interrupt_configure, |
|
.manage_callback = gpio_nct38xx_manage_callback, |
|
#ifdef CONFIG_GPIO_GET_DIRECTION |
|
.port_get_direction = gpio_nct38xx_port_get_direction, |
|
#endif /* CONFIG_GPIO_GET_DIRECTION */ |
|
}; |
|
|
|
static int gpio_nct38xx_port_init(const struct device *dev) |
|
{ |
|
const struct gpio_nct38xx_port_config *const config = dev->config; |
|
struct gpio_nct38xx_port_data *const data = dev->data; |
|
|
|
if (!device_is_ready(config->mfd)) { |
|
LOG_ERR("%s is not ready", config->mfd->name); |
|
return -ENODEV; |
|
} |
|
|
|
data->lock = mfd_nct38xx_get_lock_reference(config->mfd); |
|
data->i2c_dev = mfd_nct38xx_get_i2c_dt_spec(config->mfd); |
|
|
|
return 0; |
|
} |
|
|
|
/* NCT38XX GPIO port driver must be initialized after NCT38XX GPIO driver */ |
|
BUILD_ASSERT(CONFIG_GPIO_NCT38XX_PORT_INIT_PRIORITY > CONFIG_GPIO_NCT38XX_INIT_PRIORITY); |
|
|
|
#define GPIO_NCT38XX_PORT_DEVICE_INSTANCE(inst) \ |
|
static const struct gpio_nct38xx_port_config gpio_nct38xx_port_cfg_##inst = { \ |
|
.common = {.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(inst) & \ |
|
DT_INST_PROP(inst, pin_mask)}, \ |
|
.mfd = DEVICE_DT_GET(DT_INST_GPARENT(inst)), \ |
|
.gpio_port = DT_INST_REG_ADDR(inst), \ |
|
.pinmux_mask = COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, pinmux_mask), \ |
|
(DT_INST_PROP(inst, pinmux_mask)), (0)), \ |
|
}; \ |
|
BUILD_ASSERT( \ |
|
!(DT_INST_REG_ADDR(inst) == 0 && !(DT_INST_NODE_HAS_PROP(inst, pinmux_mask))), \ |
|
"Port 0 should assign pinmux_mask property."); \ |
|
static struct gpio_nct38xx_port_data gpio_nct38xx_port_data_##inst; \ |
|
DEVICE_DT_INST_DEFINE(inst, gpio_nct38xx_port_init, NULL, &gpio_nct38xx_port_data_##inst, \ |
|
&gpio_nct38xx_port_cfg_##inst, POST_KERNEL, \ |
|
CONFIG_GPIO_NCT38XX_PORT_INIT_PRIORITY, &gpio_nct38xx_driver); |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(GPIO_NCT38XX_PORT_DEVICE_INSTANCE)
|
|
|