/* * Copyright (c) 2023 Renesas Electronics Corporation * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT renesas_smartbond_mipi_dbi #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(smartbond_mipi_dbi, CONFIG_MIPI_DBI_LOG_LEVEL); #define SMARTBOND_IRQN DT_INST_IRQN(0) #define SMARTBOND_IRQ_PRIO DT_INST_IRQ(0, priority) #define PINCTRL_STATE_READ PINCTRL_STATE_PRIV_START #define MIPI_DBI_SMARTBOND_IS_READ_SUPPORTED \ DT_INST_NODE_HAS_PROP(0, spi_dev) #define LCDC_SMARTBOND_CLK_DIV(_freq) \ ((32000000U % (_freq)) ? (96000000U / (_freq)) : (32000000U / (_freq))) #define MIPI_DBI_SMARTBOND_IS_PLL_REQUIRED \ !!(32000000U % DT_PROP(DT_CHOSEN(zephyr_display), mipi_max_frequency)) #define MIPI_DBI_SMARTBOND_IS_TE_ENABLED \ DT_INST_PROP_OR(0, te_enable, 0) #define MIPI_DBI_SMARTBOND_IS_DMA_PREFETCH_ENABLED \ DT_INST_ENUM_IDX_OR(0, dma_prefetch, 0) #define MIPI_DBI_SMARTBOND_IS_RESET_AVAILABLE \ DT_INST_NODE_HAS_PROP(0, reset_gpios) #define LCDC_LAYER0_OFFSETX_REG_SET_FIELD(_field, _var, _val) \ ((_var)) = \ ((_var) & ~(LCDC_LCDC_LAYER0_OFFSETX_REG_ ## _field ## _Msk)) | \ (((_var) << LCDC_LCDC_LAYER0_OFFSETX_REG_ ## _field ## _Pos) & \ LCDC_LCDC_LAYER0_OFFSETX_REG_ ## _field ## _Msk) struct mipi_dbi_smartbond_data { /* Provide mutual exclusion when a display operation is requested. */ struct k_sem device_sem; /* Provide synchronization between task return and ISR firing */ 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; }; struct mipi_dbi_smartbond_config { /* Reference to device instance's pinctrl configurations */ const struct pinctrl_dev_config *pcfg; /* Reset GPIO */ const struct gpio_dt_spec reset; /* Host controller's timing settings */ lcdc_smartbond_timing_cfg timing_cfg; /* Background default color configuration */ lcdc_smartbond_bgcolor_cfg bgcolor_cfg; }; /* Mark the device is progress and so it's not allowed to enter the sleep state. */ static inline void mipi_dbi_smartbond_pm_policy_state_lock_get(void) { /* * 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); } /* Mark that device is inactive and so it's allowed to enter the sleep state */ static inline void mipi_dbi_smartbond_pm_policy_state_lock_put(void) { /* Allow the SoC to enter the nornmal sleep state once LCDC is inactive */ pm_policy_state_lock_put(PM_STATE_STANDBY, PM_ALL_SUBSTATES); } /* Helper function to trigger the LCDC fetching data from frame buffer to the connected display */ static void mipi_dbi_smartbond_send_single_frame(const struct device *dev) { struct mipi_dbi_smartbond_data *data = dev->data; #if MIPI_DBI_SMARTBOND_IS_TE_ENABLED da1469x_lcdc_te_set_status(true, DT_INST_PROP_OR(0, te_polarity, false)); /* * Wait for the TE signal to be asserted so display's refresh status can be synchronized * with the current frame update. */ k_sem_take(&data->sync_sem, K_FOREVER); #endif LCDC->LCDC_INTERRUPT_REG |= LCDC_LCDC_INTERRUPT_REG_LCDC_VSYNC_IRQ_EN_Msk; /* Setting this bit will enable the host to start outputing pixel data */ LCDC->LCDC_MODE_REG |= LCDC_LCDC_MODE_REG_LCDC_SFRAME_UPD_Msk; /* Wait for frame update to complete */ k_sem_take(&data->sync_sem, K_FOREVER); if (data->underflow_flag) { LOG_WRN("Underflow took place"); data->underflow_flag = false; } } #if MIPI_DBI_SMARTBOND_IS_RESET_AVAILABLE static int mipi_dbi_smartbond_reset(const struct device *dev, k_timeout_t delay) { const struct mipi_dbi_smartbond_config *config = dev->config; int ret; if (!gpio_is_ready_dt(&config->reset)) { LOG_ERR("Reset signal not available"); return -ENODEV; } ret = gpio_pin_set_dt(&config->reset, 1); if (ret < 0) { LOG_ERR("Cannot drive reset signal"); return ret; } k_sleep(delay); return gpio_pin_set_dt(&config->reset, 0); } #endif /* Display pixel to output color format translation */ static inline uint8_t lcdc_smartbond_pixel_to_ocm(enum display_pixel_format pixfmt) { switch (pixfmt) { case PIXEL_FORMAT_RGB_565: return (uint8_t)LCDC_SMARTBOND_OCM_RGB565; case PIXEL_FORMAT_RGB_888: return (uint8_t)LCDC_SMARTBOND_OCM_RGB888; case PIXEL_FORMAT_MONO10: return (uint8_t)LCDC_SMARTBOND_L0_L1; default: LOG_ERR("Unsupported pixel format"); return 0; }; } static inline uint8_t lcdc_smartbond_line_mode_translation(uint8_t mode) { switch (mode) { case MIPI_DBI_MODE_SPI_3WIRE: return (uint8_t)LCDC_SMARTBOND_MODE_SPI3; case MIPI_DBI_MODE_SPI_4WIRE: return (uint8_t)LCDC_SMARTBOND_MODE_SPI4; default: LOG_ERR("Unsupported SPI mode"); return 0; } } static inline uint8_t lcdc_smartbond_pixel_to_lcm(enum display_pixel_format pixfmt) { switch (pixfmt) { 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 void lcdc_smartbond_mipi_dbi_translation(const struct mipi_dbi_config *dbi_config, lcdc_smartbond_mipi_dbi_cfg *mipi_dbi_cfg, enum display_pixel_format pixfmt) { mipi_dbi_cfg->cpha = dbi_config->config.operation & SPI_MODE_CPHA; mipi_dbi_cfg->cpol = dbi_config->config.operation & SPI_MODE_CPOL; mipi_dbi_cfg->cs_active_high = dbi_config->config.operation & SPI_CS_ACTIVE_HIGH; mipi_dbi_cfg->line_mode = lcdc_smartbond_line_mode_translation(dbi_config->mode); mipi_dbi_cfg->color_mode = lcdc_smartbond_pixel_to_ocm(pixfmt); } #if MIPI_DBI_SMARTBOND_IS_READ_SUPPORTED static int mipi_dbi_smartbond_command_read(const struct device *dev, const struct mipi_dbi_config *dbi_config, uint8_t *cmd, size_t num_cmds, uint8_t *response, size_t len) { struct mipi_dbi_smartbond_data *data = dev->data; const struct mipi_dbi_smartbond_config *config = dev->config; int ret = 0; lcdc_smartbond_mipi_dbi_cfg mipi_dbi_cfg; k_sem_take(&data->device_sem, K_FOREVER); mipi_dbi_smartbond_pm_policy_state_lock_get(); /* * Add an arbitrary valid color format to satisfy subroutine. The MIPI DBI command/data * engine should not be affected. */ lcdc_smartbond_mipi_dbi_translation(dbi_config, &mipi_dbi_cfg, PIXEL_FORMAT_RGB_565); ret = da1469x_lcdc_mipi_dbi_interface_configure(&mipi_dbi_cfg); if (ret < 0) { goto _mipi_dbi_read_exit; } /* Check if the cmd/data engine is busy since the #CS line will be overruled. */ if (da1469x_lcdc_is_busy()) { LOG_WRN("MIPI DBI host is busy"); ret = -EBUSY; goto _mipi_dbi_read_exit; } /* Force CS line to low. Typically, command and data are bound in the same #CS assertion */ da1469x_lcdc_force_cs_line(true, mipi_dbi_cfg.cs_active_high); da1469x_lcdc_send_cmd_data(true, cmd, num_cmds); if (len) { const struct device *spi_dev = DEVICE_DT_GET(DT_INST_PHANDLE(0, spi_dev)); struct spi_buf buffer = { .buf = (void *)response, .len = len, }; struct spi_buf_set buf_set = { .buffers = &buffer, .count = 1, }; if (!device_is_ready(spi_dev)) { LOG_ERR("SPI device is not ready"); ret = -ENODEV; goto _mipi_dbi_read_exit; } /* Overwrite CLK and enable DI lines. CS is driven forcefully. */ ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_READ); if (ret < 0) { LOG_ERR("Could not apply MIPI DBI pins' SPI read state (%d)", ret); goto _mipi_dbi_read_exit; } /* Get response */ ret = spi_read(spi_dev, &dbi_config->config, &buf_set); if (ret < 0) { LOG_ERR("Could not read data from SPI"); goto _mipi_dbi_read_exit; } } _mipi_dbi_read_exit: /* Restore #CS line */ da1469x_lcdc_force_cs_line(false, mipi_dbi_cfg.cs_active_high); /* Make sure default LCDC pins are applied upon exit */ ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); if (ret < 0) { LOG_ERR("Could not apply MIPI DBI pins' default state (%d)", ret); } mipi_dbi_smartbond_pm_policy_state_lock_put(); k_sem_give(&data->device_sem); return ret; } #endif static int mipi_dbi_smartbond_command_write(const struct device *dev, const struct mipi_dbi_config *dbi_config, uint8_t cmd, const uint8_t *data_buf, size_t len) { struct mipi_dbi_smartbond_data *data = dev->data; int ret = 0; lcdc_smartbond_mipi_dbi_cfg mipi_dbi_cfg; k_sem_take(&data->device_sem, K_FOREVER); mipi_dbi_smartbond_pm_policy_state_lock_get(); /* * Add an arbitrary valid color format to satisfy subroutine. The MIPI DBI command/data * engine should not be affected. */ lcdc_smartbond_mipi_dbi_translation(dbi_config, &mipi_dbi_cfg, PIXEL_FORMAT_RGB_565); ret = da1469x_lcdc_mipi_dbi_interface_configure(&mipi_dbi_cfg); if (ret < 0) { goto finish; } /* Command and accompanied data should be transmitted via the DBIB interface */ da1469x_lcdc_send_cmd_data(true, &cmd, 1); if (len) { /* Data should be transmitted via the DBIB interface */ da1469x_lcdc_send_cmd_data(false, data_buf, len); } finish: mipi_dbi_smartbond_pm_policy_state_lock_put(); k_sem_give(&data->device_sem); return ret; } static int mipi_dbi_smartbond_write_display(const struct device *dev, const struct mipi_dbi_config *dbi_config, const uint8_t *framebuf, struct display_buffer_descriptor *desc, enum display_pixel_format pixfmt) { struct mipi_dbi_smartbond_data *data = dev->data; const struct mipi_dbi_smartbond_config *config = dev->config; lcdc_smartbond_layer_cfg *layer = &data->layer; int ret = 0; lcdc_smartbond_mipi_dbi_cfg mipi_dbi_cfg; uint8_t layer_color = lcdc_smartbond_pixel_to_lcm(pixfmt); if (desc->width * desc->height * (DISPLAY_BITS_PER_PIXEL(pixfmt) / BITS_PER_BYTE) != desc->buf_size) { LOG_ERR("Incorrect buffer size for given width and height"); return -EINVAL; } k_sem_take(&data->device_sem, K_FOREVER); mipi_dbi_smartbond_pm_policy_state_lock_get(); /* * Mainly check if the frame generator is busy with a pending frame update (might happen * when two frame updates take place one after the other and the display interface is * quite slow). VSYNC interrupt line should be asserted when the last line is being * outputed. */ if (da1469x_lcdc_is_busy()) { LOG_WRN("MIPI DBI host is busy"); ret = -EBUSY; goto _mipi_dbi_write_exit; } lcdc_smartbond_mipi_dbi_translation(dbi_config, &mipi_dbi_cfg, pixfmt); ret = da1469x_lcdc_mipi_dbi_interface_configure(&mipi_dbi_cfg); if (ret < 0) { goto _mipi_dbi_write_exit; } ret = da1469x_lcdc_timings_configure(desc->width, desc->height, (lcdc_smartbond_timing_cfg *)&config->timing_cfg); if (ret < 0) { goto _mipi_dbi_write_exit; } LCDC_SMARTBOND_LAYER_CONFIG(layer, framebuf, 0, 0, desc->width, desc->height, layer_color, da1469x_lcdc_stride_calculation(layer_color, desc->width)); ret = da1469x_lcdc_layer_configure(layer); if (ret < 0) { goto _mipi_dbi_write_exit; } /* Trigger single frame update via the LCDC-DMA engine */ mipi_dbi_smartbond_send_single_frame(dev); _mipi_dbi_write_exit: mipi_dbi_smartbond_pm_policy_state_lock_put(); k_sem_give(&data->device_sem); return ret; } static int mipi_dbi_smartbond_configure(const struct device *dev) { uint8_t clk_div = LCDC_SMARTBOND_CLK_DIV(DT_PROP(DT_CHOSEN(zephyr_display), mipi_max_frequency)); const struct mipi_dbi_smartbond_config *config = dev->config; /* * First enable the controller so registers can be written. In serial interfaces * clock divider is further divided by 2. */ da1469x_lcdc_set_status(true, MIPI_DBI_SMARTBOND_IS_PLL_REQUIRED, (clk_div >= 2 ? clk_div / 2 : clk_div)); if (!da1469x_lcdc_check_id()) { LOG_ERR("Mismatching LCDC ID"); da1469x_lcdc_set_status(false, 0, 0); return -EINVAL; } da1469x_lcdc_te_set_status(false, DT_INST_PROP_OR(0, te_polarity, false)); da1469x_lcdc_bgcolor_configure((lcdc_smartbond_bgcolor_cfg *)&config->bgcolor_cfg); LCDC_LAYER0_OFFSETX_REG_SET_FIELD(LCDC_L0_DMA_PREFETCH, LCDC->LCDC_LAYER0_OFFSETX_REG, MIPI_DBI_SMARTBOND_IS_DMA_PREFETCH_ENABLED); return 0; } static void smartbond_mipi_dbi_isr(const void *arg) { struct mipi_dbi_smartbond_data *data = ((const struct device *)arg)->data; /* * Underflow sticky bit will remain high until cleared by writing * any value to LCDC_INTERRUPT_REG. */ data->underflow_flag = LCDC_STATUS_REG_GET_FIELD(LCDC_STICKY_UNDERFLOW); /* Default interrupt mode is level triggering so interrupt should be cleared */ da1469x_lcdc_te_set_status(false, DT_INST_PROP_OR(0, te_polarity, false)); k_sem_give(&data->sync_sem); } static int mipi_dbi_smartbond_resume(const struct device *dev) { const struct mipi_dbi_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 MIPI_DBI_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 available; PLL cannot be used"); } else { ret = z_smartbond_select_sys_clk(SMARTBOND_CLK_PLL96M); if (ret < 0) { LOG_WRN("Could not switch to PLL. Requested speed should not be achieved."); } } #endif return mipi_dbi_smartbond_configure(dev); } #if defined(CONFIG_PM_DEVICE) static int mipi_dbi_smartbond_suspend(const struct device *dev) { const struct mipi_dbi_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 MIPI DBI pins' sleep state"); } /* Disable host controller to minimize power consumption. */ da1469x_lcdc_set_status(false, false, 0); return 0; } static int mipi_dbi_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)mipi_dbi_smartbond_suspend(dev); break; case PM_DEVICE_ACTION_RESUME: /* * The resume error code should not be taken into consideration * by the PM subsystem. */ ret = mipi_dbi_smartbond_resume(dev); break; default: return -ENOTSUP; } return ret; } #endif static int mipi_dbi_smartbond_init(const struct device *dev) { __unused const struct mipi_dbi_smartbond_config *config = dev->config; struct mipi_dbi_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); #if MIPI_DBI_SMARTBOND_IS_RESET_AVAILABLE if (gpio_is_ready_dt(&config->reset)) { ret = gpio_pin_configure_dt(&config->reset, GPIO_OUTPUT_INACTIVE); if (ret < 0) { LOG_ERR("Could not configure reset line (%d)", ret); return -EIO; } } #endif IRQ_CONNECT(SMARTBOND_IRQN, SMARTBOND_IRQ_PRIO, smartbond_mipi_dbi_isr, DEVICE_DT_INST_GET(0), 0); ret = mipi_dbi_smartbond_resume(dev); return ret; } static const struct mipi_dbi_driver_api mipi_dbi_smartbond_driver_api = { #if MIPI_DBI_SMARTBOND_IS_RESET_AVAILABLE .reset = mipi_dbi_smartbond_reset, #endif .command_write = mipi_dbi_smartbond_command_write, .write_display = mipi_dbi_smartbond_write_display, #if MIPI_DBI_SMARTBOND_IS_READ_SUPPORTED .command_read = mipi_dbi_smartbond_command_read, #endif }; #define SMARTBOND_MIPI_DBI_INIT(inst) \ PINCTRL_DT_INST_DEFINE(inst); \ \ static const struct mipi_dbi_smartbond_config mipi_dbi_smartbond_config_## inst = { \ .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \ .reset = GPIO_DT_SPEC_INST_GET_OR(inst, reset_gpios, {}), \ .timing_cfg = { 0 }, \ .bgcolor_cfg = { 0xFF, 0xFF, 0xFF, 0 }, \ }; \ \ static struct mipi_dbi_smartbond_data mipi_dbi_smartbond_data_## inst; \ \ PM_DEVICE_DT_INST_DEFINE(inst, mipi_dbi_smartbond_pm_action); \ \ DEVICE_DT_INST_DEFINE(inst, mipi_dbi_smartbond_init, \ PM_DEVICE_DT_INST_GET(inst), \ &mipi_dbi_smartbond_data_## inst, \ &mipi_dbi_smartbond_config_## inst, \ POST_KERNEL, \ CONFIG_MIPI_DBI_INIT_PRIORITY, \ &mipi_dbi_smartbond_driver_api); SMARTBOND_MIPI_DBI_INIT(0);