1 /*
2  * Copyright (c) 2015 Intel Corporation
3  * Copyright (c) 2022 Nordic Semiconductor ASA
4  * Copyright (c) 2022-2023 Jamie McCrae
5  *
6  * SPDX-License-Identifier: Apache-2.0
7  */
8 
9 #define DT_DRV_COMPAT jhd_jhd1313
10 
11 #include <string.h>
12 #include <zephyr/device.h>
13 #include <zephyr/devicetree.h>
14 #include <zephyr/drivers/i2c.h>
15 #include <zephyr/drivers/auxdisplay.h>
16 #include <zephyr/kernel.h>
17 #include <zephyr/sys/util.h>
18 #include <zephyr/logging/log.h>
19 
20 LOG_MODULE_REGISTER(auxdisplay_jhd1313, CONFIG_AUXDISPLAY_LOG_LEVEL);
21 
22 #define JHD1313_BACKLIGHT_ADDR		(0x62)
23 
24 /* Defines for the JHD1313_CMD_CURSOR_SHIFT */
25 #define JHD1313_CS_DISPLAY_SHIFT	(1 << 3)
26 #define JHD1313_CS_RIGHT_SHIFT		(1 << 2)
27 
28 /* Defines for the JHD1313_CMD_INPUT_SET to change text direction */
29 #define JHD1313_IS_INCREMENT		(1 << 1)
30 #define JHD1313_IS_DECREMENT		(0 << 1)
31 #define JHD1313_IS_SHIFT		(1 << 0)
32 
33 /* Defines for the JHD1313_CMD_FUNCTION_SET */
34 #define JHD1313_FS_8BIT_MODE		(1 << 4)
35 #define JHD1313_FS_ROWS_2		(1 << 3)
36 #define JHD1313_FS_ROWS_1		(0 << 3)
37 #define JHD1313_FS_DOT_SIZE_BIG		(1 << 2)
38 #define JHD1313_FS_DOT_SIZE_LITTLE	(0 << 2)
39 
40 /* LCD Display Commands */
41 #define JHD1313_CMD_SCREEN_CLEAR	(1 << 0)
42 #define JHD1313_CMD_CURSOR_RETURN	(1 << 1)
43 #define JHD1313_CMD_INPUT_SET		(1 << 2)
44 #define JHD1313_CMD_DISPLAY_SWITCH	(1 << 3)
45 #define JHD1313_CMD_CURSOR_SHIFT	(1 << 4)
46 #define JHD1313_CMD_FUNCTION_SET	(1 << 5)
47 #define JHD1313_CMD_SET_CGRAM_ADDR	(1 << 6)
48 #define JHD1313_CMD_SET_DDRAM_ADDR	(1 << 7)
49 
50 #define JHD1313_DS_DISPLAY_ON		(1 << 2)
51 #define JHD1313_DS_CURSOR_ON		(1 << 1)
52 #define JHD1313_DS_BLINK_ON		(1 << 0)
53 
54 #define JHD1313_LED_REG_R		0x04
55 #define JHD1313_LED_REG_G		0x03
56 #define JHD1313_LED_REG_B		0x02
57 
58 #define JHD1313_LINE_FIRST		0x80
59 #define JHD1313_LINE_SECOND		0xC0
60 
61 #define CLEAR_DELAY_MS			20
62 #define UPDATE_DELAY_MS			5
63 
64 struct auxdisplay_jhd1313_data {
65 	uint8_t input_set;
66 	bool power;
67 	bool cursor;
68 	bool blinking;
69 	uint8_t function;
70 	uint8_t backlight;
71 };
72 
73 struct auxdisplay_jhd1313_config {
74 	struct auxdisplay_capabilities capabilities;
75 	struct i2c_dt_spec bus;
76 };
77 
78 static const uint8_t colour_define[][4] = {
79 	{ 0,   0,   0   },      /* Off */
80 	{ 255, 255, 255 },	/* White */
81 	{ 255, 0,   0   },      /* Red */
82 	{ 0,   255, 0   },      /* Green */
83 	{ 0,   0,   255 },      /* Blue */
84 };
85 
auxdisplay_jhd1313_reg_set(const struct device * i2c,uint8_t addr,uint8_t data)86 static void auxdisplay_jhd1313_reg_set(const struct device *i2c, uint8_t addr, uint8_t data)
87 {
88 	uint8_t command[2] = { addr, data };
89 
90 	i2c_write(i2c, command, sizeof(command), JHD1313_BACKLIGHT_ADDR);
91 }
92 
auxdisplay_jhd1313_print(const struct device * dev,const uint8_t * data,uint16_t size)93 static int auxdisplay_jhd1313_print(const struct device *dev, const uint8_t *data, uint16_t size)
94 {
95 	const struct auxdisplay_jhd1313_config *config = dev->config;
96 	uint8_t buf[] = { JHD1313_CMD_SET_CGRAM_ADDR, 0 };
97 	int rc = 0;
98 	int16_t i;
99 
100 	for (i = 0; i < size; i++) {
101 		buf[1] = data[i];
102 		rc = i2c_write_dt(&config->bus, buf, sizeof(buf));
103 	}
104 
105 	return rc;
106 }
107 
auxdisplay_jhd1313_cursor_position_set(const struct device * dev,enum auxdisplay_position type,int16_t x,int16_t y)108 static int auxdisplay_jhd1313_cursor_position_set(const struct device *dev,
109 						  enum auxdisplay_position type, int16_t x,
110 						  int16_t y)
111 {
112 	const struct auxdisplay_jhd1313_config *config = dev->config;
113 	unsigned char data[2];
114 
115 	if (type != AUXDISPLAY_POSITION_ABSOLUTE) {
116 		return -EINVAL;
117 	}
118 
119 	if (y == 0U) {
120 		x |= JHD1313_LINE_FIRST;
121 	} else {
122 		x |= JHD1313_LINE_SECOND;
123 	}
124 
125 	data[0] = JHD1313_CMD_SET_DDRAM_ADDR;
126 	data[1] = x;
127 
128 	return i2c_write_dt(&config->bus, data, 2);
129 }
130 
auxdisplay_jhd1313_clear(const struct device * dev)131 static int auxdisplay_jhd1313_clear(const struct device *dev)
132 {
133 	int rc;
134 	const struct auxdisplay_jhd1313_config *config = dev->config;
135 	uint8_t clear[] = { 0, JHD1313_CMD_SCREEN_CLEAR };
136 
137 	rc = i2c_write_dt(&config->bus, clear, sizeof(clear));
138 	LOG_DBG("Clear, delay 20 ms");
139 
140 	k_sleep(K_MSEC(CLEAR_DELAY_MS));
141 
142 	return rc;
143 }
144 
auxdisplay_jhd1313_update_display_state(const struct auxdisplay_jhd1313_config * config,struct auxdisplay_jhd1313_data * data)145 static int auxdisplay_jhd1313_update_display_state(
146 				const struct auxdisplay_jhd1313_config *config,
147 				struct auxdisplay_jhd1313_data *data)
148 {
149 	int rc;
150 	uint8_t buf[] = { 0, JHD1313_CMD_DISPLAY_SWITCH };
151 
152 	if (data->power) {
153 		buf[1] |= JHD1313_DS_DISPLAY_ON;
154 	}
155 
156 	if (data->cursor) {
157 		buf[1] |= JHD1313_DS_CURSOR_ON;
158 	}
159 
160 	if (data->blinking) {
161 		buf[1] |= JHD1313_DS_BLINK_ON;
162 	}
163 
164 	rc = i2c_write_dt(&config->bus, buf, sizeof(buf));
165 
166 	LOG_DBG("Set display_state options, delay 5 ms");
167 	k_sleep(K_MSEC(UPDATE_DELAY_MS));
168 
169 	return rc;
170 }
171 
auxdisplay_jhd1313_cursor_set_enabled(const struct device * dev,bool enabled)172 static int auxdisplay_jhd1313_cursor_set_enabled(const struct device *dev, bool enabled)
173 {
174 	const struct auxdisplay_jhd1313_config *config = dev->config;
175 	struct auxdisplay_jhd1313_data *data = dev->data;
176 
177 	data->cursor = enabled;
178 	return auxdisplay_jhd1313_update_display_state(config, data);
179 }
180 
auxdisplay_jhd1313_position_blinking_set_enabled(const struct device * dev,bool enabled)181 static int auxdisplay_jhd1313_position_blinking_set_enabled(const struct device *dev, bool enabled)
182 {
183 	const struct auxdisplay_jhd1313_config *config = dev->config;
184 	struct auxdisplay_jhd1313_data *data = dev->data;
185 
186 	data->blinking = enabled;
187 	return auxdisplay_jhd1313_update_display_state(config, data);
188 }
189 
auxdisplay_jhd1313_input_state_set(const struct device * dev,uint8_t opt)190 static void auxdisplay_jhd1313_input_state_set(const struct device *dev, uint8_t opt)
191 {
192 	const struct auxdisplay_jhd1313_config *config = dev->config;
193 	struct auxdisplay_jhd1313_data *data = dev->data;
194 	uint8_t buf[] = { 0, 0 };
195 
196 	data->input_set = opt;
197 	buf[1] = (opt | JHD1313_CMD_INPUT_SET);
198 
199 	i2c_write_dt(&config->bus, buf, sizeof(buf));
200 	LOG_DBG("Set the input_set, no delay");
201 }
202 
auxdisplay_jhd1313_backlight_set(const struct device * dev,uint8_t colour)203 static int auxdisplay_jhd1313_backlight_set(const struct device *dev, uint8_t colour)
204 {
205 	const struct auxdisplay_jhd1313_config *config = dev->config;
206 	struct auxdisplay_jhd1313_data *data = dev->data;
207 
208 	if (colour > ARRAY_SIZE(colour_define)) {
209 		LOG_WRN("Selected colour is too high a value");
210 		return -EINVAL;
211 	}
212 
213 	data->backlight = colour;
214 
215 	auxdisplay_jhd1313_reg_set(config->bus.bus, JHD1313_LED_REG_R, colour_define[colour][0]);
216 	auxdisplay_jhd1313_reg_set(config->bus.bus, JHD1313_LED_REG_G, colour_define[colour][1]);
217 	auxdisplay_jhd1313_reg_set(config->bus.bus, JHD1313_LED_REG_B, colour_define[colour][2]);
218 
219 	return 0;
220 }
221 
auxdisplay_jhd1313_backlight_get(const struct device * dev,uint8_t * backlight)222 static int auxdisplay_jhd1313_backlight_get(const struct device *dev, uint8_t *backlight)
223 {
224 	struct auxdisplay_jhd1313_data *data = dev->data;
225 
226 	*backlight = data->backlight;
227 
228 	return 0;
229 }
230 
auxdisplay_jhd1313_function_set(const struct device * dev,uint8_t opt)231 static void auxdisplay_jhd1313_function_set(const struct device *dev, uint8_t opt)
232 {
233 	const struct auxdisplay_jhd1313_config *config = dev->config;
234 	struct auxdisplay_jhd1313_data *data = dev->data;
235 	uint8_t buf[] = { 0, 0 };
236 
237 	data->function = opt;
238 	buf[1] = (opt | JHD1313_CMD_FUNCTION_SET);
239 
240 	i2c_write_dt(&config->bus, buf, sizeof(buf));
241 
242 	LOG_DBG("Set function options, delay 5 ms");
243 	k_sleep(K_MSEC(5));
244 }
245 
auxdisplay_jhd1313_initialize(const struct device * dev)246 static int auxdisplay_jhd1313_initialize(const struct device *dev)
247 {
248 	const struct auxdisplay_jhd1313_config *config = dev->config;
249 	struct auxdisplay_jhd1313_data *data = dev->data;
250 	uint8_t cmd;
251 
252 	LOG_DBG("Initialize called");
253 
254 	if (!device_is_ready(config->bus.bus)) {
255 		return -ENODEV;
256 	}
257 
258 	/*
259 	 * Initialization sequence from the data sheet:
260 	 * 1 - Power on
261 	 *   - Wait for more than 30 ms AFTER VDD rises to 4.5v
262 	 * 2 - Send FUNCTION set
263 	 *   - Wait for 39 us
264 	 * 3 - Send DISPLAY Control
265 	 *   - wait for 39 us
266 	 * 4 - send DISPLAY Clear
267 	 *   - wait for 1.5 ms
268 	 * 5 - send ENTRY Mode
269 	 * 6 - Initialization is done
270 	 */
271 
272 	/*
273 	 * We're here! Let's just make sure we've had enough time for the
274 	 * VDD to power on, so pause a little here, 30 ms min, so we go 50
275 	 */
276 	LOG_DBG("Delay 50 ms while the VDD powers on");
277 	k_sleep(K_MSEC(50));
278 
279 	/* Configure everything for the display function first */
280 	cmd = JHD1313_CMD_FUNCTION_SET | JHD1313_FS_ROWS_2;
281 	auxdisplay_jhd1313_function_set(dev, cmd);
282 
283 	/* Turn the display on - by default no cursor and no blinking */
284 	auxdisplay_jhd1313_update_display_state(config, data);
285 
286 	/* Clear the screen */
287 	auxdisplay_jhd1313_clear(dev);
288 
289 	/*
290 	 * Initialize to the default text direction for romance languages
291 	 * (increment, no shift)
292 	 */
293 	cmd = JHD1313_IS_INCREMENT;
294 
295 	auxdisplay_jhd1313_input_state_set(dev, cmd);
296 
297 	/* Now power on the background RGB control */
298 	LOG_INF("Configuring the RGB background");
299 	auxdisplay_jhd1313_reg_set(config->bus.bus, 0x00, 0x00);
300 	auxdisplay_jhd1313_reg_set(config->bus.bus, 0x01, 0x05);
301 	auxdisplay_jhd1313_reg_set(config->bus.bus, 0x08, 0xAA);
302 
303 	/* Now set the background colour to black */
304 	LOG_DBG("Background set to off");
305 	auxdisplay_jhd1313_backlight_set(dev, 0);
306 
307 	return 0;
308 }
309 
auxdisplay_jhd1313_display_on(const struct device * dev)310 static int auxdisplay_jhd1313_display_on(const struct device *dev)
311 {
312 	const struct auxdisplay_jhd1313_config *config = dev->config;
313 	struct auxdisplay_jhd1313_data *data = dev->data;
314 
315 	data->power = true;
316 	return auxdisplay_jhd1313_update_display_state(config, data);
317 }
318 
auxdisplay_jhd1313_display_off(const struct device * dev)319 static int auxdisplay_jhd1313_display_off(const struct device *dev)
320 {
321 	const struct auxdisplay_jhd1313_config *config = dev->config;
322 	struct auxdisplay_jhd1313_data *data = dev->data;
323 
324 	data->power = false;
325 	return auxdisplay_jhd1313_update_display_state(config, data);
326 }
327 
auxdisplay_jhd1313_capabilities_get(const struct device * dev,struct auxdisplay_capabilities * capabilities)328 static int auxdisplay_jhd1313_capabilities_get(const struct device *dev,
329 					       struct auxdisplay_capabilities *capabilities)
330 {
331 	const struct auxdisplay_jhd1313_config *config = dev->config;
332 
333 	memcpy(capabilities, &config->capabilities, sizeof(struct auxdisplay_capabilities));
334 
335 	return 0;
336 }
337 
338 static const struct auxdisplay_driver_api auxdisplay_jhd1313_auxdisplay_api = {
339 	.display_on = auxdisplay_jhd1313_display_on,
340 	.display_off = auxdisplay_jhd1313_display_off,
341 	.cursor_set_enabled = auxdisplay_jhd1313_cursor_set_enabled,
342 	.position_blinking_set_enabled = auxdisplay_jhd1313_position_blinking_set_enabled,
343 	.cursor_position_set = auxdisplay_jhd1313_cursor_position_set,
344 	.capabilities_get = auxdisplay_jhd1313_capabilities_get,
345 	.clear = auxdisplay_jhd1313_clear,
346 	.backlight_get = auxdisplay_jhd1313_backlight_get,
347 	.backlight_set = auxdisplay_jhd1313_backlight_set,
348 	.write = auxdisplay_jhd1313_print,
349 };
350 
351 #define AUXDISPLAY_JHD1313_DEVICE(inst)								\
352 	static const struct auxdisplay_jhd1313_config auxdisplay_jhd1313_config_##inst = {	\
353 		.capabilities = {								\
354 			.columns = 16,								\
355 			.rows = 2,								\
356 			.mode = 0,								\
357 			.brightness.minimum = AUXDISPLAY_LIGHT_NOT_SUPPORTED,			\
358 			.brightness.maximum = AUXDISPLAY_LIGHT_NOT_SUPPORTED,			\
359 			.backlight.minimum = 0,							\
360 			.backlight.maximum = ARRAY_SIZE(colour_define),				\
361 			.custom_characters = 0,							\
362 		},										\
363 		.bus = I2C_DT_SPEC_INST_GET(inst),						\
364 	};											\
365 	static struct auxdisplay_jhd1313_data auxdisplay_jhd1313_data_##inst = {		\
366 		.power = true,									\
367 		.cursor = false,								\
368 		.blinking = false,								\
369 	};											\
370 	DEVICE_DT_INST_DEFINE(inst,								\
371 			&auxdisplay_jhd1313_initialize,						\
372 			NULL,									\
373 			&auxdisplay_jhd1313_data_##inst,					\
374 			&auxdisplay_jhd1313_config_##inst,					\
375 			POST_KERNEL,								\
376 			CONFIG_AUXDISPLAY_INIT_PRIORITY,					\
377 			&auxdisplay_jhd1313_auxdisplay_api);
378 
379 DT_INST_FOREACH_STATUS_OKAY(AUXDISPLAY_JHD1313_DEVICE)
380