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