Files
arcs/arcs-sdk/drivers/lisa_display/lisa_display_panel.c
2026-08-13 16:50:52 +08:00

423 lines
14 KiB
C

/*
* Copyright (c) 2025, LISTENAI
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "lisa_display_panel.h"
#include "lisa_device.h"
#include "lisa_display_bus.h"
#include "lisa_display.h"
#include "lisa_gpio.h"
#include "lisa_pwm.h"
#include <stddef.h>
#include <stdint.h>
#define LOG_TAG "lisa_display_panel"
#include <lisa_log.h>
static int panel_send_data_with_sram_rotate(lisa_display_panel_t *panel, uint16_t x, uint16_t y, uint16_t w, uint16_t h, const void *bitmap);
static inline void reverse_buffer_bytes(uint8_t *buf, int start, int end)
{
while (start < end) {
uint8_t temp = buf[start];
buf[start] = buf[end];
buf[end] = temp;
start++;
end--;
}
}
static void panle_prepare_cmd_buffer(const void *cmd, uint8_t cmd_bits)
{
uint8_t *from = (uint8_t *)cmd;
if (cmd_bits > 8) {
int start = 0;
int end = (cmd_bits / 8) - 1;
reverse_buffer_bytes(from, start, end);
}
}
int panel_write_cmd_data(lisa_display_panel_t *panel, int cmd, uint8_t cmd_bits, const void *data, size_t len)
{
if (!panel || !panel->bus_dev) {
return LISA_DEVICE_ERR_NOT_READY;
}
panle_prepare_cmd_buffer(&cmd, cmd_bits);
if (panel->cmd_bus_dev) {
lisa_display_cmd_bus_api_t *api = panel->cmd_bus_dev->api;
int ret = api->write_cmd(cmd, cmd_bits, data, len);
return ret;
}
lisa_display_bus_api_t *bus_api = (lisa_display_bus_api_t *)panel->bus_dev->api;
bus_api->transfer_control(panel->bus_dev, true);
int ret = bus_api->trans_cmd_data(panel->bus_dev, cmd, cmd_bits, data, len);
bus_api->transfer_control(panel->bus_dev, false);
return ret;
}
int panel_draw_pixels(lisa_display_panel_t *panel, uint32_t cmd, uint16_t cmd_bits, uint16_t x, uint16_t y, uint16_t w, uint16_t h, const void *pixels)
{
int ret = 0;
if (!panel || !panel->bus_dev) {
return LISA_DEVICE_ERR_NOT_READY;
}
if (cmd_bits > 0) {
panle_prepare_cmd_buffer(&cmd, cmd_bits);
}
lisa_display_bus_api_t *bus_api = (lisa_display_bus_api_t *)panel->bus_dev->api;
#if CONFIG_DCACHE_ENABLE
dcache_clean_range((size_t)pixels, (size_t)pixels + ((size_t)w * h * 2));
#endif
bus_api->transfer_control(panel->bus_dev, true);
ret = bus_api->trans_cmd_data(panel->bus_dev, cmd, cmd_bits, NULL, 0);
if (ret != LISA_DEVICE_OK) {
bus_api->transfer_control(panel->bus_dev, false);
return ret;
}
if (panel->caps.orientation == LISA_DISPLAY_ORIENTATION_90 ||
panel->caps.orientation == LISA_DISPLAY_ORIENTATION_270) {
ret = panel_send_data_with_sram_rotate(panel, x, y, w, h, pixels);
}
else {
ret = bus_api->write_pixels(panel->bus_dev, pixels, (size_t)w * h * 2);
if (ret != LISA_DEVICE_OK) {
bus_api->transfer_control(panel->bus_dev, false);
return ret;
}
ret = bus_api->wait_for_completion(panel->bus_dev, 1000);
if (ret != LISA_DEVICE_OK) {
bus_api->transfer_control(panel->bus_dev, false);
return ret;
}
}
bus_api->transfer_control(panel->bus_dev, false);
return ret;
}
int panel_set_backlight_brightness(lisa_display_backlight_t *backlight, uint8_t brightness)
{
if (!backlight) {
return LISA_DEVICE_ERR_INVALID;
}
switch (backlight->type) {
case LISA_DISPLAY_BACKLIGHT_TYPE_PWM: {
lisa_display_backlight_pwm_config_t *pwm = &backlight->config.pwm;
if (pwm->dev) {
if (brightness == 0) {
lisa_pwm_disable(pwm->dev, pwm->channel);
return LISA_DEVICE_OK;
}
if (brightness >= 100) {
brightness = 99;
}
lisa_pwm_config_t config = { .polarity = LISA_PWM_POLARITY_INVERTED };
if (backlight->blacklight_polarity == LISA_DISPLAY_BLACKLIGHT_POLARITY_HIGH) {
config.polarity = LISA_PWM_POLARITY_NORMAL;
}
else if (backlight->blacklight_polarity == LISA_DISPLAY_BLACKLIGHT_POLARITY_LOW) {
config.polarity = LISA_PWM_POLARITY_INVERTED;
}
lisa_pwm_configure(pwm->dev, pwm->channel, &config);
lisa_pwm_set(pwm->dev, pwm->channel, pwm->freq, brightness);
lisa_pwm_enable(pwm->dev, pwm->channel);
return LISA_DEVICE_OK;
}
break;
}
case LISA_DISPLAY_BACKLIGHT_TYPE_SINGLE_WIRE: {
lisa_display_backlight_single_wire_config_t *sw = &backlight->config.sw;
if (sw->dev) {
int level = brightness * sw->steps / 100;
if (level == sw->current_level) {
return LISA_DEVICE_OK;
}
if (level == 0) {
lisa_gpio_write_pin(sw->dev, sw->pin, 0);
lisa_thread_mdelay(3);
} else {
if (sw->current_level == 0) {
sw->current_level = sw->steps;
lisa_gpio_write_pin(sw->dev, sw->pin, 1);
lisa_thread_mdelay(30);
}
int pulses_start = sw->steps - sw->current_level;
int pulses_end = sw->steps - level;
int pulses = (sw->steps + pulses_end - pulses_start) % sw->steps;
for (int i = 0; i < pulses; i++) {
lisa_gpio_write_pin(sw->dev, sw->pin, 0);
lisa_thread_mdelay(1);
lisa_gpio_write_pin(sw->dev, sw->pin, 1);
lisa_thread_mdelay(1);
}
}
sw->current_level = level;
return LISA_DEVICE_OK;
}
break;
}
default:
break;
}
return LISA_DEVICE_ERR_NOT_SUPPORT;
}
void panel_reset_pin_set(lisa_display_panel_t *panel, uint8_t level)
{
if (!panel) {
return;
}
lisa_gpio_write_pin(panel->rst_gpio, panel->rst_pin, level);
}
void panel_set_mem_area(lisa_display_panel_t *panel, lisa_display_panel_mem_area_t *area, lisa_mem_coord_t x, lisa_mem_coord_t y)
{
uint16_t new_x, new_y, new_w, new_h;
if (!panel) {
return;
}
if (panel->caps.orientation == LISA_DISPLAY_ORIENTATION_90) {
new_x = area->panel_w - (area->y + area->h);
new_y = area->x;
new_w = area->h;
new_h = area->w;
}
else if (panel->caps.orientation == LISA_DISPLAY_ORIENTATION_270) {
new_x = area->y;
new_y = area->panel_h - area->x - area->w;
new_w = area->h;
new_h = area->w;
}
else {
new_x = area->x;
new_y = area->y;
new_w = area->w;
new_h = area->h;
}
new_x += area->x_offset;
new_y += area->y_offset;
x[0] = (new_x >> 8);
x[1] = (new_x & 0xff);
x[2] = ((new_x + new_w - 1) >> 8);
x[3] = ((new_x + new_w - 1) & 0xff);
y[0] = (new_y >> 8);
y[1] = (new_y & 0xff);
y[2] = ((new_y + new_h - 1) >> 8);
y[3] = ((new_y + new_h - 1) & 0xff);
}
#ifndef CONFIG_ROTATE_BUF_MAX_HEIGHT
#define ROTATE_BUF_MAX_HEIGHT 16
#else
#define ROTATE_BUF_MAX_HEIGHT CONFIG_ROTATE_BUF_MAX_HEIGHT
#endif
#ifndef CONFIG_ROATE_BUF_WIDTH
#define ROTATE_BUF_WIDTH 16
#else
#define ROTATE_BUF_WIDTH CONFIG_ROTATE_BUF_WIDTH
#endif
#if CONFIG_LISA_DISPLAY_CPDMA_ROTATE
#include <dma.h>
#endif
typedef struct {
#if CONFIG_LISA_DISPLAY_CPDMA_ROTATE
lisa_semaphore_t *cpdma_done_sem;
#endif
uint16_t rotate_buf_ping[ROTATE_BUF_MAX_HEIGHT * ROTATE_BUF_WIDTH];
uint16_t rotate_buf_pong[ROTATE_BUF_MAX_HEIGHT * ROTATE_BUF_WIDTH];
} panel_rotate_ctx_t;
static panel_rotate_ctx_t g_rotate_ctx;
#if CONFIG_LISA_DISPLAY_CPDMA_ROTATE
#include "sysutils.h"
__dtcm_bss__ DMA_LLI dma_llp_lists[ROTATE_BUF_MAX_HEIGHT];
static void rotate_dma_drv_event(uint32_t event_info, uint32_t xfer_bytes, uint32_t usr_param)
{
panel_rotate_ctx_t *ctx = (panel_rotate_ctx_t *)usr_param;
lisa_semaphore_give(ctx->cpdma_done_sem);
}
#endif
int panel_rotate_init(void)
{
#if CONFIG_LISA_DISPLAY_CPDMA_ROTATE
g_rotate_ctx.cpdma_done_sem = lisa_semaphore_create(1);
if (!g_rotate_ctx.cpdma_done_sem) {
return LISA_DEVICE_ERR_NO_MEM;
}
dma_initialize();
dma_channel_reserve(CONFIG_LISA_DISPLAY_CPDMA_CH, rotate_dma_drv_event, (uint32_t)(&g_rotate_ctx), DMA_CACHE_SYNC_AUTO);
#endif
return LISA_DEVICE_OK;
}
#if CONFIG_LISA_DISPLAY_CPDMA_ROTATE
static void _panel_dma_rotate(const uint16_t *src_buf, uint16_t src_buf_w,
uint16_t *dst_buf, uint16_t area_w, uint16_t area_h,
lisa_display_orientation_t orientation)
{
uint16_t link_size = area_h;
uint32_t control, config_low, config_high;
if (orientation == LISA_DISPLAY_ORIENTATION_90) {
control = DMA_CH_CTLL_INT_EN | DMA_CH_CTLL_DST_WIDTH(DMA_WIDTH_HALFWORD) |
DMA_CH_CTLL_SRC_WIDTH(DMA_WIDTH_HALFWORD) | DMA_CH_CTLL_DST_INC | DMA_CH_CTLL_SRC_INC |
DMA_CH_CTLL_DST_BSIZE(DMA_BSIZE_1) | DMA_CH_CTLL_SRC_BSIZE(DMA_BSIZE_16) | DMA_CH_CTLL_TTFC_M2M |
DMA_CH_CTLL_DMS(0) | DMA_CH_CTLL_SMS(0);
} else { /* 270 */
control = DMA_CH_CTLL_INT_EN | DMA_CH_CTLL_DST_WIDTH(DMA_WIDTH_HALFWORD) |
DMA_CH_CTLL_SRC_WIDTH(DMA_WIDTH_HALFWORD) | DMA_CH_CTLL_DST_DEC | DMA_CH_CTLL_SRC_INC |
DMA_CH_CTLL_DST_BSIZE(DMA_BSIZE_1) | DMA_CH_CTLL_SRC_BSIZE(DMA_BSIZE_16) | DMA_CH_CTLL_TTFC_M2M |
DMA_CH_CTLL_DMS(0) | DMA_CH_CTLL_SMS(0);
}
control |= DMA_CH_CTLL_D_SCAT_EN;
config_low = DMA_CH_CFGL_CH_PRIOR(0);
config_high = DMA_CH_CFGH_FIFO_MODE;
for (uint32_t i = 0; i < link_size; i++) {
dma_llp_lists[i].SAR = (uint32_t)(src_buf + src_buf_w * i);
if (orientation == LISA_DISPLAY_ORIENTATION_90) {
dma_llp_lists[i].DAR = (uint32_t)(dst_buf + (link_size - 1 - i));
} else { /* 270 */
dma_llp_lists[i].DAR = (uint32_t)(dst_buf + (area_w - 1) * link_size + i);
}
dma_llp_lists[i].LLP = (uint32_t)(&dma_llp_lists[(i + 1) % link_size]);
dma_llp_lists[i].CTL_LO = control | DMA_CH_CTLL_LLP_EN_MASK;
dma_llp_lists[i].u.SIZE = area_w;
}
dma_llp_lists[link_size - 1].LLP = 0;
dma_llp_lists[link_size - 1].CTL_LO &= ~DMA_CH_CTLL_LLP_EN_MASK;
uint32_t dst_scat = ((link_size - 1) << SG_INTERVAL_POS) | (1 << SG_COUNT_POS);
dma_channel_configure_LLP_with_size(CONFIG_LISA_DISPLAY_CPDMA_CH, dma_llp_lists, config_low, config_high, 0,
dst_scat, link_size * area_w);
if (lisa_semaphore_take(g_rotate_ctx.cpdma_done_sem, 100) != 0) {
LISA_LOGE(LOG_TAG, "CPDMA rotate timeout");
}
}
#endif
static void panel_buf_rotate_90(const uint16_t *src_buf, uint16_t src_buf_w,
uint16_t *dst_buf, uint16_t area_w, uint16_t area_h)
{
#if CONFIG_LISA_DISPLAY_CPDMA_ROTATE
_panel_dma_rotate(src_buf, src_buf_w, dst_buf, area_w, area_h, LISA_DISPLAY_ORIENTATION_90);
#else
uint32_t invert = (area_w * area_h) - 1;
uint32_t initial_i = ((area_w - 1) * area_h);
for (uint16_t y = 0; y < area_h; y++) {
uint32_t i = initial_i + y;
i = invert - i;
for (uint16_t x = 0; x < area_w; x++) {
dst_buf[i] = *(src_buf++);
i += area_h;
}
src_buf += src_buf_w - area_w;
}
#endif
}
static void panel_buf_rotate_270(const uint16_t *src_buf, uint16_t src_buf_w, uint16_t *dst_buf, uint16_t area_w,
uint16_t area_h)
{
#if CONFIG_LISA_DISPLAY_CPDMA_ROTATE
_panel_dma_rotate(src_buf, src_buf_w, dst_buf, area_w, area_h, LISA_DISPLAY_ORIENTATION_270);
#else
for (uint16_t y = 0; y < area_h; y++) {
for (uint16_t x = 0; x < area_w; x++) {
uint32_t dst_idx = ((area_w - 1 - x) * area_h) + y;
dst_buf[dst_idx] = *(src_buf++);
}
src_buf += src_buf_w - area_w;
}
#endif
}
static int panel_send_data_with_sram_rotate(lisa_display_panel_t *panel, uint16_t x, uint16_t y, uint16_t w, uint16_t h, const void *bitmap)
{
uint32_t data_offset = 0;
uint16_t *curr_buf = g_rotate_ctx.rotate_buf_ping;
uint16_t *next_buf = g_rotate_ctx.rotate_buf_pong;
const uint16_t *src_buf = (const uint16_t *)bitmap;
if (h > ROTATE_BUF_MAX_HEIGHT) {
return LISA_DEVICE_ERR_INVALID;
}
if (panel->caps.orientation == LISA_DISPLAY_ORIENTATION_90) {
data_offset = 0;
panel_buf_rotate_90(src_buf + data_offset, w, curr_buf, ROTATE_BUF_WIDTH, h);
} else if (panel->caps.orientation == LISA_DISPLAY_ORIENTATION_270) {
data_offset = w - ROTATE_BUF_WIDTH;
panel_buf_rotate_270(src_buf + data_offset, w, curr_buf, ROTATE_BUF_WIDTH, h);
}
else {
LISA_LOGE(LOG_TAG, "Invalid orientation");
return LISA_DEVICE_ERR_INVALID;
}
lisa_display_bus_api_t *bus_api = (lisa_display_bus_api_t *)panel->bus_dev->api;
for (uint16_t i = 0; i < w; i += ROTATE_BUF_WIDTH) {
size_t chunk_size = (size_t)ROTATE_BUF_WIDTH * h * sizeof(uint16_t);
// 使用异步传输,实现 CPU/DMA 并行操作
int ret = bus_api->write_pixels(panel->bus_dev,curr_buf, chunk_size);
if (ret != LISA_DEVICE_OK) {
LISA_LOGE(LOG_TAG, "Failed to start async transfer: %d", ret);
return ret;
}
// 在 DMA 传输的同时,准备下一块数据
if (panel->caps.orientation == LISA_DISPLAY_ORIENTATION_90) {
data_offset += ROTATE_BUF_WIDTH;
panel_buf_rotate_90(src_buf + data_offset, w, next_buf, ROTATE_BUF_WIDTH, h);
} else if (panel->caps.orientation == LISA_DISPLAY_ORIENTATION_270) {
data_offset -= ROTATE_BUF_WIDTH;
panel_buf_rotate_270(src_buf + data_offset, w, next_buf, ROTATE_BUF_WIDTH, h);
}
// 等待当前 DMA 传输完成
ret = bus_api->wait_for_completion(panel->bus_dev, 1000);
if (ret != LISA_DEVICE_OK) {
LISA_LOGE(LOG_TAG, "Async transfer timeout or error: %d", ret);
return ret;
}
uint16_t *temp = curr_buf;
curr_buf = next_buf;
next_buf = temp;
}
}