Add device level power management to rm67162 display. Device level power management for this controller calls the MIPI DSI detach API, in order to power down the MIPI DPHY when the display is not active. Signed-off-by: Daniel DeGrasse <daniel.degrasse@nxp.com>
633 lines
18 KiB
C
633 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) {
|
|
if (IS_ENABLED(CONFIG_PM)) {
|
|
/* 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);
|
|
if (IS_ENABLED(CONFIG_PM)) {
|
|
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_brightness(const struct device *dev,
|
|
const uint8_t brightness)
|
|
{
|
|
LOG_WRN("Set brightness not implemented");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static int rm67162_set_contrast(const struct device *dev,
|
|
const uint8_t contrast)
|
|
{
|
|
LOG_ERR("Set contrast not implemented");
|
|
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_brightness = rm67162_set_brightness,
|
|
.set_contrast = rm67162_set_contrast,
|
|
.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)
|