1 /*
2  * Copyright (c) 2022-2023 Jamie McCrae
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT noritake_itron
8 
9 #include <string.h>
10 #include <zephyr/device.h>
11 #include <zephyr/devicetree.h>
12 #include <zephyr/drivers/auxdisplay.h>
13 #include <zephyr/drivers/gpio.h>
14 #include <zephyr/drivers/uart.h>
15 #include <zephyr/sys/byteorder.h>
16 #include <zephyr/logging/log.h>
17 #include "auxdisplay_itron.h"
18 
19 LOG_MODULE_REGISTER(auxdisplay_itron, CONFIG_AUXDISPLAY_LOG_LEVEL);
20 
21 /* Display commands */
22 #define AUXDISPLAY_ITRON_CMD_USER_SETTING 0x1f
23 #define AUXDISPLAY_ITRON_CMD_ESCAPE 0x1b
24 #define AUXDISPLAY_ITRON_CMD_BRIGHTNESS 0x58
25 #define AUXDISPLAY_ITRON_CMD_DISPLAY_CLEAR 0x0c
26 #define AUXDISPLAY_ITRON_CMD_CURSOR 0x43
27 #define AUXDISPLAY_ITRON_CMD_CURSOR_SET 0x24
28 #define AUXDISPLAY_ITRON_CMD_ACTION 0x28
29 #define AUXDISPLAY_ITRON_CMD_N 0x61
30 #define AUXDISPLAY_ITRON_CMD_SCREEN_SAVER 0x40
31 
32 /* Time values when multithreading is disabled */
33 #define AUXDISPLAY_ITRON_RESET_TIME K_MSEC(2)
34 #define AUXDISPLAY_ITRON_RESET_WAIT_TIME K_MSEC(101)
35 #define AUXDISPLAY_ITRON_BUSY_DELAY_TIME_CHECK K_MSEC(4)
36 #define AUXDISPLAY_ITRON_BUSY_WAIT_LOOPS 125
37 
38 /* Time values when multithreading is enabled */
39 #define AUXDISPLAY_ITRON_BUSY_MAX_TIME K_MSEC(500)
40 
41 struct auxdisplay_itron_data {
42 	uint16_t character_x;
43 	uint16_t character_y;
44 	uint8_t brightness;
45 	bool powered;
46 #ifdef CONFIG_MULTITHREADING
47 	struct k_sem lock_sem;
48 	struct k_sem busy_wait_sem;
49 	struct gpio_callback busy_wait_callback;
50 #endif
51 };
52 
53 struct auxdisplay_itron_config {
54 	const struct device *uart;
55 	struct auxdisplay_capabilities capabilities;
56 	struct gpio_dt_spec reset_gpio;
57 	struct gpio_dt_spec busy_gpio;
58 };
59 
60 static int send_cmd(const struct device *dev, const uint8_t *command, uint8_t length, bool pm,
61 		    bool lock);
62 static int auxdisplay_itron_is_busy(const struct device *dev);
63 static int auxdisplay_itron_clear(const struct device *dev);
64 static int auxdisplay_itron_set_powered(const struct device *dev, bool enabled);
65 
66 #ifdef CONFIG_MULTITHREADING
auxdisplay_itron_busy_gpio_change_callback(const struct device * port,struct gpio_callback * cb,gpio_port_pins_t pins)67 void auxdisplay_itron_busy_gpio_change_callback(const struct device *port,
68 						struct gpio_callback *cb,
69 						gpio_port_pins_t pins)
70 {
71 	struct auxdisplay_itron_data *data = CONTAINER_OF(cb,
72 			struct auxdisplay_itron_data, busy_wait_callback);
73 	k_sem_give(&data->busy_wait_sem);
74 }
75 #endif
76 
auxdisplay_itron_init(const struct device * dev)77 static int auxdisplay_itron_init(const struct device *dev)
78 {
79 	const struct auxdisplay_itron_config *config = dev->config;
80 	struct auxdisplay_itron_data *data = dev->data;
81 	int rc;
82 
83 	if (!device_is_ready(config->uart)) {
84 		LOG_ERR("UART device not ready");
85 		return -ENODEV;
86 	}
87 
88 	/* Configure and set busy GPIO */
89 	if (config->busy_gpio.port) {
90 		rc = gpio_pin_configure_dt(&config->busy_gpio, GPIO_INPUT);
91 
92 		if (rc < 0) {
93 			LOG_ERR("Configuration of text display busy GPIO failed: %d", rc);
94 			return rc;
95 		}
96 
97 #ifdef CONFIG_MULTITHREADING
98 		k_sem_init(&data->lock_sem, 1, 1);
99 		k_sem_init(&data->busy_wait_sem, 0, 1);
100 
101 		gpio_init_callback(&data->busy_wait_callback,
102 				   auxdisplay_itron_busy_gpio_change_callback,
103 				   BIT(config->busy_gpio.pin));
104 		rc = gpio_add_callback(config->busy_gpio.port, &data->busy_wait_callback);
105 
106 		if (rc != 0) {
107 			LOG_ERR("Configuration of busy interrupt failed: %d", rc);
108 			return rc;
109 		}
110 #endif
111 	}
112 
113 	/* Configure and set reset GPIO */
114 	if (config->reset_gpio.port) {
115 		rc = gpio_pin_configure_dt(&config->reset_gpio, GPIO_OUTPUT_INACTIVE);
116 		if (rc < 0) {
117 			LOG_ERR("Configuration of text display reset GPIO failed");
118 			return rc;
119 		}
120 	}
121 
122 	data->character_x = 0;
123 	data->character_y = 0;
124 	data->brightness = 0;
125 
126 	/* Reset display to known configuration */
127 	if (config->reset_gpio.port) {
128 		uint8_t wait_loops = 0;
129 
130 		gpio_pin_set_dt(&config->reset_gpio, 1);
131 		k_sleep(AUXDISPLAY_ITRON_RESET_TIME);
132 		gpio_pin_set_dt(&config->reset_gpio, 0);
133 		k_sleep(AUXDISPLAY_ITRON_RESET_WAIT_TIME);
134 
135 		while (auxdisplay_itron_is_busy(dev) == 1) {
136 			/* Display is busy, wait */
137 			k_sleep(AUXDISPLAY_ITRON_BUSY_DELAY_TIME_CHECK);
138 			++wait_loops;
139 
140 			if (wait_loops >= AUXDISPLAY_ITRON_BUSY_WAIT_LOOPS) {
141 				/* Waited long enough for display not to be busy, bailing */
142 				return -EIO;
143 			}
144 		}
145 	} else {
146 		/* Ensure display is powered on so that it can be initialised */
147 		(void)auxdisplay_itron_set_powered(dev, true);
148 		auxdisplay_itron_clear(dev);
149 	}
150 
151 	return 0;
152 }
153 
auxdisplay_itron_set_powered(const struct device * dev,bool enabled)154 static int auxdisplay_itron_set_powered(const struct device *dev, bool enabled)
155 {
156 	int rc = 0;
157 	uint8_t cmd[] = {AUXDISPLAY_ITRON_CMD_USER_SETTING, AUXDISPLAY_ITRON_CMD_ACTION,
158 			 AUXDISPLAY_ITRON_CMD_N, AUXDISPLAY_ITRON_CMD_SCREEN_SAVER, 0};
159 
160 	if (enabled) {
161 		cmd[4] = 1;
162 	}
163 
164 	return send_cmd(dev, cmd, sizeof(cmd), true, true);
165 }
166 
auxdisplay_itron_is_powered(const struct device * dev)167 static bool auxdisplay_itron_is_powered(const struct device *dev)
168 {
169 	struct auxdisplay_itron_data *data = dev->data;
170 	bool is_powered;
171 
172 #ifdef CONFIG_MULTITHREADING
173 	k_sem_take(&data->lock_sem, K_FOREVER);
174 #endif
175 
176 	is_powered = data->powered;
177 
178 #ifdef CONFIG_MULTITHREADING
179 	k_sem_give(&data->lock_sem);
180 #endif
181 
182 	return is_powered;
183 }
184 
auxdisplay_itron_display_on(const struct device * dev)185 static int auxdisplay_itron_display_on(const struct device *dev)
186 {
187 	return auxdisplay_itron_set_powered(dev, true);
188 }
189 
auxdisplay_itron_display_off(const struct device * dev)190 static int auxdisplay_itron_display_off(const struct device *dev)
191 {
192 	return auxdisplay_itron_set_powered(dev, false);
193 }
194 
auxdisplay_itron_cursor_set_enabled(const struct device * dev,bool enabled)195 static int auxdisplay_itron_cursor_set_enabled(const struct device *dev, bool enabled)
196 {
197 	uint8_t cmd[] = {AUXDISPLAY_ITRON_CMD_USER_SETTING, AUXDISPLAY_ITRON_CMD_CURSOR,
198 			 (uint8_t)enabled};
199 
200 	return send_cmd(dev, cmd, sizeof(cmd), false, true);
201 }
202 
auxdisplay_itron_cursor_position_set(const struct device * dev,enum auxdisplay_position type,int16_t x,int16_t y)203 static int auxdisplay_itron_cursor_position_set(const struct device *dev,
204 					      enum auxdisplay_position type,
205 					      int16_t x, int16_t y)
206 {
207 	uint8_t cmd[] = {AUXDISPLAY_ITRON_CMD_USER_SETTING, AUXDISPLAY_ITRON_CMD_CURSOR_SET,
208 			 0, 0, 0, 0};
209 
210 	if (type != AUXDISPLAY_POSITION_ABSOLUTE) {
211 		return -EINVAL;
212 	}
213 
214 	sys_put_le16(x, &cmd[2]);
215 	sys_put_le16(y, &cmd[4]);
216 
217 	return send_cmd(dev, cmd, sizeof(cmd), false, true);
218 }
219 
auxdisplay_itron_capabilities_get(const struct device * dev,struct auxdisplay_capabilities * capabilities)220 static int auxdisplay_itron_capabilities_get(const struct device *dev,
221 					     struct auxdisplay_capabilities *capabilities)
222 {
223 	const struct auxdisplay_itron_config *config = dev->config;
224 
225 	memcpy(capabilities, &config->capabilities, sizeof(struct auxdisplay_capabilities));
226 
227 	return 0;
228 }
229 
auxdisplay_itron_clear(const struct device * dev)230 static int auxdisplay_itron_clear(const struct device *dev)
231 {
232 	uint8_t cmd[] = {AUXDISPLAY_ITRON_CMD_DISPLAY_CLEAR};
233 
234 	return send_cmd(dev, cmd, sizeof(cmd), false, true);
235 }
236 
auxdisplay_itron_brightness_get(const struct device * dev,uint8_t * brightness)237 static int auxdisplay_itron_brightness_get(const struct device *dev, uint8_t *brightness)
238 {
239 	struct auxdisplay_itron_data *data = dev->data;
240 
241 #ifdef CONFIG_MULTITHREADING
242 	k_sem_take(&data->lock_sem, K_FOREVER);
243 #endif
244 
245 	*brightness = data->brightness;
246 
247 #ifdef CONFIG_MULTITHREADING
248 	k_sem_give(&data->lock_sem);
249 #endif
250 
251 	return 0;
252 }
253 
auxdisplay_itron_brightness_set(const struct device * dev,uint8_t brightness)254 static int auxdisplay_itron_brightness_set(const struct device *dev, uint8_t brightness)
255 {
256 	struct auxdisplay_itron_data *data = dev->data;
257 	uint8_t cmd[] = {AUXDISPLAY_ITRON_CMD_USER_SETTING, AUXDISPLAY_ITRON_CMD_BRIGHTNESS,
258 			 brightness};
259 	int rc;
260 
261 	if (brightness < AUXDISPLAY_ITRON_BRIGHTNESS_MIN ||
262 	    brightness > AUXDISPLAY_ITRON_BRIGHTNESS_MAX) {
263 		return -EINVAL;
264 	}
265 
266 #ifdef CONFIG_MULTITHREADING
267 	k_sem_take(&data->lock_sem, K_FOREVER);
268 #endif
269 
270 	rc = send_cmd(dev, cmd, sizeof(cmd), false, false);
271 
272 	if (rc == 0) {
273 		data->brightness = brightness;
274 	}
275 
276 #ifdef CONFIG_MULTITHREADING
277 	k_sem_give(&data->lock_sem);
278 #endif
279 
280 	return rc;
281 }
282 
auxdisplay_itron_is_busy(const struct device * dev)283 static int auxdisplay_itron_is_busy(const struct device *dev)
284 {
285 	const struct auxdisplay_itron_config *config = dev->config;
286 	int rc;
287 
288 	if (config->busy_gpio.port == NULL) {
289 		return -ENOTSUP;
290 	}
291 
292 	rc = gpio_pin_get_dt(&config->busy_gpio);
293 
294 	return rc;
295 }
296 
auxdisplay_itron_is_busy_check(const struct device * dev)297 static int auxdisplay_itron_is_busy_check(const struct device *dev)
298 {
299 	struct auxdisplay_itron_data *data = dev->data;
300 	int rc;
301 
302 #ifdef CONFIG_MULTITHREADING
303 	k_sem_take(&data->lock_sem, K_FOREVER);
304 #endif
305 
306 	rc = auxdisplay_itron_is_busy(dev);
307 
308 #ifdef CONFIG_MULTITHREADING
309 	k_sem_give(&data->lock_sem);
310 #endif
311 
312 	return rc;
313 }
314 
send_cmd(const struct device * dev,const uint8_t * command,uint8_t length,bool pm,bool lock)315 static int send_cmd(const struct device *dev, const uint8_t *command, uint8_t length, bool pm,
316 		    bool lock)
317 {
318 	uint8_t i = 0;
319 	const struct auxdisplay_itron_config *config = dev->config;
320 	const struct device *uart = config->uart;
321 	int rc = 0;
322 #ifdef CONFIG_MULTITHREADING
323 	struct auxdisplay_itron_data *data = dev->data;
324 #endif
325 
326 	if (pm == false && auxdisplay_itron_is_powered(dev) == false) {
327 		/* Display is not powered, only PM commands can be used */
328 		return -ESHUTDOWN;
329 	}
330 
331 #ifdef CONFIG_MULTITHREADING
332 	if (lock) {
333 		k_sem_take(&data->lock_sem, K_FOREVER);
334 	}
335 #endif
336 
337 #ifdef CONFIG_MULTITHREADING
338 	/* Enable interrupt triggering */
339 	rc = gpio_pin_interrupt_configure_dt(&config->busy_gpio, GPIO_INT_EDGE_TO_INACTIVE);
340 
341 	if (rc != 0) {
342 		LOG_ERR("Failed to enable busy interrupt: %d", rc);
343 		goto end;
344 	}
345 #endif
346 
347 	while (i < length) {
348 #ifdef CONFIG_MULTITHREADING
349 		if (auxdisplay_itron_is_busy(dev) == 1) {
350 			if (k_sem_take(&data->busy_wait_sem,
351 				       AUXDISPLAY_ITRON_BUSY_MAX_TIME) != 0) {
352 				rc = -EIO;
353 				goto cleanup;
354 			}
355 		}
356 #else
357 		uint8_t wait_loops = 0;
358 
359 		while (auxdisplay_itron_is_busy(dev) == 1) {
360 			/* Display is busy, wait */
361 			k_sleep(AUXDISPLAY_ITRON_BUSY_DELAY_TIME_CHECK);
362 			++wait_loops;
363 
364 			if (wait_loops >= AUXDISPLAY_ITRON_BUSY_WAIT_LOOPS) {
365 				/* Waited long enough for display not to be busy, bailing */
366 				return -EIO;
367 			}
368 		}
369 #endif
370 
371 		uart_poll_out(uart, command[i]);
372 		++i;
373 	}
374 
375 #ifdef CONFIG_MULTITHREADING
376 cleanup:
377 	(void)gpio_pin_interrupt_configure_dt(&config->busy_gpio, GPIO_INT_DISABLE);
378 #endif
379 
380 end:
381 #ifdef CONFIG_MULTITHREADING
382 	if (lock) {
383 		k_sem_give(&data->lock_sem);
384 	}
385 #endif
386 
387 	return rc;
388 }
389 
auxdisplay_itron_write(const struct device * dev,const uint8_t * data,uint16_t len)390 static int auxdisplay_itron_write(const struct device *dev, const uint8_t *data, uint16_t len)
391 {
392 	uint16_t i = 0;
393 
394 	/* Check all characters are valid */
395 	while (i < len) {
396 		if (data[i] < AUXDISPLAY_ITRON_CHARACTER_MIN &&
397 		    data[i] != AUXDISPLAY_ITRON_CHARACTER_BACK_SPACE &&
398 		    data[i] != AUXDISPLAY_ITRON_CHARACTER_TAB &&
399 		    data[i] != AUXDISPLAY_ITRON_CHARACTER_LINE_FEED &&
400 		    data[i] != AUXDISPLAY_ITRON_CHARACTER_CARRIAGE_RETURN) {
401 			return -EINVAL;
402 		}
403 
404 		++i;
405 	}
406 
407 	return send_cmd(dev, data, len, false, true);
408 }
409 
410 static const struct auxdisplay_driver_api auxdisplay_itron_auxdisplay_api = {
411 	.display_on = auxdisplay_itron_display_on,
412 	.display_off = auxdisplay_itron_display_off,
413 	.cursor_set_enabled = auxdisplay_itron_cursor_set_enabled,
414 	.cursor_position_set = auxdisplay_itron_cursor_position_set,
415 	.capabilities_get = auxdisplay_itron_capabilities_get,
416 	.clear = auxdisplay_itron_clear,
417 	.brightness_get = auxdisplay_itron_brightness_get,
418 	.brightness_set = auxdisplay_itron_brightness_set,
419 	.is_busy = auxdisplay_itron_is_busy_check,
420 	.write = auxdisplay_itron_write,
421 };
422 
423 #define AUXDISPLAY_ITRON_DEVICE(inst)								\
424 	static struct auxdisplay_itron_data auxdisplay_itron_data_##inst;			\
425 	static const struct auxdisplay_itron_config auxdisplay_itron_config_##inst = {		\
426 		.uart = DEVICE_DT_GET(DT_INST_BUS(inst)),					\
427 		.capabilities = {								\
428 			.columns = DT_INST_PROP(inst, columns),					\
429 			.rows = DT_INST_PROP(inst, rows),					\
430 			.mode = AUXDISPLAY_ITRON_MODE_UART,					\
431 			.brightness.minimum = AUXDISPLAY_ITRON_BRIGHTNESS_MIN,			\
432 			.brightness.maximum = AUXDISPLAY_ITRON_BRIGHTNESS_MAX,			\
433 			.backlight.minimum = AUXDISPLAY_LIGHT_NOT_SUPPORTED,			\
434 			.backlight.maximum = AUXDISPLAY_LIGHT_NOT_SUPPORTED,			\
435 		},										\
436 		.busy_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, busy_gpios, {0}),			\
437 		.reset_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, reset_gpios, {0}),			\
438 	};											\
439 	DEVICE_DT_INST_DEFINE(inst,								\
440 			&auxdisplay_itron_init,							\
441 			NULL,									\
442 			&auxdisplay_itron_data_##inst,						\
443 			&auxdisplay_itron_config_##inst,					\
444 			POST_KERNEL,								\
445 			CONFIG_AUXDISPLAY_INIT_PRIORITY,					\
446 			&auxdisplay_itron_auxdisplay_api);
447 
448 DT_INST_FOREACH_STATUS_OKAY(AUXDISPLAY_ITRON_DEVICE)
449