1 /*
2  * Copyright (c) 2023 Jan Henke <Jan.Henke@taujhe.de>
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT sparkfun_serlcd
8 
9 #include <stdlib.h>
10 #include <string.h>
11 #include <zephyr/device.h>
12 #include <zephyr/devicetree.h>
13 #include <zephyr/drivers/i2c.h>
14 #include <zephyr/drivers/auxdisplay.h>
15 #include <zephyr/kernel.h>
16 #include <zephyr/sys/util.h>
17 #include <zephyr/logging/log.h>
18 
19 LOG_MODULE_REGISTER(auxdisplay_serlcd, CONFIG_AUXDISPLAY_LOG_LEVEL);
20 
21 /*
22  * | in ASCII, used to begin a display command
23  */
24 #define SERLCD_BEGIN_COMMAND 0x7C
25 
26 /*
27  * special command for the underlying display controller
28  */
29 #define SERLCD_BEGIN_SPECIAL_COMMAND 0xFE
30 
31 /*
32  * maximum amount of custom chars the display supports
33  */
34 #define SERLCD_CUSTOM_CHAR_MAX_COUNT 8
35 
36 /*
37  * height of a custom char in bits
38  */
39 #define SERLCD_CUSTOM_CHAR_HEIGHT 8
40 
41 /*
42  * width of a custom char in bits
43  */
44 #define SERLCD_CUSTOM_CHAR_WIDTH 5
45 
46 /*
47  * char code for the first custom char
48  */
49 #define SERLCD_CUSTOM_CHAR_INDEX_BASE 0x08
50 
51 /*
52  * bitmask for custom character detection
53  */
54 #define SERLCD_CUSTOM_CHAR_BITMASK 0xf8
55 
56 /*
57  * bit to set in the display control special command to indicate the display should be powered on
58  */
59 #define SERLCD_DISPLAY_CONTROL_POWER_BIT BIT(2)
60 
61 /*
62  * bit to set in the display control special command to indicate the cursor should be displayed
63  */
64 #define SERLCD_DISPLAY_CONTROL_CURSOR_BIT BIT(1)
65 
66 /*
67  * bit to set in the display control special command to indicate the cursor should be blinking
68  */
69 #define SERLCD_DISPLAY_CONTROL_BLINKING_BIT BIT(0)
70 
71 struct auxdisplay_serlcd_data {
72 	bool power;
73 	bool cursor;
74 	bool blinking;
75 	uint16_t cursor_x;
76 	uint16_t cursor_y;
77 };
78 
79 struct auxdisplay_serlcd_config {
80 	struct auxdisplay_capabilities capabilities;
81 	struct i2c_dt_spec bus;
82 	uint16_t command_delay_ms;
83 	uint16_t special_command_delay_ms;
84 };
85 
86 enum auxdisplay_serlcd_command {
87 	SERLCD_COMMAND_SET_CUSTOM_CHAR = 0x1B,
88 	SERLCD_COMMAND_WRITE_CUSTOM_CHAR = 0x23,
89 	SERLCD_COMMAND_CLEAR = 0x2D,
90 };
91 
92 enum auxdisplay_serlcd_special_command {
93 	SERLCD_SPECIAL_RETURN_HOME = 0x02,
94 	SERLCD_SPECIAL_DISPLAY_CONTROL = 0x08,
95 	SERLCD_SPECIAL_SET_DD_RAM_ADDRESS = 0x80,
96 };
97 
auxdisplay_serlcd_send_command(const struct device * dev,const enum auxdisplay_serlcd_command command)98 static int auxdisplay_serlcd_send_command(const struct device *dev,
99 					  const enum auxdisplay_serlcd_command command)
100 {
101 	const struct auxdisplay_serlcd_config *config = dev->config;
102 	const uint8_t buffer[2] = {SERLCD_BEGIN_COMMAND, command};
103 
104 	int rc = i2c_write_dt(&config->bus, buffer, sizeof(buffer));
105 
106 	k_sleep(K_MSEC(config->command_delay_ms));
107 	return rc;
108 }
109 
110 static int
auxdisplay_serlcd_send_special_command(const struct device * dev,const enum auxdisplay_serlcd_special_command command)111 auxdisplay_serlcd_send_special_command(const struct device *dev,
112 				       const enum auxdisplay_serlcd_special_command command)
113 {
114 	const struct auxdisplay_serlcd_config *config = dev->config;
115 	const uint8_t buffer[2] = {SERLCD_BEGIN_SPECIAL_COMMAND, command};
116 
117 	int rc = i2c_write_dt(&config->bus, buffer, sizeof(buffer));
118 
119 	k_sleep(K_MSEC(config->special_command_delay_ms));
120 	return rc;
121 }
122 
auxdisplay_serlcd_send_display_state(const struct device * dev,const struct auxdisplay_serlcd_data * data)123 static int auxdisplay_serlcd_send_display_state(const struct device *dev,
124 						const struct auxdisplay_serlcd_data *data)
125 {
126 	uint8_t command = SERLCD_SPECIAL_DISPLAY_CONTROL;
127 
128 	if (data->power) {
129 		command |= SERLCD_DISPLAY_CONTROL_POWER_BIT;
130 	}
131 	if (data->cursor) {
132 		command |= SERLCD_DISPLAY_CONTROL_CURSOR_BIT;
133 	}
134 	if (data->blinking) {
135 		command |= SERLCD_DISPLAY_CONTROL_BLINKING_BIT;
136 	}
137 
138 	return auxdisplay_serlcd_send_special_command(dev, command);
139 }
140 
auxdisplay_serlcd_display_on(const struct device * dev)141 static int auxdisplay_serlcd_display_on(const struct device *dev)
142 {
143 	struct auxdisplay_serlcd_data *data = dev->data;
144 
145 	data->power = true;
146 
147 	return auxdisplay_serlcd_send_display_state(dev, data);
148 }
149 
auxdisplay_serlcd_display_off(const struct device * dev)150 static int auxdisplay_serlcd_display_off(const struct device *dev)
151 {
152 	struct auxdisplay_serlcd_data *data = dev->data;
153 
154 	data->power = false;
155 
156 	return auxdisplay_serlcd_send_display_state(dev, data);
157 }
158 
auxdisplay_serlcd_cursor_set_enabled(const struct device * dev,bool enable)159 static int auxdisplay_serlcd_cursor_set_enabled(const struct device *dev, bool enable)
160 {
161 	struct auxdisplay_serlcd_data *data = dev->data;
162 
163 	data->cursor = enable;
164 
165 	return auxdisplay_serlcd_send_display_state(dev, data);
166 }
167 
auxdisplay_serlcd_position_blinking_set_enabled(const struct device * dev,bool enable)168 static int auxdisplay_serlcd_position_blinking_set_enabled(const struct device *dev, bool enable)
169 {
170 	struct auxdisplay_serlcd_data *data = dev->data;
171 
172 	data->blinking = enable;
173 
174 	return auxdisplay_serlcd_send_display_state(dev, data);
175 }
176 
auxdisplay_serlcd_cursor_position_set(const struct device * dev,enum auxdisplay_position type,int16_t x,int16_t y)177 static int auxdisplay_serlcd_cursor_position_set(const struct device *dev,
178 						 enum auxdisplay_position type, int16_t x,
179 						 int16_t y)
180 {
181 	static const uint8_t row_offsets[] = {0x00, 0x40, 0x14, 0x54};
182 
183 	const struct auxdisplay_serlcd_config *config = dev->config;
184 	const struct auxdisplay_capabilities capabilities = config->capabilities;
185 	const uint16_t columns = capabilities.columns;
186 	const uint16_t rows = capabilities.rows;
187 	struct auxdisplay_serlcd_data *data = dev->data;
188 
189 	if (type == AUXDISPLAY_POSITION_ABSOLUTE) {
190 		/*
191 		 * shortcut for (0,0) position
192 		 */
193 		if (x == 0 && y == 0) {
194 			data->cursor_x = x;
195 			data->cursor_y = y;
196 			return auxdisplay_serlcd_send_special_command(dev,
197 								      SERLCD_SPECIAL_RETURN_HOME);
198 		}
199 
200 		/*
201 		 * bounds checking
202 		 */
203 		if (x < 0 || x >= columns) {
204 			return -EINVAL;
205 		}
206 		if (y < 0 || y >= rows) {
207 			return -EINVAL;
208 		}
209 
210 		data->cursor_x = x;
211 		data->cursor_y = y;
212 
213 		const uint8_t cursor_address = x + row_offsets[y];
214 
215 		return auxdisplay_serlcd_send_special_command(
216 			dev, SERLCD_SPECIAL_SET_DD_RAM_ADDRESS | cursor_address);
217 
218 	} else if (type == AUXDISPLAY_POSITION_RELATIVE) {
219 		/*
220 		 * clip relative move to display dimensions
221 		 */
222 		const int new_x = (data->cursor_x + x) % columns;
223 		const int new_y = (data->cursor_y + y + x / columns) % rows;
224 		const uint16_t column = new_x < 0 ? new_x + columns : new_x;
225 		const uint16_t row = new_y < 0 ? new_y + rows : new_y;
226 
227 		data->cursor_x = column;
228 		data->cursor_y = row;
229 
230 		const uint8_t cursor_address = column + row_offsets[row];
231 
232 		return auxdisplay_serlcd_send_special_command(
233 			dev, SERLCD_SPECIAL_SET_DD_RAM_ADDRESS | cursor_address);
234 	}
235 
236 	/*
237 	 * other types of movement are not implemented/supported
238 	 */
239 	return -ENOSYS;
240 }
241 
auxdisplay_serlcd_cursor_position_get(const struct device * dev,int16_t * x,int16_t * y)242 static int auxdisplay_serlcd_cursor_position_get(const struct device *dev, int16_t *x, int16_t *y)
243 {
244 	const struct auxdisplay_serlcd_data *data = dev->data;
245 
246 	*x = (int16_t)data->cursor_x;
247 	*y = (int16_t)data->cursor_y;
248 
249 	return 0;
250 }
251 
auxdisplay_serlcd_capabilities_get(const struct device * dev,struct auxdisplay_capabilities * capabilities)252 static int auxdisplay_serlcd_capabilities_get(const struct device *dev,
253 					      struct auxdisplay_capabilities *capabilities)
254 {
255 	const struct auxdisplay_serlcd_config *config = dev->config;
256 
257 	memcpy(capabilities, &config->capabilities, sizeof(struct auxdisplay_capabilities));
258 
259 	return 0;
260 }
261 
auxdisplay_serlcd_clear(const struct device * dev)262 static int auxdisplay_serlcd_clear(const struct device *dev)
263 {
264 	const struct auxdisplay_serlcd_config *config = dev->config;
265 
266 	int rc = auxdisplay_serlcd_send_command(dev, SERLCD_COMMAND_CLEAR);
267 
268 	k_sleep(K_MSEC(config->command_delay_ms));
269 	return rc;
270 }
271 
auxdisplay_serlcd_custom_character_set(const struct device * dev,struct auxdisplay_character * character)272 static int auxdisplay_serlcd_custom_character_set(const struct device *dev,
273 						  struct auxdisplay_character *character)
274 {
275 	const struct auxdisplay_serlcd_config *config = dev->config;
276 	int rc;
277 
278 	/*
279 	 * only indexes 0..7 are supported
280 	 */
281 	const uint8_t char_index = character->index;
282 
283 	if (char_index > (SERLCD_CUSTOM_CHAR_MAX_COUNT - 1)) {
284 		return -EINVAL;
285 	}
286 
287 	/*
288 	 * custom characters are accessible via char codes 0x08..0x0f
289 	 */
290 	character->character_code = SERLCD_CUSTOM_CHAR_INDEX_BASE | char_index;
291 
292 	rc = auxdisplay_serlcd_send_command(dev, SERLCD_COMMAND_SET_CUSTOM_CHAR + char_index);
293 	if (!rc) {
294 		return rc;
295 	}
296 
297 	/*
298 	 * the display expects the custom character as 8 lines of 5 bit each, shades are not
299 	 * supported
300 	 */
301 	for (int l = 0; l < SERLCD_CUSTOM_CHAR_HEIGHT; ++l) {
302 		uint8_t buffer = 0;
303 
304 		for (int i = 0; i < SERLCD_CUSTOM_CHAR_WIDTH; ++i) {
305 			if (character->data[(l * 5) + i]) {
306 				buffer |= BIT(4 - i);
307 			}
308 		}
309 		rc = i2c_write_dt(&config->bus, &buffer, sizeof(buffer));
310 		if (!rc) {
311 			return rc;
312 		}
313 	}
314 
315 	return rc;
316 }
317 
auxdisplay_serlcd_advance_current_position(const struct device * dev)318 static void auxdisplay_serlcd_advance_current_position(const struct device *dev)
319 {
320 	const struct auxdisplay_serlcd_config *config = dev->config;
321 	struct auxdisplay_serlcd_data *data = dev->data;
322 
323 	++(data->cursor_x);
324 	if (data->cursor_x >= config->capabilities.columns) {
325 		data->cursor_x = 0;
326 		++(data->cursor_y);
327 	}
328 	if (data->cursor_y >= config->capabilities.rows) {
329 		data->cursor_y = 0;
330 	}
331 }
332 
auxdisplay_serlcd_write(const struct device * dev,const uint8_t * text,uint16_t len)333 static int auxdisplay_serlcd_write(const struct device *dev, const uint8_t *text, uint16_t len)
334 {
335 	const struct auxdisplay_serlcd_config *config = dev->config;
336 
337 	int rc = 0;
338 
339 	/*
340 	 * the display wraps around by itself, just write the text and update the position data
341 	 */
342 	for (int i = 0; i < len; ++i) {
343 		uint8_t character = text[i];
344 
345 		/*
346 		 * customer characters require a special command, so check for custom char
347 		 */
348 		if ((character & SERLCD_CUSTOM_CHAR_BITMASK) == SERLCD_CUSTOM_CHAR_INDEX_BASE) {
349 			const uint8_t command = SERLCD_COMMAND_WRITE_CUSTOM_CHAR +
350 						(character & ~SERLCD_CUSTOM_CHAR_BITMASK);
351 
352 			rc = auxdisplay_serlcd_send_command(dev, command);
353 			if (!rc) {
354 				return rc;
355 			}
356 			auxdisplay_serlcd_advance_current_position(dev);
357 		} else if (character == SERLCD_BEGIN_COMMAND ||
358 			   character == SERLCD_BEGIN_SPECIAL_COMMAND) {
359 			/*
360 			 * skip these characters in text, as they have a special meaning, if
361 			 * required a custom character can be used as replacement
362 			 */
363 			continue;
364 		} else {
365 			rc = i2c_write_dt(&config->bus, text, len);
366 			if (!rc) {
367 				return rc;
368 			}
369 			auxdisplay_serlcd_advance_current_position(dev);
370 		}
371 	}
372 
373 	return rc;
374 }
375 
auxdisplay_serlcd_init(const struct device * dev)376 static int auxdisplay_serlcd_init(const struct device *dev)
377 {
378 	const struct auxdisplay_serlcd_config *config = dev->config;
379 	struct auxdisplay_serlcd_data *data = dev->data;
380 
381 	/*
382 	 * Initialize our data structure
383 	 */
384 	data->power = true;
385 
386 	if (!device_is_ready(config->bus.bus)) {
387 		return -ENODEV;
388 	}
389 
390 	auxdisplay_serlcd_clear(dev);
391 
392 	return 0;
393 }
394 
395 static DEVICE_API(auxdisplay, auxdisplay_serlcd_auxdisplay_api) = {
396 	.display_on = auxdisplay_serlcd_display_on,
397 	.display_off = auxdisplay_serlcd_display_off,
398 	.cursor_set_enabled = auxdisplay_serlcd_cursor_set_enabled,
399 	.position_blinking_set_enabled = auxdisplay_serlcd_position_blinking_set_enabled,
400 	.cursor_position_set = auxdisplay_serlcd_cursor_position_set,
401 	.cursor_position_get = auxdisplay_serlcd_cursor_position_get,
402 	.capabilities_get = auxdisplay_serlcd_capabilities_get,
403 	.clear = auxdisplay_serlcd_clear,
404 	.custom_character_set = auxdisplay_serlcd_custom_character_set,
405 	.write = auxdisplay_serlcd_write,
406 };
407 
408 #define AUXDISPLAY_SERLCD_INST(inst)                                                               \
409 	static const struct auxdisplay_serlcd_config auxdisplay_serlcd_config_##inst = {           \
410 		.capabilities = {                                                                  \
411 				.columns = DT_INST_PROP(inst, columns),                            \
412 				.rows = DT_INST_PROP(inst, rows),                                  \
413 				.mode = 0,                                                         \
414 				.brightness.minimum = AUXDISPLAY_LIGHT_NOT_SUPPORTED,              \
415 				.brightness.maximum = AUXDISPLAY_LIGHT_NOT_SUPPORTED,              \
416 				.backlight.minimum = AUXDISPLAY_LIGHT_NOT_SUPPORTED,               \
417 				.backlight.maximum = AUXDISPLAY_LIGHT_NOT_SUPPORTED,               \
418 				.custom_characters = SERLCD_CUSTOM_CHAR_MAX_COUNT,                 \
419 				.custom_character_width = SERLCD_CUSTOM_CHAR_WIDTH,                \
420 				.custom_character_height = SERLCD_CUSTOM_CHAR_HEIGHT,              \
421 			},                                                                         \
422 		.bus = I2C_DT_SPEC_INST_GET(inst),                                                 \
423 		.command_delay_ms = DT_INST_PROP(inst, command_delay_ms),                          \
424 		.special_command_delay_ms = DT_INST_PROP(inst, special_command_delay_ms),          \
425 	};                                                                                         \
426                                                                                                    \
427 	static struct auxdisplay_serlcd_data auxdisplay_serlcd_data_##inst;                        \
428                                                                                                    \
429 	DEVICE_DT_INST_DEFINE(inst, &auxdisplay_serlcd_init, NULL, &auxdisplay_serlcd_data_##inst, \
430 			      &auxdisplay_serlcd_config_##inst, POST_KERNEL,                       \
431 			      CONFIG_AUXDISPLAY_INIT_PRIORITY, &auxdisplay_serlcd_auxdisplay_api);
432 
433 DT_INST_FOREACH_STATUS_OKAY(AUXDISPLAY_SERLCD_INST)
434