613 lines
18 KiB
C
613 lines
18 KiB
C
/*
|
|
* Copyright 2023, NXP
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT raydium_rm67162
|
|
|
|
#include <zephyr/drivers/display.h>
|
|
#include <zephyr/drivers/mipi_dsi.h>
|
|
#include <zephyr/drivers/mipi_dsi/mipi_dsi_mcux_2l.h>
|
|
#include <zephyr/drivers/gpio.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/pm/policy.h>
|
|
#include <zephyr/pm/device.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
|
|
LOG_MODULE_REGISTER(rm67162, CONFIG_DISPLAY_LOG_LEVEL);
|
|
|
|
/*
|
|
* These commands are taken from NXP's MCUXpresso SDK.
|
|
* Additional documentation is added where possible, but the
|
|
* Manufacture command set pages are not described in the datasheet
|
|
*/
|
|
static const struct {
|
|
uint8_t cmd;
|
|
uint8_t param;
|
|
} rm67162_init_400x392[] = {
|
|
/* CMD Mode switch, select manufacture command set page 0 */
|
|
{.cmd = 0xFE, .param = 0x01},
|
|
{.cmd = 0x06, .param = 0x62},
|
|
{.cmd = 0x0E, .param = 0x80},
|
|
{.cmd = 0x0F, .param = 0x80},
|
|
{.cmd = 0x10, .param = 0x71},
|
|
{.cmd = 0x13, .param = 0x81},
|
|
{.cmd = 0x14, .param = 0x81},
|
|
{.cmd = 0x15, .param = 0x82},
|
|
{.cmd = 0x16, .param = 0x82},
|
|
{.cmd = 0x18, .param = 0x88},
|
|
{.cmd = 0x19, .param = 0x55},
|
|
{.cmd = 0x1A, .param = 0x10},
|
|
{.cmd = 0x1C, .param = 0x99},
|
|
{.cmd = 0x1D, .param = 0x03},
|
|
{.cmd = 0x1E, .param = 0x03},
|
|
{.cmd = 0x1F, .param = 0x03},
|
|
{.cmd = 0x20, .param = 0x03},
|
|
{.cmd = 0x25, .param = 0x03},
|
|
{.cmd = 0x26, .param = 0x8D},
|
|
{.cmd = 0x2A, .param = 0x03},
|
|
{.cmd = 0x2B, .param = 0x8D},
|
|
{.cmd = 0x36, .param = 0x00},
|
|
{.cmd = 0x37, .param = 0x10},
|
|
{.cmd = 0x3A, .param = 0x00},
|
|
{.cmd = 0x3B, .param = 0x00},
|
|
{.cmd = 0x3D, .param = 0x20},
|
|
{.cmd = 0x3F, .param = 0x3A},
|
|
{.cmd = 0x40, .param = 0x30},
|
|
{.cmd = 0x41, .param = 0x30},
|
|
{.cmd = 0x42, .param = 0x33},
|
|
{.cmd = 0x43, .param = 0x22},
|
|
{.cmd = 0x44, .param = 0x11},
|
|
{.cmd = 0x45, .param = 0x66},
|
|
{.cmd = 0x46, .param = 0x55},
|
|
{.cmd = 0x47, .param = 0x44},
|
|
{.cmd = 0x4C, .param = 0x33},
|
|
{.cmd = 0x4D, .param = 0x22},
|
|
{.cmd = 0x4E, .param = 0x11},
|
|
{.cmd = 0x4F, .param = 0x66},
|
|
{.cmd = 0x50, .param = 0x55},
|
|
{.cmd = 0x51, .param = 0x44},
|
|
{.cmd = 0x57, .param = 0xB3},
|
|
{.cmd = 0x6B, .param = 0x19},
|
|
{.cmd = 0x70, .param = 0x55},
|
|
{.cmd = 0x74, .param = 0x0C},
|
|
|
|
/* VGMP/VGSP Voltage Control (select manufacture command set page 1 ) */
|
|
{.cmd = 0xFE, .param = 0x02},
|
|
{.cmd = 0x9B, .param = 0x40},
|
|
{.cmd = 0x9C, .param = 0x67},
|
|
{.cmd = 0x9D, .param = 0x20},
|
|
|
|
/* VGMP/VGSP Voltage Control (select manufacture command set page 2 ) */
|
|
{.cmd = 0xFE, .param = 0x03},
|
|
{.cmd = 0x9B, .param = 0x40},
|
|
{.cmd = 0x9C, .param = 0x67},
|
|
{.cmd = 0x9D, .param = 0x20},
|
|
|
|
/* VSR Command (select manufacture command set page 3 ) */
|
|
{.cmd = 0xFE, .param = 0x04},
|
|
{.cmd = 0x5D, .param = 0x10},
|
|
|
|
/* VSR1 Timing Set (select manufacture command set page 3 ) */
|
|
{.cmd = 0xFE, .param = 0x04},
|
|
{.cmd = 0x00, .param = 0x8D},
|
|
{.cmd = 0x01, .param = 0x00},
|
|
{.cmd = 0x02, .param = 0x01},
|
|
{.cmd = 0x03, .param = 0x01},
|
|
{.cmd = 0x04, .param = 0x10},
|
|
{.cmd = 0x05, .param = 0x01},
|
|
{.cmd = 0x06, .param = 0xA7},
|
|
{.cmd = 0x07, .param = 0x20},
|
|
{.cmd = 0x08, .param = 0x00},
|
|
|
|
/* VSR2 Timing Set (select manufacture command set page 3 ) */
|
|
{.cmd = 0xFE, .param = 0x04},
|
|
{.cmd = 0x09, .param = 0xC2},
|
|
{.cmd = 0x0A, .param = 0x00},
|
|
{.cmd = 0x0B, .param = 0x02},
|
|
{.cmd = 0x0C, .param = 0x01},
|
|
{.cmd = 0x0D, .param = 0x40},
|
|
{.cmd = 0x0E, .param = 0x06},
|
|
{.cmd = 0x0F, .param = 0x01},
|
|
{.cmd = 0x10, .param = 0xA7},
|
|
{.cmd = 0x11, .param = 0x00},
|
|
|
|
/* VSR3 Timing Set (select manufacture command set page 3 ) */
|
|
{.cmd = 0xFE, .param = 0x04},
|
|
{.cmd = 0x12, .param = 0xC2},
|
|
{.cmd = 0x13, .param = 0x00},
|
|
{.cmd = 0x14, .param = 0x02},
|
|
{.cmd = 0x15, .param = 0x01},
|
|
{.cmd = 0x16, .param = 0x40},
|
|
{.cmd = 0x17, .param = 0x07},
|
|
{.cmd = 0x18, .param = 0x01},
|
|
{.cmd = 0x19, .param = 0xA7},
|
|
{.cmd = 0x1A, .param = 0x00},
|
|
|
|
/* VSR4 Timing Set (select manufacture command set page 3 ) */
|
|
{.cmd = 0xFE, .param = 0x04},
|
|
{.cmd = 0x1B, .param = 0x82},
|
|
{.cmd = 0x1C, .param = 0x00},
|
|
{.cmd = 0x1D, .param = 0xFF},
|
|
{.cmd = 0x1E, .param = 0x05},
|
|
{.cmd = 0x1F, .param = 0x60},
|
|
{.cmd = 0x20, .param = 0x02},
|
|
{.cmd = 0x21, .param = 0x01},
|
|
{.cmd = 0x22, .param = 0x7C},
|
|
{.cmd = 0x23, .param = 0x00},
|
|
|
|
/* VSR5 Timing Set (select manufacture command set page 3 ) */
|
|
{.cmd = 0xFE, .param = 0x04},
|
|
{.cmd = 0x24, .param = 0xC2},
|
|
{.cmd = 0x25, .param = 0x00},
|
|
{.cmd = 0x26, .param = 0x04},
|
|
{.cmd = 0x27, .param = 0x02},
|
|
{.cmd = 0x28, .param = 0x70},
|
|
{.cmd = 0x29, .param = 0x05},
|
|
{.cmd = 0x2A, .param = 0x74},
|
|
{.cmd = 0x2B, .param = 0x8D},
|
|
{.cmd = 0x2D, .param = 0x00},
|
|
|
|
/* VSR6 Timing Set (select manufacture command set page 3 ) */
|
|
{.cmd = 0xFE, .param = 0x04},
|
|
{.cmd = 0x2F, .param = 0xC2},
|
|
{.cmd = 0x30, .param = 0x00},
|
|
{.cmd = 0x31, .param = 0x04},
|
|
{.cmd = 0x32, .param = 0x02},
|
|
{.cmd = 0x33, .param = 0x70},
|
|
{.cmd = 0x34, .param = 0x07},
|
|
{.cmd = 0x35, .param = 0x74},
|
|
{.cmd = 0x36, .param = 0x8D},
|
|
{.cmd = 0x37, .param = 0x00},
|
|
|
|
/* VSR Marping command (select manufacture command set page 3 ) */
|
|
{.cmd = 0xFE, .param = 0x04},
|
|
{.cmd = 0x5E, .param = 0x20},
|
|
{.cmd = 0x5F, .param = 0x31},
|
|
{.cmd = 0x60, .param = 0x54},
|
|
{.cmd = 0x61, .param = 0x76},
|
|
{.cmd = 0x62, .param = 0x98},
|
|
|
|
/* Select manufacture command set page 4 */
|
|
/* ELVSS -2.4V(RT4723). 0x15: RT4723. 0x01: RT4723B. 0x17: STAM1332. */
|
|
{.cmd = 0xFE, .param = 0x05},
|
|
{.cmd = 0x05, .param = 0x15},
|
|
{.cmd = 0x2A, .param = 0x04},
|
|
{.cmd = 0x91, .param = 0x00},
|
|
|
|
/* Select user command set */
|
|
{.cmd = 0xFE, .param = 0x00},
|
|
/* Set tearing effect signal to only output at V-blank*/
|
|
{.cmd = 0x35, .param = 0x00},
|
|
};
|
|
|
|
struct rm67162_config {
|
|
const struct device *mipi_dsi;
|
|
uint8_t channel;
|
|
uint8_t num_of_lanes;
|
|
const struct gpio_dt_spec reset_gpio;
|
|
const struct gpio_dt_spec bl_gpio;
|
|
const struct gpio_dt_spec te_gpio;
|
|
uint16_t panel_width;
|
|
uint16_t panel_height;
|
|
};
|
|
|
|
|
|
struct rm67162_data {
|
|
uint8_t pixel_format;
|
|
uint8_t bytes_per_pixel;
|
|
struct gpio_callback te_gpio_cb;
|
|
struct k_sem te_sem;
|
|
};
|
|
|
|
static void rm67162_te_isr_handler(const struct device *gpio_dev,
|
|
struct gpio_callback *cb, uint32_t pins)
|
|
{
|
|
struct rm67162_data *data = CONTAINER_OF(cb, struct rm67162_data, te_gpio_cb);
|
|
|
|
k_sem_give(&data->te_sem);
|
|
}
|
|
|
|
static int rm67162_init(const struct device *dev)
|
|
{
|
|
const struct rm67162_config *config = dev->config;
|
|
struct rm67162_data *data = dev->data;
|
|
struct mipi_dsi_device mdev = {0};
|
|
int ret;
|
|
uint32_t i;
|
|
uint8_t cmd, param;
|
|
|
|
/* Attach to MIPI DSI host */
|
|
mdev.data_lanes = config->num_of_lanes;
|
|
mdev.pixfmt = data->pixel_format;
|
|
|
|
ret = mipi_dsi_attach(config->mipi_dsi, config->channel, &mdev);
|
|
if (ret < 0) {
|
|
LOG_ERR("Could not attach to MIPI-DSI host");
|
|
return ret;
|
|
}
|
|
|
|
if (config->reset_gpio.port != NULL) {
|
|
ret = gpio_pin_configure_dt(&config->reset_gpio, GPIO_OUTPUT_INACTIVE);
|
|
if (ret < 0) {
|
|
LOG_ERR("Could not configure reset GPIO (%d)", ret);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Power to the display has been enabled via the regulator fixed api during
|
|
* regulator init. Per datasheet, we must wait at least 10ms before
|
|
* starting reset sequence after power on.
|
|
*/
|
|
k_sleep(K_MSEC(10));
|
|
/* Start reset sequence */
|
|
ret = gpio_pin_set_dt(&config->reset_gpio, 0);
|
|
if (ret < 0) {
|
|
LOG_ERR("Could not pull reset low (%d)", ret);
|
|
return ret;
|
|
}
|
|
/* Per datasheet, reset low pulse width should be at least 10usec */
|
|
k_sleep(K_USEC(30));
|
|
gpio_pin_set_dt(&config->reset_gpio, 1);
|
|
if (ret < 0) {
|
|
LOG_ERR("Could not pull reset high (%d)", ret);
|
|
return ret;
|
|
}
|
|
/*
|
|
* It is necessary to wait at least 120msec after releasing reset,
|
|
* before sending additional commands. This delay can be 5msec
|
|
* if we are certain the display module is in SLEEP IN state,
|
|
* but this is not guaranteed (for example, with a warm reset)
|
|
*/
|
|
k_sleep(K_MSEC(150));
|
|
}
|
|
|
|
/* Now, write initialization settings for display, running at 400x392 */
|
|
for (i = 0; i < ARRAY_SIZE(rm67162_init_400x392); i++) {
|
|
cmd = rm67162_init_400x392[i].cmd;
|
|
param = rm67162_init_400x392[i].param;
|
|
ret = mipi_dsi_dcs_write(config->mipi_dsi, config->channel,
|
|
cmd, ¶m, 1);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Set pixel format */
|
|
if (data->pixel_format == MIPI_DSI_PIXFMT_RGB888) {
|
|
param = MIPI_DCS_PIXEL_FORMAT_24BIT;
|
|
data->bytes_per_pixel = 3;
|
|
} else if (data->pixel_format == MIPI_DSI_PIXFMT_RGB565) {
|
|
param = MIPI_DCS_PIXEL_FORMAT_16BIT;
|
|
data->bytes_per_pixel = 2;
|
|
} else {
|
|
/* Unsupported pixel format */
|
|
LOG_ERR("Pixel format not supported");
|
|
return -ENOTSUP;
|
|
}
|
|
ret = mipi_dsi_dcs_write(config->mipi_dsi, config->channel,
|
|
MIPI_DCS_SET_PIXEL_FORMAT, ¶m, 1);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* Delay 50 ms before exiting sleep mode */
|
|
k_sleep(K_MSEC(50));
|
|
ret = mipi_dsi_dcs_write(config->mipi_dsi, config->channel,
|
|
MIPI_DCS_EXIT_SLEEP_MODE, NULL, 0);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
/*
|
|
* We must wait 5 ms after exiting sleep mode before sending additional
|
|
* commands. If we intend to enter sleep mode, we must delay
|
|
* 120 ms before sending that command. To be safe, delay 150ms
|
|
*/
|
|
k_sleep(K_MSEC(150));
|
|
|
|
/* Setup backlight */
|
|
if (config->bl_gpio.port != NULL) {
|
|
ret = gpio_pin_configure_dt(&config->bl_gpio, GPIO_OUTPUT_ACTIVE);
|
|
if (ret < 0) {
|
|
LOG_ERR("Could not configure bl GPIO (%d)", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (config->te_gpio.port != NULL) {
|
|
/* Setup TE pin */
|
|
ret = gpio_pin_configure_dt(&config->te_gpio, GPIO_INPUT);
|
|
if (ret < 0) {
|
|
LOG_ERR("Could not configure TE GPIO (%d)", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = gpio_pin_interrupt_configure_dt(&config->te_gpio,
|
|
GPIO_INT_EDGE_TO_ACTIVE);
|
|
if (ret < 0) {
|
|
LOG_ERR("Could not configure TE interrupt (%d)", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Init and install GPIO callback */
|
|
gpio_init_callback(&data->te_gpio_cb, rm67162_te_isr_handler,
|
|
BIT(config->te_gpio.pin));
|
|
gpio_add_callback(config->te_gpio.port, &data->te_gpio_cb);
|
|
|
|
/* Setup te pin semaphore */
|
|
k_sem_init(&data->te_sem, 0, 1);
|
|
}
|
|
|
|
/* Now, enable display */
|
|
return mipi_dsi_dcs_write(config->mipi_dsi, config->channel,
|
|
MIPI_DCS_SET_DISPLAY_ON, NULL, 0);
|
|
}
|
|
|
|
/* Helper to write framebuffer data to rm67162 via MIPI interface. */
|
|
static int rm67162_write_fb(const struct device *dev, bool first_write,
|
|
const uint8_t *src, uint32_t len)
|
|
{
|
|
const struct rm67162_config *config = dev->config;
|
|
uint32_t wlen = 0;
|
|
struct mipi_dsi_msg msg = {0};
|
|
|
|
/* Note- we need to set custom flags on the DCS message,
|
|
* so we bypass the mipi_dsi_dcs_write API
|
|
*/
|
|
if (first_write) {
|
|
msg.cmd = MIPI_DCS_WRITE_MEMORY_START;
|
|
} else {
|
|
msg.cmd = MIPI_DCS_WRITE_MEMORY_CONTINUE;
|
|
}
|
|
msg.type = MIPI_DSI_DCS_LONG_WRITE;
|
|
msg.flags = MCUX_DSI_2L_FB_DATA;
|
|
while (len > 0) {
|
|
msg.tx_len = len;
|
|
msg.tx_buf = src;
|
|
wlen = mipi_dsi_transfer(config->mipi_dsi, config->channel, &msg);
|
|
if (wlen < 0) {
|
|
return wlen;
|
|
}
|
|
/* Advance source pointer and decrement remaining */
|
|
src += wlen;
|
|
len -= wlen;
|
|
/* All future commands should use WRITE_MEMORY_CONTINUE */
|
|
msg.cmd = MIPI_DCS_WRITE_MEMORY_CONTINUE;
|
|
}
|
|
return wlen;
|
|
}
|
|
|
|
static int rm67162_write(const struct device *dev, const uint16_t x,
|
|
const uint16_t y,
|
|
const struct display_buffer_descriptor *desc,
|
|
const void *buf)
|
|
{
|
|
const struct rm67162_config *config = dev->config;
|
|
struct rm67162_data *data = dev->data;
|
|
int ret;
|
|
uint16_t start, end, h_idx;
|
|
const uint8_t *src;
|
|
bool first_cmd;
|
|
uint8_t param[4];
|
|
|
|
LOG_DBG("W=%d, H=%d @%d,%d", desc->width, desc->height, x, y);
|
|
|
|
/*
|
|
* RM67162 runs in MIPI DBI mode. This means we can use command mode
|
|
* to write to the video memory buffer on the RM67162 control IC,
|
|
* and the IC will update the display automatically.
|
|
*/
|
|
|
|
/* Set column address of target area */
|
|
/* First two bytes are starting X coordinate */
|
|
start = x;
|
|
end = x + desc->width - 1;
|
|
sys_put_be16(start, ¶m[0]);
|
|
/* Second two bytes are ending X coordinate */
|
|
sys_put_be16(end, ¶m[2]);
|
|
ret = mipi_dsi_dcs_write(config->mipi_dsi, config->channel,
|
|
MIPI_DCS_SET_COLUMN_ADDRESS, param,
|
|
sizeof(param));
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* Set page address of target area */
|
|
/* First two bytes are starting Y coordinate */
|
|
start = y;
|
|
end = y + desc->height - 1;
|
|
sys_put_be16(start, ¶m[0]);
|
|
/* Second two bytes are ending X coordinate */
|
|
sys_put_be16(end, ¶m[2]);
|
|
ret = mipi_dsi_dcs_write(config->mipi_dsi, config->channel,
|
|
MIPI_DCS_SET_PAGE_ADDRESS, param,
|
|
sizeof(param));
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Now, write the framebuffer. If the tearing effect GPIO is present,
|
|
* wait until the display controller issues an interrupt (which will
|
|
* give to the TE semaphore) before sending the frame
|
|
*/
|
|
if (config->te_gpio.port != NULL) {
|
|
/* Block sleep state until next TE interrupt so we can send
|
|
* frame during that interval
|
|
*/
|
|
pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE,
|
|
PM_ALL_SUBSTATES);
|
|
k_sem_take(&data->te_sem, K_FOREVER);
|
|
pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE,
|
|
PM_ALL_SUBSTATES);
|
|
}
|
|
src = buf;
|
|
first_cmd = true;
|
|
|
|
if (desc->pitch == desc->width) {
|
|
/* Buffer is contiguous, we can perform entire transfer */
|
|
rm67162_write_fb(dev, first_cmd, src,
|
|
desc->height * desc->width * data->bytes_per_pixel);
|
|
} else {
|
|
/* Buffer is not contiguous, we must write each line separately */
|
|
for (h_idx = 0; h_idx < desc->height; h_idx++) {
|
|
rm67162_write_fb(dev, first_cmd, src,
|
|
desc->width * data->bytes_per_pixel);
|
|
first_cmd = false;
|
|
/* The pitch is not equal to width, account for it here */
|
|
src += data->bytes_per_pixel * (desc->pitch - desc->width);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rm67162_get_capabilities(const struct device *dev,
|
|
struct display_capabilities *capabilities)
|
|
{
|
|
const struct rm67162_config *config = dev->config;
|
|
const struct rm67162_data *data = dev->data;
|
|
|
|
memset(capabilities, 0, sizeof(struct display_capabilities));
|
|
capabilities->x_resolution = config->panel_width;
|
|
capabilities->y_resolution = config->panel_height;
|
|
capabilities->supported_pixel_formats = PIXEL_FORMAT_RGB_565 |
|
|
PIXEL_FORMAT_RGB_888;
|
|
switch (data->pixel_format) {
|
|
case MIPI_DSI_PIXFMT_RGB565:
|
|
capabilities->current_pixel_format = PIXEL_FORMAT_RGB_565;
|
|
break;
|
|
case MIPI_DSI_PIXFMT_RGB888:
|
|
capabilities->current_pixel_format = PIXEL_FORMAT_RGB_888;
|
|
break;
|
|
default:
|
|
LOG_WRN("Unsupported display format");
|
|
/* Other display formats not implemented */
|
|
break;
|
|
}
|
|
capabilities->current_orientation = DISPLAY_ORIENTATION_ROTATED_90;
|
|
}
|
|
|
|
static int rm67162_blanking_off(const struct device *dev)
|
|
{
|
|
const struct rm67162_config *config = dev->config;
|
|
|
|
if (config->bl_gpio.port != NULL) {
|
|
return gpio_pin_set_dt(&config->bl_gpio, 1);
|
|
} else {
|
|
return -ENOTSUP;
|
|
}
|
|
}
|
|
|
|
static int rm67162_blanking_on(const struct device *dev)
|
|
{
|
|
const struct rm67162_config *config = dev->config;
|
|
|
|
if (config->bl_gpio.port != NULL) {
|
|
return gpio_pin_set_dt(&config->bl_gpio, 0);
|
|
} else {
|
|
return -ENOTSUP;
|
|
}
|
|
}
|
|
|
|
static int rm67162_set_pixel_format(const struct device *dev,
|
|
const enum display_pixel_format pixel_format)
|
|
{
|
|
const struct rm67162_config *config = dev->config;
|
|
struct rm67162_data *data = dev->data;
|
|
uint8_t param;
|
|
|
|
switch (pixel_format) {
|
|
case PIXEL_FORMAT_RGB_565:
|
|
data->pixel_format = MIPI_DSI_PIXFMT_RGB565;
|
|
return 0;
|
|
case PIXEL_FORMAT_RGB_888:
|
|
data->pixel_format = MIPI_DSI_PIXFMT_RGB888;
|
|
return 0;
|
|
default:
|
|
/* Other display formats not implemented */
|
|
return -ENOTSUP;
|
|
}
|
|
if (data->pixel_format == MIPI_DSI_PIXFMT_RGB888) {
|
|
param = MIPI_DCS_PIXEL_FORMAT_24BIT;
|
|
data->bytes_per_pixel = 3;
|
|
} else if (data->pixel_format == MIPI_DSI_PIXFMT_RGB565) {
|
|
param = MIPI_DCS_PIXEL_FORMAT_16BIT;
|
|
data->bytes_per_pixel = 2;
|
|
}
|
|
return mipi_dsi_dcs_write(config->mipi_dsi, config->channel,
|
|
MIPI_DCS_SET_PIXEL_FORMAT, ¶m, 1);
|
|
}
|
|
|
|
static int rm67162_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;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_DEVICE
|
|
|
|
static int rm67162_pm_action(const struct device *dev,
|
|
enum pm_device_action action)
|
|
{
|
|
const struct rm67162_config *config = dev->config;
|
|
struct rm67162_data *data = dev->data;
|
|
struct mipi_dsi_device mdev = {0};
|
|
|
|
mdev.data_lanes = config->num_of_lanes;
|
|
mdev.pixfmt = data->pixel_format;
|
|
|
|
switch (action) {
|
|
case PM_DEVICE_ACTION_SUSPEND:
|
|
/* Detach from the MIPI DSI controller */
|
|
return mipi_dsi_detach(config->mipi_dsi, config->channel, &mdev);
|
|
case PM_DEVICE_ACTION_RESUME:
|
|
return mipi_dsi_attach(config->mipi_dsi, config->channel, &mdev);
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
}
|
|
|
|
#endif /* CONFIG_PM_DEVICE */
|
|
|
|
static const struct display_driver_api rm67162_api = {
|
|
.blanking_on = rm67162_blanking_on,
|
|
.blanking_off = rm67162_blanking_off,
|
|
.get_capabilities = rm67162_get_capabilities,
|
|
.write = rm67162_write,
|
|
.set_pixel_format = rm67162_set_pixel_format,
|
|
.set_orientation = rm67162_set_orientation,
|
|
};
|
|
|
|
#define RM67162_PANEL(id) \
|
|
static const struct rm67162_config rm67162_config_##id = { \
|
|
.mipi_dsi = DEVICE_DT_GET(DT_INST_BUS(id)), \
|
|
.num_of_lanes = DT_INST_PROP_BY_IDX(id, data_lanes, 0), \
|
|
.channel = DT_INST_REG_ADDR(id), \
|
|
.reset_gpio = GPIO_DT_SPEC_INST_GET_OR(id, reset_gpios, {0}), \
|
|
.bl_gpio = GPIO_DT_SPEC_INST_GET_OR(id, bl_gpios, {0}), \
|
|
.te_gpio = GPIO_DT_SPEC_INST_GET_OR(id, te_gpios, {0}), \
|
|
.panel_width = DT_INST_PROP(id, width), \
|
|
.panel_height = DT_INST_PROP(id, height), \
|
|
}; \
|
|
static struct rm67162_data rm67162_data_##id = { \
|
|
.pixel_format = DT_INST_PROP(id, pixel_format), \
|
|
}; \
|
|
PM_DEVICE_DT_INST_DEFINE(id, rm67162_pm_action); \
|
|
DEVICE_DT_INST_DEFINE(id, \
|
|
&rm67162_init, \
|
|
PM_DEVICE_DT_INST_GET(id), \
|
|
&rm67162_data_##id, \
|
|
&rm67162_config_##id, \
|
|
POST_KERNEL, \
|
|
CONFIG_APPLICATION_INIT_PRIORITY, \
|
|
&rm67162_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(RM67162_PANEL)
|