Skip to content

Commit d368921

Browse files
S-Bhavincfriedt
authored andcommitted
drivers: display: Added LPM013M126 display driver.
Add support for JDI LPM013M126 RGB memory display Co-developed-by: Elgin Perumbilly <[email protected]> Signed-off-by: Elgin Perumbilly <[email protected]> Signed-off-by: Bhavin Sharma <[email protected]>
1 parent ccf27a9 commit d368921

File tree

5 files changed

+310
-0
lines changed

5 files changed

+310
-0
lines changed

drivers/display/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ zephyr_library_sources_ifdef(CONFIG_NT35510 display_nt35510.c)
4141
zephyr_library_sources_ifdef(CONFIG_RENESAS_RA_GLCDC display_renesas_ra.c)
4242
zephyr_library_sources_ifdef(CONFIG_ILI9806E_DSI display_ili9806e_dsi.c)
4343
zephyr_library_sources_ifdef(CONFIG_ST7701 display_st7701.c)
44+
zephyr_library_sources_ifdef(CONFIG_LPM013M126 display_lpm013m126.c)
4445

4546
zephyr_library_sources_ifdef(CONFIG_MICROBIT_DISPLAY
4647
mb_display.c

drivers/display/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,6 @@ source "drivers/display/Kconfig.nt35510"
5858
source "drivers/display/Kconfig.renesas_ra"
5959
source "drivers/display/Kconfig.ili9806e_dsi"
6060
source "drivers/display/Kconfig.st7701"
61+
source "drivers/display/Kconfig.lpm013m126"
6162

6263
endif # DISPLAY

drivers/display/Kconfig.lpm013m126

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright (c) 2025 Silicon Signals Pvt. Ltd.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config LPM013M126
5+
bool "LPM013M126 memory display controller driver"
6+
default y
7+
depends on DT_HAS_JDI_LPM013M126_ENABLED
8+
select SPI
9+
help
10+
Enable driver for JDI memory display series LPM013M126
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* Copyright (c) 2025 Silicon Signals Pvt. Ltd.
5+
* Author: Bhavin Sharma <[email protected]>
6+
* Author: Elgin Perumbilly <[email protected]>
7+
*/
8+
9+
#define DT_DRV_COMPAT jdi_lpm013m126
10+
11+
#include <zephyr/logging/log.h>
12+
LOG_MODULE_REGISTER(lpm013m126, CONFIG_DISPLAY_LOG_LEVEL);
13+
14+
#include <string.h>
15+
#include <zephyr/device.h>
16+
#include <zephyr/drivers/display.h>
17+
#include <zephyr/init.h>
18+
#include <zephyr/drivers/gpio.h>
19+
#include <zephyr/drivers/spi.h>
20+
#include <zephyr/kernel.h>
21+
22+
/* Panel properties */
23+
#define LPM_BPP 3
24+
25+
/* Command bytes */
26+
#define LPM_WRITELINE_CMD 0x80
27+
#define LPM_ALLCLEAR_CMD 0x20
28+
29+
struct lpm013m126_data {
30+
struct k_timer vcom_timer;
31+
int vcom_state;
32+
};
33+
34+
struct lpm013m126_config {
35+
struct spi_dt_spec bus;
36+
struct gpio_dt_spec disp_gpio;
37+
struct gpio_dt_spec extcomin_gpio;
38+
uint32_t extcomin_freq;
39+
uint8_t width;
40+
uint8_t height;
41+
};
42+
43+
/* bit-reversal of row address */
44+
static inline uint8_t bitrev8(uint8_t x)
45+
{
46+
x = ((x & 0xF0) >> 4) | ((x & 0x0F) << 4);
47+
x = ((x & 0xCC) >> 2) | ((x & 0x33) << 2);
48+
x = ((x & 0xAA) >> 1) | ((x & 0x55) << 1);
49+
return x;
50+
}
51+
52+
/* The native format (1 bit per channel) is rather unusual. LVGL and
53+
* other libraries don't support it. In addition, the format is not
54+
* very convenient for the application. So, we prefer to advertise
55+
* a well known format and convert it under the hood.
56+
* A native implementation of this format would allow to save memory
57+
* for the frame buffer (11kB instead of 30kB).
58+
*/
59+
static inline uint8_t rgb565_to_rgb3(uint16_t color)
60+
{
61+
uint8_t r = FIELD_GET(0xF800, color);
62+
uint8_t g = FIELD_GET(0x07E0, color);
63+
uint8_t b = FIELD_GET(0x001F, color);
64+
65+
r >>= 4;
66+
g >>= 5;
67+
b >>= 4;
68+
return (r << 2) | (g << 1) | b;
69+
}
70+
71+
/* Pack one row of RGB565 pixels into panel format */
72+
static void lpm_pack_row(const struct device *dev, uint8_t *dst, const uint16_t *src)
73+
{
74+
const struct lpm013m126_config *cfg = dev->config;
75+
76+
int bitpos = 0;
77+
uint8_t byte = 0;
78+
79+
for (int x = 0; x < cfg->width; x++) {
80+
uint8_t pix = rgb565_to_rgb3(src[x]);
81+
82+
for (int b = 2; b >= 0; b--) {
83+
byte |= ((pix >> b) & 0x1) << (7 - bitpos);
84+
bitpos++;
85+
86+
if (bitpos == 8) {
87+
*dst++ = byte;
88+
bitpos = 0;
89+
byte = 0;
90+
}
91+
}
92+
}
93+
/* flush final partial byte if needed */
94+
if (bitpos) {
95+
*dst++ = byte;
96+
}
97+
}
98+
99+
/* VCOM toggle callback */
100+
static void lpm_vcom_toggle(struct k_timer *timer)
101+
{
102+
const struct device *dev = k_timer_user_data_get(timer);
103+
const struct lpm013m126_config *cfg = dev->config;
104+
struct lpm013m126_data *data = dev->data;
105+
106+
data->vcom_state = !data->vcom_state;
107+
gpio_pin_set_dt(&cfg->extcomin_gpio, data->vcom_state);
108+
}
109+
110+
/* Send a line to the display */
111+
static int lpm_send_line(const struct device *dev, uint8_t line, uint8_t *buf, size_t len)
112+
{
113+
const struct lpm013m126_config *cfg = dev->config;
114+
115+
uint8_t cmd = LPM_WRITELINE_CMD;
116+
uint8_t addr = bitrev8(line);
117+
118+
struct spi_buf tx_bufs[] = {
119+
{ .buf = &cmd, .len = 1 },
120+
{ .buf = &addr, .len = 1 },
121+
{ .buf = buf, .len = len },
122+
};
123+
124+
struct spi_buf_set tx = {
125+
.buffers = tx_bufs,
126+
.count = ARRAY_SIZE(tx_bufs)
127+
};
128+
129+
return spi_write_dt(&cfg->bus, &tx);
130+
}
131+
132+
/* Send all-clear command */
133+
static int lpm_all_clear(const struct device *dev)
134+
{
135+
const struct lpm013m126_config *cfg = dev->config;
136+
137+
uint8_t cmd = LPM_ALLCLEAR_CMD;
138+
uint8_t dummy = 0x00;
139+
140+
struct spi_buf tx_bufs[] = {
141+
{ .buf = &cmd, .len = 1 },
142+
{ .buf = &dummy, .len = 1 },
143+
};
144+
145+
struct spi_buf_set tx = {
146+
.buffers = tx_bufs,
147+
.count = ARRAY_SIZE(tx_bufs)
148+
};
149+
150+
return spi_write_dt(&cfg->bus, &tx);
151+
}
152+
153+
/* Write buffer to panel */
154+
static int lpm_write(const struct device *dev, const uint16_t x, const uint16_t y,
155+
const struct display_buffer_descriptor *desc, const void *buf)
156+
{
157+
const struct lpm013m126_config *cfg = dev->config;
158+
159+
if (x != 0 || desc->width != cfg->width) {
160+
LOG_ERR("Only full-width writes supported");
161+
return -ENOTSUP;
162+
}
163+
if ((y + desc->height) > cfg->height) {
164+
LOG_ERR("Buffer out of bounds");
165+
return -EINVAL;
166+
}
167+
168+
const uint16_t *src = buf;
169+
size_t packed_len = DIV_ROUND_UP(cfg->width * LPM_BPP, 8);
170+
uint8_t linebuf[packed_len];
171+
172+
for (int row = 0; row < desc->height; row++) {
173+
lpm_pack_row(dev, linebuf, src);
174+
lpm_send_line(dev, y + row + 1, linebuf, packed_len);
175+
src += cfg->width;
176+
}
177+
178+
return 0;
179+
}
180+
181+
static void lpm_get_capabilities(const struct device *dev, struct display_capabilities *caps)
182+
{
183+
const struct lpm013m126_config *cfg = dev->config;
184+
185+
memset(caps, 0, sizeof(*caps));
186+
caps->x_resolution = cfg->width;
187+
caps->y_resolution = cfg->height;
188+
caps->supported_pixel_formats = PIXEL_FORMAT_RGB_565;
189+
caps->current_pixel_format = PIXEL_FORMAT_RGB_565;
190+
caps->screen_info = SCREEN_INFO_X_ALIGNMENT_WIDTH;
191+
}
192+
193+
static int lpm_set_pixel_format(const struct device *dev, enum display_pixel_format pf)
194+
{
195+
return (pf == PIXEL_FORMAT_RGB_565) ? 0 : -ENOTSUP;
196+
}
197+
198+
static int lpm_blanking_off(const struct device *dev)
199+
{
200+
const struct lpm013m126_config *cfg = dev->config;
201+
202+
return gpio_pin_set_dt(&cfg->disp_gpio, 1);
203+
}
204+
205+
static int lpm_blanking_on(const struct device *dev)
206+
{
207+
const struct lpm013m126_config *cfg = dev->config;
208+
209+
return gpio_pin_set_dt(&cfg->disp_gpio, 0);
210+
}
211+
212+
static int lpm_init(const struct device *dev)
213+
{
214+
const struct lpm013m126_config *cfg = dev->config;
215+
216+
struct lpm013m126_data *data = dev->data;
217+
218+
if (!spi_is_ready_dt(&cfg->bus)) {
219+
LOG_ERR("SPI not ready");
220+
return -ENODEV;
221+
}
222+
if (!gpio_is_ready_dt(&cfg->disp_gpio)) {
223+
LOG_ERR("DISP pin not ready");
224+
return -ENODEV;
225+
}
226+
if (!gpio_is_ready_dt(&cfg->extcomin_gpio)) {
227+
LOG_ERR("EXTCOMIN pin not ready");
228+
return -ENODEV;
229+
}
230+
231+
gpio_pin_configure_dt(&cfg->disp_gpio, GPIO_OUTPUT_HIGH);
232+
gpio_pin_configure_dt(&cfg->extcomin_gpio, GPIO_OUTPUT_LOW);
233+
234+
lpm_all_clear(dev);
235+
236+
data->vcom_state = 0;
237+
k_timer_init(&data->vcom_timer, lpm_vcom_toggle, NULL);
238+
k_timer_user_data_set(&data->vcom_timer, (void *)dev);
239+
k_timer_start(&data->vcom_timer, K_MSEC(1000 / cfg->extcomin_freq / 2),
240+
K_MSEC(1000 / cfg->extcomin_freq / 2));
241+
242+
return 0;
243+
}
244+
245+
static const struct display_driver_api lpm_api = {
246+
.blanking_on = lpm_blanking_on,
247+
.blanking_off = lpm_blanking_off,
248+
.write = lpm_write,
249+
.get_capabilities = lpm_get_capabilities,
250+
.set_pixel_format = lpm_set_pixel_format,
251+
};
252+
253+
#define LPM013M126_INIT(inst) \
254+
static const struct lpm013m126_config lpm_cfg_##inst = { \
255+
.bus = SPI_DT_SPEC_INST_GET(inst, SPI_OP_MODE_MASTER | \
256+
SPI_WORD_SET(8) | \
257+
SPI_TRANSFER_MSB, 0), \
258+
.disp_gpio = GPIO_DT_SPEC_INST_GET(inst, disp_gpios), \
259+
.extcomin_gpio = GPIO_DT_SPEC_INST_GET(inst, extcomin_gpios), \
260+
.extcomin_freq = DT_INST_PROP(inst, extcomin_frequency), \
261+
.width = DT_INST_PROP(inst, width), \
262+
.height = DT_INST_PROP(inst, height), \
263+
}; \
264+
static struct lpm013m126_data lpm_data_##inst; \
265+
DEVICE_DT_INST_DEFINE(inst, lpm_init, NULL, &lpm_data_##inst, \
266+
&lpm_cfg_##inst, POST_KERNEL, \
267+
CONFIG_DISPLAY_INIT_PRIORITY, &lpm_api);
268+
269+
DT_INST_FOREACH_STATUS_OKAY(LPM013M126_INIT);
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Copyright (c) 2025 Silicon Signals Pvt. Ltd.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
description: JDI LPM013M126 176x176 Color Memory LCD
5+
6+
compatible: "jdi,lpm013m126"
7+
8+
include: [spi-device.yaml, display-controller.yaml]
9+
10+
properties:
11+
disp-gpios:
12+
type: phandle-array
13+
description: |
14+
DISPLAY enable pin.
15+
Determines whether the LCD shows its memory content or a blank (white) screen.
16+
When defined, this pin is driven high during initialization.
17+
Blanking operations provided by the API can toggle this pin as needed.
18+
19+
extcomin-gpios:
20+
type: phandle-array
21+
description: |
22+
EXTCOMIN pin
23+
Provides a square wave on the EXTCOMIN pin to toggle VCOM (common electrode voltage)
24+
level.
25+
26+
extcomin-frequency:
27+
type: int
28+
description: |
29+
Frequency (Hz) to toggle the EXTCOMIN pin.

0 commit comments

Comments
 (0)