From 4f9f45de4a3dfe875182d7d87ffe73334af9f95a Mon Sep 17 00:00:00 2001 From: wts <1533225965@qq.com> Date: Fri, 15 Dec 2023 19:37:32 +0800 Subject: [PATCH] add LPM009M360A lcd driver --- app/module/drivers/display/CMakeLists.txt | 3 +- app/module/drivers/display/Kconfig | 1 + .../drivers/display/Kconfig.lpm009m360a | 8 + app/module/drivers/display/lpm009m360a.c | 323 ++++++++++++++++++ app/module/drivers/display/lpm009m360a.h | 22 ++ .../dts/bindings/display/jdi,lpm009m360a.yaml | 24 ++ 6 files changed, 380 insertions(+), 1 deletion(-) create mode 100644 app/module/drivers/display/Kconfig.lpm009m360a create mode 100644 app/module/drivers/display/lpm009m360a.c create mode 100644 app/module/drivers/display/lpm009m360a.h create mode 100644 app/module/dts/bindings/display/jdi,lpm009m360a.yaml diff --git a/app/module/drivers/display/CMakeLists.txt b/app/module/drivers/display/CMakeLists.txt index 6fc98c95..25495af7 100644 --- a/app/module/drivers/display/CMakeLists.txt +++ b/app/module/drivers/display/CMakeLists.txt @@ -3,4 +3,5 @@ zephyr_library_amend() -zephyr_library_sources_ifdef(CONFIG_IL0323 il0323.c) \ No newline at end of file +zephyr_library_sources_ifdef(CONFIG_IL0323 il0323.c) +zephyr_library_sources_ifdef(CONFIG_LPM009M360A lpm009m360a.c) \ No newline at end of file diff --git a/app/module/drivers/display/Kconfig b/app/module/drivers/display/Kconfig index d70aff7c..ad979bda 100644 --- a/app/module/drivers/display/Kconfig +++ b/app/module/drivers/display/Kconfig @@ -4,5 +4,6 @@ if DISPLAY rsource "Kconfig.il0323" +rsource "Kconfig.lpm009m360a" endif # DISPLAY \ No newline at end of file diff --git a/app/module/drivers/display/Kconfig.lpm009m360a b/app/module/drivers/display/Kconfig.lpm009m360a new file mode 100644 index 00000000..c0929dcf --- /dev/null +++ b/app/module/drivers/display/Kconfig.lpm009m360a @@ -0,0 +1,8 @@ +# LPM009M360A display driver configuration options + +config LPM009M360A + bool "LPM009M360A display driver" + default n + select SPI + help + Enable driver for LPM009M360A display driver. diff --git a/app/module/drivers/display/lpm009m360a.c b/app/module/drivers/display/lpm009m360a.c new file mode 100644 index 00000000..8cae4b7f --- /dev/null +++ b/app/module/drivers/display/lpm009m360a.c @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2023 Taisheng WANG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT jdi_lpm009m360a + +#include "lpm009m360a.h" + +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(display_lpm009m360a, CONFIG_DISPLAY_LOG_LEVEL); + +#define LPM009M360A_RESET_TIME K_MSEC(1) +#define LPM009M360A_EXIT_SLEEP_TIME K_MSEC(1) + +struct lpm009m360a_data { + uint8_t buf[72 * 144 / 2]; +}; + +struct lpm009m360a_config { + struct spi_dt_spec bus; + struct gpio_dt_spec extcomin; + struct gpio_dt_spec disp; + uint16_t height; + uint16_t width; + + uint8_t color_mode[1]; +}; + +static int lpm009m360a_transmit_hold(const struct device *dev, uint8_t cmd, uint8_t arg, + const uint8_t *tx_data, size_t tx_count) { + const struct lpm009m360a_config *config = dev->config; + struct spi_buf tx_buf = {.buf = &cmd, .len = 1}; + struct spi_buf_set tx_bufs = {.buffers = &tx_buf, .count = 1}; + int ret; + + ret = spi_write_dt(&config->bus, &tx_bufs); + tx_buf.buf = &arg; + ret = spi_write_dt(&config->bus, &tx_bufs); + if (ret < 0) { + return ret; + } + + if (tx_data != NULL) { + tx_buf.buf = (void *)tx_data; + tx_buf.len = tx_count; + ret = spi_write_dt(&config->bus, &tx_bufs); + if (ret < 0) { + return ret; + } + } + return 0; +} + +static int lpm009m360a_transmit(const struct device *dev, uint8_t cmd, uint8_t arg, + const uint8_t *tx_data, size_t tx_count) { + const struct lpm009m360a_config *config = dev->config; + int ret; + + ret = lpm009m360a_transmit_hold(dev, cmd, arg, tx_data, tx_count); + spi_release_dt(&config->bus); + return ret; +} + +static int lpm009m360a_exit_sleep(const struct device *dev) { + int ret; + const struct lpm009m360a_config *config = dev->config; + ret = gpio_pin_set_dt(&config->disp, 1); + if (ret < 0) { + return ret; + } + k_sleep(LPM009M360A_EXIT_SLEEP_TIME); + return 0; +} + +static int lpm009m360a_sleep(const struct device *dev) { + int ret; + const struct lpm009m360a_config *config = dev->config; + ret = gpio_pin_set_dt(&config->disp, 0); + if (ret < 0) { + return ret; + } + return 0; +} + +static int lpm009m360a_reset_display(const struct device *dev) { + int ret; + + LOG_DBG("Resetting display"); + ret = lpm009m360a_transmit(dev, LPM009M360A_CMD_ALL_CLEAR, 0, NULL, 0); + + k_sleep(LPM009M360A_RESET_TIME); + + return 0; +} + +static int lpm009m360a_blanking_on(const struct device *dev) { return lpm009m360a_sleep(dev); } + +static int lpm009m360a_blanking_off(const struct device *dev) { + return lpm009m360a_exit_sleep(dev); +} + +static int lpm009m360a_read(const struct device *dev, const uint16_t x, const uint16_t y, + const struct display_buffer_descriptor *desc, void *buf) { + return -ENOTSUP; +} + +#define RGB565_RGB111(s) ((s & 0x8000) >> 12) | ((s & 0x0400) >> 8) | ((s & 0x0010) >> 3) +static int lpm009m360a_write(const struct device *dev, const uint16_t x, const uint16_t y, + const struct display_buffer_descriptor *desc, const void *buf) { + const uint16_t *source_buf = (uint16_t *)buf; + const uint8_t *source_buf8 = (uint8_t *)buf; + const struct lpm009m360a_config *config = dev->config; + struct lpm009m360a_data *data = dev->data; + int ret = 0; + uint8_t cmd = LPM009M360A_CMD_UPDATE | (config->color_mode[0] << 2); + size_t len = (config->color_mode[0] == 0x02) ? 9 : 36; + uint8_t cnt = desc->height; + if (config->color_mode[0] == 0x04) { + if (x % 2) { + for (uint8_t i = 0; i < desc->height; i++) { + for (uint8_t j = 0; j < desc->width;) { + data->buf[(y + i) * 36 + (x + j) / 2] &= 0xf0; + data->buf[(y + i) * 36 + (x + j) / 2] |= + (0x0f & (RGB565_RGB111(source_buf[(i * desc->width) + j]))); + j++; + if (j < desc->width) { + data->buf[(y + i) * 36 + (x + j) / 2] &= 0x0f; + data->buf[(y + i) * 36 + (x + j) / 2] |= + (0x0f & (RGB565_RGB111(source_buf[i * desc->width + j]))) << 4; + j++; + } + } + } + } else { + for (uint8_t i = 0; i < desc->height; i++) { + for (uint8_t j = 0; j < desc->width;) { + data->buf[(y + i) * 36 + (x + j) / 2] &= 0x0f; + data->buf[(y + i) * 36 + (x + j) / 2] |= + (0x0f & (RGB565_RGB111(source_buf[(i * desc->width) + j]))) << 4; + j++; + if (j < desc->width) { + data->buf[(y + i) * 36 + (x + j) / 2] &= 0xf0; + data->buf[(y + i) * 36 + (x + j) / 2] |= + (0x0f & (RGB565_RGB111(source_buf[i * desc->width + j]))); + j++; + } + } + } + } + } else { + for (uint8_t i = 0; i < desc->height; i++) { + for (uint8_t j = 0; j < desc->width; j++) { + data->buf[(y + i) * 9 + (x / 8 + j)] = source_buf8[i * 9 + j]; + } + } + } + // LOG_INF("x:%d, y:%d, w:%d, h:%d", x, y, desc->width, desc->height); + for (uint8_t i = 0; i < cnt; i++) { + ret = lpm009m360a_transmit_hold(dev, cmd, i + y + 1, (uint8_t *)&data->buf[(y + i) * len], + len); + } + ret = lpm009m360a_transmit_hold(dev, LPM009M360A_CMD_NO_UPDATE, 0, NULL, 0); + ret = lpm009m360a_transmit_hold(dev, LPM009M360A_CMD_NO_UPDATE, 0, NULL, 0); + spi_release_dt(&config->bus); + return ret; +} + +static void *lpm009m360a_get_framebuffer(const struct device *dev) { return NULL; } + +static int lpm009m360a_set_brightness(const struct device *dev, const uint8_t brightness) { + return -ENOTSUP; +} + +static int lpm009m360a_set_contrast(const struct device *dev, const uint8_t contrast) { + return -ENOTSUP; +} + +static void lpm009m360a_get_capabilities(const struct device *dev, + struct display_capabilities *capabilities) { + const struct lpm009m360a_config *config = dev->config; + + memset(capabilities, 0, sizeof(struct display_capabilities)); + capabilities->x_resolution = config->width; + capabilities->y_resolution = config->height; + + capabilities->supported_pixel_formats = PIXEL_FORMAT_RGB_565 | PIXEL_FORMAT_MONO10; + if (config->color_mode[0] == 0x04) { + capabilities->current_pixel_format = PIXEL_FORMAT_RGB_565; + } else { + capabilities->current_pixel_format = PIXEL_FORMAT_MONO10; + capabilities->screen_info = SCREEN_INFO_X_ALIGNMENT_WIDTH | SCREEN_INFO_MONO_MSB_FIRST; + } + capabilities->current_orientation = DISPLAY_ORIENTATION_NORMAL; +} + +static int lpm009m360a_set_pixel_format(const struct device *dev, + const enum display_pixel_format pixel_format) { + + LOG_ERR("Pixel format change not implemented"); + + return -ENOTSUP; +} + +static int lpm009m360a_set_orientation(const struct device *dev, + const enum display_orientation orientation) { + if (orientation == DISPLAY_ORIENTATION_NORMAL) { + return 0; + } + + LOG_ERR("Changing display orientation not implemented"); + + return -ENOTSUP; +} + +static int lpm009m360a_init(const struct device *dev) { + LOG_INF("initializing"); + const struct lpm009m360a_config *config = dev->config; + int ret; + + // if (!spi_is_ready_dt(&config->bus)) { + // LOG_ERR("SPI bus %s not ready", config->bus.bus->name); + // return -ENODEV; + // } + + // if (!gpio_is_ready_dt(&config->extcomin)) { + // LOG_ERR("extcomin GPIO port for display not ready"); + // return -ENODEV; + // } + ret = gpio_pin_configure_dt(&config->extcomin, GPIO_OUTPUT_INACTIVE); + if (ret) { + LOG_ERR("Couldn't configure extcomin pin"); + return ret; + } + + // if (!gpio_is_ready_dt(&config->disp)) { + // LOG_ERR("disp GPIO port not ready"); + // return -ENODEV; + // } + ret = gpio_pin_configure_dt(&config->disp, GPIO_OUTPUT); + if (ret) { + LOG_ERR("Couldn't configure disp pin"); + return ret; + } + ret = lpm009m360a_reset_display(dev); + if (ret) { + LOG_ERR("Couldn't reset display"); + return ret; + } + LOG_INF("initialized"); + return 0; +} + +#ifdef CONFIG_PM_DEVICE +static int lpm009m360a_pm_action(const struct device *dev, enum pm_device_action action) { + int ret = 0; + + switch (action) { + case PM_DEVICE_ACTION_RESUME: + LOG_INF("resume"); + lpm009m360a_exit_sleep(dev); + break; + case PM_DEVICE_ACTION_SUSPEND: + LOG_INF("suspend"); + lpm009m360a_sleep(dev); + break; + case PM_DEVICE_ACTION_TURN_OFF: + LOG_INF("turn off"); + break; + case PM_DEVICE_ACTION_TURN_ON: + lpm009m360a_init(dev); + LOG_INF("turn on"); + break; + default: + ret = -ENOTSUP; + break; + } + + return ret; +} +#endif /* CONFIG_PM_DEVICE */ + +static const struct display_driver_api lpm009m360a_api = { + .blanking_on = lpm009m360a_blanking_on, + .blanking_off = lpm009m360a_blanking_off, + .write = lpm009m360a_write, + .read = lpm009m360a_read, + .get_framebuffer = lpm009m360a_get_framebuffer, + .set_brightness = lpm009m360a_set_brightness, + .set_contrast = lpm009m360a_set_contrast, + .get_capabilities = lpm009m360a_get_capabilities, + .set_pixel_format = lpm009m360a_set_pixel_format, + .set_orientation = lpm009m360a_set_orientation, +}; + +#define LPM009M360A_INIT(inst) \ + \ + const static struct lpm009m360a_config lpm009m360a_config_##inst = { \ + .bus = SPI_DT_SPEC_INST_GET( \ + inst, SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | SPI_HOLD_ON_CS | SPI_LOCK_ON, 0), \ + .extcomin = GPIO_DT_SPEC_INST_GET(inst, extcomin_gpios), \ + .disp = GPIO_DT_SPEC_INST_GET(inst, disp_gpios), \ + .width = DT_INST_PROP(inst, width), \ + .height = DT_INST_PROP(inst, height), \ + .color_mode = DT_INST_PROP(inst, color_mode), \ + }; \ + static struct lpm009m360a_data lpm009m360a_data_##inst = {0}; \ + \ + PM_DEVICE_DT_INST_DEFINE(inst, lpm009m360a_pm_action); \ + \ + DEVICE_DT_INST_DEFINE(inst, lpm009m360a_init, PM_DEVICE_DT_INST_GET(inst), \ + &lpm009m360a_data_##inst, &lpm009m360a_config_##inst, POST_KERNEL, \ + CONFIG_DISPLAY_INIT_PRIORITY, &lpm009m360a_api); + +DT_INST_FOREACH_STATUS_OKAY(LPM009M360A_INIT) diff --git a/app/module/drivers/display/lpm009m360a.h b/app/module/drivers/display/lpm009m360a.h new file mode 100644 index 00000000..0771898a --- /dev/null +++ b/app/module/drivers/display/lpm009m360a.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 Taisheng WANG + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef LPM009M360A_DISPLAY_DRIVER_H__ +#define LPM009M360A_DISPLAY_DRIVER_H__ + +#include + + +#define LPM009M360A_CMD_NO_UPDATE 0x00 +#define LPM009M360A_CMD_BLINKING_BLACK 0x10 +#define LPM009M360A_CMD_BLINKING_INVERSION 0x14 +#define LPM009M360A_CMD_BLINKING_WHITE 0x18 +#define LPM009M360A_CMD_ALL_CLEAR 0x20 +#define LPM009M360A_CMD_VCOM 0x40 +#define LPM009M360A_CMD_UPDATE 0x80 + + + +#endif /* LPM009M360A_DISPLAY_DRIVER_H__ */ diff --git a/app/module/dts/bindings/display/jdi,lpm009m360a.yaml b/app/module/dts/bindings/display/jdi,lpm009m360a.yaml new file mode 100644 index 00000000..89d6221f --- /dev/null +++ b/app/module/dts/bindings/display/jdi,lpm009m360a.yaml @@ -0,0 +1,24 @@ +# Copyright (c) 2023 Taisheng WANG +# SPDX-License-Identifier: Apache-2.0 + +description: LPM009M360A display controller + +compatible: "jdi,lpm009m360a" + +include: [spi-device.yaml, display-controller.yaml] + +properties: + extcomin_gpios: + type: phandle-array + description: COM Inversion Polarity Input pin. + + disp_gpios: + type: phandle-array + required: true + description: Display ON/OFF Switching signal pin. + + color_mode: + type: uint8-array + default: [0x04] + +