add LPM009M360A lcd driver
This commit is contained in:
parent
52ed49b4bb
commit
4f9f45de4a
6 changed files with 380 additions and 1 deletions
|
@ -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)
|
|
@ -4,5 +4,6 @@
|
|||
if DISPLAY
|
||||
|
||||
rsource "Kconfig.il0323"
|
||||
rsource "Kconfig.lpm009m360a"
|
||||
|
||||
endif # DISPLAY
|
8
app/module/drivers/display/Kconfig.lpm009m360a
Normal file
8
app/module/drivers/display/Kconfig.lpm009m360a
Normal 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.
|
323
app/module/drivers/display/lpm009m360a.c
Normal file
323
app/module/drivers/display/lpm009m360a.c
Normal 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)
|
22
app/module/drivers/display/lpm009m360a.h
Normal file
22
app/module/drivers/display/lpm009m360a.h
Normal 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__ */
|
24
app/module/dts/bindings/display/jdi,lpm009m360a.yaml
Normal file
24
app/module/dts/bindings/display/jdi,lpm009m360a.yaml
Normal 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]
|
||||
|
||||
|
Loading…
Add table
Reference in a new issue