1 /*
2  * Copyright (c) 2022 Matthias Freese
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT ti_sn74hc595
8 
9 /**
10  * @file Driver for 74 HC shift register
11  */
12 
13 #include <zephyr/drivers/gpio.h>
14 #include <zephyr/drivers/spi.h>
15 #include <zephyr/kernel.h>
16 #include <zephyr/device.h>
17 #include <zephyr/kernel.h>
18 
19 #include <zephyr/drivers/gpio/gpio_utils.h>
20 
21 #include <zephyr/logging/log.h>
22 LOG_MODULE_REGISTER(gpio_sn74hc595, CONFIG_GPIO_LOG_LEVEL);
23 
24 #if CONFIG_SPI_INIT_PRIORITY >= CONFIG_GPIO_SN74HC595_INIT_PRIORITY
25 #error SPI_INIT_PRIORITY must be lower than SN74HC595_INIT_PRIORITY
26 #endif
27 
28 struct gpio_sn74hc595_config {
29 	/* gpio_driver_config needs to be first */
30 	struct gpio_driver_config config;
31 
32 	struct spi_dt_spec bus;
33 	struct gpio_dt_spec reset_gpio;
34 };
35 
36 struct gpio_sn74hc595_drv_data {
37 	/* gpio_driver_data needs to be first */
38 	struct gpio_driver_data data;
39 
40 	struct k_mutex lock;
41 	uint8_t output;
42 };
43 
sn74hc595_spi_write(const struct device * dev,void * buf,size_t len_bytes)44 static int sn74hc595_spi_write(const struct device *dev, void *buf, size_t len_bytes)
45 {
46 	const struct gpio_sn74hc595_config *config = dev->config;
47 
48 	__ASSERT(((buf != NULL) || (len_bytes == 0)), "no valid buffer given");
49 	__ASSERT(!k_is_in_isr(), "attempt to access SPI from ISR");
50 
51 	struct spi_buf tx_buf[] = { { .buf = buf, .len = len_bytes } };
52 	const struct spi_buf_set tx = { .buffers = tx_buf, .count = 1 };
53 
54 	return spi_write_dt(&config->bus, &tx);
55 }
56 
gpio_sn74hc595_config(const struct device * dev,gpio_pin_t pin,gpio_flags_t flags)57 static int gpio_sn74hc595_config(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags)
58 {
59 	ARG_UNUSED(dev);
60 	ARG_UNUSED(pin);
61 	ARG_UNUSED(flags);
62 	return 0;
63 }
64 
gpio_sn74hc595_port_get_raw(const struct device * dev,uint32_t * value)65 static int gpio_sn74hc595_port_get_raw(const struct device *dev, uint32_t *value)
66 {
67 	struct gpio_sn74hc595_drv_data *drv_data = dev->data;
68 
69 	k_mutex_lock(&drv_data->lock, K_FOREVER);
70 
71 	*value = drv_data->output;
72 
73 	k_mutex_unlock(&drv_data->lock);
74 
75 	return 0;
76 }
77 
gpio_sn74hc595_port_set_masked_raw(const struct device * dev,uint32_t mask,uint32_t value)78 static int gpio_sn74hc595_port_set_masked_raw(const struct device *dev, uint32_t mask,
79 					      uint32_t value)
80 {
81 	struct gpio_sn74hc595_drv_data *drv_data = dev->data;
82 	int ret = 0;
83 	uint8_t output;
84 
85 	k_mutex_lock(&drv_data->lock, K_FOREVER);
86 
87 	/* check if we need to do something at all      */
88 	/* current output differs from new masked value */
89 	if ((drv_data->output & mask) != (mask & value)) {
90 		output = (drv_data->output & ~mask) | (mask & value);
91 
92 		ret = sn74hc595_spi_write(dev, &output, 1U);
93 		if (ret < 0) {
94 			goto unlock;
95 		}
96 
97 		drv_data->output = output;
98 	}
99 
100 unlock:
101 	k_mutex_unlock(&drv_data->lock);
102 	return ret;
103 }
104 
gpio_sn74hc595_port_set_bits_raw(const struct device * dev,uint32_t mask)105 static int gpio_sn74hc595_port_set_bits_raw(const struct device *dev, uint32_t mask)
106 {
107 	return gpio_sn74hc595_port_set_masked_raw(dev, mask, mask);
108 }
109 
gpio_sn74hc595_port_clear_bits_raw(const struct device * dev,uint32_t mask)110 static int gpio_sn74hc595_port_clear_bits_raw(const struct device *dev, uint32_t mask)
111 {
112 	return gpio_sn74hc595_port_set_masked_raw(dev, mask, 0U);
113 }
114 
gpio_sn74hc595_port_toggle_bits(const struct device * dev,uint32_t mask)115 static int gpio_sn74hc595_port_toggle_bits(const struct device *dev, uint32_t mask)
116 {
117 	struct gpio_sn74hc595_drv_data *drv_data = dev->data;
118 	int ret;
119 	uint8_t toggled_output;
120 
121 	k_mutex_lock(&drv_data->lock, K_FOREVER);
122 
123 	toggled_output = drv_data->output ^ mask;
124 
125 	ret = sn74hc595_spi_write(dev, &toggled_output, 1U);
126 	if (ret < 0) {
127 		goto unlock;
128 	}
129 
130 	drv_data->output ^= mask;
131 
132 unlock:
133 	k_mutex_unlock(&drv_data->lock);
134 	return ret;
135 }
136 
137 static const struct gpio_driver_api gpio_sn74hc595_drv_api_funcs = {
138 	.pin_configure = gpio_sn74hc595_config,
139 	.port_get_raw = gpio_sn74hc595_port_get_raw,
140 	.port_set_masked_raw = gpio_sn74hc595_port_set_masked_raw,
141 	.port_set_bits_raw = gpio_sn74hc595_port_set_bits_raw,
142 	.port_clear_bits_raw = gpio_sn74hc595_port_clear_bits_raw,
143 	.port_toggle_bits = gpio_sn74hc595_port_toggle_bits,
144 };
145 
146 /**
147  * @brief Initialization function of sn74hc595
148  *
149  * @param dev Device struct
150  * @return 0 if successful, failed otherwise.
151  */
gpio_sn74hc595_init(const struct device * dev)152 static int gpio_sn74hc595_init(const struct device *dev)
153 {
154 	const struct gpio_sn74hc595_config *config = dev->config;
155 	struct gpio_sn74hc595_drv_data *drv_data = dev->data;
156 
157 	if (!spi_is_ready_dt(&config->bus)) {
158 		LOG_ERR("SPI bus %s not ready", config->bus.bus->name);
159 		return -ENODEV;
160 	}
161 
162 	if (!gpio_is_ready_dt(&config->reset_gpio)) {
163 		LOG_ERR("GPIO port %s not ready", config->reset_gpio.port->name);
164 		return -ENODEV;
165 	}
166 
167 	if (gpio_pin_configure_dt(&config->reset_gpio, GPIO_OUTPUT_ACTIVE) < 0) {
168 		LOG_ERR("Unable to configure RST GPIO pin %u", config->reset_gpio.pin);
169 		return -EINVAL;
170 	}
171 
172 	gpio_pin_set(config->reset_gpio.port, config->reset_gpio.pin, 0);
173 
174 	drv_data->output = 0U;
175 	return 0;
176 }
177 
178 #define SN74HC595_SPI_OPERATION									\
179 	((uint16_t)(SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8)))
180 
181 #define SN74HC595_INIT(n)									\
182 	static struct gpio_sn74hc595_drv_data sn74hc595_data_##n = {				\
183 		.output = 0,									\
184 		.lock = Z_MUTEX_INITIALIZER(sn74hc595_data_##n.lock),				\
185 	};											\
186 												\
187 	static const struct gpio_sn74hc595_config sn74hc595_config_##n = {			\
188 		.config = {									\
189 			.port_pin_mask =							\
190 				GPIO_PORT_PIN_MASK_FROM_DT_INST(n),				\
191 		},										\
192 		.bus = SPI_DT_SPEC_INST_GET(n, SN74HC595_SPI_OPERATION, 0),			\
193 		.reset_gpio = GPIO_DT_SPEC_INST_GET(n, reset_gpios),				\
194 	};											\
195 												\
196 	DEVICE_DT_DEFINE(DT_DRV_INST(n), &gpio_sn74hc595_init, NULL,				\
197 			 &sn74hc595_data_##n, &sn74hc595_config_##n, POST_KERNEL,		\
198 			 CONFIG_GPIO_SN74HC595_INIT_PRIORITY, &gpio_sn74hc595_drv_api_funcs);
199 
200 DT_INST_FOREACH_STATUS_OKAY(SN74HC595_INIT)
201