blob: b51ba33585abec22deff9784884e6564c2397653 [file] [log] [blame]
/*
* 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/pm/device.h>
#include <zephyr/pm/policy.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)) / BITS_PER_BYTE)
#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;
#if defined(CONFIG_PM_DEVICE)
ATOMIC_DEFINE(pm_policy_state_flag, 1);
#endif
};
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;
};
static inline void lcdc_smartbond_pm_policy_state_lock_get(struct display_smartbond_data *data)
{
#ifdef CONFIG_PM_DEVICE
if (atomic_test_and_set_bit(data->pm_policy_state_flag, 0) == 0) {
/*
* Prevent the SoC from etering the normal sleep state as PDC does not support
* waking up the application core following LCDC events.
*/
pm_policy_state_lock_get(PM_STATE_STANDBY, PM_ALL_SUBSTATES);
}
#endif
}
static inline void lcdc_smartbond_pm_policy_state_lock_put(struct display_smartbond_data *data)
{
#ifdef CONFIG_PM_DEVICE
if (atomic_test_and_clear_bit(data->pm_policy_state_flag, 0) == 1) {
/* Allow the SoC to enter the nornmal sleep state once LCDC is inactive */
pm_policy_state_lock_put(PM_STATE_STANDBY, PM_ALL_SUBSTATES);
}
#endif
}
/* 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 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;
data->dma_cfg.error_callback_dis = 1;
/* 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_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
ret = display_smartbond_dma_config(dev);
if (ret < 0) {
return ret;
}
return display_smartbond_configure(dev);
}
#if defined(CONFIG_PM_DEVICE)
static void display_smartbond_dma_deconfig(const struct device *dev)
{
struct display_smartbond_data *data = dev->data;
__ASSERT(data->dma, "DMA should be already initialized");
dma_release_channel(data->dma, data->dma_channel);
}
static int display_smartbond_suspend(const struct device *dev)
{
const struct display_smartbond_config *config = dev->config;
int ret;
/* Select sleep state; it's OK if settings fails for any reason */
ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_SLEEP);
if (ret < 0) {
LOG_WRN("Could not apply DISPLAY pins' sleep state");
}
/* Disable host controller to minimize power consumption */
da1469x_lcdc_set_status(false, false, 0);
display_smartbond_dma_deconfig(dev);
return 0;
}
#endif
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;
}
}
IRQ_CONNECT(SMARTBOND_IRQN, SMARTBOND_IRQ_PRIO, smartbond_display_isr,
DEVICE_DT_INST_GET(0), 0);
/*
* Currently, there is no API to explicitly enable/disable the display controller.
* At the same time, the controller is set to continuous mode meaning that
* as long as a display panel is turned on, frame updates should happen all
* the time (otherwise contents on the display pane will be lost as the latter
* does not integrate an SDRAM memory to keep its frame).
* As such, resume/suspend operations are bound to blanking operations.
* That is, when the display is blanked on we can safely consider that display
* is no longer functional and thus, the controller can be suspended (allowing the
* SoC to enter the sleep state). Once the display is blanked off, then we consider
* that the controller should be resumed and sleep should be prevented at all
* (this is because the controller is powered by the same power domain used to
* power the application core). Side effect of the above is that the controller
* should be configured at initialization phase as display operations might
* be requested before the display is blanked off for the very first time.
*/
ret = display_smartbond_resume(dev);
if (ret == 0) {
/* Display port should be enabled at this moment and so sleep is not allowed. */
lcdc_smartbond_pm_policy_state_lock_get(data);
}
return ret;
}
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");
}
}
/*
* At this moment the display panel should be turned off and so the device
* can enter the suspend state.
*/
lcdc_smartbond_pm_policy_state_lock_put(data);
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;
/*
* At this moment the display should be turned on and so the device
* cannot enter the suspend state.
*/
lcdc_smartbond_pm_policy_state_lock_get(data);
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;
}
#if defined(CONFIG_PM_DEVICE)
static int display_smartbond_pm_action(const struct device *dev, enum pm_device_action action)
{
int ret = 0;
switch (action) {
case PM_DEVICE_ACTION_SUSPEND:
/* A non-zero value should not affect sleep */
(void)display_smartbond_suspend(dev);
break;
case PM_DEVICE_ACTION_RESUME:
/*
* The resume error code should not be taken into consideration
* by the PM subsystem
*/
ret = display_smartbond_resume(dev);
break;
default:
ret = -ENOTSUP;
}
return ret;
}
#endif
static const 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); \
PM_DEVICE_DT_INST_DEFINE(inst, display_smartbond_pm_action); \
\
__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, PM_DEVICE_DT_INST_GET(inst), \
&display_smartbond_data_## inst, \
&display_smartbond_config_## inst, \
POST_KERNEL, \
CONFIG_DISPLAY_INIT_PRIORITY, \
&display_smartbond_driver_api);
SMARTBOND_DISPLAY_INIT(0);