1 /*
2  * Copyright (c) 2022 Würth Elektronik eiSos GmbH & Co. KG
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT we_wsen_tids
8 
9 #include <stdlib.h>
10 
11 #include <zephyr/logging/log.h>
12 
13 #include "wsen_tids.h"
14 
15 LOG_MODULE_DECLARE(WSEN_TIDS, CONFIG_SENSOR_LOG_LEVEL);
16 
17 #define THRESHOLD_TEMPERATURE2REGISTER_OFFSET (double)63.
18 #define THRESHOLD_TEMPERATURE2REGISTER_STEP   (double)0.64
19 
20 /* Enable/disable threshold interrupt handling */
tids_setup_threshold_interrupt(const struct device * dev,bool enable)21 static inline void tids_setup_threshold_interrupt(const struct device *dev, bool enable)
22 {
23 	const struct tids_config *cfg = dev->config;
24 	unsigned int flags = enable ? GPIO_INT_EDGE_TO_ACTIVE : GPIO_INT_DISABLE;
25 
26 	gpio_pin_interrupt_configure_dt(&cfg->gpio_threshold, flags);
27 }
28 
29 /*
30  * Is called when a "threshold exceeded" interrupt occurred. Triggers
31  * asynchronous processing of the interrupt in tids_process_threshold_interrupt().
32  */
tids_handle_threshold_interrupt(const struct device * dev)33 static void tids_handle_threshold_interrupt(const struct device *dev)
34 {
35 	struct tids_data *data = dev->data;
36 
37 	/* Disable interrupt handling until the interrupt has been processed */
38 	tids_setup_threshold_interrupt(dev, false);
39 
40 #if defined(CONFIG_WSEN_TIDS_TRIGGER_OWN_THREAD)
41 	k_sem_give(&data->threshold_sem);
42 #elif defined(CONFIG_WSEN_TIDS_TRIGGER_GLOBAL_THREAD)
43 	k_work_submit(&data->work);
44 #endif
45 }
46 
47 /*
48  * Is called after a "threshold exceeded" interrupt occurred.
49  * Checks the sensor's status register for the limit exceeded flags and
50  * calls the trigger handler if one of the flags is set.
51  */
tids_process_threshold_interrupt(const struct device * dev)52 static void tids_process_threshold_interrupt(const struct device *dev)
53 {
54 	struct tids_data *data = dev->data;
55 	TIDS_status_t status;
56 
57 	/*
58 	 * Read the sensor's status register - this also causes the interrupt pin
59 	 * to be de-asserted
60 	 */
61 	if (TIDS_getStatusRegister(&data->sensor_interface, &status) != WE_SUCCESS) {
62 		LOG_ERR("Failed to read status register");
63 		return;
64 	}
65 
66 	if (data->threshold_handler != NULL &&
67 	    (status.upperLimitExceeded != 0 || status.lowerLimitExceeded != 0)) {
68 		data->threshold_handler(dev, data->threshold_trigger);
69 	}
70 
71 	if (data->threshold_handler != NULL) {
72 		tids_setup_threshold_interrupt(dev, true);
73 	}
74 }
75 
76 /* Enables/disables processing of the "threshold exceeded" interrupt. */
tids_trigger_set(const struct device * dev,const struct sensor_trigger * trig,sensor_trigger_handler_t handler)77 int tids_trigger_set(const struct device *dev, const struct sensor_trigger *trig,
78 		     sensor_trigger_handler_t handler)
79 {
80 	struct tids_data *data = dev->data;
81 	const struct tids_config *cfg = dev->config;
82 
83 	if (trig->type != SENSOR_TRIG_THRESHOLD) {
84 		LOG_ERR("Unsupported sensor trigger");
85 		return -ENOTSUP;
86 	}
87 
88 	tids_setup_threshold_interrupt(dev, false);
89 
90 	data->threshold_handler = handler;
91 	if (handler == NULL) {
92 		return 0;
93 	}
94 
95 	data->threshold_trigger = trig;
96 
97 	tids_setup_threshold_interrupt(dev, true);
98 
99 	/*
100 	 * If threshold interrupt is active we probably won't get the rising edge, so
101 	 * invoke the callback manually.
102 	 */
103 	if (gpio_pin_get_dt(&cfg->gpio_threshold) > 0) {
104 		tids_handle_threshold_interrupt(dev);
105 	}
106 
107 	return 0;
108 }
109 
tids_threshold_callback(const struct device * dev,struct gpio_callback * cb,uint32_t pins)110 static void tids_threshold_callback(const struct device *dev, struct gpio_callback *cb,
111 				    uint32_t pins)
112 {
113 	struct tids_data *data = CONTAINER_OF(cb, struct tids_data, threshold_cb);
114 
115 	ARG_UNUSED(pins);
116 
117 	tids_handle_threshold_interrupt(data->dev);
118 }
119 
120 #ifdef CONFIG_WSEN_TIDS_TRIGGER_OWN_THREAD
tids_thread(struct tids_data * tids)121 static void tids_thread(struct tids_data *tids)
122 {
123 	while (true) {
124 		k_sem_take(&tids->threshold_sem, K_FOREVER);
125 		tids_process_threshold_interrupt(tids->dev);
126 	}
127 }
128 #endif /* CONFIG_WSEN_TIDS_TRIGGER_OWN_THREAD */
129 
130 #ifdef CONFIG_WSEN_TIDS_TRIGGER_GLOBAL_THREAD
tids_work_cb(struct k_work * work)131 static void tids_work_cb(struct k_work *work)
132 {
133 	struct tids_data *tids = CONTAINER_OF(work, struct tids_data, work);
134 
135 	tids_process_threshold_interrupt(tids->dev);
136 }
137 #endif /* CONFIG_WSEN_TIDS_TRIGGER_GLOBAL_THREAD */
138 
tids_threshold_set(const struct device * dev,const struct sensor_value * thresh_value,bool upper)139 int tids_threshold_set(const struct device *dev, const struct sensor_value *thresh_value,
140 		       bool upper)
141 {
142 	struct tids_data *data = dev->data;
143 	double thresh = (sensor_value_to_double(thresh_value) / THRESHOLD_TEMPERATURE2REGISTER_STEP)
144 					+ THRESHOLD_TEMPERATURE2REGISTER_OFFSET;
145 
146 	if (thresh < 0) {
147 		thresh = 0;
148 	} else if (thresh > 255) {
149 		thresh = 255;
150 	}
151 
152 	if (upper) {
153 		if (TIDS_setTempHighLimit(&data->sensor_interface, (uint8_t)thresh) != WE_SUCCESS) {
154 			LOG_ERR("Failed to set high temperature threshold to %d.%d (%d).",
155 				thresh_value->val1, abs(thresh_value->val2), (uint8_t)thresh);
156 			return -EIO;
157 		}
158 	} else {
159 		if (TIDS_setTempLowLimit(&data->sensor_interface, (uint8_t)thresh) != WE_SUCCESS) {
160 			LOG_ERR("Failed to set low temperature threshold to %d.%d (%d).",
161 				thresh_value->val1, abs(thresh_value->val2), (uint8_t)thresh);
162 			return -EIO;
163 		}
164 	}
165 
166 	return 0;
167 }
168 
tids_init_interrupt(const struct device * dev)169 int tids_init_interrupt(const struct device *dev)
170 {
171 	struct tids_data *data = dev->data;
172 	const struct tids_config *cfg = dev->config;
173 	int status;
174 	struct sensor_value upper_limit;
175 	struct sensor_value lower_limit;
176 
177 	if (cfg->gpio_threshold.port == NULL) {
178 		LOG_ERR("int-gpios is not defined in the device tree.");
179 		return -EINVAL;
180 	}
181 
182 	if (!gpio_is_ready_dt(&cfg->gpio_threshold)) {
183 		LOG_ERR("Device %s is not ready", cfg->gpio_threshold.port->name);
184 		return -ENODEV;
185 	}
186 
187 	data->dev = dev;
188 
189 	/* Setup threshold gpio interrupt */
190 	status = gpio_pin_configure_dt(&cfg->gpio_threshold, GPIO_INPUT);
191 	if (status < 0) {
192 		LOG_ERR("Failed to configure %s.%02u", cfg->gpio_threshold.port->name,
193 			cfg->gpio_threshold.pin);
194 		return status;
195 	}
196 
197 	gpio_init_callback(&data->threshold_cb, tids_threshold_callback,
198 			   BIT(cfg->gpio_threshold.pin));
199 
200 	status = gpio_add_callback(cfg->gpio_threshold.port, &data->threshold_cb);
201 	if (status < 0) {
202 		LOG_ERR("Failed to set gpio callback.");
203 		return status;
204 	}
205 
206 	/*
207 	 * Enable interrupt on high/low temperature (interrupt generation is enabled if at
208 	 * least one threshold is non-zero)
209 	 */
210 	upper_limit.val1 = cfg->high_threshold;
211 	upper_limit.val2 = 0;
212 	lower_limit.val1 = cfg->low_threshold;
213 	lower_limit.val2 = 0;
214 
215 	status = tids_threshold_set(dev, &upper_limit, true);
216 	if (status < 0) {
217 		return status;
218 	}
219 
220 	status = tids_threshold_set(dev, &lower_limit, false);
221 	if (status < 0) {
222 		return status;
223 	}
224 
225 #if defined(CONFIG_WSEN_TIDS_TRIGGER_OWN_THREAD)
226 	k_sem_init(&data->threshold_sem, 0, K_SEM_MAX_LIMIT);
227 
228 	k_thread_create(&data->thread, data->thread_stack, CONFIG_WSEN_TIDS_THREAD_STACK_SIZE,
229 			(k_thread_entry_t)tids_thread, data, NULL, NULL,
230 			K_PRIO_COOP(CONFIG_WSEN_TIDS_THREAD_PRIORITY), 0, K_NO_WAIT);
231 #elif defined(CONFIG_WSEN_TIDS_TRIGGER_GLOBAL_THREAD)
232 	data->work.handler = tids_work_cb;
233 #endif
234 
235 	tids_setup_threshold_interrupt(dev, true);
236 
237 	return 0;
238 }
239