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