/* * Copyright (c) 2020 Actions Technology Co., Ltd * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include "lcdc_lark.h" #include LOG_MODULE_REGISTER(lcdc, CONFIG_DISPLAY_LOG_LEVEL); #define USE_LCDC_TE 0 #define SUPPORTED_PIXEL_FORMATS (PIXEL_FORMAT_BGR_565 | PIXEL_FORMAT_RGB_888 | \ PIXEL_FORMAT_BGR_888 | PIXEL_FORMAT_ARGB_8888 | PIXEL_FORMAT_XRGB_8888) #define LCDC ((LCDC_Type *)LCDC_REG_BASE) #define DMACHAN ((LCDC_DMACHAN_CTL_Type *)(DMA_REG_BASE + 0x100 + CONFIG_LCDC_DMA_CHAN_ID * 0x100)) #define DMALINE ((LCDC_DMALINE_CTL_Type *)(DMA_LINE0_REG_BASE + 0x20 * CONFIG_LCDC_DMA_LINE_ID)) #define SPI_AHB_CMD_MASK \ (LCD_SPI_SRC_MASK | LCD_SPI_AHB_CSX_MASK | LCD_SPI_CDX_MASK) #define SPI_AHB_DATA_MASK \ (SPI_AHB_CMD_MASK | LCD_SPI_AHB_F565_MASK | \ LCD_SPI_AHB_CFG_DATA | LCD_SPI_RWL_MASK) #define CPU_AHB_CMD_MASK \ (LCD_CPU_SRC_MASK | LCD_CPU_AHB_F565_MASK | \ LCD_CPU_RS_MASK | LCD_CPU_AHB_DATA_MASK | LCD_CPU_AHB_CSX_MASK) #define CPU_AHB_DATA_MASK CPU_AHB_CMD_MASK struct lcdc_data { /* pointer to current video port passed in interface enable() */ const struct display_videoport *port; /* pointer to current video mode passed in interface set_mode() */ const struct display_videomode *mode; /* enum display_controller_source_type */ uint8_t source_type; display_controller_complete_t complete_fn; void *complete_fn_arg; bool busy; }; static int lcdc_config_port(const struct device *dev, const struct display_videoport *port) { uint32_t lcd_ctl = LCD_EN | LCD_CLK_EN; if (port->type != DISPLAY_PORT_QSPI_SYNC) { lcd_ctl |= LCD_HOLD_EN; } switch (port->major_type) { case DISPLAY_PORT_MCU: lcd_ctl |= (port->minor_type == DISPLAY_MCU_8080) ? LCD_IF_SEL_MCU_8080 : LCD_IF_SEL_MCU_6880; lcd_ctl |= LCD_IF_CE_SEL(port->mcu_mode.cs) | LCD_IF_MLS_SEL(port->mcu_mode.lsb_first); LCDC->CPU_CTL = LCD_CPU_FEND_IRQ_EN | LCD_CPU_AHB_F565_16BIT | LCD_CPU_AHB_CSX(1); LCDC->CPU_CLK = LCD_CPU_CLK(port->mcu_mode.clk_high_duration, port->mcu_mode.clk_low_duration, port->mcu_mode.clk_low_duration); break; case DISPLAY_PORT_SPI: lcd_ctl |= LCD_IF_SEL_SPI; lcd_ctl |= LCD_IF_CE_SEL(port->spi_mode.cs) | LCD_IF_MLS_SEL(port->spi_mode.lsb_first); LCDC->SPI_CTL = LCD_SPI_FTC_IRQ_EN | LCD_SPI_FEND_PD_EN | LCD_SPI_CDX(1) | LCD_SPI_AHB_CSX(1) | LCD_SPI_AHB_F565_16BIT | LCD_SPI_TYPE_SEL(port->minor_type == LCD_QSPI_SYNC ? LCD_QSPI : port->minor_type) | LCD_SPI_SCLK_POL((port->spi_mode.cpol == port->spi_mode.cpha) ? 0 : 1) | LCD_SPI_DELAY_CHAIN_SEL(port->spi_mode.rd_delay_ns) | LCD_SPI_RDLC_SEL(port->spi_mode.rd_dummy_cycles) | LCD_SPI_DCP_SEL(port->spi_mode.dcp_mode) | LCD_SPI_DUAL_LANE_SEL(port->spi_mode.dual_lane); break; default: return -ENOTSUP; } LCDC->CTL = lcd_ctl; return 0; } static int lcdc_config_src_pixel_format(const struct device *dev, uint32_t pixel_format, uint8_t *bytes_per_pixel) { struct lcdc_data *data = dev->data; if (pixel_format == PIXEL_FORMAT_BGR_565) { *bytes_per_pixel = 2; switch (data->port->major_type) { case DISPLAY_PORT_SPI: LCDC->SPI_CTL = (LCDC->SPI_CTL & ~LCD_SPI_SDT_MASK) | LCD_SPI_SDT_RGB565; break; default: case DISPLAY_PORT_MCU: LCDC->CPU_CTL = (LCDC->CPU_CTL & ~LCD_SPI_SDT_MASK) | LCD_CPU_SDT_RGB565; break; } } else if (pixel_format == PIXEL_FORMAT_ARGB_8888 || pixel_format == PIXEL_FORMAT_XRGB_8888) { *bytes_per_pixel = 4; switch (data->port->major_type) { case DISPLAY_PORT_SPI: LCDC->SPI_CTL = (LCDC->SPI_CTL & ~LCD_SPI_SDT_MASK) | LCD_SPI_SDT_ARGB8888; break; case DISPLAY_PORT_MCU: default: LCDC->CPU_CTL = (LCDC->CPU_CTL & ~LCD_SPI_SDT_MASK) | LCD_CPU_SDT_ARGB8888; break; } } else if (pixel_format == PIXEL_FORMAT_RGB_888) { *bytes_per_pixel = 3; switch (data->port->major_type) { case DISPLAY_PORT_SPI: LCDC->SPI_CTL = (LCDC->SPI_CTL & ~LCD_SPI_SDT_MASK) | LCD_SPI_SDT_RGB888; break; case DISPLAY_PORT_MCU: default: LCDC->CPU_CTL = (LCDC->CPU_CTL & ~LCD_SPI_SDT_MASK) | LCD_CPU_SDT_RGB888; break; } } else if (pixel_format == PIXEL_FORMAT_BGR_888) { *bytes_per_pixel = 3; switch (data->port->major_type) { case DISPLAY_PORT_SPI: LCDC->SPI_CTL = (LCDC->SPI_CTL & ~LCD_SPI_SDT_MASK) | LCD_SPI_SDT_BGR888; break; case DISPLAY_PORT_MCU: default: LCDC->CPU_CTL = (LCDC->CPU_CTL & ~LCD_SPI_SDT_MASK) | LCD_CPU_SDT_BGR888; break; } else { return -EINVAL; } return 0; } static int lcdc_config_mode(const struct device *dev, const struct display_videomode *mode) { struct lcdc_data *data = dev->data; clk_set_rate(CLOCK_ID_LCD, KHZ(mode->pixel_clk)); LCDC->CTL &= ~(LCD_TE_EN | LCD_TE_MODE_MASK | LCD_OUT_FORMAT_MASK); #if USE_LCDC_TE if (mode->flags & DISPLAY_FLAGS_TE_HIGH) { LCDC->CTL |= LCD_TE_EN | LCD_TE_MODE_RISING_EDGE; } else if (mode->flags & DISPLAY_FLAGS_TE_LOW) { LCDC->CTL |= LCD_TE_EN | LCD_TE_MODE_FALLING_EDGE; } #endif switch (mode->pixel_format) { case PIXEL_FORMAT_BGR_565: LCDC->CTL |= LCD_OUT_FORMAT_RGB565; break; case PIXEL_FORMAT_RGB_888: case PIXEL_FORMAT_BGR_888: case PIXEL_FORMAT_ARGB_8888: case PIXEL_FORMAT_XRGB_8888: default: LCDC->CTL |= LCD_OUT_FORMAT_RGB888; break; } if (data->port->type == DISPLAY_PORT_QSPI_SYNC) { uint16_t data_cycles = mode->hactive * display_format_get_bits_per_pixel(mode->pixel_format) / 4; LCDC->QSPI_DTAS = LCD_QSPI_DTAS(0, data_cycles); LCDC->QSPI_SYNC_TIM = LCD_QSPI_SYNC_TIM( mode->hsync_len + mode->hback_porch + mode->hfront_porch, mode->vfront_porch, mode->vback_porch); } return 0; } static int lcdc_config_source(const struct device *dev, enum display_controller_source_type source_type) { static const uint32_t mcu_source_set[DISPLAY_CONTROLLER_NUM_SOURCES] = { LCD_CPU_SRC_SEL_AHB, LCD_CPU_SRC_SEL_DE | LCD_CPU_RS_HIGH, LCD_CPU_SRC_SEL_DMA | LCD_CPU_RS_HIGH, }; static const uint32_t spi_source_set[DISPLAY_CONTROLLER_NUM_SOURCES] = { LCD_SPI_SRC_SEL_AHB | LCD_SPI_CDX(1), LCD_SPI_SRC_SEL_DE | LCD_SPI_CDX(1), LCD_SPI_SRC_SEL_DMA | LCD_SPI_CDX(1), }; struct lcdc_data *data = dev->data; #if CONFIG_LCDC_DMA_CHAN_ID < 0 if (source_type == DISPLAY_CONTROLLER_SOURCE_DMA) return -EINVAL; #endif switch (data->port->major_type) { case DISPLAY_PORT_MCU: LCDC->CPU_CTL = (LCDC->CPU_CTL & ~(LCD_CPU_SRC_MASK | LCD_CPU_RS_MASK)) | mcu_source_set[source_type]; break; case DISPLAY_PORT_SPI: LCDC->SPI_CTL = (LCDC->SPI_CTL & ~(LCD_SPI_SRC_MASK | LCD_SPI_CDX_MASK)) | spi_source_set[source_type]; break; default: return -EINVAL; } return 0; } static int lcdc_enable(const struct device *dev, const struct display_videoport *port) { struct lcdc_data *data = dev->data; if (data->port != NULL) { return -EALREADY; } if (port == NULL) { return -EINVAL; } /* initially use the CMU_LCDCLK default setting: HOSC 1/1 */ //clk_set_rate(CLOCK_ID_LCD, MHZ(50)); acts_reset_peripheral_assert(RESET_ID_LCD); acts_clock_peripheral_enable(CLOCK_ID_LCD); acts_reset_peripheral_deassert(RESET_ID_LCD); if (lcdc_config_port(dev, port)) { return -EINVAL; } /* FIXME: just save the pointer ? */ data->port = port; data->mode = NULL; data->source_type = UINT8_MAX; return 0; } static int lcdc_disable(const struct device *dev) { struct lcdc_data *data = dev->data; if (data->port != NULL) { data->port = NULL; data->busy = false; #if CONFIG_LCDC_DMA_CHAN_ID >= 0 DMACHAN->START = 0; /* force stop DMA */ #endif acts_clock_peripheral_disable(CLOCK_ID_LCD); } return 0; } static int lcdc_set_mode(const struct device *dev, const struct display_videomode *mode) { struct lcdc_data *data = dev->data; if (mode == NULL) { return -EINVAL; } if (mode == data->mode) { return 0; } if (lcdc_config_mode(dev, mode)) { return -EINVAL; } /* FIXME: just save the pointer ? */ data->mode = mode; return 0; } static int lcdc_set_source(const struct device *dev, enum display_controller_source_type source_type, const struct device *source_dev) { struct lcdc_data *data = dev->data; if (data->port == NULL) { return -EINVAL; } if (source_type == data->source_type) { return 0; } if (lcdc_config_source(dev, source_type)) { return -EINVAL; } data->source_type = source_type; return 0; } static void _read_config_data32(uint8_t *buf8, const uint32_t *data, uint32_t len) { int pos = (len >= 4) ? 24 : (len - 1) * 8; while (len-- > 0) { *buf8++ = (data[0] >> pos) & 0xFF; pos -= 8; if (pos < 0) { pos = 24; data++; } } } static int lcdc_read_config(const struct device *dev, uint32_t cmd, void *buf, uint32_t len) { struct lcdc_data *lcdc_data = dev->data; if (lcdc_data->port == NULL) { return -EINVAL; } if (buf == NULL || len == 0) { return -EINVAL; } if (lcdc_data->port->major_type == DISPLAY_PORT_SPI) { uint32_t lcdc_spi_ctl = LCDC->SPI_CTL; /* save REG SPI_CTL */ if (lcdc_data->port->minor_type < DISPLAY_QSPI) { LCDC->SPI_CTL &= ~SPI_AHB_DATA_MASK; LCDC->SPI_CTL |= LCD_SPI_SRC_SEL_AHB | LCD_SPI_CDX(0); LCDC->DATA = cmd; LCDC->SPI_CTL |= LCD_SPI_AHB_CSX(1); LCDC->SPI_CTL &= ~(SPI_AHB_DATA_MASK | LCD_SPI_DUAL_LANE_MASK); if (len <= 4) { LCDC->SPI_CTL |= LCD_SPI_SRC_SEL_AHB | LCD_SPI_AHB_CFG_DATA | LCD_SPI_RWL(len); uint32_t data = LCDC->DATA; _read_config_data32(buf, &data, len); } else { LCDC->SPI_CTL |= LCD_SPI_SRC_SEL_AHB | LCD_SPI_AHB_CFG_DATA | LCD_SPI_RWL(1); do { *(uint8_t *)buf = LCDC->DATA & 0xff; buf = (uint8_t *)buf + 1; } while (--len > 0); } LCDC->SPI_CTL |= LCD_SPI_AHB_CSX(1); } else { uint32_t tmp_data[8]; int i; if (len > 32) return -EDOM; /* config spi type 'LCD_QSPI_SYNC' to 'LCD_QSPI' temporarily */ LCDC->SPI_CTL &= ~(SPI_AHB_DATA_MASK | LCD_SPI_TYPE_MASK); LCDC->SPI_CTL |= LCD_QSPI | LCD_SPI_SRC_SEL_AHB | LCD_SPI_AHB_CFG_DATA | LCD_SPI_CDX(0) | LCD_SPI_RWL(len); LCDC->QSPI_CMD = cmd; tmp_data[0] = LCDC->DATA; for (i = (len - 1) / 4 - 1; i >= 0; i--) { tmp_data[i + 1] = LCDC->DATA_1[i]; } _read_config_data32(buf, tmp_data, len); LCDC->SPI_CTL |= LCD_SPI_AHB_CSX(1); } /* restore REG SPI_CTL */ LCDC->SPI_CTL = lcdc_spi_ctl; } else if (lcdc_data->port->major_type == DISPLAY_PORT_MCU) { uint32_t lcdc_cpu_ctl = LCDC->CPU_CTL; /* save REG CPU_CTL */ LCDC->CPU_CTL &= ~CPU_AHB_CMD_MASK; LCDC->CPU_CTL |= LCD_CPU_AHB_DATA_SEL_CFG | LCD_CPU_RS_LOW; LCDC->DATA = cmd; LCDC->CPU_CTL |= LCD_CPU_AHB_CSX(1); LCDC->CPU_CTL &= ~CPU_AHB_CMD_MASK; LCDC->CPU_CTL |= LCD_CPU_AHB_DATA_SEL_CFG | LCD_CPU_RS_HIGH; do { *(uint8_t *)buf = LCDC->DATA & 0xff; buf = (uint8_t *)buf + 1; } while (--len > 0); LCDC->CPU_CTL |= LCD_CPU_AHB_CSX(1); /* restore REG CPU_CTL */ LCDC->CPU_CTL = lcdc_cpu_ctl; } return 0; } static int _fill_spi_config_data32(uint32_t *data, const uint8_t *buf8, uint32_t len) { int data_num = (len + 3) >> 2; for (; len >= 4; len -= 4) { *data++ = ((uint32_t)buf8[0] << 24) | ((uint32_t)buf8[1] << 16) | ((uint32_t)buf8[2] << 8) | buf8[3]; buf8 += 4; } if (len > 0) { *data = *buf8++; while (--len > 0) { *data = (*data << 8) | (*buf8++); } } return data_num; } static int lcdc_write_config(const struct device *dev, uint32_t cmd, const void *buf, uint32_t len) { struct lcdc_data *lcdc_data = dev->data; const uint8_t *buf8 = buf; if (lcdc_data->port == NULL) { return -EINVAL; } if (lcdc_data->port->major_type == DISPLAY_PORT_SPI) { uint32_t lcdc_spi_ctl = LCDC->SPI_CTL; if (lcdc_data->port->minor_type < DISPLAY_QSPI) { if (cmd != DC_INVALID_CMD) { /* make sure source has selected AHB successfully */ LCDC->SPI_CTL &= ~SPI_AHB_DATA_MASK; LCDC->SPI_CTL |= LCD_SPI_SRC_SEL_AHB | LCD_SPI_CDX(0); LCDC->DATA = cmd; LCDC->SPI_CTL |= LCD_SPI_AHB_CSX(1); } if (len > 0) { uint32_t data; LCDC->SPI_CTL &= ~(SPI_AHB_DATA_MASK | LCD_SPI_DUAL_LANE_MASK); if (len <= 4) { LCDC->SPI_CTL |= LCD_SPI_SRC_SEL_AHB | LCD_SPI_AHB_CFG_DATA | LCD_SPI_CDX(1) | LCD_SPI_RWL(len); _fill_spi_config_data32(&data, buf8, len); LCDC->DATA = data; } else { LCDC->SPI_CTL |= LCD_SPI_SRC_SEL_AHB | LCD_SPI_AHB_CFG_DATA | LCD_SPI_CDX(1) | LCD_SPI_RWL(1); do { LCDC->DATA = *buf8++; } while (--len > 0); } LCDC->SPI_CTL |= LCD_SPI_AHB_CSX(1); } } else { uint32_t tmp_data[8]; int i; if (len > 32) return -EDOM; /* config spi type 'LCD_QSPI_SYNC' to 'LCD_QSPI' temporarily */ LCDC->SPI_CTL &= ~(SPI_AHB_DATA_MASK | LCD_SPI_TYPE_MASK); LCDC->SPI_CTL |= LCD_QSPI | LCD_SPI_SRC_SEL_AHB | LCD_SPI_AHB_CFG_DATA | LCD_SPI_CDX(0) | LCD_SPI_RWL(len); /* Transfer sequence: * 1) DATA, DATA_1, ..., DATA_7 * 2) In every DATA: always (effective) MSB first: * if 32 bit in DATA, then [31..24], [23..16], [15..8], [7..0] * if 24 bit in DATA, then [23..16], [15..8], [7..0] * if 16 bit in DATA, then [15..8], [7..0] * if 8 bit in DATA, then [7..0] */ i = _fill_spi_config_data32(tmp_data, buf8, len); LCDC->QSPI_CMD = cmd; for (i -= 1; i > 0; i--) { LCDC->DATA_1[i - 1] = tmp_data[i]; } LCDC->DATA = tmp_data[0]; LCDC->SPI_CTL |= LCD_SPI_AHB_CSX(1); } /* restore REG SPI_CTL */ LCDC->SPI_CTL = lcdc_spi_ctl; } else if (lcdc_data->port->major_type == DISPLAY_PORT_MCU) { uint32_t lcdc_cpu_ctl = LCDC->CPU_CTL; /* save REG CPU_CTL */ if (cmd != DC_INVALID_CMD) { LCDC->CPU_CTL &= ~CPU_AHB_CMD_MASK; LCDC->CPU_CTL |= LCD_CPU_AHB_DATA_SEL_CFG | LCD_CPU_RS_LOW; LCDC->DATA = cmd; LCDC->CPU_CTL |= LCD_CPU_AHB_CSX(1); } if (len > 0) { LCDC->CPU_CTL &= ~CPU_AHB_CMD_MASK; LCDC->CPU_CTL |= LCD_CPU_AHB_DATA_SEL_CFG | LCD_CPU_RS_HIGH; do { LCDC->DATA = *buf8++; } while (--len > 0); LCDC->CPU_CTL |= LCD_CPU_AHB_CSX(1); } /* restore REG CPU_CTL */ LCDC->CPU_CTL = lcdc_cpu_ctl; } return 0; } static int lcdc_write_pixels_by_mcu(const struct device *dev, uint32_t cmd, const struct display_buffer_descriptor *desc, const void *buf) { struct lcdc_data *lcdc_data = dev->data; uint32_t pixel_format = desc->pixel_format; uint16_t bytes_per_line; bool f565_24bit = false; int i, j; if (pixel_format == 0) { pixel_format = lcdc_data->mode->pixel_format; } else if (pixel_format != lcdc_data->mode->pixel_format) { if (pixel_format == PIXEL_FORMAT_ARGB_8888 && ( lcdc_data->mode->pixel_format == PIXEL_FORMAT_BGR_565)) { f565_24bit = true; } else { return -EINVAL; } } bytes_per_line = (desc->pitch > 0) ? desc->pitch : (desc->width * display_format_get_bits_per_pixel(pixel_format) / 8); lcdc_data->busy = true; if (lcdc_data->port->major_type == DISPLAY_PORT_SPI) { uint32_t lcdc_spi_ctl = LCDC->SPI_CTL; /* save LCD_SPI_CTL */ LCDC->SPI_CTL &= ~SPI_AHB_DATA_MASK; LCDC->SPI_CTL |= LCD_SPI_SRC_SEL_AHB | LCD_SPI_CDX(1) | (f565_24bit ? LCD_SPI_AHB_F565_24BIT : LCD_SPI_AHB_F565_16BIT); if (lcdc_data->port->minor_type < DISPLAY_QSPI) { if (pixel_format == PIXEL_FORMAT_BGR_565) { // rgb565 for (j = desc->height; j > 0; j--) { for (i = desc->width; i > 0; i--) { LCDC->DATA = *(uint16_t *)buf; buf = (uint16_t *)buf + 1; } buf = (uint8_t *)buf + (bytes_per_line - desc->width * 2); } } else { /* argb_8888 */ for (j = desc->height; j > 0; j--) { for (i = desc->width; i > 0; i--) { LCDC->DATA = *(uint32_t *)buf; buf = (uint32_t *)buf + 1; } buf = (uint8_t *)buf + (bytes_per_line - desc->width * 4); } } LCDC->SPI_CTL |= LCD_SPI_AHB_CSX(1); } else { if (pixel_format == PIXEL_FORMAT_BGR_565) { // rgb565 for (j = desc->height; j > 0; j--) { for (i = desc->width; i > 0; i--) { LCDC->SPI_CTL &= ~LCD_SPI_AHB_CSX_MASK; LCDC->QSPI_CMD = cmd; LCDC->DATA = *(uint16_t *)buf; LCDC->SPI_CTL |= LCD_SPI_AHB_CSX(1); buf = (uint16_t *)buf + 1; } buf = (uint8_t *)buf + (bytes_per_line - desc->width * 2); } } else { /* argb_8888 */ for (j = desc->height; j > 0; j--) { for (i = desc->width; i > 0; i--) { LCDC->SPI_CTL &= ~LCD_SPI_AHB_CSX_MASK; LCDC->QSPI_CMD = cmd; LCDC->DATA = *(uint32_t *)buf; LCDC->SPI_CTL |= LCD_SPI_AHB_CSX(1); buf = (uint32_t *)buf + 1; } buf = (uint8_t *)buf + (bytes_per_line - desc->width * 4); } } } /* restore LCD_SPI_CTL */ LCDC->SPI_CTL = lcdc_spi_ctl; } else if (lcdc_data->port->major_type == DISPLAY_PORT_MCU) { uint32_t lcdc_cpu_ctl = LCDC->CPU_CTL; /* save LCD_CPU_CTL */ LCDC->CPU_CTL &= ~CPU_AHB_CMD_MASK; LCDC->CPU_CTL |= LCD_CPU_AHB_DATA_SEL_IMG | LCD_CPU_RS_HIGH | (f565_24bit ? LCD_CPU_AHB_F565_24BIT : LCD_CPU_AHB_F565_16BIT); if (pixel_format == PIXEL_FORMAT_BGR_565) { // rgb565 for (j = desc->height; j > 0; j--) { for (i = desc->width; i > 0; i--) { LCDC->DATA = *(uint16_t *)buf; buf = (uint16_t *)buf + 1; } buf = (uint8_t *)buf + (bytes_per_line - desc->width * 2); } } else { /* argb_8888 */ for (j = desc->height; j > 0; j--) { for (i = desc->width; i > 0; i--) { LCDC->DATA = *(uint32_t *)buf; buf = (uint32_t *)buf + 1; } buf = (uint8_t *)buf + (bytes_per_line - desc->width * 4); } } /* restore LCD_CPU_CTL */ LCDC->CPU_CTL = lcdc_cpu_ctl; } lcdc_data->busy = false; /* notify transfer completed */ if (lcdc_data->complete_fn) { lcdc_data->complete_fn(lcdc_data->complete_fn_arg); } return 0; } static int lcdc_write_pixels_by_de(const struct device *dev, uint32_t cmd, uint32_t hsync_cmd, const struct display_buffer_descriptor *desc) { struct lcdc_data *data = dev->data; data->busy = true; /* only required for non-sync mode */ LCDC->TPL = (uint32_t)desc->width * desc->height - 1; switch (data->port->major_type) { case DISPLAY_PORT_SPI: LOG_DBG("start spi-if\n"); LCDC->QSPI_CMD = cmd; if (data->port->minor_type == DISPLAY_QSPI_SYNC) { LCDC->QSPI_CMD1 = hsync_cmd; /* only required for QUAD_SYNC */ LCDC->QSPI_IMG_SIZE = LCD_QSPI_IMG_SIZE(desc->width, desc->height); LCDC->CTL &= ~LCD_EN; /* set to quad sync mode */ LCDC->SPI_CTL = (LCDC->SPI_CTL & ~LCD_SPI_TYPE_MASK) | LCD_QSPI_SYNC; LCDC->CTL |= LCD_EN; } LCDC->SPI_CTL |= LCD_SPI_START; break; case DISPLAY_PORT_MCU: default: LOG_DBG("start cpu-if\n"); LCDC->CPU_CTL |= LCD_CPU_START; break; } return 0; } #if CONFIG_LCDC_DMA_CHAN_ID >= 0 static int lcdc_write_pixels_by_dma(const struct device *dev, uint32_t cmd, uint32_t hsync_cmd, const struct display_buffer_descriptor *desc, const void *buf) { struct lcdc_data *data = dev->data; uint32_t pixel_format = desc->pixel_format; uint16_t copy_per_line, bytes_per_line; uint8_t bytes_per_pixel; if ((uintptr_t)buf & 0x3) { LOG_ERR("unaligned addr %p", buf); return -EINVAL; } if (pixel_format == 0) { pixel_format = data->mode->pixel_format; } if (lcdc_config_src_pixel_format(dev, pixel_format, &bytes_per_pixel)) { LOG_ERR("invalid pixel format 0x%x", pixel_format); return -EINVAL; } copy_per_line = desc->width * bytes_per_pixel; bytes_per_line = (desc->pitch > 0) ? desc->pitch : copy_per_line; if (copy_per_line != bytes_per_line && ((copy_per_line & 0x3) || (bytes_per_line & 0x3))) { LOG_ERR("width %u or stride %u unaligned\n", desc->width, desc->pitch); return -EINVAL; } if (buf_is_psram(buf)) { buf = cache_to_uncache((void *)buf); } data->busy = true; /* configure DMA */ assert(DMACHAN->START == 0); DMACHAN->BC = (uint32_t)copy_per_line * desc->height; DMACHAN->SADDR0 = (uint32_t)buf; DMACHAN->DADDR0 = (uint32_t)&LCDC->DATA; if (copy_per_line == bytes_per_line) { DMACHAN->CTL = BIT(15) | (0x16 << 8); } else { DMALINE->COUNT = desc->height; DMALINE->LENGTH = copy_per_line; DMALINE->STRIDE = bytes_per_line; DMACHAN->CTL = BIT(15) | (0x16 << 8) | BIT(24) | (CONFIG_LCDC_DMA_LINE_ID << 25); } /* start DMA */ DMACHAN->START = 0x1; LCDC->DISP_SIZE = LCD_DISP_SIZE(desc->width, desc->height); if (data->port->major_type == DISPLAY_PORT_SPI) { LCDC->QSPI_CMD = cmd; if (data->port->minor_type == DISPLAY_QSPI_SYNC) { LCDC->QSPI_CMD1 = hsync_cmd; LCDC->QSPI_IMG_SIZE = LCD_QSPI_IMG_SIZE(desc->width, desc->height); LCDC->CTL &= ~LCD_EN; /* set to quad sync mode */ LCDC->SPI_CTL = (LCDC->SPI_CTL & ~LCD_SPI_TYPE_MASK) | LCD_QSPI_SYNC; LCDC->CTL |= LCD_EN; } LCDC->SPI_CTL |= LCD_SPI_START; } else { /* DISPLAY_PORT_MCU */ LCDC->CPU_CTL |= LCD_CPU_START; } return 0; } #endif /* CONFIG_LCDC_DMA_CHAN_ID >= 0 */ static int lcdc_write_pixels(const struct device *dev, uint32_t cmd, uint32_t hsync_cmd, const struct display_buffer_descriptor *desc, const void *buf) { struct lcdc_data *data = dev->data; if (data->port == NULL || data->mode == NULL) { return -EINVAL; } switch (data->source_type) { #if CONFIG_LCDC_DMA_CHAN_ID >= 0 case DISPLAY_CONTROLLER_SOURCE_DMA: return lcdc_write_pixels_by_dma(dev, cmd, hsync_cmd, desc, buf); #endif case DISPLAY_CONTROLLER_SOURCE_ENGINE: return lcdc_write_pixels_by_de(dev, cmd, hsync_cmd, desc); case DISPLAY_CONTROLLER_SOURCE_MCU: return lcdc_write_pixels_by_mcu(dev, cmd, desc, buf); default: return -EINVAL; } } static int lcdc_read_pixels(const struct device *dev, uint32_t cmd, const struct display_buffer_descriptor *desc, void *buf) { return -ENOTSUP; } static int lcdc_control(const struct device *dev, int cmd, void *arg1, void *arg2) { struct lcdc_data *data = dev->data; switch (cmd) { case DISPLAY_CONTROLLER_CTRL_COMPLETE_CB: data->complete_fn_arg = arg2; data->complete_fn = arg1; break; default: return -EINVAL; } return 0; } void lcdc_dump(void) { int i; printk("lcdc regs:\n"); printk("\t LCD_CTL 0x%08x\n", LCDC->CTL); printk("\t LCD_DISP_SIZE 0x%08x\n", LCDC->DISP_SIZE); printk("\t LCD_CPU_CTL 0x%08x\n", LCDC->CPU_CTL); printk("\t LCD_DATA 0x%08x\n", LCDC->DATA); printk("\t LCD_CPU_CLK 0x%08x\n", LCDC->CPU_CLK); printk("\t LCD_TPL 0x%08x\n", LCDC->TPL); printk("\t LCD_SPI_CTL 0x%08x\n", LCDC->SPI_CTL); printk("\t LCD_QSPI_CMD 0x%08x\n", LCDC->QSPI_CMD); printk("\t LCD_QSPI_CMD1 0x%08x\n", LCDC->QSPI_CMD1); printk("\t LCD_QSPI_SYNC_TIM 0x%08x\n", LCDC->QSPI_SYNC_TIM); printk("\t LCD_QSPI_IMG_SIZE 0x%08x\n", LCDC->QSPI_IMG_SIZE); printk("\t DE_INTERFACE_CTL 0x%08x\n", LCDC->DE_INTERFACE_CTL); printk("\t LCD_PENDING 0x%08x\n", LCDC->PENDING); printk("\t LCD_QSPI_DTAS 0x%08x\n", LCDC->QSPI_DTAS); for (i = 0; i < ARRAY_SIZE(LCDC->DATA_1); i++) { printk("\t LCD_DATA_%d 0x%08x\n", i + 1, LCDC->DATA_1[i]); } #if CONFIG_LCDC_DMA_CHAN_ID >= 0 printk("\t DMA_CTL 0x%08x\n", DMACHAN->CTL); printk("\t DMA_START 0x%08x\n", DMACHAN->START); printk("\t DMA_SADDR0 0x%08x\n", DMACHAN->SADDR0); printk("\t DMA_SADDR1 0x%08x\n", DMACHAN->SADDR1); printk("\t DMA_DADDR0 0x%08x\n", DMACHAN->DADDR0); printk("\t DMA_DADDR1 0x%08x\n", DMACHAN->DADDR1); printk("\t DMA_BC 0x%08x\n", DMACHAN->BC); printk("\t DMA_RC 0x%08x\n", DMACHAN->RC); printk("\t DMA_LINE_LENGTH 0x%08x\n", DMALINE->LENGTH); printk("\t DMA_LINE_COUNT 0x%08x\n", DMALINE->COUNT); printk("\t DMA_LINE_STRIDE 0x%08x\n", DMALINE->STRIDE); printk("\t DMA_LINE_REMAIN 0x%08x\n", DMALINE->REMAIN); #endif } static void lcdc_isr(const void *arg) { const struct device *dev = arg; struct lcdc_data *data = dev->data; uint32_t pending = LCDC->PENDING; LCDC->PENDING = pending; if (pending & LCD_STAT_FTC) { if (pending & LCD_STAT_QSPI_SYNC_FTC) { #if CONFIG_LCDC_DMA_CHAN_ID >= 0 if (data->source_type == DISPLAY_CONTROLLER_SOURCE_DMA) { if (DMACHAN->START > 0) { LOG_ERR("LCD remain %u", DMACHAN->RC); DMACHAN->START = 0; } /* restart DMA */ DMACHAN->START = 0x1; } #endif } else { data->busy = false; } if (data->complete_fn) data->complete_fn(data->complete_fn_arg); } LOG_DBG("LCD pending 0x%x", pending); } DEVICE_DECLARE(lcdc); static int lcdc_init(const struct device *dev) { IRQ_CONNECT(IRQ_ID_LCD, 0, lcdc_isr, DEVICE_GET(lcdc), 0); irq_enable(IRQ_ID_LCD); return 0; } #ifdef CONFIG_PM_DEVICE static int lcdc_pm_control(const struct device *dev, enum pm_device_action action) { struct lcdc_data *data = dev->data; int ret = 0; switch (action) { case PM_DEVICE_ACTION_SUSPEND: case PM_DEVICE_ACTION_FORCE_SUSPEND: case PM_DEVICE_ACTION_TURN_OFF: if (data->busy) { LOG_WRN("lcdc busy (action=%d)", action); ret = -EBUSY; } break; default: break; } return ret; } #endif /* CONFIG_PM_DEVICE */ static const struct display_controller_driver_api lcdc_api = { .control = lcdc_control, .enable = lcdc_enable, .disable = lcdc_disable, .set_mode = lcdc_set_mode, .set_source = lcdc_set_source, .read_config = lcdc_read_config, .write_config = lcdc_write_config, .read_pixels = lcdc_read_pixels, .write_pixels = lcdc_write_pixels, }; static struct lcdc_data lcdc_drv_data; DEVICE_DEFINE(lcdc, CONFIG_LCDC_DEV_NAME, &lcdc_init, lcdc_pm_control, &lcdc_drv_data, NULL, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &lcdc_api);