/* * Copyright (c) 2023 Renesas Electronics Corporation * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT renesas_smartbond_display #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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; #if defined(CONFIG_PM_DEVICE) || defined(CONFIG_PM_DEVICE_RUNTIME) 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) { #if defined(CONFIG_PM_DEVICE) || defined(CONFIG_PM_DEVICE_RUNTIME) 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) { #if defined(CONFIG_PM_DEVICE) || defined(CONFIG_PM_DEVICE_RUNTIME) 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; /* 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) || defined(CONFIG_PM_DEVICE_RUNTIME) 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); #ifdef CONFIG_PM_DEVICE_RUNTIME /* Make sure device state is marked as suspended */ pm_device_init_suspended(dev); ret = pm_device_runtime_enable(dev); #else /* Resume if either PM is not used at all or if PM without runtime is used. */ ret = display_smartbond_resume(dev); #endif 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) || defined(CONFIG_PM_DEVICE_RUNTIME) 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 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);