drivers: display: smartbond: Add support for the display driver class.
Exhibit Renesas LCD controller's driver implementation. The driver is intended to employ the controller in the continuous mode so it can drive display panels in the parallel RGB mode. Signed-off-by: Ioannis Karachalios <ioannis.karachalios.px@renesas.com>
This commit is contained in:
parent
ca52603b09
commit
96677e402e
|
@ -25,6 +25,7 @@ zephyr_library_sources_ifdef(CONFIG_RM67162 display_rm67162.c)
|
|||
zephyr_library_sources_ifdef(CONFIG_HX8394 display_hx8394.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_GC9X01X display_gc9x01x.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_LED_STRIP_MATRIX display_led_strip_matrix.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_DISPLAY_RENESAS_LCDC display_renesas_lcdc.c)
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_MICROBIT_DISPLAY
|
||||
mb_display.c
|
||||
|
|
|
@ -42,5 +42,6 @@ source "drivers/display/Kconfig.otm8009a"
|
|||
source "drivers/display/Kconfig.hx8394"
|
||||
source "drivers/display/Kconfig.gc9x01x"
|
||||
source "drivers/display/Kconfig.led_strip_matrix"
|
||||
source "drivers/display/Kconfig.renesas_lcdc"
|
||||
|
||||
endif # DISPLAY
|
||||
|
|
19
drivers/display/Kconfig.renesas_lcdc
Normal file
19
drivers/display/Kconfig.renesas_lcdc
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Smartbond display controller configuration options
|
||||
|
||||
# Copyright (c) 2023 Renesas Electronics Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config DISPLAY_RENESAS_LCDC
|
||||
bool "Smartbond display controller driver"
|
||||
depends on DT_HAS_RENESAS_SMARTBOND_DISPLAY_ENABLED
|
||||
select DMA
|
||||
default y
|
||||
help
|
||||
Enable Smartbond display controller.
|
||||
|
||||
config DISPLAY_RENESAS_LCDC_BUFFER_PSRAM
|
||||
bool "Allocate the display buffer into PSRAM"
|
||||
depends on DISPLAY_RENESAS_LCDC
|
||||
select MEMC
|
||||
help
|
||||
Allocate the display buffer into PSRAM
|
582
drivers/display/display_renesas_lcdc.c
Normal file
582
drivers/display/display_renesas_lcdc.c
Normal file
|
@ -0,0 +1,582 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Renesas Electronics Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT renesas_smartbond_display
|
||||
|
||||
#include <zephyr/drivers/pinctrl.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/irq.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/clock_control.h>
|
||||
#include <zephyr/drivers/clock_control/smartbond_clock_control.h>
|
||||
#include <zephyr/drivers/display.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
#include <zephyr/drivers/dma.h>
|
||||
#include <da1469x_lcdc.h>
|
||||
#include <DA1469xAB.h>
|
||||
#include <da1469x_pd.h>
|
||||
#include <zephyr/linker/devicetree_regions.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(smartbond_display, CONFIG_DISPLAY_LOG_LEVEL);
|
||||
|
||||
#define SMARTBOND_IRQN DT_INST_IRQN(0)
|
||||
#define SMARTBOND_IRQ_PRIO DT_INST_IRQ(0, priority)
|
||||
|
||||
#define LCDC_SMARTBOND_CLK_DIV(_freq) \
|
||||
((32000000U % (_freq)) ? (96000000U / (_freq)) : (32000000U / (_freq)))
|
||||
|
||||
#define LCDC_SMARTBOND_IS_PLL_REQUIRED \
|
||||
!!(32000000U % DT_PROP(DT_INST_CHILD(0, display_timings), clock_frequency))
|
||||
|
||||
#define DISPLAY_SMARTBOND_IS_DMA_PREFETCH_ENABLED \
|
||||
DT_INST_ENUM_IDX_OR(0, dma_prefetch, 0)
|
||||
|
||||
#define LCDC_LAYER0_OFFSETX_REG_SET_FIELD(_field, _var, _val)\
|
||||
((_var)) = \
|
||||
((_var) & ~(LCDC_LCDC_LAYER0_OFFSETX_REG_ ## _field ## _Msk)) | \
|
||||
(((_val) << LCDC_LCDC_LAYER0_OFFSETX_REG_ ## _field ## _Pos) & \
|
||||
LCDC_LCDC_LAYER0_OFFSETX_REG_ ## _field ## _Msk)
|
||||
|
||||
#define DISPLAY_SMARTBOND_PIXEL_SIZE(inst) \
|
||||
(DISPLAY_BITS_PER_PIXEL(DT_INST_PROP(inst, pixel_format)) / 8)
|
||||
|
||||
#if CONFIG_DISPLAY_RENESAS_LCDC_BUFFER_PSRAM
|
||||
#define DISPLAY_BUFFER_LINKER_SECTION \
|
||||
Z_GENERIC_SECTION(LINKER_DT_NODE_REGION_NAME(DT_NODELABEL(psram)))
|
||||
#else
|
||||
#define DISPLAY_BUFFER_LINKER_SECTION
|
||||
#endif
|
||||
|
||||
struct display_smartbond_data {
|
||||
/* Provide mutual exclusion when a display operation is requested. */
|
||||
struct k_sem device_sem;
|
||||
/* Frame update synchronization token */
|
||||
struct k_sem sync_sem;
|
||||
/* Flag indicating whether or not an underflow took place */
|
||||
volatile bool underflow_flag;
|
||||
/* Layer settings */
|
||||
lcdc_smartbond_layer_cfg layer;
|
||||
/* Frame buffer */
|
||||
uint8_t *buffer;
|
||||
/* DMA device */
|
||||
const struct device *dma;
|
||||
/* DMA configuration structures */
|
||||
struct dma_config dma_cfg;
|
||||
struct dma_block_config dma_block_cfg;
|
||||
/* DMA memory transfer synchronization token */
|
||||
struct k_sem dma_sync_sem;
|
||||
/* Granted DMA channel used for memory transfers */
|
||||
int dma_channel;
|
||||
};
|
||||
|
||||
struct display_smartbond_config {
|
||||
/* Reference to device instance's pinctrl configurations */
|
||||
const struct pinctrl_dev_config *pcfg;
|
||||
/* Display ON/OFF GPIO */
|
||||
const struct gpio_dt_spec disp;
|
||||
/* Host controller's timing settings */
|
||||
lcdc_smartbond_timing_cfg timing_cfg;
|
||||
/* Parallel interface settings */
|
||||
lcdc_smartbond_mode_cfg mode;
|
||||
/* Background default color configuration */
|
||||
lcdc_smartbond_bgcolor_cfg bgcolor_cfg;
|
||||
/* Display dimensions */
|
||||
const uint16_t x_res;
|
||||
const uint16_t y_res;
|
||||
/* Pixel size in bytes */
|
||||
uint8_t pixel_size;
|
||||
enum display_pixel_format pixel_format;
|
||||
};
|
||||
|
||||
/* Display pixel to layer color format translation */
|
||||
static uint8_t lcdc_smartbond_pixel_to_lcm(enum display_pixel_format pixel_format)
|
||||
{
|
||||
switch (pixel_format) {
|
||||
case PIXEL_FORMAT_RGB_565:
|
||||
return (uint8_t)LCDC_SMARTBOND_L0_RGB565;
|
||||
case PIXEL_FORMAT_ARGB_8888:
|
||||
return (uint8_t)LCDC_SMARTBOND_L0_ARGB8888;
|
||||
default:
|
||||
LOG_ERR("Unsupported pixel format");
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
static int display_smartbond_configure(const struct device *dev)
|
||||
{
|
||||
uint8_t clk_div =
|
||||
LCDC_SMARTBOND_CLK_DIV(DT_PROP(DT_INST_CHILD(0, display_timings), clock_frequency));
|
||||
|
||||
const struct display_smartbond_config *config = dev->config;
|
||||
struct display_smartbond_data *data = dev->data;
|
||||
|
||||
int ret = 0;
|
||||
|
||||
/* First enable the controller so registers can be written. */
|
||||
da1469x_lcdc_set_status(true, LCDC_SMARTBOND_IS_PLL_REQUIRED, clk_div);
|
||||
|
||||
if (!da1469x_lcdc_check_id()) {
|
||||
LOG_ERR("Invalid LCDC ID");
|
||||
da1469x_lcdc_set_status(false, false, 0);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
da1469x_lcdc_parallel_interface_configure((lcdc_smartbond_mode_cfg *)&config->mode);
|
||||
da1469x_lcdc_bgcolor_configure((lcdc_smartbond_bgcolor_cfg *)&config->bgcolor_cfg);
|
||||
|
||||
/*
|
||||
* Partial update is not supported and so timing and layer settings can be configured
|
||||
* once at initialization.
|
||||
*/
|
||||
ret = da1469x_lcdc_timings_configure(config->x_res, config->y_res,
|
||||
(lcdc_smartbond_timing_cfg *)&config->timing_cfg);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Unable to configure timing settings");
|
||||
da1469x_lcdc_set_status(false, false, 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Stride should be updated at the end of a frame update (typically in ISR context).
|
||||
* It's OK to update stride here as continuous mode should not be enabled yet.
|
||||
*/
|
||||
data->layer.color_format =
|
||||
lcdc_smartbond_pixel_to_lcm(config->pixel_format);
|
||||
data->layer.stride =
|
||||
da1469x_lcdc_stride_calculation(data->layer.color_format, config->x_res);
|
||||
|
||||
ret = da1469x_lcdc_layer_configure(&data->layer);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Unable to configure layer settings");
|
||||
da1469x_lcdc_set_status(false, false, 0);
|
||||
}
|
||||
|
||||
LCDC_LAYER0_OFFSETX_REG_SET_FIELD(LCDC_L0_DMA_PREFETCH,
|
||||
LCDC->LCDC_LAYER0_OFFSETX_REG, DISPLAY_SMARTBOND_IS_DMA_PREFETCH_ENABLED);
|
||||
|
||||
LCDC->LCDC_MODE_REG |= LCDC_LCDC_MODE_REG_LCDC_MODE_EN_Msk;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void smartbond_display_isr(const void *arg)
|
||||
{
|
||||
struct display_smartbond_data *data = ((const struct device *)arg)->data;
|
||||
|
||||
data->underflow_flag = LCDC_STATUS_REG_GET_FIELD(LCDC_STICKY_UNDERFLOW);
|
||||
|
||||
/*
|
||||
* Underflow sticky bit will remain high until cleared by writing
|
||||
* any value to LCDC_INTERRUPT_REG.
|
||||
*/
|
||||
LCDC->LCDC_INTERRUPT_REG &= ~LCDC_LCDC_INTERRUPT_REG_LCDC_VSYNC_IRQ_EN_Msk;
|
||||
|
||||
/* Notify that current frame update is completed */
|
||||
k_sem_give(&data->sync_sem);
|
||||
}
|
||||
|
||||
static int display_smartbond_resume(const struct device *dev)
|
||||
{
|
||||
const struct display_smartbond_config *config = dev->config;
|
||||
int ret;
|
||||
|
||||
/* Select default state */
|
||||
ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Could not apply LCDC pins' default state (%d)", ret);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
#if LCDC_SMARTBOND_IS_PLL_REQUIRED
|
||||
const struct device *clock_dev = DEVICE_DT_GET(DT_NODELABEL(osc));
|
||||
|
||||
if (!device_is_ready(clock_dev)) {
|
||||
LOG_WRN("Clock device is not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = z_smartbond_select_sys_clk(SMARTBOND_CLK_PLL96M);
|
||||
if (ret < 0) {
|
||||
LOG_WRN("Could not switch to PLL");
|
||||
return -EIO;
|
||||
}
|
||||
#endif
|
||||
|
||||
return display_smartbond_configure(dev);
|
||||
}
|
||||
|
||||
static void display_smartbond_dma_cb(const struct device *dma, void *arg,
|
||||
uint32_t id, int status)
|
||||
{
|
||||
struct display_smartbond_data *data = arg;
|
||||
|
||||
if (status < 0) {
|
||||
LOG_WRN("DMA transfer did not complete");
|
||||
}
|
||||
|
||||
k_sem_give(&data->dma_sync_sem);
|
||||
}
|
||||
|
||||
static int display_smartbond_dma_config(const struct device *dev)
|
||||
{
|
||||
struct display_smartbond_data *data = dev->data;
|
||||
|
||||
data->dma = DEVICE_DT_GET(DT_NODELABEL(dma));
|
||||
if (!device_is_ready(data->dma)) {
|
||||
LOG_ERR("DMA device is not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
data->dma_cfg.channel_direction = MEMORY_TO_MEMORY;
|
||||
data->dma_cfg.user_data = data;
|
||||
data->dma_cfg.dma_callback = display_smartbond_dma_cb;
|
||||
data->dma_cfg.block_count = 1;
|
||||
data->dma_cfg.head_block = &data->dma_block_cfg;
|
||||
|
||||
/* Request an arbitrary DMA channel */
|
||||
data->dma_channel = dma_request_channel(data->dma, NULL);
|
||||
if (data->dma_channel < 0) {
|
||||
LOG_ERR("Could not acquire a DMA channel");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int display_smartbond_init(const struct device *dev)
|
||||
{
|
||||
const struct display_smartbond_config *config = dev->config;
|
||||
struct display_smartbond_data *data = dev->data;
|
||||
int ret;
|
||||
|
||||
/* Device should be ready to be acquired */
|
||||
k_sem_init(&data->device_sem, 1, 1);
|
||||
/* Event should be signaled by LCDC ISR */
|
||||
k_sem_init(&data->sync_sem, 0, 1);
|
||||
/* Event should be signaled by DMA ISR */
|
||||
k_sem_init(&data->dma_sync_sem, 0, 1);
|
||||
|
||||
/* As per docs, display port should be enabled by default. */
|
||||
if (gpio_is_ready_dt(&config->disp)) {
|
||||
ret = gpio_pin_configure_dt(&config->disp, GPIO_OUTPUT_ACTIVE);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Could not activate display port");
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
ret = display_smartbond_resume(dev);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = display_smartbond_dma_config(dev);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
IRQ_CONNECT(SMARTBOND_IRQN, SMARTBOND_IRQ_PRIO, smartbond_display_isr,
|
||||
DEVICE_DT_INST_GET(0), 0);
|
||||
|
||||
#if CONFIG_PM
|
||||
/*
|
||||
* When in continues mode, the display device should always be refreshed
|
||||
* and so the controller is not allowed to be turned off. The latter, is
|
||||
* powered by PD_SYS which is turned off when the SoC enters the extended
|
||||
* sleep state. By acquiring PD_SYS, the deep sleep state is prevented
|
||||
* and the system enters the low-power state (i.e. ARM WFI) when possible.
|
||||
*
|
||||
* XXX CONFIG_PM_DEVICE_RUNTIME is no supported yet!
|
||||
*/
|
||||
da1469x_pd_acquire_noconf(MCU_PD_DOMAIN_SYS);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int display_smartbond_blanking_on(const struct device *dev)
|
||||
{
|
||||
const struct display_smartbond_config *config = dev->config;
|
||||
struct display_smartbond_data *data = dev->data;
|
||||
int ret = 0;
|
||||
|
||||
k_sem_take(&data->device_sem, K_FOREVER);
|
||||
|
||||
/*
|
||||
* This bit will force LCD controller's output to blank that is,
|
||||
* the controller will keep operating without outputting any
|
||||
* pixel data.
|
||||
*/
|
||||
LCDC->LCDC_MODE_REG |= LCDC_LCDC_MODE_REG_LCDC_FORCE_BLANK_Msk;
|
||||
|
||||
/* If enabled, disable display port. */
|
||||
if (gpio_is_ready_dt(&config->disp)) {
|
||||
ret = gpio_pin_configure_dt(&config->disp, GPIO_OUTPUT_INACTIVE);
|
||||
if (ret < 0) {
|
||||
LOG_WRN("Display port could not be de-activated");
|
||||
}
|
||||
}
|
||||
|
||||
k_sem_give(&data->device_sem);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int display_smartbond_blanking_off(const struct device *dev)
|
||||
{
|
||||
const struct display_smartbond_config *config = dev->config;
|
||||
struct display_smartbond_data *data = dev->data;
|
||||
int ret = 0;
|
||||
|
||||
k_sem_take(&data->device_sem, K_FOREVER);
|
||||
|
||||
/* If used, enable display port */
|
||||
if (gpio_is_ready_dt(&config->disp)) {
|
||||
ret = gpio_pin_configure_dt(&config->disp, GPIO_OUTPUT_ACTIVE);
|
||||
if (ret < 0) {
|
||||
LOG_WRN("Display port could not be activated");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This bit will force LCD controller's output to blank that is,
|
||||
* the controller will keep operating without outputting any
|
||||
* pixel data.
|
||||
*/
|
||||
LCDC->LCDC_MODE_REG &= ~LCDC_LCDC_MODE_REG_LCDC_FORCE_BLANK_Msk;
|
||||
|
||||
k_sem_give(&data->device_sem);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void *display_smartbond_get_framebuffer(const struct device *dev)
|
||||
{
|
||||
struct display_smartbond_data *data = dev->data;
|
||||
|
||||
return ((void *)data->buffer);
|
||||
}
|
||||
|
||||
static void display_smartbond_get_capabilities(const struct device *dev,
|
||||
struct display_capabilities *capabilities)
|
||||
{
|
||||
memset(capabilities, 0, sizeof(*capabilities));
|
||||
|
||||
/*
|
||||
* Multiple color formats should be supported by LCDC. Currently, RGB56 and ARGB888
|
||||
* exposed by display API are supported. In the future we should consider supporting
|
||||
* more color formats which should require changes in LVGL porting.
|
||||
* Here, only one color format should be supported as the frame buffer is accessed
|
||||
* directly by LCDC and is allocated statically during device initialization. The color
|
||||
* format is defined based on the pixel-format property dictated by lcd-controller
|
||||
* bindings.
|
||||
*/
|
||||
capabilities->supported_pixel_formats = DT_INST_PROP(0, pixel_format);
|
||||
capabilities->current_orientation = DISPLAY_ORIENTATION_NORMAL;
|
||||
capabilities->current_pixel_format = DT_INST_PROP(0, pixel_format);
|
||||
capabilities->x_resolution = DT_INST_PROP(0, width);
|
||||
capabilities->y_resolution = DT_INST_PROP(0, height);
|
||||
}
|
||||
|
||||
static int display_smartbond_read(const struct device *dev,
|
||||
const uint16_t x, const uint16_t y,
|
||||
const struct display_buffer_descriptor *desc,
|
||||
void *buf)
|
||||
{
|
||||
struct display_smartbond_data *data = dev->data;
|
||||
const struct display_smartbond_config *config = dev->config;
|
||||
uint8_t *dst = buf;
|
||||
const uint8_t *src = data->buffer;
|
||||
|
||||
k_sem_take(&data->device_sem, K_FOREVER);
|
||||
|
||||
/* pointer to upper left pixel of the rectangle */
|
||||
src += (x * config->pixel_size);
|
||||
src += (y * data->layer.stride);
|
||||
|
||||
data->dma_block_cfg.block_size = desc->width * config->pixel_size;
|
||||
/*
|
||||
* Source and destination base address is word aligned.
|
||||
* Data size should be selected based on color depth as
|
||||
* cursor is shifted multiple of pixel color depth.
|
||||
*/
|
||||
data->dma_cfg.source_data_size = data->dma_cfg.dest_data_size =
|
||||
!(config->pixel_size & 3) ? 4 :
|
||||
!(config->pixel_size & 1) ? 2 : 1;
|
||||
|
||||
data->dma_cfg.dest_burst_length = data->dma_cfg.source_burst_length =
|
||||
!((data->dma_block_cfg.block_size / data->dma_cfg.source_data_size) & 7) ? 8 :
|
||||
!((data->dma_block_cfg.block_size / data->dma_cfg.source_data_size) & 3) ? 4 : 1;
|
||||
|
||||
for (int row = 0; row < desc->height; row++) {
|
||||
|
||||
data->dma_block_cfg.dest_address = (uint32_t)dst;
|
||||
data->dma_block_cfg.source_address = (uint32_t)src;
|
||||
|
||||
if (dma_config(data->dma, data->dma_channel, &data->dma_cfg)) {
|
||||
LOG_ERR("Could not configure DMA");
|
||||
k_sem_give(&data->device_sem);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (dma_start(data->dma, data->dma_channel)) {
|
||||
LOG_ERR("Could not start DMA");
|
||||
k_sem_give(&data->device_sem);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
k_sem_take(&data->dma_sync_sem, K_FOREVER);
|
||||
|
||||
src += data->layer.stride;
|
||||
dst += (desc->pitch * config->pixel_size);
|
||||
}
|
||||
|
||||
if (dma_stop(data->dma, data->dma_channel)) {
|
||||
LOG_WRN("Could not stop DMA");
|
||||
}
|
||||
|
||||
k_sem_give(&data->device_sem);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int display_smartbond_write(const struct device *dev,
|
||||
const uint16_t x, const uint16_t y,
|
||||
const struct display_buffer_descriptor *desc,
|
||||
const void *buf)
|
||||
{
|
||||
struct display_smartbond_data *data = dev->data;
|
||||
const struct display_smartbond_config *config = dev->config;
|
||||
uint8_t *dst = data->buffer;
|
||||
const uint8_t *src = buf;
|
||||
|
||||
k_sem_take(&data->device_sem, K_FOREVER);
|
||||
|
||||
/* pointer to upper left pixel of the rectangle */
|
||||
dst += (x * config->pixel_size);
|
||||
dst += (y * data->layer.stride);
|
||||
|
||||
/*
|
||||
* Wait for the current frame to finish. Do not disable continuous mode as this
|
||||
* will have visual artifacts.
|
||||
*/
|
||||
LCDC->LCDC_INTERRUPT_REG |= LCDC_LCDC_INTERRUPT_REG_LCDC_VSYNC_IRQ_EN_Msk;
|
||||
k_sem_take(&data->sync_sem, K_FOREVER);
|
||||
|
||||
data->dma_block_cfg.block_size = desc->width * config->pixel_size;
|
||||
/*
|
||||
* Source and destination base address is word aligned.
|
||||
* Data size should be selected based on color depth as
|
||||
* cursor is shifted multiple of pixel color depth.
|
||||
*/
|
||||
data->dma_cfg.source_data_size = data->dma_cfg.dest_data_size =
|
||||
!(config->pixel_size & 3) ? 4 :
|
||||
!(config->pixel_size & 1) ? 2 : 1;
|
||||
|
||||
data->dma_cfg.dest_burst_length = data->dma_cfg.source_burst_length =
|
||||
!((data->dma_block_cfg.block_size / data->dma_cfg.source_data_size) & 7) ? 8 :
|
||||
!((data->dma_block_cfg.block_size / data->dma_cfg.source_data_size) & 3) ? 4 : 1;
|
||||
|
||||
for (int row = 0; row < desc->height; row++) {
|
||||
|
||||
data->dma_block_cfg.dest_address = (uint32_t)dst;
|
||||
data->dma_block_cfg.source_address = (uint32_t)src;
|
||||
|
||||
if (dma_config(data->dma, data->dma_channel, &data->dma_cfg)) {
|
||||
LOG_ERR("Could not configure DMA");
|
||||
k_sem_give(&data->device_sem);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (dma_start(data->dma, data->dma_channel)) {
|
||||
LOG_ERR("Could not start DMA");
|
||||
k_sem_give(&data->device_sem);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
k_sem_take(&data->dma_sync_sem, K_FOREVER);
|
||||
|
||||
dst += data->layer.stride;
|
||||
src += (desc->pitch * config->pixel_size);
|
||||
}
|
||||
|
||||
if (dma_stop(data->dma, data->dma_channel)) {
|
||||
LOG_WRN("Could not stop DMA");
|
||||
}
|
||||
|
||||
k_sem_give(&data->device_sem);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static struct display_driver_api display_smartbond_driver_api = {
|
||||
.write = display_smartbond_write,
|
||||
.read = display_smartbond_read,
|
||||
.get_framebuffer = display_smartbond_get_framebuffer,
|
||||
.get_capabilities = display_smartbond_get_capabilities,
|
||||
.blanking_off = display_smartbond_blanking_off,
|
||||
.blanking_on = display_smartbond_blanking_on
|
||||
};
|
||||
|
||||
#define SMARTBOND_DISPLAY_INIT(inst) \
|
||||
PINCTRL_DT_INST_DEFINE(inst); \
|
||||
\
|
||||
__aligned(4) static uint8_t buffer_ ## inst[(((DT_INST_PROP(inst, width) * \
|
||||
DISPLAY_SMARTBOND_PIXEL_SIZE(inst)) + 0x3) & ~0x3) * \
|
||||
DT_INST_PROP(inst, height)] DISPLAY_BUFFER_LINKER_SECTION; \
|
||||
\
|
||||
static const struct display_smartbond_config display_smartbond_config_## inst = { \
|
||||
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
|
||||
.disp = GPIO_DT_SPEC_INST_GET_OR(inst, disp_gpios, {}), \
|
||||
.timing_cfg.vsync_len = \
|
||||
DT_PROP(DT_INST_CHILD(inst, display_timings), vsync_len), \
|
||||
.timing_cfg.hsync_len = \
|
||||
DT_PROP(DT_INST_CHILD(inst, display_timings), hsync_len), \
|
||||
.timing_cfg.hfront_porch = \
|
||||
DT_PROP(DT_INST_CHILD(inst, display_timings), hfront_porch), \
|
||||
.timing_cfg.vfront_porch = \
|
||||
DT_PROP(DT_INST_CHILD(inst, display_timings), vfront_porch), \
|
||||
.timing_cfg.hback_porch = \
|
||||
DT_PROP(DT_INST_CHILD(inst, display_timings), hback_porch), \
|
||||
.timing_cfg.vback_porch = \
|
||||
DT_PROP(DT_INST_CHILD(inst, display_timings), vback_porch), \
|
||||
.bgcolor_cfg = {0xFF, 0xFF, 0xFF, 0}, \
|
||||
.x_res = DT_INST_PROP(inst, width), \
|
||||
.y_res = DT_INST_PROP(inst, height), \
|
||||
.pixel_size = DISPLAY_SMARTBOND_PIXEL_SIZE(inst), \
|
||||
.pixel_format = DT_INST_PROP(0, pixel_format), \
|
||||
.mode.vsync_pol = \
|
||||
DT_PROP(DT_INST_CHILD(inst, display_timings), vsync_active) ? 0 : 1, \
|
||||
.mode.hsync_pol = \
|
||||
DT_PROP(DT_INST_CHILD(inst, display_timings), vsync_active) ? 0 : 1, \
|
||||
.mode.de_pol = \
|
||||
DT_PROP(DT_INST_CHILD(inst, display_timings), de_active) ? 0 : 1, \
|
||||
.mode.pixelclk_pol = \
|
||||
DT_PROP(DT_INST_CHILD(inst, display_timings), pixelclk_active) ? 0 : 1, \
|
||||
}; \
|
||||
\
|
||||
static struct display_smartbond_data display_smartbond_data_## inst = { \
|
||||
.buffer = buffer_ ##inst, \
|
||||
.layer.start_x = 0, \
|
||||
.layer.start_y = 0, \
|
||||
.layer.size_x = DT_INST_PROP(inst, width), \
|
||||
.layer.size_y = DT_INST_PROP(inst, height), \
|
||||
.layer.frame_buf = (uint32_t)buffer_ ## inst, \
|
||||
}; \
|
||||
\
|
||||
\
|
||||
DEVICE_DT_INST_DEFINE(inst, display_smartbond_init, NULL, \
|
||||
&display_smartbond_data_## inst, \
|
||||
&display_smartbond_config_## inst, \
|
||||
POST_KERNEL, \
|
||||
CONFIG_DISPLAY_INIT_PRIORITY, \
|
||||
&display_smartbond_driver_api);
|
||||
|
||||
SMARTBOND_DISPLAY_INIT(0);
|
37
dts/bindings/display/renesas,smartbond-display.yaml
Normal file
37
dts/bindings/display/renesas,smartbond-display.yaml
Normal file
|
@ -0,0 +1,37 @@
|
|||
# Copyright (c) 2023 Renesas Electronics Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
include: [display-controller.yaml, lcd-controller.yaml, pinctrl-device.yaml]
|
||||
|
||||
description: Renesas Smartbond(tm) display controller
|
||||
|
||||
compatible: "renesas,smartbond-display"
|
||||
|
||||
properties:
|
||||
reg:
|
||||
required: true
|
||||
|
||||
interrupts:
|
||||
required: true
|
||||
|
||||
disp-gpios:
|
||||
type: phandle-array
|
||||
description: |
|
||||
Display ON/OFF port control.
|
||||
|
||||
dma-prefetch:
|
||||
type: string
|
||||
enum:
|
||||
- "no-prefetch"
|
||||
- "prefetch-44-bytes"
|
||||
- "prefetch-84-bytes"
|
||||
- "prefetch-116-bytes"
|
||||
- "prefetch-108-bytes"
|
||||
description: |
|
||||
Host controller will wait for at least the specified number of bytes before triggering
|
||||
a single frame update. The prefetch mechanism should be enabled when frame buffer(s)
|
||||
is stored into external storage mediums, e.g. PSRAM, that introduce comparable delays.
|
||||
In such a case it might case that the controller runs into underrun conditions which
|
||||
results in correpting the whole frame update. It's user's responsibility to ensure that
|
||||
the selected value does not exceed frame's total size as otherwise the controller will
|
||||
not be able to trigger the frame update.
|
Loading…
Reference in a new issue