drivers: auxdisplay: Port Jinghua Display JHD1313 driver

Ports the Jinghua Display JHD1313 LCD (with RGB backlight) driver
to use the new auxdisplay driver interface. This driver is used on
the seeed grove LCD RGB display, and replaces it.

Signed-off-by: Jamie McCrae <spam@helper3000.net>
diff --git a/drivers/auxdisplay/CMakeLists.txt b/drivers/auxdisplay/CMakeLists.txt
index 6ad590d..2cb1c92 100644
--- a/drivers/auxdisplay/CMakeLists.txt
+++ b/drivers/auxdisplay/CMakeLists.txt
@@ -2,3 +2,4 @@
 
 zephyr_library()
 zephyr_library_sources_ifdef(CONFIG_AUXDISPLAY_ITRON		auxdisplay_itron.c)
+zephyr_library_sources_ifdef(CONFIG_AUXDISPLAY_JHD1313		auxdisplay_jhd1313.c)
diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig
index d6f2477..94c3cff 100644
--- a/drivers/auxdisplay/Kconfig
+++ b/drivers/auxdisplay/Kconfig
@@ -21,5 +21,6 @@
 source "subsys/logging/Kconfig.template.log_config"
 
 source "drivers/auxdisplay/Kconfig.itron"
+source "drivers/auxdisplay/Kconfig.jhd1313"
 
 endif # AUXDISPLAY
diff --git a/drivers/auxdisplay/Kconfig.jhd1313 b/drivers/auxdisplay/Kconfig.jhd1313
new file mode 100644
index 0000000..5a6cf8d
--- /dev/null
+++ b/drivers/auxdisplay/Kconfig.jhd1313
@@ -0,0 +1,10 @@
+# Copyright (c) 2022 Jamie McCrae
+# SPDX-License-Identifier: Apache-2.0
+
+config AUXDISPLAY_JHD1313
+	bool "Jinghua Display JHD1313 driver"
+	default y
+	select I2C
+	depends on DT_HAS_JHD_JHD1313_ENABLED
+	help
+	  Enable driver for Jinghua Display JHD1313 display.
diff --git a/drivers/auxdisplay/auxdisplay_jhd1313.c b/drivers/auxdisplay/auxdisplay_jhd1313.c
new file mode 100644
index 0000000..8d30485
--- /dev/null
+++ b/drivers/auxdisplay/auxdisplay_jhd1313.c
@@ -0,0 +1,374 @@
+/*
+ * 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 <string.h>
+#include <zephyr/device.h>
+#include <zephyr/devicetree.h>
+#include <zephyr/drivers/i2c.h>
+#include <zephyr/drivers/auxdisplay.h>
+#include <zephyr/kernel.h>
+#include <zephyr/sys/util.h>
+#include <zephyr/logging/log.h>
+
+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_SHIFT_INCREMENT	(1 << 1)
+#define JHD1313_IS_SHIFT_DECREMENT	(0 << 1)
+#define JHD1313_IS_ENTRY_LEFT		(1 << 0)
+#define JHD1313_IS_ENTRY_RIGHT		(0 << 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)
+{
+	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 */
+	cmd = JHD1313_IS_ENTRY_LEFT | JHD1313_IS_SHIFT_DECREMENT;
+
+	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 white */
+	LOG_DBG("Background set to off");
+	auxdisplay_jhd1313_backlight_set(dev, 0);
+
+	return 0;
+}
+
+static int auxdisplay_jhd1313_display_on(const struct device *dev)
+{
+	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)
+{
+	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 = 2,								\
+			.rows = 16,								\
+			.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)
diff --git a/dts/bindings/auxdisplay/jhd,jhd1313.yaml b/dts/bindings/auxdisplay/jhd,jhd1313.yaml
new file mode 100644
index 0000000..fd57835
--- /dev/null
+++ b/dts/bindings/auxdisplay/jhd,jhd1313.yaml
@@ -0,0 +1,11 @@
+#
+# Copyright (c) 2022 Jamie McCrae
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+description: Jinghua Display JHD1313
+
+compatible: "jhd,jhd1313"
+
+include: [auxdisplay-device.yaml, i2c-device.yaml]
diff --git a/dts/bindings/vendor-prefixes.txt b/dts/bindings/vendor-prefixes.txt
index 8668bee..755436d 100644
--- a/dts/bindings/vendor-prefixes.txt
+++ b/dts/bindings/vendor-prefixes.txt
@@ -298,6 +298,7 @@
 jedec	JEDEC Solid State Technology Association
 jesurun	Shenzhen Jesurun Electronics Business Dept.
 jianda	Jiandangjing Technology Co., Ltd.
+jhd	Shenzhen Jinghua Displays Electronics Co., Ltd.
 kam	Kamstrup A/S
 karo	Ka-Ro electronics GmbH
 keithkoep	Keith & Koep GmbH