1 /*
2  * Copyright (c) 2021 Synopsys
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT cypress_cy8c95xx_gpio_port
8 
9 #include <errno.h>
10 
11 #include <zephyr/kernel.h>
12 #include <zephyr/device.h>
13 #include <zephyr/init.h>
14 #include <zephyr/drivers/gpio.h>
15 #include <zephyr/drivers/i2c.h>
16 #include <zephyr/sys/byteorder.h>
17 #include <zephyr/sys/util.h>
18 
19 #include <zephyr/logging/log.h>
20 LOG_MODULE_REGISTER(cy8c95xx, CONFIG_GPIO_LOG_LEVEL);
21 
22 #include <zephyr/drivers/gpio/gpio_utils.h>
23 
24 /** Cache of the output configuration and data of the pins. */
25 struct cy8c95xx_pin_state {
26 	uint8_t dir;
27 	uint8_t data_out;
28 	uint8_t pull_up;
29 	uint8_t pull_down;
30 };
31 
32 /** Runtime driver data */
33 struct cy8c95xx_drv_data {
34 	/* gpio_driver_data needs to be first */
35 	struct gpio_driver_data common;
36 	struct cy8c95xx_pin_state pin_state;
37 	struct k_sem *lock;
38 };
39 
40 /** Configuration data */
41 struct cy8c95xx_config {
42 	/* gpio_driver_config needs to be first */
43 	struct gpio_driver_config common;
44 	struct i2c_dt_spec i2c;
45 	uint8_t port_num;
46 };
47 
48 #define CY8C95XX_REG_INPUT_DATA0                0x00
49 #define CY8C95XX_REG_OUTPUT_DATA0               0x08
50 #define CY8C95XX_REG_PORT_SELECT                0x18
51 #define CY8C95XX_REG_DIR                        0x1C
52 #define CY8C95XX_REG_PULL_UP                    0x1D
53 #define CY8C95XX_REG_PULL_DOWN                  0x1E
54 #define CY8C95XX_REG_ID                         0x2E
55 
write_pin_state(const struct cy8c95xx_config * cfg,struct cy8c95xx_pin_state * pins)56 static int write_pin_state(const struct cy8c95xx_config *cfg, struct cy8c95xx_pin_state *pins)
57 {
58 	int rc;
59 
60 	rc = i2c_reg_write_byte_dt(&cfg->i2c, CY8C95XX_REG_OUTPUT_DATA0 + cfg->port_num,
61 				   pins->data_out);
62 	if (rc) {
63 		return rc;
64 	}
65 
66 	rc = i2c_reg_write_byte_dt(&cfg->i2c, CY8C95XX_REG_PORT_SELECT, cfg->port_num);
67 	if (rc) {
68 		return rc;
69 	}
70 
71 	rc = i2c_reg_write_byte_dt(&cfg->i2c, CY8C95XX_REG_DIR, pins->dir);
72 	if (rc) {
73 		return rc;
74 	}
75 
76 	rc = i2c_reg_write_byte_dt(&cfg->i2c, CY8C95XX_REG_PULL_UP, pins->pull_up);
77 	if (rc) {
78 		return rc;
79 	}
80 
81 	rc = i2c_reg_write_byte_dt(&cfg->i2c, CY8C95XX_REG_PULL_DOWN, pins->pull_down);
82 
83 	return rc;
84 }
85 
cy8c95xx_config(const struct device * dev,gpio_pin_t pin,gpio_flags_t flags)86 static int cy8c95xx_config(const struct device *dev,
87 			   gpio_pin_t pin,
88 			   gpio_flags_t flags)
89 {
90 	const struct cy8c95xx_config *cfg = dev->config;
91 	struct cy8c95xx_drv_data *drv_data = dev->data;
92 	struct cy8c95xx_pin_state *pins = &drv_data->pin_state;
93 	int rc = 0;
94 	uint32_t io_flags;
95 
96 	/* Can't do I2C bus operations from an ISR */
97 	if (k_is_in_isr()) {
98 		return -EWOULDBLOCK;
99 	}
100 
101 	/* Open-drain not implemented */
102 	if ((flags & GPIO_SINGLE_ENDED) != 0U) {
103 		return -ENOTSUP;
104 	}
105 
106 	WRITE_BIT(pins->pull_up, pin, (flags & GPIO_PULL_UP) != 0U);
107 	WRITE_BIT(pins->pull_down, pin, (flags & GPIO_PULL_DOWN) != 0U);
108 
109 	/* Disconnected pin not implemented*/
110 	io_flags = flags & (GPIO_INPUT | GPIO_OUTPUT);
111 	if (io_flags == GPIO_DISCONNECTED) {
112 		return -ENOTSUP;
113 	}
114 
115 	k_sem_take(drv_data->lock, K_FOREVER);
116 
117 	if ((flags & GPIO_OUTPUT) != 0) {
118 		pins->dir &= ~BIT(pin);
119 		if ((flags & GPIO_OUTPUT_INIT_LOW) != 0) {
120 			pins->data_out &= ~BIT(pin);
121 		} else if ((flags & GPIO_OUTPUT_INIT_HIGH) != 0) {
122 			pins->data_out |= BIT(pin);
123 		}
124 	} else {
125 		pins->dir |= BIT(pin);
126 	}
127 
128 	LOG_DBG("CFG %u %x : DIR %04x ; DAT %04x",
129 		pin, flags, pins->dir, pins->data_out);
130 
131 	rc = write_pin_state(cfg, pins);
132 
133 	k_sem_give(drv_data->lock);
134 	return rc;
135 }
136 
port_get(const struct device * dev,gpio_port_value_t * value)137 static int port_get(const struct device *dev,
138 		    gpio_port_value_t *value)
139 {
140 	const struct cy8c95xx_config *cfg = dev->config;
141 	struct cy8c95xx_drv_data *drv_data = dev->data;
142 	uint8_t pin_data = 0;
143 	int rc = 0;
144 
145 	/* Can't do I2C bus operations from an ISR */
146 	if (k_is_in_isr()) {
147 		return -EWOULDBLOCK;
148 	}
149 
150 	k_sem_take(drv_data->lock, K_FOREVER);
151 
152 	rc = i2c_reg_read_byte_dt(&cfg->i2c, CY8C95XX_REG_INPUT_DATA0 + cfg->port_num, &pin_data);
153 
154 	if (rc == 0) {
155 		*value = pin_data;
156 	}
157 
158 	k_sem_give(drv_data->lock);
159 	return rc;
160 }
161 
port_write(const struct device * dev,gpio_port_pins_t mask,gpio_port_value_t value,gpio_port_value_t toggle)162 static int port_write(const struct device *dev,
163 		      gpio_port_pins_t mask,
164 		      gpio_port_value_t value,
165 		      gpio_port_value_t toggle)
166 {
167 	const struct cy8c95xx_config *cfg = dev->config;
168 	struct cy8c95xx_drv_data *drv_data = dev->data;
169 	uint8_t *outp = &drv_data->pin_state.data_out;
170 	uint8_t out = ((*outp & ~mask) | (value & mask)) ^ toggle;
171 
172 	/* Can't do I2C bus operations from an ISR */
173 	if (k_is_in_isr()) {
174 		return -EWOULDBLOCK;
175 	}
176 
177 	k_sem_take(drv_data->lock, K_FOREVER);
178 
179 	int rc = i2c_reg_write_byte_dt(&cfg->i2c, CY8C95XX_REG_OUTPUT_DATA0 + cfg->port_num, out);
180 
181 	if (rc == 0) {
182 		*outp = out;
183 	}
184 	k_sem_give(drv_data->lock);
185 
186 	LOG_DBG("write msk %04x val %04x tog %04x => %04x: %d",
187 		mask, value, toggle, out, rc);
188 
189 	return rc;
190 }
191 
port_set_masked(const struct device * dev,gpio_port_pins_t mask,gpio_port_value_t value)192 static int port_set_masked(const struct device *dev,
193 			   gpio_port_pins_t mask,
194 			   gpio_port_value_t value)
195 {
196 	return port_write(dev, mask, value, 0);
197 }
198 
port_set_bits(const struct device * dev,gpio_port_pins_t pins)199 static int port_set_bits(const struct device *dev,
200 			 gpio_port_pins_t pins)
201 {
202 	return port_write(dev, pins, pins, 0);
203 }
204 
port_clear_bits(const struct device * dev,gpio_port_pins_t pins)205 static int port_clear_bits(const struct device *dev,
206 			   gpio_port_pins_t pins)
207 {
208 	return port_write(dev, pins, 0, 0);
209 }
210 
port_toggle_bits(const struct device * dev,gpio_port_pins_t pins)211 static int port_toggle_bits(const struct device *dev,
212 			    gpio_port_pins_t pins)
213 {
214 	return port_write(dev, 0, 0, pins);
215 }
216 
217 /**
218  * @brief Initialization function of CY8C95XX
219  *
220  * @param dev Device struct
221  * @return 0 if successful, failed otherwise.
222  */
cy8c95xx_init(const struct device * dev)223 static int cy8c95xx_init(const struct device *dev)
224 {
225 	const struct cy8c95xx_config *cfg = dev->config;
226 	struct cy8c95xx_drv_data *drv_data = dev->data;
227 	int rc;
228 	uint8_t data = 0;
229 
230 	k_sem_take(drv_data->lock, K_FOREVER);
231 
232 	if (!device_is_ready(cfg->i2c.bus)) {
233 		LOG_ERR("%s is not ready", cfg->i2c.bus->name);
234 		rc = -ENODEV;
235 		goto out;
236 	}
237 
238 	rc = i2c_reg_read_byte_dt(&cfg->i2c, CY8C95XX_REG_ID, &data);
239 	if (rc) {
240 		goto out;
241 	}
242 	LOG_DBG("cy8c95xx device ID %02X", data & 0xF0);
243 	if ((data & 0xF0) != 0x20) {
244 		LOG_WRN("driver only support [0-2] ports operations");
245 	}
246 
247 	/* Reset state mediated by initial configuration */
248 	drv_data->pin_state = (struct cy8c95xx_pin_state) {
249 		.dir = 0xFF,
250 		.data_out = 0xFF,
251 		.pull_up = 0xFF,
252 		.pull_down = 0x00,
253 	};
254 	rc = write_pin_state(cfg, &drv_data->pin_state);
255 out:
256 	if (rc != 0) {
257 		LOG_ERR("%s init failed: %d", dev->name, rc);
258 	} else {
259 		LOG_DBG("%s init ok", dev->name);
260 	}
261 	k_sem_give(drv_data->lock);
262 	return rc;
263 }
264 
265 static const struct gpio_driver_api api_table = {
266 	.pin_configure = cy8c95xx_config,
267 	.port_get_raw = port_get,
268 	.port_set_masked_raw = port_set_masked,
269 	.port_set_bits_raw = port_set_bits,
270 	.port_clear_bits_raw = port_clear_bits,
271 	.port_toggle_bits = port_toggle_bits,
272 };
273 
274 static struct k_sem cy8c95xx_lock = Z_SEM_INITIALIZER(cy8c95xx_lock, 1, 1);
275 
276 #define GPIO_PORT_INIT(idx) \
277 static const struct cy8c95xx_config cy8c95xx_##idx##_cfg = { \
278 	.common = { \
279 		.port_pin_mask = 0xFF, \
280 	}, \
281 	.i2c = I2C_DT_SPEC_GET(DT_PARENT(DT_INST(idx, DT_DRV_COMPAT))), \
282 	.port_num = DT_INST_REG_ADDR(idx), \
283 }; \
284 static struct cy8c95xx_drv_data cy8c95xx_##idx##_drvdata = { \
285 	.lock = &cy8c95xx_lock, \
286 }; \
287 DEVICE_DT_INST_DEFINE(idx, cy8c95xx_init, NULL, \
288 				&cy8c95xx_##idx##_drvdata, &cy8c95xx_##idx##_cfg, \
289 				POST_KERNEL, CONFIG_GPIO_CY8C95XX_INIT_PRIORITY, \
290 				&api_table);
291 
292 DT_INST_FOREACH_STATUS_OKAY(GPIO_PORT_INIT)
293