1 /*
2 * Copyright (c) 2021 Nuvoton Technology Corporation.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT nuvoton_nct38xx_gpio
8
9 #include <zephyr/device.h>
10 #include <zephyr/drivers/gpio.h>
11 #include <zephyr/drivers/gpio/gpio_nct38xx.h>
12 #include <zephyr/drivers/mfd/nct38xx.h>
13 #include <zephyr/kernel.h>
14 #include <zephyr/sys/util_macro.h>
15
16 #include "gpio_nct38xx.h"
17
18 #include <zephyr/logging/log.h>
19 LOG_MODULE_REGISTER(gpio_ntc38xx, CONFIG_GPIO_LOG_LEVEL);
20
21 /* Driver config */
22 struct gpio_nct38xx_config {
23 /* Multi-function device, parent to the NCT38xx GPIO controller */
24 const struct device *mfd;
25 /* GPIO ports */
26 const struct device **sub_gpio_dev;
27 uint8_t sub_gpio_port_num;
28 /* Alert handler */
29 const struct device *alert_dev;
30 };
31
32 /* Driver data */
33 struct gpio_nct38xx_data {
34 /* NCT38XX device */
35 const struct device *dev;
36 /* lock NCT38xx register access */
37 struct k_sem *lock;
38 /* I2C device for the MFD parent */
39 const struct i2c_dt_spec *i2c_dev;
40 };
41
nct38xx_gpio_alert_handler(const struct device * dev)42 void nct38xx_gpio_alert_handler(const struct device *dev)
43 {
44 const struct gpio_nct38xx_config *const config = dev->config;
45
46 for (int i = 0; i < config->sub_gpio_port_num; i++) {
47 gpio_nct38xx_dispatch_port_isr(config->sub_gpio_dev[i]);
48 }
49 }
50
nct38xx_init_interrupt(const struct device * dev)51 static int nct38xx_init_interrupt(const struct device *dev)
52 {
53 uint16_t alert, alert_mask = 0;
54 int ret = 0;
55 struct gpio_nct38xx_data *data = dev->data;
56
57 k_sem_take(data->lock, K_FOREVER);
58
59 /* Disable all interrupt */
60 if (i2c_burst_write_dt(data->i2c_dev, NCT38XX_REG_ALERT_MASK, (uint8_t *)&alert_mask,
61 sizeof(alert_mask))) {
62 ret = -EIO;
63 goto unlock;
64 }
65
66 /* Enable vendor-defined alert for GPIO. */
67 alert_mask |= BIT(NCT38XX_REG_ALERT_MASK_VENDOR_DEFINDED_ALERT);
68
69 /* Clear alert */
70 if (i2c_burst_read_dt(data->i2c_dev, NCT38XX_REG_ALERT, (uint8_t *)&alert, sizeof(alert))) {
71 ret = -EIO;
72 goto unlock;
73 }
74 alert &= alert_mask;
75 if (alert) {
76 if (i2c_burst_write_dt(data->i2c_dev, NCT38XX_REG_ALERT, (uint8_t *)&alert,
77 sizeof(alert))) {
78 ret = -EIO;
79 goto unlock;
80 }
81 }
82
83 if (i2c_burst_write_dt(data->i2c_dev, NCT38XX_REG_ALERT_MASK, (uint8_t *)&alert_mask,
84 sizeof(alert_mask))) {
85 ret = -EIO;
86 goto unlock;
87 }
88
89 unlock:
90 k_sem_give(data->lock);
91 return ret;
92 }
93
nct38xx_gpio_init(const struct device * dev)94 static int nct38xx_gpio_init(const struct device *dev)
95 {
96 const struct gpio_nct38xx_config *const config = dev->config;
97 struct gpio_nct38xx_data *data = dev->data;
98
99 /* Verify multi-function parent is ready */
100 if (!device_is_ready(config->mfd)) {
101 LOG_ERR("%s device not ready", config->mfd->name);
102 return -ENODEV;
103 }
104
105 data->lock = mfd_nct38xx_get_lock_reference(config->mfd);
106 data->i2c_dev = mfd_nct38xx_get_i2c_dt_spec(config->mfd);
107
108 if (IS_ENABLED(CONFIG_GPIO_NCT38XX_ALERT)) {
109 nct38xx_init_interrupt(dev);
110 }
111
112 return 0;
113 }
114
115 #define GPIO_NCT38XX_DEVICE_INSTANCE(inst) \
116 static const struct device *sub_gpio_dev_##inst[] = { \
117 DT_INST_FOREACH_CHILD_STATUS_OKAY_SEP(inst, DEVICE_DT_GET, (,)) \
118 }; \
119 static const struct gpio_nct38xx_config gpio_nct38xx_cfg_##inst = { \
120 .mfd = DEVICE_DT_GET(DT_INST_PARENT(inst)), \
121 .sub_gpio_dev = sub_gpio_dev_##inst, \
122 .sub_gpio_port_num = ARRAY_SIZE(sub_gpio_dev_##inst), \
123 }; \
124 static struct gpio_nct38xx_data gpio_nct38xx_data_##inst = { \
125 .dev = DEVICE_DT_INST_GET(inst), \
126 }; \
127 DEVICE_DT_INST_DEFINE(inst, nct38xx_gpio_init, NULL, &gpio_nct38xx_data_##inst, \
128 &gpio_nct38xx_cfg_##inst, POST_KERNEL, \
129 CONFIG_GPIO_NCT38XX_INIT_PRIORITY, NULL);
130
131 DT_INST_FOREACH_STATUS_OKAY(GPIO_NCT38XX_DEVICE_INSTANCE)
132
133 /* The nct38xx MFD parent must be initialized before this driver */
134 BUILD_ASSERT(CONFIG_GPIO_NCT38XX_INIT_PRIORITY > CONFIG_MFD_INIT_PRIORITY);
135