/* * Copyright (c) 2022 Byte-Lab d.o.o. * Copyright 2023 NXP * Copyright (c) 2024 STMicroelectronics * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT st_stm32_ltdc #include #include #include #include #include #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(display_stm32_ltdc, CONFIG_DISPLAY_LOG_LEVEL); /* Horizontal synchronization pulse polarity */ #define LTDC_HSPOL_ACTIVE_LOW 0x00000000 #define LTDC_HSPOL_ACTIVE_HIGH 0x80000000 /* Vertical synchronization pulse polarity */ #define LTDC_VSPOL_ACTIVE_LOW 0x00000000 #define LTDC_VSPOL_ACTIVE_HIGH 0x40000000 /* Data enable pulse polarity */ #define LTDC_DEPOL_ACTIVE_LOW 0x00000000 #define LTDC_DEPOL_ACTIVE_HIGH 0x20000000 /* Pixel clock polarity */ #define LTDC_PCPOL_ACTIVE_LOW 0x00000000 #define LTDC_PCPOL_ACTIVE_HIGH 0x10000000 #if CONFIG_STM32_LTDC_ARGB8888 #define STM32_LTDC_INIT_PIXEL_SIZE 4u #define STM32_LTDC_INIT_PIXEL_FORMAT LTDC_PIXEL_FORMAT_ARGB8888 #define DISPLAY_INIT_PIXEL_FORMAT PIXEL_FORMAT_ARGB_8888 #elif CONFIG_STM32_LTDC_RGB888 #define STM32_LTDC_INIT_PIXEL_SIZE 3u #define STM32_LTDC_INIT_PIXEL_FORMAT LTDC_PIXEL_FORMAT_RGB888 #define DISPLAY_INIT_PIXEL_FORMAT PIXEL_FORMAT_RGB_888 #elif CONFIG_STM32_LTDC_RGB565 #define STM32_LTDC_INIT_PIXEL_SIZE 2u #define STM32_LTDC_INIT_PIXEL_FORMAT LTDC_PIXEL_FORMAT_RGB565 #define DISPLAY_INIT_PIXEL_FORMAT PIXEL_FORMAT_RGB_565 #else #error "Invalid LTDC pixel format chosen" #endif struct display_stm32_ltdc_data { LTDC_HandleTypeDef hltdc; enum display_pixel_format current_pixel_format; uint8_t current_pixel_size; uint8_t *frame_buffer; uint32_t frame_buffer_len; const uint8_t *pend_buf; const uint8_t *front_buf; struct k_sem sem; }; struct display_stm32_ltdc_config { uint32_t width; uint32_t height; struct gpio_dt_spec disp_on_gpio; struct gpio_dt_spec bl_ctrl_gpio; struct stm32_pclken pclken; const struct pinctrl_dev_config *pctrl; void (*irq_config_func)(const struct device *dev); const struct device *display_controller; }; static void stm32_ltdc_global_isr(const struct device *dev) { struct display_stm32_ltdc_data *data = dev->data; if (__HAL_LTDC_GET_FLAG(&data->hltdc, LTDC_FLAG_LI) && __HAL_LTDC_GET_IT_SOURCE(&data->hltdc, LTDC_IT_LI)) { if (data->front_buf != data->pend_buf) { data->front_buf = data->pend_buf; LTDC_LAYER(&data->hltdc, LTDC_LAYER_1)->CFBAR = (uint32_t)data->front_buf; __HAL_LTDC_RELOAD_CONFIG(&data->hltdc); k_sem_give(&data->sem); } __HAL_LTDC_CLEAR_FLAG(&data->hltdc, LTDC_FLAG_LI); } } static int stm32_ltdc_set_pixel_format(const struct device *dev, const enum display_pixel_format format) { int err; struct display_stm32_ltdc_data *data = dev->data; switch (format) { case PIXEL_FORMAT_RGB_565: err = HAL_LTDC_SetPixelFormat(&data->hltdc, LTDC_PIXEL_FORMAT_RGB565, 0); data->current_pixel_format = PIXEL_FORMAT_RGB_565; data->current_pixel_size = 2u; break; case PIXEL_FORMAT_RGB_888: err = HAL_LTDC_SetPixelFormat(&data->hltdc, LTDC_PIXEL_FORMAT_RGB888, 0); data->current_pixel_format = PIXEL_FORMAT_RGB_888; data->current_pixel_size = 3u; break; case PIXEL_FORMAT_ARGB_8888: err = HAL_LTDC_SetPixelFormat(&data->hltdc, LTDC_PIXEL_FORMAT_ARGB8888, 0); data->current_pixel_format = PIXEL_FORMAT_ARGB_8888; data->current_pixel_size = 4u; default: err = -ENOTSUP; break; } return err; } static int stm32_ltdc_set_orientation(const struct device *dev, const enum display_orientation orientation) { ARG_UNUSED(dev); if (orientation == DISPLAY_ORIENTATION_NORMAL) { return 0; } return -ENOTSUP; } static void stm32_ltdc_get_capabilities(const struct device *dev, struct display_capabilities *capabilities) { struct display_stm32_ltdc_data *data = dev->data; memset(capabilities, 0, sizeof(struct display_capabilities)); capabilities->x_resolution = data->hltdc.LayerCfg[0].WindowX1 - data->hltdc.LayerCfg[0].WindowX0; capabilities->y_resolution = data->hltdc.LayerCfg[0].WindowY1 - data->hltdc.LayerCfg[0].WindowY0; capabilities->supported_pixel_formats = PIXEL_FORMAT_ARGB_8888 | PIXEL_FORMAT_RGB_888 | PIXEL_FORMAT_RGB_565; capabilities->screen_info = 0; capabilities->current_pixel_format = data->current_pixel_format; capabilities->current_orientation = DISPLAY_ORIENTATION_NORMAL; } static int stm32_ltdc_write(const struct device *dev, const uint16_t x, const uint16_t y, const struct display_buffer_descriptor *desc, const void *buf) { const struct display_stm32_ltdc_config *config = dev->config; struct display_stm32_ltdc_data *data = dev->data; uint8_t *dst = NULL; const uint8_t *pend_buf = NULL; const uint8_t *src = buf; uint16_t row; if ((x == 0) && (y == 0) && (desc->width == config->width) && (desc->height == config->height) && (desc->pitch == desc->width)) { /* Use buf as ltdc frame buffer directly if it length same as ltdc frame buffer. */ pend_buf = buf; } else { if (CONFIG_STM32_LTDC_FB_NUM == 0) { LOG_ERR("Partial write requires internal frame buffer"); return -ENOTSUP; } dst = data->frame_buffer; if (CONFIG_STM32_LTDC_FB_NUM == 2) { if (data->front_buf == data->frame_buffer) { dst = data->frame_buffer + data->frame_buffer_len; } memcpy(dst, data->front_buf, data->frame_buffer_len); } pend_buf = dst; /* dst = pointer to upper left pixel of the rectangle * to be updated in frame buffer. */ dst += (x * data->current_pixel_size); dst += (y * config->width * data->current_pixel_size); for (row = 0; row < desc->height; row++) { (void) memcpy(dst, src, desc->width * data->current_pixel_size); sys_cache_data_flush_range(dst, desc->width * data->current_pixel_size); dst += (config->width * data->current_pixel_size); src += (desc->pitch * data->current_pixel_size); } } if (data->front_buf == pend_buf) { return 0; } k_sem_reset(&data->sem); data->pend_buf = pend_buf; k_sem_take(&data->sem, K_FOREVER); return 0; } static int stm32_ltdc_read(const struct device *dev, const uint16_t x, const uint16_t y, const struct display_buffer_descriptor *desc, void *buf) { const struct display_stm32_ltdc_config *config = dev->config; struct display_stm32_ltdc_data *data = dev->data; uint8_t *dst = buf; const uint8_t *src = data->front_buf; uint16_t row; /* src = pointer to upper left pixel of the rectangle to be read from frame buffer */ src += (x * data->current_pixel_size); src += (y * config->width * data->current_pixel_size); for (row = 0; row < desc->height; row++) { (void) memcpy(dst, src, desc->width * data->current_pixel_size); sys_cache_data_flush_range(dst, desc->width * data->current_pixel_size); src += (config->width * data->current_pixel_size); dst += (desc->pitch * data->current_pixel_size); } return 0; } static int stm32_ltdc_display_blanking_off(const struct device *dev) { const struct display_stm32_ltdc_config *config = dev->config; const struct device *display_dev = config->display_controller; if (display_dev == NULL) { return 0; } if (!device_is_ready(display_dev)) { LOG_ERR("Display device %s not ready", display_dev->name); return -ENODEV; } return display_blanking_off(display_dev); } static int stm32_ltdc_display_blanking_on(const struct device *dev) { const struct display_stm32_ltdc_config *config = dev->config; const struct device *display_dev = config->display_controller; if (display_dev == NULL) { return 0; } if (!device_is_ready(config->display_controller)) { LOG_ERR("Display device %s not ready", display_dev->name); return -ENODEV; } return display_blanking_on(display_dev); } static int stm32_ltdc_init(const struct device *dev) { int err; const struct display_stm32_ltdc_config *config = dev->config; struct display_stm32_ltdc_data *data = dev->data; /* Configure and set display on/off GPIO */ if (config->disp_on_gpio.port) { err = gpio_pin_configure_dt(&config->disp_on_gpio, GPIO_OUTPUT_ACTIVE); if (err < 0) { LOG_ERR("Configuration of display on/off control GPIO failed"); return err; } } /* Configure and set display backlight control GPIO */ if (config->bl_ctrl_gpio.port) { err = gpio_pin_configure_dt(&config->bl_ctrl_gpio, GPIO_OUTPUT_ACTIVE); if (err < 0) { LOG_ERR("Configuration of display backlight control GPIO failed"); return err; } } /* Configure DT provided pins */ if (!IS_ENABLED(CONFIG_MIPI_DSI)) { err = pinctrl_apply_state(config->pctrl, PINCTRL_STATE_DEFAULT); if (err < 0) { LOG_ERR("LTDC pinctrl setup failed"); return err; } } if (!device_is_ready(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE))) { LOG_ERR("clock control device not ready"); return -ENODEV; } /* Turn on LTDC peripheral clock */ err = clock_control_on(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE), (clock_control_subsys_t) &config->pclken); if (err < 0) { LOG_ERR("Could not enable LTDC peripheral clock"); return err; } #if defined(CONFIG_SOC_SERIES_STM32F4X) LL_RCC_PLLSAI_Disable(); LL_RCC_PLLSAI_ConfigDomain_LTDC(LL_RCC_PLLSOURCE_HSE, LL_RCC_PLLSAIM_DIV_8, 192, LL_RCC_PLLSAIR_DIV_4, LL_RCC_PLLSAIDIVR_DIV_8); LL_RCC_PLLSAI_Enable(); while (LL_RCC_PLLSAI_IsReady() != 1) { } #endif #if defined(CONFIG_SOC_SERIES_STM32F7X) LL_RCC_PLLSAI_Disable(); LL_RCC_PLLSAI_ConfigDomain_LTDC(LL_RCC_PLLSOURCE_HSE, LL_RCC_PLLM_DIV_25, 384, LL_RCC_PLLSAIR_DIV_5, LL_RCC_PLLSAIDIVR_DIV_8); LL_RCC_PLLSAI_Enable(); while (LL_RCC_PLLSAI_IsReady() != 1) { } #endif /* reset LTDC peripheral */ __HAL_RCC_LTDC_FORCE_RESET(); __HAL_RCC_LTDC_RELEASE_RESET(); data->current_pixel_format = DISPLAY_INIT_PIXEL_FORMAT; data->current_pixel_size = STM32_LTDC_INIT_PIXEL_SIZE; k_sem_init(&data->sem, 0, 1); config->irq_config_func(dev); #ifdef CONFIG_STM32_LTDC_DISABLE_FMC_BANK1 /* Clear MBKEN and MTYP[1:0] bits. */ #ifdef CONFIG_SOC_SERIES_STM32F7X FMC_Bank1->BTCR[0] &= ~(0x0000000D); #else /* CONFIG_SOC_SERIES_STM32H7X */ FMC_Bank1_R->BTCR[0] &= ~(0x0000000D); #endif #endif /* CONFIG_STM32_LTDC_DISABLE_FMC_BANK1 */ /* Initialise the LTDC peripheral */ err = HAL_LTDC_Init(&data->hltdc); if (err != HAL_OK) { return err; } /* Configure layer 1 (only one layer is used) */ /* LTDC starts fetching pixels and sending them to display after this call */ err = HAL_LTDC_ConfigLayer(&data->hltdc, &data->hltdc.LayerCfg[0], LTDC_LAYER_1); if (err != HAL_OK) { return err; } /* Disable layer 2, since it not used */ __HAL_LTDC_LAYER_DISABLE(&data->hltdc, LTDC_LAYER_2); /* Set the line interrupt position */ LTDC->LIPCR = 0U; __HAL_LTDC_CLEAR_FLAG(&data->hltdc, LTDC_FLAG_LI); __HAL_LTDC_ENABLE_IT(&data->hltdc, LTDC_IT_LI); return 0; } #ifdef CONFIG_PM_DEVICE static int stm32_ltdc_suspend(const struct device *dev) { const struct display_stm32_ltdc_config *config = dev->config; int err; /* Turn off disp_en (if its GPIO is defined in device tree) */ if (config->disp_on_gpio.port) { err = gpio_pin_set_dt(&config->disp_on_gpio, 0); if (err < 0) { return err; } } /* Turn off backlight (if its GPIO is defined in device tree) */ if (config->bl_ctrl_gpio.port) { err = gpio_pin_set_dt(&config->bl_ctrl_gpio, 0); if (err < 0) { return err; } } /* Reset LTDC peripheral registers */ __HAL_RCC_LTDC_FORCE_RESET(); __HAL_RCC_LTDC_RELEASE_RESET(); /* Turn off LTDC peripheral clock */ err = clock_control_off(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE), (clock_control_subsys_t) &config->pclken); return err; } static int stm32_ltdc_pm_action(const struct device *dev, enum pm_device_action action) { int err; switch (action) { case PM_DEVICE_ACTION_RESUME: err = stm32_ltdc_init(dev); break; case PM_DEVICE_ACTION_SUSPEND: err = stm32_ltdc_suspend(dev); break; default: return -ENOTSUP; } if (err < 0) { LOG_ERR("%s: failed to set power mode", dev->name); } return err; } #endif /* CONFIG_PM_DEVICE */ static const struct display_driver_api stm32_ltdc_display_api = { .write = stm32_ltdc_write, .read = stm32_ltdc_read, .get_capabilities = stm32_ltdc_get_capabilities, .set_pixel_format = stm32_ltdc_set_pixel_format, .set_orientation = stm32_ltdc_set_orientation, .blanking_off = stm32_ltdc_display_blanking_off, .blanking_on = stm32_ltdc_display_blanking_on, }; #if DT_INST_NODE_HAS_PROP(0, ext_sdram) #if DT_SAME_NODE(DT_INST_PHANDLE(0, ext_sdram), DT_NODELABEL(sdram1)) #define FRAME_BUFFER_SECTION __stm32_sdram1_section #elif DT_SAME_NODE(DT_INST_PHANDLE(0, ext_sdram), DT_NODELABEL(sdram2)) #define FRAME_BUFFER_SECTION __stm32_sdram2_section #else #error "LTDC ext-sdram property in device tree does not reference SDRAM1 or SDRAM2 node" #define FRAME_BUFFER_SECTION #endif /* DT_SAME_NODE(DT_INST_PHANDLE(0, ext_sdram), DT_NODELABEL(sdram1)) */ #else #define FRAME_BUFFER_SECTION #endif /* DT_INST_NODE_HAS_PROP(0, ext_sdram) */ #ifdef CONFIG_MIPI_DSI #define STM32_LTDC_DEVICE_PINCTRL_INIT(n) #define STM32_LTDC_DEVICE_PINCTRL_GET(n) (NULL) #else #define STM32_LTDC_DEVICE_PINCTRL_INIT(n) PINCTRL_DT_INST_DEFINE(n) #define STM32_LTDC_DEVICE_PINCTRL_GET(n) PINCTRL_DT_INST_DEV_CONFIG_GET(n) #endif #define STM32_LTDC_FRAME_BUFFER_LEN(inst) \ (STM32_LTDC_INIT_PIXEL_SIZE * DT_INST_PROP(inst, height) * DT_INST_PROP(inst, width)) \ #define STM32_LTDC_DEVICE(inst) \ STM32_LTDC_DEVICE_PINCTRL_INIT(inst); \ PM_DEVICE_DT_INST_DEFINE(inst, stm32_ltdc_pm_action); \ static void stm32_ltdc_irq_config_func_##inst(const struct device *dev) \ { \ IRQ_CONNECT(DT_INST_IRQN(inst), \ DT_INST_IRQ(inst, priority), \ stm32_ltdc_global_isr, \ DEVICE_DT_INST_GET(inst), \ 0); \ irq_enable(DT_INST_IRQN(inst)); \ } \ /* frame buffer aligned to cache line width for optimal cache flushing */ \ FRAME_BUFFER_SECTION static uint8_t __aligned(32) \ frame_buffer_##inst[CONFIG_STM32_LTDC_FB_NUM * \ STM32_LTDC_FRAME_BUFFER_LEN(inst)]; \ static struct display_stm32_ltdc_data stm32_ltdc_data_##inst = { \ .frame_buffer = frame_buffer_##inst, \ .frame_buffer_len = STM32_LTDC_FRAME_BUFFER_LEN(inst), \ .front_buf = frame_buffer_##inst, \ .pend_buf = frame_buffer_##inst, \ .hltdc = { \ .Instance = (LTDC_TypeDef *) DT_INST_REG_ADDR(inst), \ .Init = { \ .HSPolarity = (DT_PROP(DT_INST_CHILD(inst, display_timings), \ hsync_active) ? \ LTDC_HSPOL_ACTIVE_HIGH : LTDC_HSPOL_ACTIVE_LOW),\ .VSPolarity = (DT_PROP(DT_INST_CHILD(inst, \ display_timings), vsync_active) ? \ LTDC_VSPOL_ACTIVE_HIGH : LTDC_VSPOL_ACTIVE_LOW),\ .DEPolarity = (DT_PROP(DT_INST_CHILD(inst, \ display_timings), de_active) ? \ LTDC_DEPOL_ACTIVE_HIGH : LTDC_DEPOL_ACTIVE_LOW),\ .PCPolarity = (DT_PROP(DT_INST_CHILD(inst, \ display_timings), pixelclk_active) ? \ LTDC_PCPOL_ACTIVE_HIGH : LTDC_PCPOL_ACTIVE_LOW),\ .HorizontalSync = DT_PROP(DT_INST_CHILD(inst, \ display_timings), hsync_len) - 1, \ .VerticalSync = DT_PROP(DT_INST_CHILD(inst, \ display_timings), vsync_len) - 1, \ .AccumulatedHBP = DT_PROP(DT_INST_CHILD(inst, \ display_timings), hback_porch) + \ DT_PROP(DT_INST_CHILD(inst, \ display_timings), hsync_len) - 1, \ .AccumulatedVBP = DT_PROP(DT_INST_CHILD(inst, \ display_timings), vback_porch) + \ DT_PROP(DT_INST_CHILD(inst, \ display_timings), vsync_len) - 1, \ .AccumulatedActiveW = DT_PROP(DT_INST_CHILD(inst, \ display_timings), hback_porch) + \ DT_PROP(DT_INST_CHILD(inst, \ display_timings), hsync_len) + \ DT_INST_PROP(inst, width) - 1, \ .AccumulatedActiveH = DT_PROP(DT_INST_CHILD(inst, \ display_timings), vback_porch) + \ DT_PROP(DT_INST_CHILD(inst, \ display_timings), vsync_len) + \ DT_INST_PROP(inst, height) - 1, \ .TotalWidth = DT_PROP(DT_INST_CHILD(inst, \ display_timings), hback_porch) + \ DT_PROP(DT_INST_CHILD(inst, \ display_timings), hsync_len) + \ DT_INST_PROP(inst, width) + \ DT_PROP(DT_INST_CHILD(inst, \ display_timings), hfront_porch) - 1, \ .TotalHeigh = DT_PROP(DT_INST_CHILD(inst, \ display_timings), vback_porch) + \ DT_PROP(DT_INST_CHILD(inst, \ display_timings), vsync_len) + \ DT_INST_PROP(inst, height) + \ DT_PROP(DT_INST_CHILD(inst, \ display_timings), vfront_porch) - 1, \ .Backcolor.Red = \ DT_INST_PROP_OR(inst, def_back_color_red, 0xFF), \ .Backcolor.Green = \ DT_INST_PROP_OR(inst, def_back_color_green, 0xFF), \ .Backcolor.Blue = \ DT_INST_PROP_OR(inst, def_back_color_blue, 0xFF), \ }, \ .LayerCfg[0] = { \ .WindowX0 = DT_INST_PROP_OR(inst, window0_x0, 0), \ .WindowX1 = DT_INST_PROP_OR(inst, window0_x1, \ DT_INST_PROP(inst, width)), \ .WindowY0 = DT_INST_PROP_OR(inst, window0_y0, 0), \ .WindowY1 = DT_INST_PROP_OR(inst, window0_y1, \ DT_INST_PROP(inst, height)), \ .PixelFormat = STM32_LTDC_INIT_PIXEL_FORMAT, \ .Alpha = 255, \ .Alpha0 = 0, \ .BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA, \ .BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA, \ .FBStartAdress = (uint32_t) frame_buffer_##inst, \ .ImageWidth = DT_INST_PROP(inst, width), \ .ImageHeight = DT_INST_PROP(inst, height), \ .Backcolor.Red = \ DT_INST_PROP_OR(inst, def_back_color_red, 0xFF), \ .Backcolor.Green = \ DT_INST_PROP_OR(inst, def_back_color_green, 0xFF), \ .Backcolor.Blue = \ DT_INST_PROP_OR(inst, def_back_color_blue, 0xFF), \ }, \ }, \ }; \ static const struct display_stm32_ltdc_config stm32_ltdc_config_##inst = { \ .width = DT_INST_PROP(inst, width), \ .height = DT_INST_PROP(inst, height), \ .disp_on_gpio = COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, disp_on_gpios), \ (GPIO_DT_SPEC_INST_GET(inst, disp_on_gpios)), ({ 0 })), \ .bl_ctrl_gpio = COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, bl_ctrl_gpios), \ (GPIO_DT_SPEC_INST_GET(inst, bl_ctrl_gpios)), ({ 0 })), \ .pclken = { \ .enr = DT_INST_CLOCKS_CELL(inst, bits), \ .bus = DT_INST_CLOCKS_CELL(inst, bus) \ }, \ .pctrl = STM32_LTDC_DEVICE_PINCTRL_GET(inst), \ .irq_config_func = stm32_ltdc_irq_config_func_##inst, \ .display_controller = DEVICE_DT_GET_OR_NULL( \ DT_INST_PHANDLE(inst, display_controller)), \ }; \ DEVICE_DT_INST_DEFINE(inst, \ &stm32_ltdc_init, \ PM_DEVICE_DT_INST_GET(inst), \ &stm32_ltdc_data_##inst, \ &stm32_ltdc_config_##inst, \ POST_KERNEL, \ CONFIG_DISPLAY_INIT_PRIORITY, \ &stm32_ltdc_display_api); DT_INST_FOREACH_STATUS_OKAY(STM32_LTDC_DEVICE)