/* * Copyright (c) 2020 Actions Technology Co., Ltd * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include "lcdc_leopard.h" #include LOG_MODULE_REGISTER(lcdc, CONFIG_DISPLAY_LOG_LEVEL); #define USE_LCDC_TE 0 #define USE_LCDC_SEND_DBG 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 SDMA0 ((LCDC_SDMA_Type *)SDMA0_REG_BASE) #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; #ifdef CONFIG_PM_DEVICE const struct display_videoport *saved_port; const struct display_videomode *saved_mode; #endif bool busy; }; static int lcdc_config_port(const struct device *dev, const struct display_videoport *port) { uint32_t lcd_ctl; #if USE_LCDC_SEND_DBG lcd_ctl = LCD_EN | LCD_CLK_EN | LCD_FIFO_EN | LCD_AUTO_RST_FIFO_EN | LCD_SDCHK_CLK_EN | LCD_IN_RGB666_CVT_LOW | LCD_IN_RGB565_CVT_LOW /*| LCD_OUT_RGB565_FORCE_555*/; LCDC->SD_CNT = LCD_SDCNT_EN; LCDC->SD_CHECKSUM = LCD_CHKSUM_EN; #else lcd_ctl = LCD_EN | LCD_CLK_EN | LCD_FIFO_EN | LCD_AUTO_RST_FIFO_EN | LCD_IN_RGB666_CVT_LOW | LCD_IN_RGB565_CVT_LOW /*| LCD_OUT_RGB565_FORCE_555*/; #endif /* USE_LCDC_SEND_DBG */ 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_RX_DELAY_SEL(0) | 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_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_RD_DELAY_CHAIN_SEL(port->spi_mode.rd_delay_ns) | LCD_SPI_RD_LANE_SEL(port->spi_mode.rd_lane) | LCD_SPI_DCP_SEL(port->spi_mode.dcp_mode) | LCD_SPI_DUAL_LANE_SEL(port->spi_mode.dual_lane); LCDC->SPI_CTL1 = LCD_SPI_RDLC_SEL(port->spi_mode.rd_dummy_cycles) | LCD_SPI_AHB_CLK_DIV(port->spi_mode.ahb_clk_div); LCDC->SPI_CTL2 = LCD_SPI_SIG_DELAY(port->spi_mode.delay_csx_ns, port->spi_mode.delay_scl_ns, port->spi_mode.wr_delay_d0_ns, port->spi_mode.wr_delay_d1_ns, port->spi_mode.wr_delay_d2_ns, port->spi_mode.wr_delay_d3_ns); if (port->minor_type >= DISPLAY_QSPI_DDR_0) { LCDC->QSPI_DDR_CTL = LCD_SPI_MODE(port->spi_mode.cpol, port->spi_mode.cpha) | LCD_SPI_DDR_SEL(port->minor_type - DISPLAY_QSPI_DDR_0 + 1) | LCD_SPI_CLK_KEEP_SEL(0) | LCD_SPI_DDR_EN; } else if (port->minor_type == DISPLAY_QSPI) { LCDC->QSPI_DDR_CTL = LCD_SPI_MODE(port->spi_mode.cpol, port->spi_mode.cpha) | LCD_SPI_DDR_SEL(0) | LCD_SPI_CLK_KEEP_SEL(1) | LCD_SPI_DDR_EN; } else { LCDC->SPI_CTL |= LCD_SPI_SCLK_POL((port->spi_mode.cpol == port->spi_mode.cpha) ? 0 : 1); } break; case DISPLAY_PORT_TR: lcd_ctl |= LCD_IF_SEL_TR; LCDC->TR_CTL = LCD_TR_FTC_IRQ_EN | LCD_TR_F565_16BIT | LCD_TR_LB_SEL(port->tr_mode.low_bit) | LCD_TR_HCK_TAIL_EN(port->tr_mode.hck_tail_on) | LCD_TR_VCK_CONT_EN(port->tr_mode.vck_on_xrstl) | LCD_TR_VCK_MODE(port->tr_mode.vck_on_idle) | LCD_TR_HCK_MODE(port->tr_mode.hck_on_idle) | LCD_TR_PU_MODE(port->tr_mode.ptl_on) | LCD_TR_FRP_EN(port->tr_mode.frp_on) | LCD_TR_VCOM_INV(port->tr_mode.vcom_inv) | LCD_TR_FRP_INV(port->tr_mode.frp_inv) | LCD_TR_XFRP_INV(port->tr_mode.xfrp_inv) | LCD_TR_XRST_INV(port->tr_mode.xrst_inv) | LCD_TR_VST_INV(port->tr_mode.vst_inv) | LCD_TR_HST_INV(port->tr_mode.hst_inv) | LCD_TR_VCK_INV(port->tr_mode.vck_inv) | LCD_TR_HCK_INV(port->tr_mode.hck_inv) | LCD_TR_ENB_INV(port->tr_mode.enb_inv); LCDC->TR_TIM[0] = LCD_TR_TIM0(port->tr_mode.tw_xrst, port->tr_mode.tw_vcom); LCDC->TR_TIM[1] = LCD_TR_TIM1(port->tr_mode.td_vst, port->tr_mode.tw_vst, port->tr_mode.td_hst, port->tr_mode.tw_hst); LCDC->TR_TIM[2] = LCD_TR_TIM2(port->tr_mode.td_vck, port->tr_mode.tw_vck, port->tr_mode.tp_hck, port->tr_mode.td_hck); LCDC->TR_TIM[3] = LCD_TR_TIM3(port->tr_mode.ts_enb, port->tr_mode.th_enb, port->tr_mode.td_data); LCDC->TR_TIM[4] = LCD_TR_TIM4(port->tr_mode.td_enb, port->tr_mode.tw_enb); LCDC->TR_TIM[5] = LCD_TR_TIM5(port->tr_mode.tsm_enb, port->tr_mode.thm_enb, port->tr_mode.twm_vck); 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; case DISPLAY_PORT_MCU: LCDC->CPU_CTL = (LCDC->CPU_CTL & ~LCD_SPI_SDT_MASK) | LCD_CPU_SDT_RGB565; case DISPLAY_PORT_TR: default: LCDC->TR_CTL = (LCDC->TR_CTL & ~LCD_TR_SDT_MASK) | LCD_TR_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: LCDC->CPU_CTL = (LCDC->CPU_CTL & ~LCD_SPI_SDT_MASK) | LCD_CPU_SDT_ARGB8888; case DISPLAY_PORT_TR: default: LCDC->TR_CTL = (LCDC->TR_CTL & ~LCD_TR_SDT_MASK) | LCD_TR_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_BGR888; break; case DISPLAY_PORT_MCU: LCDC->CPU_CTL = (LCDC->CPU_CTL & ~LCD_SPI_SDT_MASK) | LCD_CPU_SDT_BGR888; case DISPLAY_PORT_TR: default: LCDC->TR_CTL = (LCDC->TR_CTL & ~LCD_TR_SDT_MASK) | LCD_TR_SDT_BGR888; 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_RGB888; break; case DISPLAY_PORT_MCU: LCDC->CPU_CTL = (LCDC->CPU_CTL & ~LCD_SPI_SDT_MASK) | LCD_CPU_SDT_RGB888; case DISPLAY_PORT_TR: default: LCDC->TR_CTL = (LCDC->TR_CTL & ~LCD_TR_SDT_MASK) | LCD_TR_SDT_RGB888; 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; if (mode->hactive > 512 || mode->vactive > 512 || (mode->pixel_format & SUPPORTED_PIXEL_FORMATS) == 0) { return -ENOTSUP; } 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_BGR888; 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); } else if (data->port->type == DISPLAY_PORT_TR_LCD) { LCDC->TR_IMG_SIZE = LCD_TR_IMG_SIZE(mode->hactive, mode->vactive); } 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), }; static const uint32_t tr_source_set[DISPLAY_CONTROLLER_NUM_SOURCES] = { LCD_TR_SRC_SEL_DE, LCD_TR_SRC_SEL_DMA, }; struct lcdc_data *data = dev->data; if (source_type >= DISPLAY_CONTROLLER_NUM_SOURCES) { return -EINVAL; } 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; case DISPLAY_PORT_TR: if (source_type == DISPLAY_CONTROLLER_SOURCE_MCU) return -EINVAL; LCDC->TR_CTL = (LCDC->TR_CTL & ~LCD_TR_SRC_MASK) | tr_source_set[source_type - 1]; 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; SDMA0->START = 0; /* force stop DMA */ 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 */ uint32_t lcdc_spi_ctl1 = LCDC->SPI_CTL1; /* save REG SPI_CTL1 */ LCDC->SPI_CTL1 |= LCD_SPI_AHB_CLK_DIV_MAX; /* set lowest clock */ 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; LCDC->SPI_CTL1 = lcdc_spi_ctl1; } 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 = (LCDC->SPI_CTL & ~(SPI_AHB_DATA_MASK | LCD_SPI_DUAL_LANE_MASK)) | (LCD_SPI_SRC_SEL_AHB | LCD_SPI_AHB_CFG_DATA | LCD_SPI_CDX(1) | LCD_SPI_RWL(4)); for (; len >= 4; buf8 += 4, len -= 4) { LCDC->SPI_CTL &= ~LCD_SPI_AHB_CSX_MASK; _fill_spi_config_data32(&data, buf8, 4); LCDC->DATA = data; LCDC->SPI_CTL |= LCD_SPI_AHB_CSX(1); } if (len > 0) { LCDC->SPI_CTL = (LCDC->SPI_CTL & ~(LCD_SPI_RWL_MASK | LCD_SPI_AHB_CSX_MASK)) | LCD_SPI_RWL(len); _fill_spi_config_data32(&data, buf8, len); LCDC->DATA = data; 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; const uint8_t *buf8 = buf; int16_t row_offset; uint16_t bytes_per_line; uint8_t bytes_per_pixel; bool f565_24bit = false; int i, j; if (lcdc_data->port->type == DISPLAY_PORT_TR_LCD || lcdc_data->port->type == DISPLAY_PORT_QSPI_SYNC) { return -ENOTSUP; } if (pixel_format == 0) { pixel_format = lcdc_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; } bytes_per_line = (desc->pitch > 0) ? desc->pitch : desc->width * bytes_per_pixel; row_offset = bytes_per_line - desc->width * bytes_per_pixel; if (CONFIG_LCDC_Y_FLIP) { row_offset -= bytes_per_line * 2; buf8 += (uint32_t)bytes_per_line * (desc->height - 1); } 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) { for (j = desc->height; j > 0; j--) { for (i = desc->width; i > 0; i--) { uint32_t color; switch (bytes_per_pixel) { case 2: /* PIXEL_FORMAT_BGR_565 */ color = buf8[0] | ((uint32_t)buf8[1] << 8); break; case 3: /* PIXEL_FORMAT_RGB_888 or PIXEL_FORMAT_BGR_888 */ color = ((uint32_t)buf8[0] << 16) | ((uint32_t)buf8[1] << 8) | buf8[2]; break; default: color = buf8[0] | ((uint32_t)buf8[1] << 8) | ((uint32_t)buf8[2] << 16) | ((uint32_t)buf8[3] << 24); break; } LCDC->DATA = color; buf8 += bytes_per_pixel; } buf8 += row_offset; } LCDC->SPI_CTL |= LCD_SPI_AHB_CSX(1); } else { for (j = desc->height; j > 0; j--) { for (i = desc->width; i > 0; i--) { uint32_t color; switch (bytes_per_pixel) { case 2: /* PIXEL_FORMAT_BGR_565 */ color = buf8[0] | ((uint32_t)buf8[1] << 8); break; case 3: /* PIXEL_FORMAT_RGB_888 or PIXEL_FORMAT_BGR_888 */ color = ((uint32_t)buf8[0] << 16) | ((uint32_t)buf8[1] << 8) | buf8[2]; break; default: color = buf8[0] | ((uint32_t)buf8[1] << 8) | ((uint32_t)buf8[2] << 16) | ((uint32_t)buf8[3] << 24); break; } LCDC->SPI_CTL &= ~LCD_SPI_AHB_CSX_MASK; LCDC->QSPI_CMD = cmd; LCDC->DATA = color; LCDC->SPI_CTL |= LCD_SPI_AHB_CSX(1); buf8 += bytes_per_pixel; } buf8 += row_offset; } } /* 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); for (j = desc->height; j > 0; j--) { for (i = desc->width; i > 0; i--) { uint32_t color; switch (bytes_per_pixel) { case 2: /* PIXEL_FORMAT_BGR_565 */ color = buf8[0] | ((uint32_t)buf8[1] << 8); break; case 3: /* PIXEL_FORMAT_RGB_888 or PIXEL_FORMAT_BGR_888 */ color = ((uint32_t)buf8[0] << 16) | ((uint32_t)buf8[1] << 8) | buf8[2]; break; default: color = buf8[0] | ((uint32_t)buf8[1] << 8) | ((uint32_t)buf8[2] << 16) | ((uint32_t)buf8[3] << 24); break; } LCDC->DATA = color; buf8 += bytes_per_pixel; } buf8 += row_offset; } /* 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: LOG_DBG("start cpu-if\n"); LCDC->CPU_CTL |= LCD_CPU_START; break; case DISPLAY_PORT_TR: default: LOG_DBG("start tr-if\n"); LCDC->TR_CTL |= LCD_TR_START; break; } return 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 (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; buf = cache_to_uncache((void *)buf); data->busy = true; /* configure DMA */ assert(SDMA0->START == 0); if (CONFIG_LCDC_Y_FLIP) { SDMA0->CTL = BIT(24) | BIT(6); /* stride mode and address decrease mode */ SDMA0->SADDR = (uint32_t)buf + bytes_per_line * (desc->height - 1); } else { SDMA0->CTL = BIT(24); /* stride mode */ SDMA0->SADDR = (uint32_t)buf; } SDMA0->COUNT = desc->height; SDMA0->LENGTH = copy_per_line; SDMA0->SSTRIDE = bytes_per_line; SDMA0->BC = (uint32_t)copy_per_line * desc->height; /* start DMA */ SDMA0->START = 0x1; LCDC->DISP_SIZE = LCD_DISP_SIZE(desc->width, desc->height); switch (data->port->major_type) { case 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; } LOG_DBG("start spi-if\n"); LCDC->SPI_CTL |= LCD_SPI_START; break; case DISPLAY_PORT_MCU: LOG_DBG("start cpu-if\n"); LCDC->CPU_CTL |= LCD_CPU_START; break; case DISPLAY_PORT_TR: default: LOG_DBG("start tr-if\n"); LCDC->TR_CTL |= LCD_TR_START; break; } return 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) { case DISPLAY_CONTROLLER_SOURCE_DMA: return lcdc_write_pixels_by_dma(dev, cmd, hsync_cmd, desc, buf); 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]); } printk("\t LCD_SPI_CTL1 0x%08x\n", LCDC->SPI_CTL1); printk("\t LCD_SPI_CTL2 0x%08x\n", LCDC->SPI_CTL2); printk("\t LCD_QSPI_DDR_CTL 0x%08x\n", LCDC->QSPI_DDR_CTL); printk("\t LCD_SD_CNT 0x%08x\n", LCDC->SD_CNT); printk("\t LCD_SD_CHECKSUM 0x%08x\n", LCDC->SD_CHECKSUM); printk("\t LCD_TR_CTL 0x%08x\n", LCDC->TR_CTL); printk("\t LCD_TR_IMG_SIZE 0x%08x\n", LCDC->TR_IMG_SIZE); for (i = 0; i < ARRAY_SIZE(LCDC->TR_TIM); i++) { printk("\t LCD_TR_TIM_%d 0x%08x\n", i, LCDC->TR_TIM[i]); } printk("\t SDMA0_CTL 0x%08x\n", SDMA0->CTL); printk("\t SDMA0_START 0x%08x\n", SDMA0->START); printk("\t SDMA0_SADDR 0x%08x\n", SDMA0->SADDR); printk("\t SDMA0_BC 0x%08x\n", SDMA0->BC); printk("\t SDMA0_RC 0x%08x\n", SDMA0->RC); printk("\t SDMA0_LINE_LENGTH 0x%08x\n", SDMA0->LENGTH); printk("\t SDMA0_LINE_COUNT 0x%08x\n", SDMA0->COUNT); printk("\t SDMA0_LINE_SSTRIDE 0x%08x\n", SDMA0->SSTRIDE); printk("\t SDMA0_LINE_REMAIN 0x%08x\n", SDMA0->REMAIN); } 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 (data->source_type == DISPLAY_CONTROLLER_SOURCE_DMA) { if (SDMA0->START > 0) { LOG_ERR("LCD remain %u", SDMA0->RC); SDMA0->START = 0; } /* restart DMA */ SDMA0->START = 0x1; } } 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: sl_dbg("lcdc suspend\n"); if (data->busy) { LOG_WRN("lcdc busy (action=%d)", action); ret = -EBUSY; } else { data->saved_port = data->port; data->saved_mode = data->mode; lcdc_disable(dev); } break; case PM_DEVICE_ACTION_RESUME: if (data->saved_port) { lcdc_enable(dev, data->saved_port); if (data->saved_mode) lcdc_set_mode(dev, data->saved_mode); } sl_dbg("lcdc resume\n"); 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);