| /* |
| * Copyright (c) 2024-2025 Renesas Electronics Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT renesas_ra_glcdc |
| |
| #include "display_renesas_ra.h" |
| #include "r_glcdc.h" |
| #include <zephyr/drivers/clock_control/renesas_ra_cgc.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/irq.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/logging/log.h> |
| |
| LOG_MODULE_REGISTER(display_renesas_ra, CONFIG_DISPLAY_LOG_LEVEL); |
| |
| struct display_ra_config { |
| const struct pinctrl_dev_config *pincfg; |
| const struct gpio_dt_spec backlight_gpio; |
| const struct device *clock_dev; |
| struct clock_control_ra_subsys_cfg clock_glcdc_subsys; |
| uint16_t height; |
| uint16_t width; |
| void (*irq_configure)(void); |
| }; |
| |
| struct display_ra_data { |
| glcdc_instance_ctrl_t display_ctrl; |
| display_cfg_t display_fsp_cfg; |
| const uint8_t *pend_buf; |
| const uint8_t *front_buf; |
| uint8_t pixel_size; |
| enum display_pixel_format current_pixel_format; |
| uint8_t *frame_buffer; |
| uint32_t frame_buffer_len; |
| struct k_sem frame_buf_sem; |
| }; |
| |
| extern void glcdc_line_detect_isr(void); |
| |
| static void renesas_ra_glcdc_isr(const struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| glcdc_line_detect_isr(); |
| } |
| |
| static void renesas_ra_callback_adapter(display_callback_args_t *p_args) |
| { |
| const struct device *dev = p_args->p_context; |
| struct display_ra_data *data = dev->data; |
| |
| if (p_args->event == DISPLAY_EVENT_LINE_DETECTION) { |
| if (data->front_buf != data->pend_buf) { |
| data->front_buf = data->pend_buf; |
| } |
| |
| k_sem_give(&data->frame_buf_sem); |
| } |
| } |
| |
| static int ra_display_write(const struct device *dev, const uint16_t x, const uint16_t y, |
| const struct display_buffer_descriptor *desc, const void *buf) |
| { |
| struct display_ra_data *data = dev->data; |
| const struct display_ra_config *config = dev->config; |
| const uint8_t *l_pend_buf = NULL; |
| bool vsync_wait = false; |
| fsp_err_t err; |
| |
| if (desc->pitch < desc->width) { |
| LOG_ERR("Pitch is smaller than width"); |
| return -EINVAL; |
| } |
| |
| if ((desc->pitch * data->pixel_size * desc->height) > desc->buf_size) { |
| LOG_ERR("Input buffer too small"); |
| return -EINVAL; |
| } |
| |
| if (x == 0 && y == 0 && desc->height == config->height && desc->width == config->width) { |
| l_pend_buf = buf; |
| } else { |
| #if CONFIG_RENESAS_RA_GLCDC_FB_NUM == 0 |
| LOG_ERR("Partial write requires internal frame buffer"); |
| return -ENOTSUP; |
| #else |
| const uint8_t *src = buf; |
| uint8_t *dst = NULL; |
| uint16_t row; |
| |
| dst = data->frame_buffer; |
| |
| if (CONFIG_RENESAS_RA_GLCDC_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); |
| } |
| |
| l_pend_buf = dst; |
| |
| /* dst = pointer to upper left pixel of the rectangle |
| * to be updated in frame buffer. |
| */ |
| dst += (x * data->pixel_size); |
| dst += (y * config->width * data->pixel_size); |
| |
| for (row = 0; row < desc->height; row++) { |
| (void)memcpy(dst, src, desc->width * data->pixel_size); |
| dst += (config->width * data->pixel_size); |
| src += (desc->pitch * data->pixel_size); |
| } |
| #endif /* CONFIG_RENESAS_RA_GLCDC_FB_NUM == 0 */ |
| } |
| |
| k_sem_reset(&data->frame_buf_sem); |
| |
| if (data->front_buf != l_pend_buf) { |
| data->pend_buf = l_pend_buf; |
| |
| err = R_GLCDC_BufferChange(&data->display_ctrl, (uint8_t *)data->pend_buf, |
| DISPLAY_FRAME_LAYER_1); |
| if (err != FSP_SUCCESS) { |
| LOG_ERR("GLCDC buffer change failed"); |
| return -EIO; |
| } |
| |
| vsync_wait = true; |
| } |
| |
| if (data->display_ctrl.state != DISPLAY_STATE_DISPLAYING) { |
| err = R_GLCDC_Start(&data->display_ctrl); |
| if (err != FSP_SUCCESS) { |
| LOG_ERR("GLCDC start failed"); |
| return -EIO; |
| } |
| |
| vsync_wait = true; |
| } |
| |
| if (vsync_wait) { |
| k_sem_take(&data->frame_buf_sem, K_FOREVER); |
| } |
| |
| return 0; |
| } |
| |
| static int ra_display_read(const struct device *dev, const uint16_t x, const uint16_t y, |
| const struct display_buffer_descriptor *desc, void *buf) |
| { |
| struct display_ra_data *data = dev->data; |
| const struct display_ra_config *config = dev->config; |
| 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->pixel_size); |
| src += (y * config->width * data->pixel_size); |
| |
| for (row = 0; row < desc->height; row++) { |
| (void)memcpy(dst, src, desc->width * data->pixel_size); |
| src += (config->width * data->pixel_size); |
| dst += (desc->pitch * data->pixel_size); |
| } |
| |
| return 0; |
| } |
| |
| static int ra_display_blanking_on(const struct device *dev) |
| { |
| const struct display_ra_config *config = dev->config; |
| int ret = 0; |
| |
| if (config->backlight_gpio.port != NULL) { |
| ret = gpio_pin_set_dt(&config->backlight_gpio, 0); |
| } else { |
| ret = -ENOTSUP; |
| } |
| |
| return ret; |
| } |
| |
| static int ra_display_blanking_off(const struct device *dev) |
| { |
| const struct display_ra_config *config = dev->config; |
| int ret = 0; |
| |
| if (config->backlight_gpio.port != NULL) { |
| ret = gpio_pin_set_dt(&config->backlight_gpio, 1); |
| } else { |
| ret = -ENOTSUP; |
| } |
| |
| return ret; |
| } |
| |
| static void ra_display_get_capabilities(const struct device *dev, |
| struct display_capabilities *capabilities) |
| { |
| const struct display_ra_config *config = dev->config; |
| struct display_ra_data *data = dev->data; |
| |
| capabilities->x_resolution = config->width; |
| capabilities->y_resolution = config->height; |
| capabilities->current_orientation = DISPLAY_ORIENTATION_NORMAL; |
| capabilities->supported_pixel_formats = |
| PIXEL_FORMAT_RGB_888 | PIXEL_FORMAT_ARGB_8888 | PIXEL_FORMAT_RGB_565; |
| capabilities->current_pixel_format = data->current_pixel_format; |
| capabilities->screen_info = 0U; |
| } |
| |
| static int ra_display_set_pixel_format(const struct device *dev, |
| const enum display_pixel_format pixel_format) |
| { |
| const struct display_ra_config *config = dev->config; |
| struct display_ra_data *data = dev->data; |
| display_runtime_cfg_t layer_cfg; |
| enum display_pixel_format set_pixel_format; |
| display_in_format_t hardware_pixel_format; |
| uint32_t buf_len; |
| fsp_err_t err; |
| |
| if (pixel_format == data->current_pixel_format) { |
| return 0; |
| } |
| |
| if (data->display_ctrl.state == DISPLAY_STATE_DISPLAYING) { |
| LOG_ERR("Cannot change the display format while displaying"); |
| return -EWOULDBLOCK; |
| } |
| |
| switch (pixel_format) { |
| case PIXEL_FORMAT_RGB_888: |
| set_pixel_format = PIXEL_FORMAT_ARGB_8888; |
| hardware_pixel_format = DISPLAY_IN_FORMAT_32BITS_RGB888; |
| break; |
| |
| case PIXEL_FORMAT_ARGB_8888: |
| set_pixel_format = PIXEL_FORMAT_ARGB_8888; |
| hardware_pixel_format = DISPLAY_IN_FORMAT_32BITS_ARGB8888; |
| break; |
| |
| case PIXEL_FORMAT_RGB_565: |
| set_pixel_format = PIXEL_FORMAT_RGB_565; |
| hardware_pixel_format = DISPLAY_IN_FORMAT_16BITS_RGB565; |
| break; |
| |
| default: |
| return -ENOTSUP; |
| } |
| |
| buf_len = (config->height * config->width * DISPLAY_BITS_PER_PIXEL(set_pixel_format)) >> 3; |
| |
| if (buf_len > data->frame_buffer_len) { |
| LOG_ERR("Frame buffer is smaller than new pixel format require"); |
| return -ENOTSUP; |
| } |
| |
| memcpy(&layer_cfg.input, &data->display_fsp_cfg.input[0], sizeof(display_input_cfg_t)); |
| memcpy(&layer_cfg.layer, &data->display_fsp_cfg.layer[0], sizeof(display_layer_t)); |
| layer_cfg.input.format = hardware_pixel_format; |
| layer_cfg.input.hstride = |
| ROUND_UP(layer_cfg.input.hsize * DISPLAY_BITS_PER_PIXEL(set_pixel_format), |
| NUM_BITS(uint64_t)) / |
| DISPLAY_BITS_PER_PIXEL(set_pixel_format); |
| |
| err = R_GLCDC_LayerChange(&data->display_ctrl, &layer_cfg, DISPLAY_FRAME_LAYER_1); |
| if (err != FSP_SUCCESS) { |
| LOG_ERR("Failed to change the pixel format"); |
| return -EIO; |
| } |
| |
| data->current_pixel_format = pixel_format; |
| data->pixel_size = DISPLAY_BITS_PER_PIXEL(set_pixel_format) >> 3; |
| |
| return 0; |
| } |
| |
| static int ra_display_color_config(const struct device *dev, |
| const display_correction_t *display_color_cfg) |
| { |
| struct display_ra_data *data = dev->data; |
| fsp_err_t err; |
| int ret; |
| |
| err = R_GLCDC_ColorCorrection(&data->display_ctrl, display_color_cfg); |
| switch (err) { |
| case FSP_SUCCESS: |
| ret = 0; |
| break; |
| case FSP_ERR_INVALID_UPDATE_TIMING: |
| ret = -EWOULDBLOCK; |
| break; |
| default: |
| ret = -EIO; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| #define RENESAS_RA_GLCDC_BRIGHTNESS_MAX 1023U |
| |
| static int ra_display_set_brightness(const struct device *dev, const uint8_t brightness) |
| { |
| struct display_ra_data *data = dev->data; |
| const uint32_t brightness_adj = |
| DIV_ROUND_CLOSEST(brightness * RENESAS_RA_GLCDC_BRIGHTNESS_MAX, UINT8_MAX); |
| display_correction_t display_color_cfg; |
| |
| if (brightness_adj == 0) { |
| return -EINVAL; |
| } |
| |
| memcpy(&display_color_cfg.contrast, &data->display_fsp_cfg.output.contrast, |
| sizeof(display_contrast_t)); |
| display_color_cfg.brightness = (display_brightness_t){ |
| .enable = true, .r = brightness_adj, .g = brightness_adj, .b = brightness_adj}; |
| |
| return ra_display_color_config(dev, &display_color_cfg); |
| } |
| |
| static int ra_display_set_contrast(const struct device *dev, const uint8_t contrast) |
| { |
| struct display_ra_data *data = dev->data; |
| display_correction_t display_color_cfg; |
| |
| if (contrast == 0) { |
| return -EINVAL; |
| } |
| |
| memcpy(&display_color_cfg.brightness, &data->display_fsp_cfg.output.brightness, |
| sizeof(display_brightness_t)); |
| display_color_cfg.contrast = |
| (display_contrast_t){.enable = true, .r = contrast, .g = contrast, .b = contrast}; |
| |
| return ra_display_color_config(dev, &display_color_cfg); |
| } |
| |
| static void *ra_display_get_framebuffer(const struct device *dev) |
| { |
| struct display_ra_data *data = dev->data; |
| |
| return (void *)data->front_buf; |
| } |
| |
| static DEVICE_API(display, display_api) = { |
| .blanking_on = ra_display_blanking_on, |
| .blanking_off = ra_display_blanking_off, |
| .get_capabilities = ra_display_get_capabilities, |
| .set_pixel_format = ra_display_set_pixel_format, |
| .write = ra_display_write, |
| .read = ra_display_read, |
| .set_brightness = ra_display_set_brightness, |
| .set_contrast = ra_display_set_contrast, |
| .get_framebuffer = ra_display_get_framebuffer, |
| }; |
| |
| static int display_init(const struct device *dev) |
| { |
| const struct display_ra_config *config = dev->config; |
| struct display_ra_data *data = dev->data; |
| int err; |
| |
| #if BSP_FEATURE_BSP_HAS_GRAPHICS_DOMAIN |
| |
| R_BSP_RegisterProtectDisable(BSP_REG_PROTECT_OM_LPC_BATT); |
| FSP_HARDWARE_REGISTER_WAIT( |
| (R_SYSTEM->PDCTRGD & (R_SYSTEM_PDCTRGD_PDCSF_Msk | R_SYSTEM_PDCTRGD_PDPGSF_Msk)), |
| R_SYSTEM_PDCTRGD_PDPGSF_Msk); |
| R_SYSTEM->PDCTRGD = 0; |
| FSP_HARDWARE_REGISTER_WAIT( |
| (R_SYSTEM->PDCTRGD & (R_SYSTEM_PDCTRGD_PDCSF_Msk | R_SYSTEM_PDCTRGD_PDPGSF_Msk)), |
| 0); |
| R_BSP_RegisterProtectEnable(BSP_REG_PROTECT_OM_LPC_BATT); |
| |
| #endif |
| |
| if (config->pincfg != NULL) { |
| err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT); |
| if (err) { |
| LOG_ERR("pin function initial failed"); |
| return err; |
| } |
| } |
| |
| k_sem_init(&data->frame_buf_sem, 0, 1); |
| |
| err = clock_control_on(config->clock_dev, |
| (clock_control_subsys_t)&config->clock_glcdc_subsys); |
| if (err) { |
| LOG_ERR("Enable GLCDC clock failed!"); |
| return err; |
| } |
| |
| err = R_GLCDC_Open(&data->display_ctrl, &data->display_fsp_cfg); |
| if (err) { |
| LOG_ERR("GLCDC open failed"); |
| return -EIO; |
| } |
| |
| err = gpio_pin_configure_dt(&config->backlight_gpio, GPIO_OUTPUT_ACTIVE); |
| if (err) { |
| LOG_ERR("config backlight gpio failed"); |
| return err; |
| } |
| |
| config->irq_configure(); |
| |
| return 0; |
| } |
| |
| #define IRQ_CONFIGURE_FUNC(id) \ |
| static void glcdc_renesas_ra_configure_func_##id(void) \ |
| { \ |
| R_ICU->IELSR[DT_INST_IRQ_BY_NAME(id, line, irq)] = \ |
| BSP_PRV_IELS_ENUM(EVENT_GLCDC_LINE_DETECT); \ |
| IRQ_CONNECT(DT_INST_IRQ_BY_NAME(id, line, irq), \ |
| DT_INST_IRQ_BY_NAME(id, line, priority), renesas_ra_glcdc_isr, \ |
| DEVICE_DT_INST_GET(id), 0); \ |
| irq_enable(DT_INST_IRQ_BY_NAME(id, line, irq)); \ |
| } |
| |
| #define IRQ_CONFIGURE_DEFINE(id) .irq_configure = glcdc_renesas_ra_configure_func_##id |
| |
| #define RENESAS_RA_FRAME_BUFFER_LEN(id) \ |
| (RENESAS_RA_GLCDC_PIXEL_BYTE_SIZE(id) * DT_INST_PROP(id, height) * DT_INST_PROP(id, width)) |
| |
| #ifdef CONFIG_RENESAS_RA_GLCDC_FRAME_BUFFER_SECTION |
| #define FRAME_BUFFER_SECTION Z_GENERIC_SECTION(CONFIG_RENESAS_RA_GLCDC_FRAME_BUFFER_SECTION) |
| #else |
| #define FRAME_BUFFER_SECTION |
| #endif /* CONFIG_RENESAS_RA_GLCDC_FRAME_BUFFER_SECTION */ |
| |
| #define RENESAS_RA_GLCDC_DEVICE_PINCTRL_INIT(n) \ |
| COND_CODE_1(DT_INST_NODE_HAS_PROP(n, pinctrl_0), (PINCTRL_DT_INST_DEFINE(n)), ()) |
| |
| #define RENESAS_RA_GLCDC_DEVICE_PINCTRL_GET(n) \ |
| COND_CODE_1(DT_INST_NODE_HAS_PROP(n, pinctrl_0), (PINCTRL_DT_INST_DEV_CONFIG_GET(n)), \ |
| (NULL)) |
| |
| #define RENESAS_RA_DEVICE_INIT(id) \ |
| RENESAS_RA_GLCDC_DEVICE_PINCTRL_INIT(id); \ |
| IRQ_CONFIGURE_FUNC(id) \ |
| FRAME_BUFFER_SECTION static uint8_t __aligned(64) \ |
| fb_background##id[CONFIG_RENESAS_RA_GLCDC_FB_NUM * RENESAS_RA_FRAME_BUFFER_LEN(id)]; \ |
| static const glcdc_extended_cfg_t display_extend_cfg##id = { \ |
| .tcon_hsync = RENESAS_RA_GLCDC_TCON_HSYNC_PIN(id), \ |
| .tcon_vsync = RENESAS_RA_GLCDC_TCON_VSYNC_PIN(id), \ |
| .tcon_de = RENESAS_RA_GLCDC_TCON_DE_PIN(id), \ |
| .correction_proc_order = GLCDC_CORRECTION_PROC_ORDER_BRIGHTNESS_CONTRAST2GAMMA, \ |
| .clksrc = GLCDC_CLK_SRC_INTERNAL, \ |
| .clock_div_ratio = RENESAS_RA_GLCDC_OUTPUT_CLOCK_DIV(id), \ |
| .phy_layer = NULL}; \ |
| static struct display_ra_data ra_data##id = { \ |
| .frame_buffer = fb_background##id, \ |
| .frame_buffer_len = RENESAS_RA_FRAME_BUFFER_LEN(id), \ |
| .front_buf = fb_background##id, \ |
| .pend_buf = fb_background##id, \ |
| .pixel_size = RENESAS_RA_GLCDC_PIXEL_BYTE_SIZE(id), \ |
| .current_pixel_format = RENESAS_RA_DISPLAY_GET_PIXEL_FORMAT(id), \ |
| .display_fsp_cfg = { \ |
| .input[0] = {.p_base = (uint32_t *)fb_background##id, \ |
| .hsize = DISPLAY_HSIZE(id), \ |
| .vsize = DISPLAY_VSIZE(id), \ |
| .hstride = RENESAS_RA_DISPLAY_BUFFER_HSTRIDE_BYTE(id), \ |
| .format = RENESAS_RA_GLCDC_IN_PIXEL_FORMAT(id), \ |
| .line_descending_enable = false, \ |
| .lines_repeat_enable = false, \ |
| .lines_repeat_times = 0}, \ |
| .layer[0] = {.coordinate = {.x = 0, .y = 0}, \ |
| .bg_color = RENESAS_RA_GLCDC_BG_COLOR(id), \ |
| .fade_control = DISPLAY_FADE_CONTROL_NONE, \ |
| .fade_speed = 0}, \ |
| .input[1] = {.p_base = NULL}, \ |
| .output = {.htiming = RENESAS_RA_GLCDC_HTIMING(id), \ |
| .vtiming = RENESAS_RA_GLCDC_VTIMING(id), \ |
| .format = RENESAS_RA_GLCDC_OUT_PIXEL_FORMAT(id), \ |
| .endian = RENESAS_RA_GLCDC_OUTPUT_ENDIAN(id), \ |
| .color_order = RENESAS_RA_GLCDC_OUTPUT_COLOR_ODER(id), \ |
| .data_enable_polarity = \ |
| RENESAS_RA_GLCDC_OUTPUT_DE_POLARITY(id), \ |
| .sync_edge = RENESAS_RA_GLCDC_OUTPUT_SYNC_EDGE(id), \ |
| .bg_color = RENESAS_RA_GLCDC_BG_COLOR(id), \ |
| .brightness = {.enable = false}, \ |
| .contrast = {.enable = false}, \ |
| .dithering_on = false}, \ |
| .p_callback = renesas_ra_callback_adapter, \ |
| .p_context = DEVICE_DT_INST_GET(id), \ |
| .p_extend = (void *)(&display_extend_cfg##id), \ |
| .line_detect_irq = DT_INST_IRQ_BY_NAME(id, line, irq), \ |
| .line_detect_ipl = DT_INST_IRQ_BY_NAME(id, line, priority), \ |
| .underflow_1_irq = BSP_IRQ_DISABLED, \ |
| .underflow_2_irq = BSP_IRQ_DISABLED}}; \ |
| static struct display_ra_config ra_config##id = { \ |
| IRQ_CONFIGURE_DEFINE(id), \ |
| .pincfg = RENESAS_RA_GLCDC_DEVICE_PINCTRL_GET(id), \ |
| .backlight_gpio = GPIO_DT_SPEC_INST_GET_OR(id, backlight_gpios, NULL), \ |
| .height = DT_INST_PROP(id, height), \ |
| .width = DT_INST_PROP(id, width), \ |
| .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(id)), \ |
| .clock_glcdc_subsys = {.mstp = (uint32_t)DT_INST_CLOCKS_CELL_BY_IDX(id, 0, mstp), \ |
| .stop_bit = DT_INST_CLOCKS_CELL_BY_IDX(id, 0, stop_bit)}}; \ |
| DEVICE_DT_INST_DEFINE(id, &display_init, NULL, &ra_data##id, &ra_config##id, POST_KERNEL, \ |
| CONFIG_DISPLAY_INIT_PRIORITY, &display_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(RENESAS_RA_DEVICE_INIT) |