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_alert
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_DECLARE(gpio_ntc38xx, CONFIG_GPIO_LOG_LEVEL);
20
21 struct nct38xx_mfd {
22 /* Lock for NCT38xx register access */
23 struct k_sem *lock;
24 /* I2C device used for register access */
25 const struct i2c_dt_spec *i2c_dev;
26 };
27
28 /* Driver config */
29 struct nct38xx_alert_config {
30 /* Alert GPIO pin */
31 const struct gpio_dt_spec irq_gpio;
32 /* NCT38XX devices which share the same alert pin */
33 const struct device **nct38xx_dev;
34 /* Number of NCT38XX devices on the alert pin */
35 uint32_t nct38xx_num;
36 };
37
38 /* Driver data */
39 struct nct38xx_alert_data {
40 /* Alert handler device */
41 const struct device *alert_dev;
42 /* Alert pin callback */
43 struct gpio_callback gpio_cb;
44 /* Alert worker */
45 struct k_work alert_worker;
46 /* Lock for NCT38xx register access */
47 struct nct38xx_mfd *mfd;
48 };
49
nct38xx_alert_callback(const struct device * dev,struct gpio_callback * cb,uint32_t pins)50 static void nct38xx_alert_callback(const struct device *dev, struct gpio_callback *cb,
51 uint32_t pins)
52 {
53 ARG_UNUSED(pins);
54 struct nct38xx_alert_data *data = CONTAINER_OF(cb, struct nct38xx_alert_data, gpio_cb);
55
56 k_work_submit(&data->alert_worker);
57 }
58
nct38xx_alert_is_active(struct nct38xx_mfd * mfd)59 static bool nct38xx_alert_is_active(struct nct38xx_mfd *mfd)
60 {
61 int ret;
62 uint16_t alert, mask;
63
64 k_sem_take(mfd->lock, K_FOREVER);
65
66 /* Clear alert */
67 ret = i2c_burst_read_dt(mfd->i2c_dev, NCT38XX_REG_ALERT, (uint8_t *)&alert,
68 sizeof(alert));
69 if (ret < 0) {
70 goto release_lock;
71 }
72 ret = i2c_burst_read_dt(mfd->i2c_dev, NCT38XX_REG_ALERT_MASK,
73 (uint8_t *)&mask, sizeof(mask));
74 if (ret < 0) {
75 goto release_lock;
76 }
77
78 alert &= mask;
79 if (alert) {
80 ret = i2c_burst_write_dt(mfd->i2c_dev, NCT38XX_REG_ALERT,
81 (uint8_t *)&alert, sizeof(alert));
82 }
83
84 release_lock:
85 k_sem_give(mfd->lock);
86
87 if (ret < 0) {
88 LOG_ERR("i2c access failed");
89 return false;
90 }
91
92 if (alert & BIT(NCT38XX_REG_ALERT_VENDOR_DEFINDED_ALERT)) {
93 return true;
94 }
95
96 return false;
97 }
98
nct38xx_alert_worker(struct k_work * work)99 static void nct38xx_alert_worker(struct k_work *work)
100 {
101 struct nct38xx_alert_data *const data =
102 CONTAINER_OF(work, struct nct38xx_alert_data, alert_worker);
103 const struct nct38xx_alert_config *const config = data->alert_dev->config;
104
105 do {
106 /* NCT38XX device handler */
107 for (int i = 0; i < config->nct38xx_num; i++) {
108 struct nct38xx_mfd *mfd = &data->mfd[i];
109
110 if (nct38xx_alert_is_active(mfd)) {
111 nct38xx_gpio_alert_handler(config->nct38xx_dev[i]);
112 }
113 }
114 /* While the interrupt signal is still active; we have more work to do. */
115 } while (gpio_pin_get_dt(&config->irq_gpio));
116 }
117
nct38xx_alert_init(const struct device * dev)118 static int nct38xx_alert_init(const struct device *dev)
119 {
120 const struct nct38xx_alert_config *const config = dev->config;
121 struct nct38xx_alert_data *const data = dev->data;
122 int ret;
123
124 /* Check NCT38XX devices are all ready. */
125 for (int i = 0; i < config->nct38xx_num; i++) {
126 if (!device_is_ready(config->nct38xx_dev[i])) {
127 LOG_ERR("%s device not ready", config->nct38xx_dev[i]->name);
128 return -ENODEV;
129 }
130
131 data->mfd[i].lock = mfd_nct38xx_get_lock_reference(config->nct38xx_dev[i]);
132 data->mfd[i].i2c_dev = mfd_nct38xx_get_i2c_dt_spec(config->nct38xx_dev[i]);
133 }
134
135 /* Set the alert pin for handling the interrupt */
136 k_work_init(&data->alert_worker, nct38xx_alert_worker);
137
138 if (!gpio_is_ready_dt(&config->irq_gpio)) {
139 LOG_ERR("%s device not ready", config->irq_gpio.port->name);
140 return -ENODEV;
141 }
142
143 gpio_pin_configure_dt(&config->irq_gpio, GPIO_INPUT);
144
145 gpio_init_callback(&data->gpio_cb, nct38xx_alert_callback, BIT(config->irq_gpio.pin));
146
147 ret = gpio_add_callback(config->irq_gpio.port, &data->gpio_cb);
148 if (ret < 0) {
149 return ret;
150 }
151
152 gpio_pin_interrupt_configure_dt(&config->irq_gpio, GPIO_INT_EDGE_TO_ACTIVE);
153
154 return 0;
155 }
156
157 /* NCT38XX alert driver must be initialized after NCT38XX GPIO driver */
158 BUILD_ASSERT(CONFIG_GPIO_NCT38XX_ALERT_INIT_PRIORITY > CONFIG_GPIO_NCT38XX_INIT_PRIORITY);
159
160 #define NCT38XX_DEV_AND_COMMA(node_id, prop, idx) \
161 DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)),
162
163 #define NCT38XX_ALERT_DEVICE_INSTANCE(inst) \
164 const struct device *nct38xx_dev_##inst[] = { \
165 DT_INST_FOREACH_PROP_ELEM(inst, nct38xx_dev, NCT38XX_DEV_AND_COMMA)}; \
166 static struct nct38xx_mfd nct38xx_mfd_##inst[DT_INST_PROP_LEN(inst, nct38xx_dev)]; \
167 static const struct nct38xx_alert_config nct38xx_alert_cfg_##inst = { \
168 .irq_gpio = GPIO_DT_SPEC_INST_GET(inst, irq_gpios), \
169 .nct38xx_dev = &nct38xx_dev_##inst[0], \
170 .nct38xx_num = DT_INST_PROP_LEN(inst, nct38xx_dev), \
171 }; \
172 static struct nct38xx_alert_data nct38xx_alert_data_##inst = { \
173 .alert_dev = DEVICE_DT_INST_GET(inst), \
174 .mfd = nct38xx_mfd_##inst, \
175 }; \
176 DEVICE_DT_INST_DEFINE(inst, nct38xx_alert_init, NULL, &nct38xx_alert_data_##inst, \
177 &nct38xx_alert_cfg_##inst, POST_KERNEL, \
178 CONFIG_GPIO_NCT38XX_ALERT_INIT_PRIORITY, NULL);
179
180 DT_INST_FOREACH_STATUS_OKAY(NCT38XX_ALERT_DEVICE_INSTANCE)
181