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