Browse Source
Add support for RM67162 MIPI display controller. This controller is configured to run in MIPI command/DBI mode, driving a 400x392 OLED display. Signed-off-by: Daniel DeGrasse <daniel.degrasse@nxp.com>pull/60571/head
5 changed files with 653 additions and 0 deletions
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
# Copyright 2023, NXP |
||||
# SPDX-License-Identifier: Apache-2.0 |
||||
|
||||
config RM67162 |
||||
bool "RM67162 display driver" |
||||
default y |
||||
select MIPI_DSI |
||||
depends on DT_HAS_RAYDIUM_RM67162_ENABLED |
||||
help |
||||
Enable driver for RM67162 display driver. |
@ -0,0 +1,610 @@
@@ -0,0 +1,610 @@
|
||||
/*
|
||||
* 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/gpio.h> |
||||
#include <zephyr/kernel.h> |
||||
#include <zephyr/logging/log.h> |
||||
#include <zephyr/pm/policy.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}, |
||||
}; |
||||
|
||||
#define DSI_TX_MAX_PAYLOAD_BYTE (64U * 4U) |
||||
|
||||
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 data to rm67162 via MIPI interface. */ |
||||
static int rm67162_write_buf(const struct device *dev, bool first_write, |
||||
const uint8_t *src, uint32_t len) |
||||
{ |
||||
const struct rm67162_config *config = dev->config; |
||||
struct rm67162_data *data = dev->data; |
||||
int ret = 0; |
||||
uint32_t max_write, wlen; |
||||
uint8_t cmd; |
||||
|
||||
/*
|
||||
* Max write len: one byte is reserved for DSC command, and |
||||
* pixels should not be split across transfers |
||||
*/ |
||||
max_write = ((DSI_TX_MAX_PAYLOAD_BYTE - 1) / data->bytes_per_pixel) * |
||||
data->bytes_per_pixel; |
||||
if (first_write) { |
||||
cmd = MIPI_DCS_WRITE_MEMORY_START; |
||||
} else { |
||||
cmd = MIPI_DCS_WRITE_MEMORY_CONTINUE; |
||||
} |
||||
while (len > 0) { |
||||
/* Cap each tx to max DSI APB transfer size */ |
||||
wlen = MIN(max_write, len); |
||||
ret = mipi_dsi_dcs_write(config->mipi_dsi, config->channel, |
||||
cmd, src, wlen); |
||||
if (ret < 0) { |
||||
return ret; |
||||
} |
||||
/* Advance source pointer and decrement remaining */ |
||||
src += wlen; |
||||
len -= wlen; |
||||
/* All future commands should use WRITE_MEMORY_CONTINUE */ |
||||
cmd = MIPI_DCS_WRITE_MEMORY_CONTINUE; |
||||
} |
||||
return ret; |
||||
} |
||||
|
||||
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_buf(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_buf(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; |
||||
} |
||||
|
||||
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), \ |
||||
}; \ |
||||
DEVICE_DT_INST_DEFINE(id, \ |
||||
&rm67162_init, \ |
||||
NULL, \ |
||||
&rm67162_data_##id, \ |
||||
&rm67162_config_##id, \ |
||||
POST_KERNEL, \ |
||||
CONFIG_APPLICATION_INIT_PRIORITY, \ |
||||
&rm67162_api); |
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(RM67162_PANEL) |
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
# |
||||
# Copyright 2023, NXP |
||||
# |
||||
# SPDX-License-Identifier: Apache-2.0 |
||||
# |
||||
|
||||
description: Raydium RM67162 Panel |
||||
|
||||
compatible: "raydium,rm67162" |
||||
|
||||
include: [mipi-dsi-device.yaml, display-controller.yaml] |
||||
|
||||
properties: |
||||
reset-gpios: |
||||
type: phandle-array |
||||
description: | |
||||
The RESETn pin is asserted to disable the sensor causing a hard |
||||
reset. The sensor receives this as an active-low signal. |
||||
|
||||
bl-gpios: |
||||
type: phandle-array |
||||
description: | |
||||
The BLn pin is asserted to control the backlight of the panel. |
||||
The sensor receives this as an active-high signal. |
||||
|
||||
te-gpios: |
||||
type: phandle-array |
||||
description: | |
||||
The tearing effect pin is asserted by the controller at a display |
||||
VSYNC interval. This permits the controller to send new display |
||||
data during a VSYNC interval, removing tearing. |
Loading…
Reference in new issue