This macro doesn't accept a delay parameter anymore (well, it does, but it's deprecated and will trigger build warnings). Just remove it from the places that were still passing it. Signed-off-by: Johan Hedberg <johan.hedberg@silabs.com>
270 lines
6.9 KiB
C
270 lines
6.9 KiB
C
/*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Copyright (c) 2025 Silicon Signals Pvt. Ltd.
|
|
* Author: Bhavin Sharma <bhavin.sharma@siliconsignals.io>
|
|
* Author: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io>
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT jdi_lpm013m126
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(lpm013m126, CONFIG_DISPLAY_LOG_LEVEL);
|
|
|
|
#include <string.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/drivers/display.h>
|
|
#include <zephyr/init.h>
|
|
#include <zephyr/drivers/gpio.h>
|
|
#include <zephyr/drivers/spi.h>
|
|
#include <zephyr/kernel.h>
|
|
|
|
/* Panel properties */
|
|
#define LPM_BPP 3
|
|
|
|
/* Command bytes */
|
|
#define LPM_WRITELINE_CMD 0x80
|
|
#define LPM_ALLCLEAR_CMD 0x20
|
|
|
|
struct lpm013m126_data {
|
|
struct k_timer vcom_timer;
|
|
int vcom_state;
|
|
};
|
|
|
|
struct lpm013m126_config {
|
|
struct spi_dt_spec bus;
|
|
struct gpio_dt_spec disp_gpio;
|
|
struct gpio_dt_spec extcomin_gpio;
|
|
uint32_t extcomin_freq;
|
|
uint8_t width;
|
|
uint8_t height;
|
|
};
|
|
|
|
/* bit-reversal of row address */
|
|
static inline uint8_t bitrev8(uint8_t x)
|
|
{
|
|
x = ((x & 0xF0) >> 4) | ((x & 0x0F) << 4);
|
|
x = ((x & 0xCC) >> 2) | ((x & 0x33) << 2);
|
|
x = ((x & 0xAA) >> 1) | ((x & 0x55) << 1);
|
|
return x;
|
|
}
|
|
|
|
/* The native format (1 bit per channel) is rather unusual. LVGL and
|
|
* other libraries don't support it. In addition, the format is not
|
|
* very convenient for the application. So, we prefer to advertise
|
|
* a well known format and convert it under the hood.
|
|
* A native implementation of this format would allow to save memory
|
|
* for the frame buffer (11kB instead of 30kB).
|
|
*/
|
|
static inline uint8_t rgb565_to_rgb3(uint16_t color)
|
|
{
|
|
uint8_t r = FIELD_GET(0xF800, color);
|
|
uint8_t g = FIELD_GET(0x07E0, color);
|
|
uint8_t b = FIELD_GET(0x001F, color);
|
|
|
|
r >>= 4;
|
|
g >>= 5;
|
|
b >>= 4;
|
|
return (r << 2) | (g << 1) | b;
|
|
}
|
|
|
|
/* Pack one row of RGB565 pixels into panel format */
|
|
static void lpm_pack_row(const struct device *dev, uint8_t *dst, const uint16_t *src)
|
|
{
|
|
const struct lpm013m126_config *cfg = dev->config;
|
|
|
|
int bitpos = 0;
|
|
uint8_t byte = 0;
|
|
|
|
for (int x = 0; x < cfg->width; x++) {
|
|
uint8_t pix = rgb565_to_rgb3(src[x]);
|
|
|
|
for (int b = 2; b >= 0; b--) {
|
|
byte |= ((pix >> b) & 0x1) << (7 - bitpos);
|
|
bitpos++;
|
|
|
|
if (bitpos == 8) {
|
|
*dst++ = byte;
|
|
bitpos = 0;
|
|
byte = 0;
|
|
}
|
|
}
|
|
}
|
|
/* flush final partial byte if needed */
|
|
if (bitpos) {
|
|
*dst++ = byte;
|
|
}
|
|
}
|
|
|
|
/* VCOM toggle callback */
|
|
static void lpm_vcom_toggle(struct k_timer *timer)
|
|
{
|
|
const struct device *dev = k_timer_user_data_get(timer);
|
|
const struct lpm013m126_config *cfg = dev->config;
|
|
struct lpm013m126_data *data = dev->data;
|
|
|
|
data->vcom_state = !data->vcom_state;
|
|
gpio_pin_set_dt(&cfg->extcomin_gpio, data->vcom_state);
|
|
}
|
|
|
|
/* Send a line to the display */
|
|
static int lpm_send_line(const struct device *dev, uint8_t line, uint8_t *buf, size_t len)
|
|
{
|
|
const struct lpm013m126_config *cfg = dev->config;
|
|
|
|
uint8_t cmd = LPM_WRITELINE_CMD;
|
|
uint8_t addr = bitrev8(line);
|
|
|
|
struct spi_buf tx_bufs[] = {
|
|
{ .buf = &cmd, .len = 1 },
|
|
{ .buf = &addr, .len = 1 },
|
|
{ .buf = buf, .len = len },
|
|
};
|
|
|
|
struct spi_buf_set tx = {
|
|
.buffers = tx_bufs,
|
|
.count = ARRAY_SIZE(tx_bufs)
|
|
};
|
|
|
|
return spi_write_dt(&cfg->bus, &tx);
|
|
}
|
|
|
|
/* Send all-clear command */
|
|
static int lpm_all_clear(const struct device *dev)
|
|
{
|
|
const struct lpm013m126_config *cfg = dev->config;
|
|
|
|
uint8_t cmd = LPM_ALLCLEAR_CMD;
|
|
uint8_t dummy = 0x00;
|
|
|
|
struct spi_buf tx_bufs[] = {
|
|
{ .buf = &cmd, .len = 1 },
|
|
{ .buf = &dummy, .len = 1 },
|
|
};
|
|
|
|
struct spi_buf_set tx = {
|
|
.buffers = tx_bufs,
|
|
.count = ARRAY_SIZE(tx_bufs)
|
|
};
|
|
|
|
return spi_write_dt(&cfg->bus, &tx);
|
|
}
|
|
|
|
/* Write buffer to panel */
|
|
static int lpm_write(const struct device *dev, const uint16_t x, const uint16_t y,
|
|
const struct display_buffer_descriptor *desc, const void *buf)
|
|
{
|
|
const struct lpm013m126_config *cfg = dev->config;
|
|
|
|
if (x != 0 || desc->width != cfg->width) {
|
|
LOG_ERR("Only full-width writes supported");
|
|
return -ENOTSUP;
|
|
}
|
|
if ((y + desc->height) > cfg->height) {
|
|
LOG_ERR("Buffer out of bounds");
|
|
return -EINVAL;
|
|
}
|
|
|
|
const uint16_t *src = buf;
|
|
size_t packed_len = DIV_ROUND_UP(cfg->width * LPM_BPP, 8);
|
|
uint8_t linebuf[packed_len];
|
|
|
|
for (int row = 0; row < desc->height; row++) {
|
|
lpm_pack_row(dev, linebuf, src);
|
|
lpm_send_line(dev, y + row + 1, linebuf, packed_len);
|
|
src += cfg->width;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void lpm_get_capabilities(const struct device *dev, struct display_capabilities *caps)
|
|
{
|
|
const struct lpm013m126_config *cfg = dev->config;
|
|
|
|
memset(caps, 0, sizeof(*caps));
|
|
caps->x_resolution = cfg->width;
|
|
caps->y_resolution = cfg->height;
|
|
caps->supported_pixel_formats = PIXEL_FORMAT_RGB_565;
|
|
caps->current_pixel_format = PIXEL_FORMAT_RGB_565;
|
|
caps->screen_info = SCREEN_INFO_X_ALIGNMENT_WIDTH;
|
|
}
|
|
|
|
static int lpm_set_pixel_format(const struct device *dev, enum display_pixel_format pf)
|
|
{
|
|
return (pf == PIXEL_FORMAT_RGB_565) ? 0 : -ENOTSUP;
|
|
}
|
|
|
|
static int lpm_blanking_off(const struct device *dev)
|
|
{
|
|
const struct lpm013m126_config *cfg = dev->config;
|
|
|
|
return gpio_pin_set_dt(&cfg->disp_gpio, 1);
|
|
}
|
|
|
|
static int lpm_blanking_on(const struct device *dev)
|
|
{
|
|
const struct lpm013m126_config *cfg = dev->config;
|
|
|
|
return gpio_pin_set_dt(&cfg->disp_gpio, 0);
|
|
}
|
|
|
|
static int lpm_init(const struct device *dev)
|
|
{
|
|
const struct lpm013m126_config *cfg = dev->config;
|
|
|
|
struct lpm013m126_data *data = dev->data;
|
|
|
|
if (!spi_is_ready_dt(&cfg->bus)) {
|
|
LOG_ERR("SPI not ready");
|
|
return -ENODEV;
|
|
}
|
|
if (!gpio_is_ready_dt(&cfg->disp_gpio)) {
|
|
LOG_ERR("DISP pin not ready");
|
|
return -ENODEV;
|
|
}
|
|
if (!gpio_is_ready_dt(&cfg->extcomin_gpio)) {
|
|
LOG_ERR("EXTCOMIN pin not ready");
|
|
return -ENODEV;
|
|
}
|
|
|
|
gpio_pin_configure_dt(&cfg->disp_gpio, GPIO_OUTPUT_HIGH);
|
|
gpio_pin_configure_dt(&cfg->extcomin_gpio, GPIO_OUTPUT_LOW);
|
|
|
|
lpm_all_clear(dev);
|
|
|
|
data->vcom_state = 0;
|
|
k_timer_init(&data->vcom_timer, lpm_vcom_toggle, NULL);
|
|
k_timer_user_data_set(&data->vcom_timer, (void *)dev);
|
|
k_timer_start(&data->vcom_timer, K_MSEC(1000 / cfg->extcomin_freq / 2),
|
|
K_MSEC(1000 / cfg->extcomin_freq / 2));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct display_driver_api lpm_api = {
|
|
.blanking_on = lpm_blanking_on,
|
|
.blanking_off = lpm_blanking_off,
|
|
.write = lpm_write,
|
|
.get_capabilities = lpm_get_capabilities,
|
|
.set_pixel_format = lpm_set_pixel_format,
|
|
};
|
|
|
|
#define LPM013M126_INIT(inst) \
|
|
static const struct lpm013m126_config lpm_cfg_##inst = { \
|
|
.bus = SPI_DT_SPEC_INST_GET(inst, SPI_OP_MODE_MASTER | \
|
|
SPI_WORD_SET(8) | \
|
|
SPI_TRANSFER_MSB), \
|
|
.disp_gpio = GPIO_DT_SPEC_INST_GET(inst, disp_gpios), \
|
|
.extcomin_gpio = GPIO_DT_SPEC_INST_GET(inst, extcomin_gpios), \
|
|
.extcomin_freq = DT_INST_PROP(inst, extcomin_frequency), \
|
|
.width = DT_INST_PROP(inst, width), \
|
|
.height = DT_INST_PROP(inst, height), \
|
|
}; \
|
|
static struct lpm013m126_data lpm_data_##inst; \
|
|
DEVICE_DT_INST_DEFINE(inst, lpm_init, NULL, &lpm_data_##inst, \
|
|
&lpm_cfg_##inst, POST_KERNEL, \
|
|
CONFIG_DISPLAY_INIT_PRIORITY, &lpm_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(LPM013M126_INIT);
|