1 /**
2  * Copyright (c)
3  * 2022 Ithinx GmbH
4  * 2023 Amrith Venkat Kesavamoorthi <amrith@mr-beam.org>
5  * 2023 Mr Beam Lasers GmbH.
6  *
7  * SPDX-License-Identifier: Apache-2.0
8  *
9  * @see https://www.nxp.com/docs/en/data-sheet/PCF8575.pdf
10  * @see https://www.nxp.com/docs/en/data-sheet/PCF8574_PCF8574A.pdf
11  */
12 
13 #define DT_DRV_COMPAT nxp_pcf857x
14 
15 #include <zephyr/drivers/gpio/gpio_utils.h>
16 
17 #include <zephyr/drivers/gpio.h>
18 #include <zephyr/drivers/i2c.h>
19 #include <zephyr/kernel.h>
20 #include <zephyr/logging/log.h>
21 #include <zephyr/sys/byteorder.h>
22 
23 LOG_MODULE_REGISTER(pcf857x, CONFIG_GPIO_LOG_LEVEL);
24 
25 struct pcf857x_pins_cfg {
26 	uint16_t configured_as_outputs; /* 0 for input, 1 for output */
27 	uint16_t outputs_state;
28 };
29 
30 /** Runtime driver data of the pcf857x*/
31 struct pcf857x_drv_data {
32 	/* gpio_driver_data needs to be first */
33 	struct gpio_driver_data common;
34 	struct pcf857x_pins_cfg pins_cfg;
35 	sys_slist_t callbacks;
36 	struct k_sem lock;
37 	struct k_work work;
38 	const struct device *dev;
39 	struct gpio_callback int_gpio_cb;
40 	uint16_t input_port_last;
41 	int num_bytes;
42 };
43 
44 /** Configuration data*/
45 struct pcf857x_drv_cfg {
46 	/* gpio_driver_config needs to be first */
47 	struct gpio_driver_config common;
48 	struct i2c_dt_spec i2c;
49 	struct gpio_dt_spec gpio_int;
50 };
51 
52 /**
53  * @brief Reads the value of the pins from pcf857x respectively from a connected device.
54  *
55  * @param dev Pointer to the device structure of the driver instance.
56  * @param value Pointer to the input value. It contains the received Bytes(receives 2 Bytes for P0
57  * and P1).
58  *
59  * @retval 0 If successful.
60  * @retval Negative value for error code.
61  */
pcf857x_process_input(const struct device * dev,gpio_port_value_t * value)62 static int pcf857x_process_input(const struct device *dev, gpio_port_value_t *value)
63 {
64 	const struct pcf857x_drv_cfg *drv_cfg = dev->config;
65 	struct pcf857x_drv_data *drv_data = dev->data;
66 	int rc = 0;
67 	uint8_t rx_buf[2] = {0};
68 
69 	rc = i2c_read_dt(&drv_cfg->i2c, rx_buf, drv_data->num_bytes);
70 	if (rc != 0) {
71 		LOG_ERR("%s: failed to read from device: %d", dev->name, rc);
72 		return -EIO;
73 	}
74 
75 	if (value) {
76 		*value = sys_get_le16(rx_buf); /*format P17-P10..P07-P00 (bit15-bit8..bit7-bit0)*/
77 	}
78 
79 	drv_data->input_port_last = sys_get_le16(rx_buf);
80 
81 	return rc;
82 }
83 
84 /** Register the read-task as work*/
pcf857x_work_handler(struct k_work * work)85 static void pcf857x_work_handler(struct k_work *work)
86 {
87 	struct pcf857x_drv_data *drv_data = CONTAINER_OF(work, struct pcf857x_drv_data, work);
88 
89 	k_sem_take(&drv_data->lock, K_FOREVER);
90 
91 	uint32_t changed_pins;
92 	uint16_t input_port_last_temp = drv_data->input_port_last;
93 	int rc = pcf857x_process_input(drv_data->dev, &changed_pins);
94 
95 	if (rc) {
96 		LOG_ERR("Failed to read interrupt sources: %d", rc);
97 	}
98 	k_sem_give(&drv_data->lock);
99 	if (input_port_last_temp != (uint16_t)changed_pins && !rc) {
100 
101 		/** Find changed bits*/
102 		changed_pins ^= input_port_last_temp;
103 		gpio_fire_callbacks(&drv_data->callbacks, drv_data->dev, changed_pins);
104 	}
105 }
106 
107 /** Callback for interrupt through some level changes on pcf857x pins*/
pcf857x_int_gpio_handler(const struct device * dev,struct gpio_callback * gpio_cb,uint32_t pins)108 static void pcf857x_int_gpio_handler(const struct device *dev, struct gpio_callback *gpio_cb,
109 				     uint32_t pins)
110 {
111 	ARG_UNUSED(dev);
112 	ARG_UNUSED(pins);
113 
114 	struct pcf857x_drv_data *drv_data =
115 		CONTAINER_OF(gpio_cb, struct pcf857x_drv_data, int_gpio_cb);
116 
117 	k_work_submit(&drv_data->work);
118 }
119 
120 /**
121  * @brief This function reads a value from the connected device
122  *
123  * @param dev Pointer to the device structure of a port.
124  * @param value Pointer to a variable where pin values will be stored.
125  *
126  * @retval 0 If successful.
127  * @retval Negative value for error code.
128  */
pcf857x_port_get_raw(const struct device * dev,gpio_port_value_t * value)129 static int pcf857x_port_get_raw(const struct device *dev, gpio_port_value_t *value)
130 {
131 	struct pcf857x_drv_data *drv_data = dev->data;
132 	int rc;
133 
134 	if (k_is_in_isr()) {
135 		return -EWOULDBLOCK;
136 	}
137 
138 	if ((~drv_data->pins_cfg.configured_as_outputs & (uint16_t)*value) != (uint16_t)*value) {
139 		LOG_ERR("Pin(s) is/are configured as output which should be input.");
140 		return -EOPNOTSUPP;
141 	}
142 
143 	k_sem_take(&drv_data->lock, K_FOREVER);
144 
145 	/**
146 	 * Reading of the input port also clears the generated interrupt,
147 	 * thus the configured callbacks must be fired also here if needed.
148 	 */
149 	rc = pcf857x_process_input(dev, value);
150 
151 	k_sem_give(&drv_data->lock);
152 
153 	return rc;
154 }
155 
156 /**
157  * @brief This function realizes the write connection to the i2c device.
158  *
159  * @param dev A pointer to the device structure
160  * @param mask A mask of bits to set some bits to LOW or HIGH
161  * @param value The value which is written via i2c to the pcf857x's output pins
162  * @param toggle A way to toggle some bits with xor
163  *
164  * @retval 0 If successful.
165  * @retval Negative value for error code.
166  */
pcf857x_port_set_raw(const struct device * dev,uint16_t mask,uint16_t value,uint16_t toggle)167 static int pcf857x_port_set_raw(const struct device *dev, uint16_t mask, uint16_t value,
168 				uint16_t toggle)
169 {
170 	const struct pcf857x_drv_cfg *drv_cfg = dev->config;
171 	struct pcf857x_drv_data *drv_data = dev->data;
172 	int rc = 0;
173 	uint16_t tx_buf;
174 	uint8_t tx_buf_p[2];
175 
176 	if (k_is_in_isr()) {
177 		return -EWOULDBLOCK;
178 	}
179 
180 	if ((drv_data->pins_cfg.configured_as_outputs & value) != value) {
181 		LOG_ERR("Pin(s) is/are configured as input which should be output.");
182 		return -EOPNOTSUPP;
183 	}
184 
185 	tx_buf = (drv_data->pins_cfg.outputs_state & ~mask);
186 	tx_buf |= (value & mask);
187 	tx_buf ^= toggle;
188 	sys_put_le16(tx_buf, tx_buf_p);
189 
190 	rc = i2c_write_dt(&drv_cfg->i2c, tx_buf_p, drv_data->num_bytes);
191 	if (rc != 0) {
192 		LOG_ERR("%s: failed to write output port: %d", dev->name, rc);
193 		return -EIO;
194 	}
195 	k_sem_take(&drv_data->lock, K_FOREVER);
196 	drv_data->pins_cfg.outputs_state = tx_buf;
197 	k_sem_give(&drv_data->lock);
198 
199 	return 0;
200 }
201 
202 /**
203  * @brief This function fills a dummy because the pcf857x has no pins to configure.
204  * You can use it to set some pins permanent to HIGH or LOW until reset. It uses the port_set_raw
205  * function to set the pins of pcf857x directly.
206  *
207  * @param dev Pointer to the device structure for the driver instance.
208  * @param pin The bit in the io register which is set to high
209  * @param flags Flags like the GPIO direction or the state
210  *
211  * @retval 0 If successful.
212  * @retval Negative value for error.
213  */
pcf857x_pin_configure(const struct device * dev,gpio_pin_t pin,gpio_flags_t flags)214 static int pcf857x_pin_configure(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags)
215 {
216 	struct pcf857x_drv_data *drv_data = dev->data;
217 	int ret = 0;
218 	uint16_t temp_pins = drv_data->pins_cfg.outputs_state;
219 	uint16_t temp_outputs = drv_data->pins_cfg.configured_as_outputs;
220 
221 	if (flags & (GPIO_PULL_UP | GPIO_PULL_DOWN | GPIO_DISCONNECTED | GPIO_SINGLE_ENDED)) {
222 		return -ENOTSUP;
223 	}
224 	if (flags & GPIO_INPUT) {
225 		temp_outputs &= ~BIT(pin);
226 		temp_pins &= ~(1 << pin);
227 	} else if (flags & GPIO_OUTPUT) {
228 		drv_data->pins_cfg.configured_as_outputs |= BIT(pin);
229 		temp_outputs = drv_data->pins_cfg.configured_as_outputs;
230 	}
231 	if (flags & GPIO_OUTPUT_INIT_HIGH) {
232 		temp_pins |= (1 << pin);
233 	}
234 	if (flags & GPIO_OUTPUT_INIT_LOW) {
235 		temp_pins &= ~(1 << pin);
236 	}
237 
238 	ret = pcf857x_port_set_raw(dev, drv_data->pins_cfg.configured_as_outputs, temp_pins, 0);
239 
240 	if (ret == 0) {
241 		k_sem_take(&drv_data->lock, K_FOREVER);
242 		drv_data->pins_cfg.outputs_state = temp_pins;
243 		drv_data->pins_cfg.configured_as_outputs = temp_outputs;
244 		k_sem_give(&drv_data->lock);
245 	}
246 
247 	return ret;
248 }
249 
250 /**
251  * @brief Sets a value to the pins of pcf857x
252  *
253  * @param dev Pointer to the device structure for the driver instance.
254  * @param mask The bit mask which bits should be set
255  * @param value	The value which should be set
256  *
257  * @retval 0 If successful.
258  * @retval Negative value for error.
259  */
pcf857x_port_set_masked_raw(const struct device * dev,gpio_port_pins_t mask,gpio_port_value_t value)260 static int pcf857x_port_set_masked_raw(const struct device *dev, gpio_port_pins_t mask,
261 				       gpio_port_value_t value)
262 {
263 	return pcf857x_port_set_raw(dev, (uint16_t)mask, (uint16_t)value, 0);
264 }
265 
266 /**
267  * @brief Sets some output pins of the pcf857x
268  *
269  * @param dev Pointer to the device structure for the driver instance.
270  * @param pins The pin(s) which will be set in a range from P17-P10..P07-P00
271  *
272  * @retval 0 If successful.
273  * @retval Negative value for error.
274  */
pcf857x_port_set_bits_raw(const struct device * dev,gpio_port_pins_t pins)275 static int pcf857x_port_set_bits_raw(const struct device *dev, gpio_port_pins_t pins)
276 {
277 	return pcf857x_port_set_raw(dev, (uint16_t)pins, (uint16_t)pins, 0);
278 }
279 
280 /**
281  * @brief clear some bits
282  *
283  * @param dev Pointer to the device structure for the driver instance.
284  * @param pins Pins which will be cleared
285  *
286  * @retval 0 If successful.
287  * @retval Negative value for error.
288  */
pcf857x_port_clear_bits_raw(const struct device * dev,gpio_port_pins_t pins)289 static int pcf857x_port_clear_bits_raw(const struct device *dev, gpio_port_pins_t pins)
290 {
291 	return pcf857x_port_set_raw(dev, (uint16_t)pins, 0, 0);
292 }
293 
294 /**
295  * @brief Toggle some bits
296  *
297  * @param dev Pointer to the device structure for the driver instance.
298  * @param pins Pins which will be toggled
299  *
300  * @retval 0 If successful.
301  * @retval Negative value for error.
302  */
pcf857x_port_toggle_bits(const struct device * dev,gpio_port_pins_t pins)303 static int pcf857x_port_toggle_bits(const struct device *dev, gpio_port_pins_t pins)
304 {
305 	return pcf857x_port_set_raw(dev, 0, 0, (uint16_t)pins);
306 }
307 
308 /* Each pin gives an interrupt at pcf857x. In this function the configuration is checked. */
pcf857x_pin_interrupt_configure(const struct device * dev,gpio_pin_t pin,enum gpio_int_mode mode,enum gpio_int_trig trig)309 static int pcf857x_pin_interrupt_configure(const struct device *dev, gpio_pin_t pin,
310 					   enum gpio_int_mode mode, enum gpio_int_trig trig)
311 {
312 	const struct pcf857x_drv_cfg *drv_cfg = dev->config;
313 
314 	if (!drv_cfg->gpio_int.port) {
315 		return -ENOTSUP;
316 	}
317 
318 	/* This device supports only edge-triggered interrupts. */
319 	if (mode == GPIO_INT_MODE_LEVEL) {
320 		return -ENOTSUP;
321 	}
322 
323 	return 0;
324 }
325 
326 /** Register the callback in the callback list */
pcf857x_manage_callback(const struct device * dev,struct gpio_callback * callback,bool set)327 static int pcf857x_manage_callback(const struct device *dev, struct gpio_callback *callback,
328 				   bool set)
329 {
330 	struct pcf857x_drv_data *drv_data = dev->data;
331 
332 	return gpio_manage_callback(&drv_data->callbacks, callback, set);
333 }
334 
335 /** Initialize the pcf857x */
pcf857x_init(const struct device * dev)336 static int pcf857x_init(const struct device *dev)
337 {
338 	const struct pcf857x_drv_cfg *drv_cfg = dev->config;
339 	struct pcf857x_drv_data *drv_data = dev->data;
340 	int rc;
341 
342 	if (!device_is_ready(drv_cfg->i2c.bus)) {
343 		LOG_ERR("%s is not ready", drv_cfg->i2c.bus->name);
344 		return -ENODEV;
345 	}
346 
347 	/* If the INT line is available, configure the callback for it. */
348 	if (drv_cfg->gpio_int.port) {
349 		if (!gpio_is_ready_dt(&drv_cfg->gpio_int)) {
350 			LOG_ERR("Port is not ready");
351 			return -ENODEV;
352 		}
353 
354 		rc = gpio_pin_configure_dt(&drv_cfg->gpio_int, GPIO_INPUT);
355 		if (rc != 0) {
356 			LOG_ERR("%s: failed to configure INT line: %d", dev->name, rc);
357 			return -EIO;
358 		}
359 
360 		rc = gpio_pin_interrupt_configure_dt(&drv_cfg->gpio_int, GPIO_INT_EDGE_TO_ACTIVE);
361 		if (rc != 0) {
362 			LOG_ERR("%s: failed to configure INT interrupt: %d", dev->name, rc);
363 			return -EIO;
364 		}
365 
366 		gpio_init_callback(&drv_data->int_gpio_cb, pcf857x_int_gpio_handler,
367 				   BIT(drv_cfg->gpio_int.pin));
368 		rc = gpio_add_callback(drv_cfg->gpio_int.port, &drv_data->int_gpio_cb);
369 		if (rc != 0) {
370 			LOG_ERR("%s: failed to add INT callback: %d", dev->name, rc);
371 			return -EIO;
372 		}
373 	}
374 
375 	return 0;
376 }
377 
378 /** Realizes the functions of gpio.h for pcf857x*/
379 static const struct gpio_driver_api pcf857x_drv_api = {
380 	.pin_configure = pcf857x_pin_configure,
381 	.port_get_raw = pcf857x_port_get_raw,
382 	.port_set_masked_raw = pcf857x_port_set_masked_raw,
383 	.port_set_bits_raw = pcf857x_port_set_bits_raw,
384 	.port_clear_bits_raw = pcf857x_port_clear_bits_raw,
385 	.port_toggle_bits = pcf857x_port_toggle_bits,
386 	.pin_interrupt_configure = pcf857x_pin_interrupt_configure,
387 	.manage_callback = pcf857x_manage_callback,
388 };
389 
390 #define GPIO_PCF857X_INST(idx)                                                                     \
391 	static const struct pcf857x_drv_cfg pcf857x_cfg##idx = {                                   \
392 		.common =                                                                          \
393 			{                                                                          \
394 				.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(idx),             \
395 			},                                                                         \
396 		.gpio_int = GPIO_DT_SPEC_INST_GET_OR(idx, int_gpios, {0}),                         \
397 		.i2c = I2C_DT_SPEC_INST_GET(idx),                                                  \
398 	};                                                                                         \
399 	static struct pcf857x_drv_data pcf857x_data##idx = {                                       \
400 		.lock = Z_SEM_INITIALIZER(pcf857x_data##idx.lock, 1, 1),                           \
401 		.work = Z_WORK_INITIALIZER(pcf857x_work_handler),                                  \
402 		.dev = DEVICE_DT_INST_GET(idx),                                                    \
403 		.num_bytes = DT_INST_ENUM_IDX(idx, ngpios) + 1,                                    \
404 	};                                                                                         \
405 	DEVICE_DT_INST_DEFINE(idx, pcf857x_init, NULL, &pcf857x_data##idx, &pcf857x_cfg##idx,      \
406 			      POST_KERNEL, CONFIG_GPIO_PCF857X_INIT_PRIORITY, &pcf857x_drv_api);
407 
408 DT_INST_FOREACH_STATUS_OKAY(GPIO_PCF857X_INST);
409