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 uint8_t cmd[] = {AUXDISPLAY_ITRON_CMD_USER_SETTING, AUXDISPLAY_ITRON_CMD_ACTION,
157 AUXDISPLAY_ITRON_CMD_N, AUXDISPLAY_ITRON_CMD_SCREEN_SAVER, 0};
158
159 if (enabled) {
160 cmd[4] = 1;
161 }
162
163 return send_cmd(dev, cmd, sizeof(cmd), true, true);
164 }
165
auxdisplay_itron_is_powered(const struct device * dev)166 static bool auxdisplay_itron_is_powered(const struct device *dev)
167 {
168 struct auxdisplay_itron_data *data = dev->data;
169 bool is_powered;
170
171 #ifdef CONFIG_MULTITHREADING
172 k_sem_take(&data->lock_sem, K_FOREVER);
173 #endif
174
175 is_powered = data->powered;
176
177 #ifdef CONFIG_MULTITHREADING
178 k_sem_give(&data->lock_sem);
179 #endif
180
181 return is_powered;
182 }
183
auxdisplay_itron_display_on(const struct device * dev)184 static int auxdisplay_itron_display_on(const struct device *dev)
185 {
186 return auxdisplay_itron_set_powered(dev, true);
187 }
188
auxdisplay_itron_display_off(const struct device * dev)189 static int auxdisplay_itron_display_off(const struct device *dev)
190 {
191 return auxdisplay_itron_set_powered(dev, false);
192 }
193
auxdisplay_itron_cursor_set_enabled(const struct device * dev,bool enabled)194 static int auxdisplay_itron_cursor_set_enabled(const struct device *dev, bool enabled)
195 {
196 uint8_t cmd[] = {AUXDISPLAY_ITRON_CMD_USER_SETTING, AUXDISPLAY_ITRON_CMD_CURSOR,
197 (uint8_t)enabled};
198
199 return send_cmd(dev, cmd, sizeof(cmd), false, true);
200 }
201
auxdisplay_itron_cursor_position_set(const struct device * dev,enum auxdisplay_position type,int16_t x,int16_t y)202 static int auxdisplay_itron_cursor_position_set(const struct device *dev,
203 enum auxdisplay_position type,
204 int16_t x, int16_t y)
205 {
206 uint8_t cmd[] = {AUXDISPLAY_ITRON_CMD_USER_SETTING, AUXDISPLAY_ITRON_CMD_CURSOR_SET,
207 0, 0, 0, 0};
208
209 if (type != AUXDISPLAY_POSITION_ABSOLUTE) {
210 return -EINVAL;
211 }
212
213 sys_put_le16(x, &cmd[2]);
214 sys_put_le16(y, &cmd[4]);
215
216 return send_cmd(dev, cmd, sizeof(cmd), false, true);
217 }
218
auxdisplay_itron_capabilities_get(const struct device * dev,struct auxdisplay_capabilities * capabilities)219 static int auxdisplay_itron_capabilities_get(const struct device *dev,
220 struct auxdisplay_capabilities *capabilities)
221 {
222 const struct auxdisplay_itron_config *config = dev->config;
223
224 memcpy(capabilities, &config->capabilities, sizeof(struct auxdisplay_capabilities));
225
226 return 0;
227 }
228
auxdisplay_itron_clear(const struct device * dev)229 static int auxdisplay_itron_clear(const struct device *dev)
230 {
231 uint8_t cmd[] = {AUXDISPLAY_ITRON_CMD_DISPLAY_CLEAR};
232
233 return send_cmd(dev, cmd, sizeof(cmd), false, true);
234 }
235
auxdisplay_itron_brightness_get(const struct device * dev,uint8_t * brightness)236 static int auxdisplay_itron_brightness_get(const struct device *dev, uint8_t *brightness)
237 {
238 struct auxdisplay_itron_data *data = dev->data;
239
240 #ifdef CONFIG_MULTITHREADING
241 k_sem_take(&data->lock_sem, K_FOREVER);
242 #endif
243
244 *brightness = data->brightness;
245
246 #ifdef CONFIG_MULTITHREADING
247 k_sem_give(&data->lock_sem);
248 #endif
249
250 return 0;
251 }
252
auxdisplay_itron_brightness_set(const struct device * dev,uint8_t brightness)253 static int auxdisplay_itron_brightness_set(const struct device *dev, uint8_t brightness)
254 {
255 struct auxdisplay_itron_data *data = dev->data;
256 uint8_t cmd[] = {AUXDISPLAY_ITRON_CMD_USER_SETTING, AUXDISPLAY_ITRON_CMD_BRIGHTNESS,
257 brightness};
258 int rc;
259
260 if (brightness < AUXDISPLAY_ITRON_BRIGHTNESS_MIN ||
261 brightness > AUXDISPLAY_ITRON_BRIGHTNESS_MAX) {
262 return -EINVAL;
263 }
264
265 #ifdef CONFIG_MULTITHREADING
266 k_sem_take(&data->lock_sem, K_FOREVER);
267 #endif
268
269 rc = send_cmd(dev, cmd, sizeof(cmd), false, false);
270
271 if (rc == 0) {
272 data->brightness = brightness;
273 }
274
275 #ifdef CONFIG_MULTITHREADING
276 k_sem_give(&data->lock_sem);
277 #endif
278
279 return rc;
280 }
281
auxdisplay_itron_is_busy(const struct device * dev)282 static int auxdisplay_itron_is_busy(const struct device *dev)
283 {
284 const struct auxdisplay_itron_config *config = dev->config;
285 int rc;
286
287 if (config->busy_gpio.port == NULL) {
288 return -ENOTSUP;
289 }
290
291 rc = gpio_pin_get_dt(&config->busy_gpio);
292
293 return rc;
294 }
295
auxdisplay_itron_is_busy_check(const struct device * dev)296 static int auxdisplay_itron_is_busy_check(const struct device *dev)
297 {
298 struct auxdisplay_itron_data *data = dev->data;
299 int rc;
300
301 #ifdef CONFIG_MULTITHREADING
302 k_sem_take(&data->lock_sem, K_FOREVER);
303 #endif
304
305 rc = auxdisplay_itron_is_busy(dev);
306
307 #ifdef CONFIG_MULTITHREADING
308 k_sem_give(&data->lock_sem);
309 #endif
310
311 return rc;
312 }
313
send_cmd(const struct device * dev,const uint8_t * command,uint8_t length,bool pm,bool lock)314 static int send_cmd(const struct device *dev, const uint8_t *command, uint8_t length, bool pm,
315 bool lock)
316 {
317 uint8_t i = 0;
318 const struct auxdisplay_itron_config *config = dev->config;
319 const struct device *uart = config->uart;
320 int rc = 0;
321 #ifdef CONFIG_MULTITHREADING
322 struct auxdisplay_itron_data *data = dev->data;
323 #endif
324
325 if (pm == false && auxdisplay_itron_is_powered(dev) == false) {
326 /* Display is not powered, only PM commands can be used */
327 return -ESHUTDOWN;
328 }
329
330 #ifdef CONFIG_MULTITHREADING
331 if (lock) {
332 k_sem_take(&data->lock_sem, K_FOREVER);
333 }
334 #endif
335
336 #ifdef CONFIG_MULTITHREADING
337 /* Enable interrupt triggering */
338 rc = gpio_pin_interrupt_configure_dt(&config->busy_gpio, GPIO_INT_EDGE_TO_INACTIVE);
339
340 if (rc != 0) {
341 LOG_ERR("Failed to enable busy interrupt: %d", rc);
342 goto end;
343 }
344 #endif
345
346 while (i < length) {
347 #ifdef CONFIG_MULTITHREADING
348 if (auxdisplay_itron_is_busy(dev) == 1) {
349 if (k_sem_take(&data->busy_wait_sem,
350 AUXDISPLAY_ITRON_BUSY_MAX_TIME) != 0) {
351 rc = -EIO;
352 goto cleanup;
353 }
354 }
355 #else
356 uint8_t wait_loops = 0;
357
358 while (auxdisplay_itron_is_busy(dev) == 1) {
359 /* Display is busy, wait */
360 k_sleep(AUXDISPLAY_ITRON_BUSY_DELAY_TIME_CHECK);
361 ++wait_loops;
362
363 if (wait_loops >= AUXDISPLAY_ITRON_BUSY_WAIT_LOOPS) {
364 /* Waited long enough for display not to be busy, bailing */
365 return -EIO;
366 }
367 }
368 #endif
369
370 uart_poll_out(uart, command[i]);
371 ++i;
372 }
373
374 #ifdef CONFIG_MULTITHREADING
375 cleanup:
376 (void)gpio_pin_interrupt_configure_dt(&config->busy_gpio, GPIO_INT_DISABLE);
377 #endif
378
379 end:
380 #ifdef CONFIG_MULTITHREADING
381 if (lock) {
382 k_sem_give(&data->lock_sem);
383 }
384 #endif
385
386 return rc;
387 }
388
auxdisplay_itron_write(const struct device * dev,const uint8_t * data,uint16_t len)389 static int auxdisplay_itron_write(const struct device *dev, const uint8_t *data, uint16_t len)
390 {
391 uint16_t i = 0;
392
393 /* Check all characters are valid */
394 while (i < len) {
395 if (data[i] < AUXDISPLAY_ITRON_CHARACTER_MIN &&
396 data[i] != AUXDISPLAY_ITRON_CHARACTER_BACK_SPACE &&
397 data[i] != AUXDISPLAY_ITRON_CHARACTER_TAB &&
398 data[i] != AUXDISPLAY_ITRON_CHARACTER_LINE_FEED &&
399 data[i] != AUXDISPLAY_ITRON_CHARACTER_CARRIAGE_RETURN) {
400 return -EINVAL;
401 }
402
403 ++i;
404 }
405
406 return send_cmd(dev, data, len, false, true);
407 }
408
409 static DEVICE_API(auxdisplay, auxdisplay_itron_auxdisplay_api) = {
410 .display_on = auxdisplay_itron_display_on,
411 .display_off = auxdisplay_itron_display_off,
412 .cursor_set_enabled = auxdisplay_itron_cursor_set_enabled,
413 .cursor_position_set = auxdisplay_itron_cursor_position_set,
414 .capabilities_get = auxdisplay_itron_capabilities_get,
415 .clear = auxdisplay_itron_clear,
416 .brightness_get = auxdisplay_itron_brightness_get,
417 .brightness_set = auxdisplay_itron_brightness_set,
418 .is_busy = auxdisplay_itron_is_busy_check,
419 .write = auxdisplay_itron_write,
420 };
421
422 #define AUXDISPLAY_ITRON_DEVICE(inst) \
423 static struct auxdisplay_itron_data auxdisplay_itron_data_##inst; \
424 static const struct auxdisplay_itron_config auxdisplay_itron_config_##inst = { \
425 .uart = DEVICE_DT_GET(DT_INST_BUS(inst)), \
426 .capabilities = { \
427 .columns = DT_INST_PROP(inst, columns), \
428 .rows = DT_INST_PROP(inst, rows), \
429 .mode = AUXDISPLAY_ITRON_MODE_UART, \
430 .brightness.minimum = AUXDISPLAY_ITRON_BRIGHTNESS_MIN, \
431 .brightness.maximum = AUXDISPLAY_ITRON_BRIGHTNESS_MAX, \
432 .backlight.minimum = AUXDISPLAY_LIGHT_NOT_SUPPORTED, \
433 .backlight.maximum = AUXDISPLAY_LIGHT_NOT_SUPPORTED, \
434 }, \
435 .busy_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, busy_gpios, {0}), \
436 .reset_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, reset_gpios, {0}), \
437 }; \
438 DEVICE_DT_INST_DEFINE(inst, \
439 &auxdisplay_itron_init, \
440 NULL, \
441 &auxdisplay_itron_data_##inst, \
442 &auxdisplay_itron_config_##inst, \
443 POST_KERNEL, \
444 CONFIG_AUXDISPLAY_INIT_PRIORITY, \
445 &auxdisplay_itron_auxdisplay_api);
446
447 DT_INST_FOREACH_STATUS_OKAY(AUXDISPLAY_ITRON_DEVICE)
448