Skip to content

Commit 2571ae8

Browse files
stglooriousmmahadevan108
authored andcommitted
drivers: mipi_dbi: add support for parallel 8080/6800 modes using GPIO
Introduce GPIO-based driver for MIPI DBI class that allows MIPI DBI type A and B displays to be used on general platforms. Since each data pin GPIO can be selected individually, the bus pins are set in a loop, which has a significant negative impact on performance. When using 8-bit mode and all the data GPIO pins are on the same port, a look-up table is generated to set the whole port at once as a performance optimization. This creates a ROM overhead of about 1 kiB. Tested 8-bit 8080 mode with ILI9486 display on nRF52840-DK board. Signed-off-by: Stefan Gloor <[email protected]>
1 parent 1d8c3c0 commit 2571ae8

File tree

6 files changed

+417
-1
lines changed

6 files changed

+417
-1
lines changed

drivers/mipi_dbi/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# SPDX-License-Identifier: Apache-2.0
44

55
zephyr_sources_ifdef(CONFIG_MIPI_DBI_SPI mipi_dbi_spi.c)
6+
zephyr_sources_ifdef(CONFIG_MIPI_DBI_BITBANG mipi_dbi_bitbang.c)
67
zephyr_sources_ifdef(CONFIG_MIPI_DBI_SMARTBOND mipi_dbi_smartbond.c)
78
zephyr_sources_ifdef(CONFIG_MIPI_DBI_NXP_LCDIC mipi_dbi_nxp_lcdic.c)
89
zephyr_sources_ifdef(CONFIG_MIPI_DBI_NXP_FLEXIO_LCDIF mipi_dbi_nxp_flexio_lcdif.c)

drivers/mipi_dbi/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ config MIPI_DBI_INIT_PRIORITY
2222
MIPI-DBI Host Controllers initialization priority.
2323

2424
source "drivers/mipi_dbi/Kconfig.spi"
25+
source "drivers/mipi_dbi/Kconfig.bitbang"
2526
source "drivers/mipi_dbi/Kconfig.smartbond"
2627
source "drivers/mipi_dbi/Kconfig.nxp_lcdic"
2728
source "drivers/mipi_dbi/Kconfig.nxp_flexio_lcdif"

drivers/mipi_dbi/Kconfig.bitbang

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Copyright 2024 Stefan Gloor
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config MIPI_DBI_BITBANG
5+
bool "MIPI DBI bit banging driver"
6+
default y
7+
depends on DT_HAS_ZEPHYR_MIPI_DBI_BITBANG_ENABLED
8+
select GPIO
9+
help
10+
Enable support for MIPI DBI bit banging driver. This driver implements
11+
a MIPI-DBI mode A and B compatible controller using GPIO.
Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
/*
2+
* MIPI DBI Type A and B driver using GPIO
3+
*
4+
* Copyright 2024 Stefan Gloor
5+
*
6+
* SPDX-License-Identifier: Apache-2.0
7+
*/
8+
9+
#define DT_DRV_COMPAT zephyr_mipi_dbi_bitbang
10+
11+
#include <zephyr/drivers/mipi_dbi.h>
12+
#include <zephyr/drivers/gpio.h>
13+
14+
#include <zephyr/logging/log.h>
15+
LOG_MODULE_REGISTER(mipi_dbi_bitbang, CONFIG_MIPI_DBI_LOG_LEVEL);
16+
17+
/* The MIPI DBI spec allows 8, 9, and 16 bits */
18+
#define MIPI_DBI_MAX_DATA_BUS_WIDTH 16
19+
20+
/* Compile in a data bus LUT for improved performance if at least one instance uses an 8-bit bus */
21+
#define _8_BIT_MODE_PRESENT(n) (DT_INST_PROP_LEN(n, data_gpios) == 8) |
22+
#define MIPI_DBI_8_BIT_MODE DT_INST_FOREACH_STATUS_OKAY(_8_BIT_MODE_PRESENT) 0
23+
24+
struct mipi_dbi_bitbang_config {
25+
/* Parallel 8080/6800 data GPIOs */
26+
const struct gpio_dt_spec data[MIPI_DBI_MAX_DATA_BUS_WIDTH];
27+
const uint8_t data_bus_width;
28+
29+
/* Read (type B) GPIO */
30+
const struct gpio_dt_spec rd;
31+
32+
/* Write (type B) or Read/!Write (type A) GPIO */
33+
const struct gpio_dt_spec wr;
34+
35+
/* Enable/strobe GPIO (type A) */
36+
const struct gpio_dt_spec e;
37+
38+
/* Chip-select GPIO */
39+
const struct gpio_dt_spec cs;
40+
41+
/* Command/Data GPIO */
42+
const struct gpio_dt_spec cmd_data;
43+
44+
/* Reset GPIO */
45+
const struct gpio_dt_spec reset;
46+
47+
#if MIPI_DBI_8_BIT_MODE
48+
/* Data GPIO remap look-up table. Valid if mipi_dbi_bitbang_data.single_port is set */
49+
const uint32_t data_lut[256];
50+
51+
/* Mask of all data pins. Valid if mipi_dbi_bitbang_data.single_port is set */
52+
const uint32_t data_mask;
53+
#endif
54+
};
55+
56+
struct mipi_dbi_bitbang_data {
57+
struct k_mutex lock;
58+
59+
#if MIPI_DBI_8_BIT_MODE
60+
/* Indicates whether all data GPIO pins are on the same port and the data LUT is used. */
61+
bool single_port;
62+
63+
/* Data GPIO port device. Valid if mipi_dbi_bitbang_data.single_port is set */
64+
const struct device *data_port;
65+
#endif
66+
};
67+
68+
static inline void mipi_dbi_bitbang_set_data_gpios(const struct mipi_dbi_bitbang_config *config,
69+
struct mipi_dbi_bitbang_data *data,
70+
uint32_t value)
71+
{
72+
#if MIPI_DBI_8_BIT_MODE
73+
if (data->single_port) {
74+
gpio_port_set_masked(data->data_port, config->data_mask, config->data_lut[value]);
75+
} else {
76+
#endif
77+
for (int i = 0; i < config->data_bus_width; i++) {
78+
gpio_pin_set_dt(&config->data[i], (value & (1 << i)) != 0);
79+
}
80+
#if MIPI_DBI_8_BIT_MODE
81+
}
82+
#endif
83+
}
84+
85+
static int mipi_dbi_bitbang_write_helper(const struct device *dev,
86+
const struct mipi_dbi_config *dbi_config, bool cmd_present,
87+
uint8_t cmd, const uint8_t *data_buf, size_t len)
88+
{
89+
const struct mipi_dbi_bitbang_config *config = dev->config;
90+
struct mipi_dbi_bitbang_data *data = dev->data;
91+
int ret = 0;
92+
uint8_t value;
93+
94+
ret = k_mutex_lock(&data->lock, K_FOREVER);
95+
if (ret < 0) {
96+
return ret;
97+
}
98+
99+
switch (dbi_config->mode) {
100+
case MIPI_DBI_MODE_8080_BUS_8_BIT:
101+
case MIPI_DBI_MODE_8080_BUS_9_BIT:
102+
case MIPI_DBI_MODE_8080_BUS_16_BIT:
103+
gpio_pin_set_dt(&config->cs, 1);
104+
if (cmd_present) {
105+
gpio_pin_set_dt(&config->wr, 0);
106+
gpio_pin_set_dt(&config->cmd_data, 0);
107+
mipi_dbi_bitbang_set_data_gpios(config, data, cmd);
108+
gpio_pin_set_dt(&config->wr, 1);
109+
}
110+
if (len > 0) {
111+
gpio_pin_set_dt(&config->cmd_data, 1);
112+
while (len > 0) {
113+
value = *(data_buf++);
114+
gpio_pin_set_dt(&config->wr, 0);
115+
mipi_dbi_bitbang_set_data_gpios(config, data, value);
116+
gpio_pin_set_dt(&config->wr, 1);
117+
len--;
118+
}
119+
}
120+
gpio_pin_set_dt(&config->cs, 0);
121+
break;
122+
123+
/* Clocked E mode */
124+
case MIPI_DBI_MODE_6800_BUS_8_BIT:
125+
case MIPI_DBI_MODE_6800_BUS_9_BIT:
126+
case MIPI_DBI_MODE_6800_BUS_16_BIT:
127+
gpio_pin_set_dt(&config->cs, 1);
128+
gpio_pin_set_dt(&config->wr, 0);
129+
if (cmd_present) {
130+
gpio_pin_set_dt(&config->e, 1);
131+
gpio_pin_set_dt(&config->cmd_data, 0);
132+
mipi_dbi_bitbang_set_data_gpios(config, data, cmd);
133+
gpio_pin_set_dt(&config->e, 0);
134+
}
135+
if (len > 0) {
136+
gpio_pin_set_dt(&config->cmd_data, 1);
137+
while (len > 0) {
138+
value = *(data_buf++);
139+
gpio_pin_set_dt(&config->e, 1);
140+
mipi_dbi_bitbang_set_data_gpios(config, data, value);
141+
gpio_pin_set_dt(&config->e, 0);
142+
len--;
143+
}
144+
}
145+
gpio_pin_set_dt(&config->cs, 0);
146+
break;
147+
148+
default:
149+
LOG_ERR("MIPI DBI mode %u is not supported.", dbi_config->mode);
150+
ret = -ENOTSUP;
151+
}
152+
153+
k_mutex_unlock(&data->lock);
154+
return ret;
155+
}
156+
157+
static int mipi_dbi_bitbang_command_write(const struct device *dev,
158+
const struct mipi_dbi_config *dbi_config, uint8_t cmd,
159+
const uint8_t *data_buf, size_t len)
160+
{
161+
return mipi_dbi_bitbang_write_helper(dev, dbi_config, true, cmd, data_buf, len);
162+
}
163+
164+
static int mipi_dbi_bitbang_write_display(const struct device *dev,
165+
const struct mipi_dbi_config *dbi_config,
166+
const uint8_t *framebuf,
167+
struct display_buffer_descriptor *desc,
168+
enum display_pixel_format pixfmt)
169+
{
170+
ARG_UNUSED(pixfmt);
171+
172+
return mipi_dbi_bitbang_write_helper(dev, dbi_config, false, 0x0, framebuf, desc->buf_size);
173+
}
174+
175+
static int mipi_dbi_bitbang_reset(const struct device *dev, k_timeout_t delay)
176+
{
177+
const struct mipi_dbi_bitbang_config *config = dev->config;
178+
int ret;
179+
180+
LOG_DBG("Performing hw reset.");
181+
182+
ret = gpio_pin_set_dt(&config->reset, 1);
183+
if (ret < 0) {
184+
return ret;
185+
}
186+
k_sleep(delay);
187+
return gpio_pin_set_dt(&config->reset, 0);
188+
}
189+
190+
static int mipi_dbi_bitbang_init(const struct device *dev)
191+
{
192+
const struct mipi_dbi_bitbang_config *config = dev->config;
193+
const char *failed_pin = NULL;
194+
int ret = 0;
195+
#if MIPI_DBI_8_BIT_MODE
196+
struct mipi_dbi_bitbang_data *data = dev->data;
197+
#endif
198+
199+
if (gpio_is_ready_dt(&config->cmd_data)) {
200+
ret = gpio_pin_configure_dt(&config->cmd_data, GPIO_OUTPUT_ACTIVE);
201+
if (ret < 0) {
202+
failed_pin = "cmd_data";
203+
goto fail;
204+
}
205+
gpio_pin_set_dt(&config->cmd_data, 0);
206+
}
207+
if (gpio_is_ready_dt(&config->rd)) {
208+
gpio_pin_configure_dt(&config->rd, GPIO_OUTPUT_ACTIVE);
209+
/* Don't emit an error because this pin is unused in type A */
210+
gpio_pin_set_dt(&config->rd, 1);
211+
}
212+
if (gpio_is_ready_dt(&config->wr)) {
213+
ret = gpio_pin_configure_dt(&config->wr, GPIO_OUTPUT_ACTIVE);
214+
if (ret < 0) {
215+
failed_pin = "wr";
216+
goto fail;
217+
}
218+
gpio_pin_set_dt(&config->wr, 1);
219+
}
220+
if (gpio_is_ready_dt(&config->e)) {
221+
gpio_pin_configure_dt(&config->e, GPIO_OUTPUT_ACTIVE);
222+
/* Don't emit an error because this pin is unused in type B */
223+
gpio_pin_set_dt(&config->e, 0);
224+
}
225+
if (gpio_is_ready_dt(&config->cs)) {
226+
ret = gpio_pin_configure_dt(&config->cs, GPIO_OUTPUT_ACTIVE);
227+
if (ret < 0) {
228+
failed_pin = "cs";
229+
goto fail;
230+
}
231+
gpio_pin_set_dt(&config->cs, 0);
232+
}
233+
if (gpio_is_ready_dt(&config->reset)) {
234+
ret = gpio_pin_configure_dt(&config->reset, GPIO_OUTPUT_ACTIVE);
235+
if (ret < 0) {
236+
failed_pin = "reset";
237+
goto fail;
238+
}
239+
gpio_pin_set_dt(&config->reset, 0);
240+
}
241+
for (int i = 0; i < config->data_bus_width; i++) {
242+
if (gpio_is_ready_dt(&config->data[i])) {
243+
ret = gpio_pin_configure_dt(&config->data[i], GPIO_OUTPUT_ACTIVE);
244+
if (ret < 0) {
245+
failed_pin = "data";
246+
goto fail;
247+
}
248+
gpio_pin_set_dt(&config->data[i], 0);
249+
}
250+
}
251+
252+
#if MIPI_DBI_8_BIT_MODE
253+
/* To optimize performance, we test whether all the data pins are
254+
* on the same port. If they are, we can set the whole port in one go
255+
* instead of setting each pin individually.
256+
* For 8-bit mode only because LUT size grows exponentially.
257+
*/
258+
if (config->data_bus_width == 8) {
259+
data->single_port = true;
260+
data->data_port = config->data[0].port;
261+
for (int i = 1; i < config->data_bus_width; i++) {
262+
if (data->data_port != config->data[i].port) {
263+
data->single_port = false;
264+
}
265+
}
266+
}
267+
if (data->single_port) {
268+
LOG_DBG("LUT optimization enabled. data_mask=0x%x", config->data_mask);
269+
}
270+
#endif
271+
272+
return ret;
273+
fail:
274+
LOG_ERR("Failed to configure %s GPIO pin.", failed_pin);
275+
return ret;
276+
}
277+
278+
static const struct mipi_dbi_driver_api mipi_dbi_bitbang_driver_api = {
279+
.reset = mipi_dbi_bitbang_reset,
280+
.command_write = mipi_dbi_bitbang_command_write,
281+
.write_display = mipi_dbi_bitbang_write_display
282+
};
283+
284+
/* This macro is repeatedly called by LISTIFY() at compile-time to generate the data bus LUT */
285+
#define LUT_GEN(i, n) (((i & (1 << 0)) ? (1 << DT_INST_GPIO_PIN_BY_IDX(n, data_gpios, 0)) : 0) | \
286+
((i & (1 << 1)) ? (1 << DT_INST_GPIO_PIN_BY_IDX(n, data_gpios, 1)) : 0) | \
287+
((i & (1 << 2)) ? (1 << DT_INST_GPIO_PIN_BY_IDX(n, data_gpios, 2)) : 0) | \
288+
((i & (1 << 3)) ? (1 << DT_INST_GPIO_PIN_BY_IDX(n, data_gpios, 3)) : 0) | \
289+
((i & (1 << 4)) ? (1 << DT_INST_GPIO_PIN_BY_IDX(n, data_gpios, 4)) : 0) | \
290+
((i & (1 << 5)) ? (1 << DT_INST_GPIO_PIN_BY_IDX(n, data_gpios, 5)) : 0) | \
291+
((i & (1 << 6)) ? (1 << DT_INST_GPIO_PIN_BY_IDX(n, data_gpios, 6)) : 0) | \
292+
((i & (1 << 7)) ? (1 << DT_INST_GPIO_PIN_BY_IDX(n, data_gpios, 7)) : 0))
293+
294+
/* If at least one instance has an 8-bit bus, add a data look-up table to the read-only config.
295+
* Whether or not it is valid and actually used for a particular instance is decided at runtime
296+
* and stored in the instance's mipi_dbi_bitbang_data.single_port.
297+
*/
298+
#if MIPI_DBI_8_BIT_MODE
299+
#define DATA_LUT_OPTIMIZATION(n) \
300+
.data_lut = { LISTIFY(256, LUT_GEN, (,), n) }, \
301+
.data_mask = ((1 << DT_INST_GPIO_PIN_BY_IDX(n, data_gpios, 0)) | \
302+
(1 << DT_INST_GPIO_PIN_BY_IDX(n, data_gpios, 1)) | \
303+
(1 << DT_INST_GPIO_PIN_BY_IDX(n, data_gpios, 2)) | \
304+
(1 << DT_INST_GPIO_PIN_BY_IDX(n, data_gpios, 3)) | \
305+
(1 << DT_INST_GPIO_PIN_BY_IDX(n, data_gpios, 4)) | \
306+
(1 << DT_INST_GPIO_PIN_BY_IDX(n, data_gpios, 5)) | \
307+
(1 << DT_INST_GPIO_PIN_BY_IDX(n, data_gpios, 6)) | \
308+
(1 << DT_INST_GPIO_PIN_BY_IDX(n, data_gpios, 7)))
309+
#else
310+
#define DATA_LUT_OPTIMIZATION(n)
311+
#endif
312+
313+
#define MIPI_DBI_BITBANG_INIT(n) \
314+
static const struct mipi_dbi_bitbang_config mipi_dbi_bitbang_config_##n = { \
315+
.data = {GPIO_DT_SPEC_INST_GET_BY_IDX_OR(n, data_gpios, 0, {0}), \
316+
GPIO_DT_SPEC_INST_GET_BY_IDX_OR(n, data_gpios, 1, {0}), \
317+
GPIO_DT_SPEC_INST_GET_BY_IDX_OR(n, data_gpios, 2, {0}), \
318+
GPIO_DT_SPEC_INST_GET_BY_IDX_OR(n, data_gpios, 3, {0}), \
319+
GPIO_DT_SPEC_INST_GET_BY_IDX_OR(n, data_gpios, 4, {0}), \
320+
GPIO_DT_SPEC_INST_GET_BY_IDX_OR(n, data_gpios, 5, {0}), \
321+
GPIO_DT_SPEC_INST_GET_BY_IDX_OR(n, data_gpios, 6, {0}), \
322+
GPIO_DT_SPEC_INST_GET_BY_IDX_OR(n, data_gpios, 7, {0}), \
323+
GPIO_DT_SPEC_INST_GET_BY_IDX_OR(n, data_gpios, 8, {0}), \
324+
GPIO_DT_SPEC_INST_GET_BY_IDX_OR(n, data_gpios, 9, {0}), \
325+
GPIO_DT_SPEC_INST_GET_BY_IDX_OR(n, data_gpios, 10, {0}), \
326+
GPIO_DT_SPEC_INST_GET_BY_IDX_OR(n, data_gpios, 11, {0}), \
327+
GPIO_DT_SPEC_INST_GET_BY_IDX_OR(n, data_gpios, 12, {0}), \
328+
GPIO_DT_SPEC_INST_GET_BY_IDX_OR(n, data_gpios, 13, {0}), \
329+
GPIO_DT_SPEC_INST_GET_BY_IDX_OR(n, data_gpios, 14, {0}), \
330+
GPIO_DT_SPEC_INST_GET_BY_IDX_OR(n, data_gpios, 15, {0})}, \
331+
.data_bus_width = DT_INST_PROP_LEN(n, data_gpios), \
332+
.rd = GPIO_DT_SPEC_INST_GET_OR(n, rd_gpios, {}), \
333+
.wr = GPIO_DT_SPEC_INST_GET_OR(n, wr_gpios, {}), \
334+
.e = GPIO_DT_SPEC_INST_GET_OR(n, e_gpios, {}), \
335+
.cs = GPIO_DT_SPEC_INST_GET_OR(n, cs_gpios, {}), \
336+
.cmd_data = GPIO_DT_SPEC_INST_GET_OR(n, dc_gpios, {}), \
337+
.reset = GPIO_DT_SPEC_INST_GET_OR(n, reset_gpios, {}), \
338+
DATA_LUT_OPTIMIZATION(n) \
339+
}; \
340+
BUILD_ASSERT(DT_INST_PROP_LEN(n, data_gpios) < MIPI_DBI_MAX_DATA_BUS_WIDTH, \
341+
"Number of data GPIOs in DT exceeds MIPI_DBI_MAX_DATA_BUS_WIDTH"); \
342+
static struct mipi_dbi_bitbang_data mipi_dbi_bitbang_data_##n; \
343+
DEVICE_DT_INST_DEFINE(n, mipi_dbi_bitbang_init, NULL, &mipi_dbi_bitbang_data_##n, \
344+
&mipi_dbi_bitbang_config_##n, POST_KERNEL, \
345+
CONFIG_MIPI_DBI_INIT_PRIORITY, &mipi_dbi_bitbang_driver_api);
346+
347+
DT_INST_FOREACH_STATUS_OKAY(MIPI_DBI_BITBANG_INIT)

0 commit comments

Comments
 (0)