add LPM009M360A lcd driver

This commit is contained in:
wts 2023-12-15 19:37:32 +08:00
parent 52ed49b4bb
commit 4f9f45de4a
6 changed files with 380 additions and 1 deletions

View file

@ -3,4 +3,5 @@
zephyr_library_amend()
zephyr_library_sources_ifdef(CONFIG_IL0323 il0323.c)
zephyr_library_sources_ifdef(CONFIG_IL0323 il0323.c)
zephyr_library_sources_ifdef(CONFIG_LPM009M360A lpm009m360a.c)

View file

@ -4,5 +4,6 @@
if DISPLAY
rsource "Kconfig.il0323"
rsource "Kconfig.lpm009m360a"
endif # DISPLAY

View file

@ -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.

View file

@ -0,0 +1,323 @@
/*
* Copyright (c) 2023 Taisheng WANG <wstrn66@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT jdi_lpm009m360a
#include "lpm009m360a.h"
#include <zephyr/device.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/pm/device.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/drivers/display.h>
#include <zephyr/logging/log.h>
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)

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2023 Taisheng WANG <wstrn66@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef LPM009M360A_DISPLAY_DRIVER_H__
#define LPM009M360A_DISPLAY_DRIVER_H__
#include <zephyr/kernel.h>
#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__ */

View file

@ -0,0 +1,24 @@
# Copyright (c) 2023 Taisheng WANG <wstrn66@gmail.com>
# 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]