/* * Copyright (c) 2022 Byte-Lab d.o.o. * Copyright 2023 NXP * * 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 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 #if defined(CONFIG_HAS_CMSIS_CORE_M) #include #if __DCACHE_PRESENT == 1 #define CACHE_INVALIDATE(addr, size) SCB_InvalidateDCache_by_Addr((addr), (size)) #define CACHE_CLEAN(addr, size) SCB_CleanDCache_by_Addr((addr), (size)) #else #define CACHE_INVALIDATE(addr, size) #define CACHE_CLEAN(addr, size) barrier_dsync_fence_full(); #endif /* __DCACHE_PRESENT == 1 */ #else #define CACHE_INVALIDATE(addr, size) #define CACHE_CLEAN(addr, size) #endif /* CONFIG_HAS_CMSIS_CORE_M */ struct display_stm32_ltdc_data { LTDC_HandleTypeDef hltdc; enum display_pixel_format current_pixel_format; uint8_t current_pixel_size; uint8_t *frame_buffer; }; 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; }; static int stm32_ltdc_blanking_on(const struct device *dev) { return -ENOTSUP; } static int stm32_ltdc_blanking_off(const struct device *dev) { return -ENOTSUP; } static void *stm32_ltdc_get_framebuffer(const struct device *dev) { struct display_stm32_ltdc_data *data = dev->data; return (void *) data->frame_buffer; } static int stm32_ltdc_set_brightness(const struct device *dev, const uint8_t brightness) { return -ENOTSUP; } static int stm32_ltdc_set_contrast(const struct device *dev, const uint8_t contrast) { return -ENOTSUP; } 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 = data->frame_buffer; const uint8_t *src = buf; uint16_t row; /* 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); CACHE_CLEAN(dst, desc->width * data->current_pixel_size); dst += (config->width * data->current_pixel_size); src += (desc->pitch * data->current_pixel_size); } 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->frame_buffer; 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); CACHE_CLEAN(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_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; /* Initialise the LTDC peripheral */ err = HAL_LTDC_Init(&data->hltdc); if (err != HAL_OK) { return err; } /* Configure layer 0 (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], 0); if (err != HAL_OK) { return err; } 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->disp_on_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 = { .blanking_on = stm32_ltdc_blanking_on, .blanking_off = stm32_ltdc_blanking_off, .write = stm32_ltdc_write, .read = stm32_ltdc_read, .get_framebuffer = stm32_ltdc_get_framebuffer, .set_brightness = stm32_ltdc_set_brightness, .set_contrast = stm32_ltdc_set_contrast, .get_capabilities = stm32_ltdc_get_capabilities, .set_pixel_format = stm32_ltdc_set_pixel_format, .set_orientation = stm32_ltdc_set_orientation }; #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_DEVICE(inst) \ STM32_LTDC_DEVICE_PINCTRL_INIT(inst); \ PM_DEVICE_DT_INST_DEFINE(inst, stm32_ltdc_pm_action); \ /* frame buffer aligned to cache line width for optimal cache flushing */ \ FRAME_BUFFER_SECTION static uint8_t __aligned(32) \ frame_buffer_##inst[STM32_LTDC_INIT_PIXEL_SIZE * \ DT_INST_PROP(inst, height) * \ DT_INST_PROP(inst, width)]; \ static struct display_stm32_ltdc_data stm32_ltdc_data_##inst = { \ .frame_buffer = 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), \ }; \ 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)