/* * Copyright (c) 2015 Intel Corporation * Copyright (c) 2022 Nordic Semiconductor ASA * Copyright (c) 2022-2023 Jamie McCrae * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT jhd_jhd1313 #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(auxdisplay_jhd1313, CONFIG_AUXDISPLAY_LOG_LEVEL); #define JHD1313_BACKLIGHT_ADDR (0x62) /* Defines for the JHD1313_CMD_CURSOR_SHIFT */ #define JHD1313_CS_DISPLAY_SHIFT (1 << 3) #define JHD1313_CS_RIGHT_SHIFT (1 << 2) /* Defines for the JHD1313_CMD_INPUT_SET to change text direction */ #define JHD1313_IS_INCREMENT (1 << 1) #define JHD1313_IS_DECREMENT (0 << 1) #define JHD1313_IS_SHIFT (1 << 0) /* Defines for the JHD1313_CMD_FUNCTION_SET */ #define JHD1313_FS_8BIT_MODE (1 << 4) #define JHD1313_FS_ROWS_2 (1 << 3) #define JHD1313_FS_ROWS_1 (0 << 3) #define JHD1313_FS_DOT_SIZE_BIG (1 << 2) #define JHD1313_FS_DOT_SIZE_LITTLE (0 << 2) /* LCD Display Commands */ #define JHD1313_CMD_SCREEN_CLEAR (1 << 0) #define JHD1313_CMD_CURSOR_RETURN (1 << 1) #define JHD1313_CMD_INPUT_SET (1 << 2) #define JHD1313_CMD_DISPLAY_SWITCH (1 << 3) #define JHD1313_CMD_CURSOR_SHIFT (1 << 4) #define JHD1313_CMD_FUNCTION_SET (1 << 5) #define JHD1313_CMD_SET_CGRAM_ADDR (1 << 6) #define JHD1313_CMD_SET_DDRAM_ADDR (1 << 7) #define JHD1313_DS_DISPLAY_ON (1 << 2) #define JHD1313_DS_CURSOR_ON (1 << 1) #define JHD1313_DS_BLINK_ON (1 << 0) #define JHD1313_LED_REG_R 0x04 #define JHD1313_LED_REG_G 0x03 #define JHD1313_LED_REG_B 0x02 #define JHD1313_LINE_FIRST 0x80 #define JHD1313_LINE_SECOND 0xC0 #define CLEAR_DELAY_MS 20 #define UPDATE_DELAY_MS 5 struct auxdisplay_jhd1313_data { uint8_t input_set; bool power; bool cursor; bool blinking; uint8_t function; uint8_t backlight; }; struct auxdisplay_jhd1313_config { struct auxdisplay_capabilities capabilities; struct i2c_dt_spec bus; }; static const uint8_t colour_define[][4] = { { 0, 0, 0 }, /* Off */ { 255, 255, 255 }, /* White */ { 255, 0, 0 }, /* Red */ { 0, 255, 0 }, /* Green */ { 0, 0, 255 }, /* Blue */ }; static void auxdisplay_jhd1313_reg_set(const struct device *i2c, uint8_t addr, uint8_t data) { uint8_t command[2] = { addr, data }; i2c_write(i2c, command, sizeof(command), JHD1313_BACKLIGHT_ADDR); } static int auxdisplay_jhd1313_print(const struct device *dev, const uint8_t *data, uint16_t size) { const struct auxdisplay_jhd1313_config *config = dev->config; uint8_t buf[] = { JHD1313_CMD_SET_CGRAM_ADDR, 0 }; int rc = 0; int16_t i; for (i = 0; i < size; i++) { buf[1] = data[i]; rc = i2c_write_dt(&config->bus, buf, sizeof(buf)); } return rc; } static int auxdisplay_jhd1313_cursor_position_set(const struct device *dev, enum auxdisplay_position type, int16_t x, int16_t y) { const struct auxdisplay_jhd1313_config *config = dev->config; unsigned char data[2]; if (type != AUXDISPLAY_POSITION_ABSOLUTE) { return -EINVAL; } if (y == 0U) { x |= JHD1313_LINE_FIRST; } else { x |= JHD1313_LINE_SECOND; } data[0] = JHD1313_CMD_SET_DDRAM_ADDR; data[1] = x; return i2c_write_dt(&config->bus, data, 2); } static int auxdisplay_jhd1313_clear(const struct device *dev) { int rc; const struct auxdisplay_jhd1313_config *config = dev->config; uint8_t clear[] = { 0, JHD1313_CMD_SCREEN_CLEAR }; rc = i2c_write_dt(&config->bus, clear, sizeof(clear)); LOG_DBG("Clear, delay 20 ms"); k_sleep(K_MSEC(CLEAR_DELAY_MS)); return rc; } static int auxdisplay_jhd1313_update_display_state( const struct auxdisplay_jhd1313_config *config, struct auxdisplay_jhd1313_data *data) { int rc; uint8_t buf[] = { 0, JHD1313_CMD_DISPLAY_SWITCH }; if (data->power) { buf[1] |= JHD1313_DS_DISPLAY_ON; } if (data->cursor) { buf[1] |= JHD1313_DS_CURSOR_ON; } if (data->blinking) { buf[1] |= JHD1313_DS_BLINK_ON; } rc = i2c_write_dt(&config->bus, buf, sizeof(buf)); LOG_DBG("Set display_state options, delay 5 ms"); k_sleep(K_MSEC(UPDATE_DELAY_MS)); return rc; } static int auxdisplay_jhd1313_cursor_set_enabled(const struct device *dev, bool enabled) { const struct auxdisplay_jhd1313_config *config = dev->config; struct auxdisplay_jhd1313_data *data = dev->data; data->cursor = enabled; return auxdisplay_jhd1313_update_display_state(config, data); } static int auxdisplay_jhd1313_position_blinking_set_enabled(const struct device *dev, bool enabled) { const struct auxdisplay_jhd1313_config *config = dev->config; struct auxdisplay_jhd1313_data *data = dev->data; data->blinking = enabled; return auxdisplay_jhd1313_update_display_state(config, data); } static void auxdisplay_jhd1313_input_state_set(const struct device *dev, uint8_t opt) { const struct auxdisplay_jhd1313_config *config = dev->config; struct auxdisplay_jhd1313_data *data = dev->data; uint8_t buf[] = { 0, 0 }; data->input_set = opt; buf[1] = (opt | JHD1313_CMD_INPUT_SET); i2c_write_dt(&config->bus, buf, sizeof(buf)); LOG_DBG("Set the input_set, no delay"); } static int auxdisplay_jhd1313_backlight_set(const struct device *dev, uint8_t colour) { const struct auxdisplay_jhd1313_config *config = dev->config; struct auxdisplay_jhd1313_data *data = dev->data; if (colour > ARRAY_SIZE(colour_define)) { LOG_WRN("Selected colour is too high a value"); return -EINVAL; } data->backlight = colour; auxdisplay_jhd1313_reg_set(config->bus.bus, JHD1313_LED_REG_R, colour_define[colour][0]); auxdisplay_jhd1313_reg_set(config->bus.bus, JHD1313_LED_REG_G, colour_define[colour][1]); auxdisplay_jhd1313_reg_set(config->bus.bus, JHD1313_LED_REG_B, colour_define[colour][2]); return 0; } static int auxdisplay_jhd1313_backlight_get(const struct device *dev, uint8_t *backlight) { struct auxdisplay_jhd1313_data *data = dev->data; *backlight = data->backlight; return 0; } static void auxdisplay_jhd1313_function_set(const struct device *dev, uint8_t opt) { const struct auxdisplay_jhd1313_config *config = dev->config; struct auxdisplay_jhd1313_data *data = dev->data; uint8_t buf[] = { 0, 0 }; data->function = opt; buf[1] = (opt | JHD1313_CMD_FUNCTION_SET); i2c_write_dt(&config->bus, buf, sizeof(buf)); LOG_DBG("Set function options, delay 5 ms"); k_sleep(K_MSEC(5)); } static int auxdisplay_jhd1313_initialize(const struct device *dev) { const struct auxdisplay_jhd1313_config *config = dev->config; struct auxdisplay_jhd1313_data *data = dev->data; uint8_t cmd; LOG_DBG("Initialize called"); if (!device_is_ready(config->bus.bus)) { return -ENODEV; } /* * Initialization sequence from the data sheet: * 1 - Power on * - Wait for more than 30 ms AFTER VDD rises to 4.5v * 2 - Send FUNCTION set * - Wait for 39 us * 3 - Send DISPLAY Control * - wait for 39 us * 4 - send DISPLAY Clear * - wait for 1.5 ms * 5 - send ENTRY Mode * 6 - Initialization is done */ /* * We're here! Let's just make sure we've had enough time for the * VDD to power on, so pause a little here, 30 ms min, so we go 50 */ LOG_DBG("Delay 50 ms while the VDD powers on"); k_sleep(K_MSEC(50)); /* Configure everything for the display function first */ cmd = JHD1313_CMD_FUNCTION_SET | JHD1313_FS_ROWS_2; auxdisplay_jhd1313_function_set(dev, cmd); /* Turn the display on - by default no cursor and no blinking */ auxdisplay_jhd1313_update_display_state(config, data); /* Clear the screen */ auxdisplay_jhd1313_clear(dev); /* * Initialize to the default text direction for romance languages * (increment, no shift) */ cmd = JHD1313_IS_INCREMENT; auxdisplay_jhd1313_input_state_set(dev, cmd); /* Now power on the background RGB control */ LOG_INF("Configuring the RGB background"); auxdisplay_jhd1313_reg_set(config->bus.bus, 0x00, 0x00); auxdisplay_jhd1313_reg_set(config->bus.bus, 0x01, 0x05); auxdisplay_jhd1313_reg_set(config->bus.bus, 0x08, 0xAA); /* Now set the background colour to black */ LOG_DBG("Background set to off"); auxdisplay_jhd1313_backlight_set(dev, 0); return 0; } static int auxdisplay_jhd1313_display_on(const struct device *dev) { const struct auxdisplay_jhd1313_config *config = dev->config; struct auxdisplay_jhd1313_data *data = dev->data; data->power = true; return auxdisplay_jhd1313_update_display_state(config, data); } static int auxdisplay_jhd1313_display_off(const struct device *dev) { const struct auxdisplay_jhd1313_config *config = dev->config; struct auxdisplay_jhd1313_data *data = dev->data; data->power = false; return auxdisplay_jhd1313_update_display_state(config, data); } static int auxdisplay_jhd1313_capabilities_get(const struct device *dev, struct auxdisplay_capabilities *capabilities) { const struct auxdisplay_jhd1313_config *config = dev->config; memcpy(capabilities, &config->capabilities, sizeof(struct auxdisplay_capabilities)); return 0; } static const struct auxdisplay_driver_api auxdisplay_jhd1313_auxdisplay_api = { .display_on = auxdisplay_jhd1313_display_on, .display_off = auxdisplay_jhd1313_display_off, .cursor_set_enabled = auxdisplay_jhd1313_cursor_set_enabled, .position_blinking_set_enabled = auxdisplay_jhd1313_position_blinking_set_enabled, .cursor_position_set = auxdisplay_jhd1313_cursor_position_set, .capabilities_get = auxdisplay_jhd1313_capabilities_get, .clear = auxdisplay_jhd1313_clear, .backlight_get = auxdisplay_jhd1313_backlight_get, .backlight_set = auxdisplay_jhd1313_backlight_set, .write = auxdisplay_jhd1313_print, }; #define AUXDISPLAY_JHD1313_DEVICE(inst) \ static const struct auxdisplay_jhd1313_config auxdisplay_jhd1313_config_##inst = { \ .capabilities = { \ .columns = 16, \ .rows = 2, \ .mode = 0, \ .brightness.minimum = AUXDISPLAY_LIGHT_NOT_SUPPORTED, \ .brightness.maximum = AUXDISPLAY_LIGHT_NOT_SUPPORTED, \ .backlight.minimum = 0, \ .backlight.maximum = ARRAY_SIZE(colour_define), \ .custom_characters = 0, \ }, \ .bus = I2C_DT_SPEC_INST_GET(inst), \ }; \ static struct auxdisplay_jhd1313_data auxdisplay_jhd1313_data_##inst = { \ .power = true, \ .cursor = false, \ .blinking = false, \ }; \ DEVICE_DT_INST_DEFINE(inst, \ &auxdisplay_jhd1313_initialize, \ NULL, \ &auxdisplay_jhd1313_data_##inst, \ &auxdisplay_jhd1313_config_##inst, \ POST_KERNEL, \ CONFIG_AUXDISPLAY_INIT_PRIORITY, \ &auxdisplay_jhd1313_auxdisplay_api); DT_INST_FOREACH_STATUS_OKAY(AUXDISPLAY_JHD1313_DEVICE)