You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
681 lines
21 KiB
681 lines
21 KiB
/* |
|
* Copyright (c) 2023 Renesas Electronics Corporation |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT renesas_smartbond_display |
|
|
|
#include <zephyr/drivers/pinctrl.h> |
|
#include <zephyr/drivers/gpio.h> |
|
#include <zephyr/irq.h> |
|
#include <zephyr/kernel.h> |
|
#include <zephyr/device.h> |
|
#include <zephyr/drivers/clock_control.h> |
|
#include <zephyr/drivers/clock_control/smartbond_clock_control.h> |
|
#include <zephyr/drivers/display.h> |
|
#include <zephyr/sys/util.h> |
|
#include <zephyr/drivers/dma.h> |
|
#include <da1469x_lcdc.h> |
|
#include <DA1469xAB.h> |
|
#include <da1469x_pd.h> |
|
#include <zephyr/linker/devicetree_regions.h> |
|
#include <zephyr/pm/device.h> |
|
#include <zephyr/pm/policy.h> |
|
|
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(smartbond_display, CONFIG_DISPLAY_LOG_LEVEL); |
|
|
|
#define SMARTBOND_IRQN DT_INST_IRQN(0) |
|
#define SMARTBOND_IRQ_PRIO DT_INST_IRQ(0, priority) |
|
|
|
#define LCDC_SMARTBOND_CLK_DIV(_freq) \ |
|
((32000000U % (_freq)) ? (96000000U / (_freq)) : (32000000U / (_freq))) |
|
|
|
#define LCDC_SMARTBOND_IS_PLL_REQUIRED \ |
|
!!(32000000U % DT_PROP(DT_INST_CHILD(0, display_timings), clock_frequency)) |
|
|
|
#define DISPLAY_SMARTBOND_IS_DMA_PREFETCH_ENABLED \ |
|
DT_INST_ENUM_IDX_OR(0, dma_prefetch, 0) |
|
|
|
#define LCDC_LAYER0_OFFSETX_REG_SET_FIELD(_field, _var, _val)\ |
|
((_var)) = \ |
|
((_var) & ~(LCDC_LCDC_LAYER0_OFFSETX_REG_ ## _field ## _Msk)) | \ |
|
(((_val) << LCDC_LCDC_LAYER0_OFFSETX_REG_ ## _field ## _Pos) & \ |
|
LCDC_LCDC_LAYER0_OFFSETX_REG_ ## _field ## _Msk) |
|
|
|
#define DISPLAY_SMARTBOND_PIXEL_SIZE(inst) \ |
|
(DISPLAY_BITS_PER_PIXEL(DT_INST_PROP(inst, pixel_format)) / BITS_PER_BYTE) |
|
|
|
#if CONFIG_DISPLAY_RENESAS_LCDC_BUFFER_PSRAM |
|
#define DISPLAY_BUFFER_LINKER_SECTION \ |
|
Z_GENERIC_SECTION(LINKER_DT_NODE_REGION_NAME(DT_NODELABEL(psram))) |
|
#else |
|
#define DISPLAY_BUFFER_LINKER_SECTION |
|
#endif |
|
|
|
struct display_smartbond_data { |
|
/* Provide mutual exclusion when a display operation is requested. */ |
|
struct k_sem device_sem; |
|
/* Frame update synchronization token */ |
|
struct k_sem sync_sem; |
|
/* Flag indicating whether or not an underflow took place */ |
|
volatile bool underflow_flag; |
|
/* Layer settings */ |
|
lcdc_smartbond_layer_cfg layer; |
|
/* Frame buffer */ |
|
uint8_t *buffer; |
|
/* DMA device */ |
|
const struct device *dma; |
|
/* DMA configuration structures */ |
|
struct dma_config dma_cfg; |
|
struct dma_block_config dma_block_cfg; |
|
/* DMA memory transfer synchronization token */ |
|
struct k_sem dma_sync_sem; |
|
/* Granted DMA channel used for memory transfers */ |
|
int dma_channel; |
|
#if defined(CONFIG_PM_DEVICE) |
|
ATOMIC_DEFINE(pm_policy_state_flag, 1); |
|
#endif |
|
}; |
|
|
|
struct display_smartbond_config { |
|
/* Reference to device instance's pinctrl configurations */ |
|
const struct pinctrl_dev_config *pcfg; |
|
/* Display ON/OFF GPIO */ |
|
const struct gpio_dt_spec disp; |
|
/* Host controller's timing settings */ |
|
lcdc_smartbond_timing_cfg timing_cfg; |
|
/* Parallel interface settings */ |
|
lcdc_smartbond_mode_cfg mode; |
|
/* Background default color configuration */ |
|
lcdc_smartbond_bgcolor_cfg bgcolor_cfg; |
|
/* Display dimensions */ |
|
const uint16_t x_res; |
|
const uint16_t y_res; |
|
/* Pixel size in bytes */ |
|
uint8_t pixel_size; |
|
enum display_pixel_format pixel_format; |
|
}; |
|
|
|
static inline void lcdc_smartbond_pm_policy_state_lock_get(struct display_smartbond_data *data) |
|
{ |
|
#ifdef CONFIG_PM_DEVICE |
|
if (atomic_test_and_set_bit(data->pm_policy_state_flag, 0) == 0) { |
|
/* |
|
* Prevent the SoC from etering the normal sleep state as PDC does not support |
|
* waking up the application core following LCDC events. |
|
*/ |
|
pm_policy_state_lock_get(PM_STATE_STANDBY, PM_ALL_SUBSTATES); |
|
} |
|
#endif |
|
} |
|
|
|
static inline void lcdc_smartbond_pm_policy_state_lock_put(struct display_smartbond_data *data) |
|
{ |
|
#ifdef CONFIG_PM_DEVICE |
|
if (atomic_test_and_clear_bit(data->pm_policy_state_flag, 0) == 1) { |
|
/* Allow the SoC to enter the nornmal sleep state once LCDC is inactive */ |
|
pm_policy_state_lock_put(PM_STATE_STANDBY, PM_ALL_SUBSTATES); |
|
} |
|
#endif |
|
} |
|
|
|
/* Display pixel to layer color format translation */ |
|
static uint8_t lcdc_smartbond_pixel_to_lcm(enum display_pixel_format pixel_format) |
|
{ |
|
switch (pixel_format) { |
|
case PIXEL_FORMAT_RGB_565: |
|
return (uint8_t)LCDC_SMARTBOND_L0_RGB565; |
|
case PIXEL_FORMAT_ARGB_8888: |
|
return (uint8_t)LCDC_SMARTBOND_L0_ARGB8888; |
|
default: |
|
LOG_ERR("Unsupported pixel format"); |
|
return 0; |
|
}; |
|
} |
|
|
|
static int display_smartbond_configure(const struct device *dev) |
|
{ |
|
uint8_t clk_div = |
|
LCDC_SMARTBOND_CLK_DIV(DT_PROP(DT_INST_CHILD(0, display_timings), clock_frequency)); |
|
|
|
const struct display_smartbond_config *config = dev->config; |
|
struct display_smartbond_data *data = dev->data; |
|
|
|
int ret = 0; |
|
|
|
/* First enable the controller so registers can be written. */ |
|
da1469x_lcdc_set_status(true, LCDC_SMARTBOND_IS_PLL_REQUIRED, clk_div); |
|
|
|
if (!da1469x_lcdc_check_id()) { |
|
LOG_ERR("Invalid LCDC ID"); |
|
da1469x_lcdc_set_status(false, false, 0); |
|
return -EINVAL; |
|
} |
|
|
|
da1469x_lcdc_parallel_interface_configure((lcdc_smartbond_mode_cfg *)&config->mode); |
|
da1469x_lcdc_bgcolor_configure((lcdc_smartbond_bgcolor_cfg *)&config->bgcolor_cfg); |
|
|
|
/* |
|
* Partial update is not supported and so timing and layer settings can be configured |
|
* once at initialization. |
|
*/ |
|
ret = da1469x_lcdc_timings_configure(config->x_res, config->y_res, |
|
(lcdc_smartbond_timing_cfg *)&config->timing_cfg); |
|
if (ret < 0) { |
|
LOG_ERR("Unable to configure timing settings"); |
|
da1469x_lcdc_set_status(false, false, 0); |
|
return ret; |
|
} |
|
|
|
/* |
|
* Stride should be updated at the end of a frame update (typically in ISR context). |
|
* It's OK to update stride here as continuous mode should not be enabled yet. |
|
*/ |
|
data->layer.color_format = |
|
lcdc_smartbond_pixel_to_lcm(config->pixel_format); |
|
data->layer.stride = |
|
da1469x_lcdc_stride_calculation(data->layer.color_format, config->x_res); |
|
|
|
ret = da1469x_lcdc_layer_configure(&data->layer); |
|
if (ret < 0) { |
|
LOG_ERR("Unable to configure layer settings"); |
|
da1469x_lcdc_set_status(false, false, 0); |
|
} |
|
|
|
LCDC_LAYER0_OFFSETX_REG_SET_FIELD(LCDC_L0_DMA_PREFETCH, |
|
LCDC->LCDC_LAYER0_OFFSETX_REG, DISPLAY_SMARTBOND_IS_DMA_PREFETCH_ENABLED); |
|
|
|
LCDC->LCDC_MODE_REG |= LCDC_LCDC_MODE_REG_LCDC_MODE_EN_Msk; |
|
|
|
return ret; |
|
} |
|
|
|
static void smartbond_display_isr(const void *arg) |
|
{ |
|
struct display_smartbond_data *data = ((const struct device *)arg)->data; |
|
|
|
data->underflow_flag = LCDC_STATUS_REG_GET_FIELD(LCDC_STICKY_UNDERFLOW); |
|
|
|
/* |
|
* Underflow sticky bit will remain high until cleared by writing |
|
* any value to LCDC_INTERRUPT_REG. |
|
*/ |
|
LCDC->LCDC_INTERRUPT_REG &= ~LCDC_LCDC_INTERRUPT_REG_LCDC_VSYNC_IRQ_EN_Msk; |
|
|
|
/* Notify that current frame update is completed */ |
|
k_sem_give(&data->sync_sem); |
|
} |
|
|
|
static void display_smartbond_dma_cb(const struct device *dma, void *arg, |
|
uint32_t id, int status) |
|
{ |
|
struct display_smartbond_data *data = arg; |
|
|
|
if (status < 0) { |
|
LOG_WRN("DMA transfer did not complete"); |
|
} |
|
|
|
k_sem_give(&data->dma_sync_sem); |
|
} |
|
|
|
static int display_smartbond_dma_config(const struct device *dev) |
|
{ |
|
struct display_smartbond_data *data = dev->data; |
|
|
|
data->dma = DEVICE_DT_GET(DT_NODELABEL(dma)); |
|
if (!device_is_ready(data->dma)) { |
|
LOG_ERR("DMA device is not ready"); |
|
return -ENODEV; |
|
} |
|
|
|
data->dma_cfg.channel_direction = MEMORY_TO_MEMORY; |
|
data->dma_cfg.user_data = data; |
|
data->dma_cfg.dma_callback = display_smartbond_dma_cb; |
|
data->dma_cfg.block_count = 1; |
|
data->dma_cfg.head_block = &data->dma_block_cfg; |
|
data->dma_cfg.error_callback_dis = 1; |
|
|
|
/* Request an arbitrary DMA channel */ |
|
data->dma_channel = dma_request_channel(data->dma, NULL); |
|
if (data->dma_channel < 0) { |
|
LOG_ERR("Could not acquire a DMA channel"); |
|
return -EIO; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int display_smartbond_resume(const struct device *dev) |
|
{ |
|
const struct display_smartbond_config *config = dev->config; |
|
int ret; |
|
|
|
/* Select default state */ |
|
ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); |
|
if (ret < 0) { |
|
LOG_ERR("Could not apply LCDC pins' default state (%d)", ret); |
|
return -EIO; |
|
} |
|
|
|
#if LCDC_SMARTBOND_IS_PLL_REQUIRED |
|
const struct device *clock_dev = DEVICE_DT_GET(DT_NODELABEL(osc)); |
|
|
|
if (!device_is_ready(clock_dev)) { |
|
LOG_WRN("Clock device is not ready"); |
|
return -ENODEV; |
|
} |
|
|
|
ret = z_smartbond_select_sys_clk(SMARTBOND_CLK_PLL96M); |
|
if (ret < 0) { |
|
LOG_WRN("Could not switch to PLL"); |
|
return -EIO; |
|
} |
|
#endif |
|
|
|
ret = display_smartbond_dma_config(dev); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
|
|
return display_smartbond_configure(dev); |
|
} |
|
|
|
#if defined(CONFIG_PM_DEVICE) |
|
static void display_smartbond_dma_deconfig(const struct device *dev) |
|
{ |
|
struct display_smartbond_data *data = dev->data; |
|
|
|
__ASSERT(data->dma, "DMA should be already initialized"); |
|
|
|
dma_release_channel(data->dma, data->dma_channel); |
|
} |
|
|
|
static int display_smartbond_suspend(const struct device *dev) |
|
{ |
|
const struct display_smartbond_config *config = dev->config; |
|
int ret; |
|
|
|
/* Select sleep state; it's OK if settings fails for any reason */ |
|
ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_SLEEP); |
|
if (ret < 0) { |
|
LOG_WRN("Could not apply DISPLAY pins' sleep state"); |
|
} |
|
|
|
/* Disable host controller to minimize power consumption */ |
|
da1469x_lcdc_set_status(false, false, 0); |
|
|
|
display_smartbond_dma_deconfig(dev); |
|
|
|
return 0; |
|
} |
|
#endif |
|
|
|
static int display_smartbond_init(const struct device *dev) |
|
{ |
|
const struct display_smartbond_config *config = dev->config; |
|
struct display_smartbond_data *data = dev->data; |
|
int ret; |
|
|
|
/* Device should be ready to be acquired */ |
|
k_sem_init(&data->device_sem, 1, 1); |
|
/* Event should be signaled by LCDC ISR */ |
|
k_sem_init(&data->sync_sem, 0, 1); |
|
/* Event should be signaled by DMA ISR */ |
|
k_sem_init(&data->dma_sync_sem, 0, 1); |
|
|
|
/* As per docs, display port should be enabled by default. */ |
|
if (gpio_is_ready_dt(&config->disp)) { |
|
ret = gpio_pin_configure_dt(&config->disp, GPIO_OUTPUT_ACTIVE); |
|
if (ret < 0) { |
|
LOG_ERR("Could not activate display port"); |
|
return -EIO; |
|
} |
|
} |
|
|
|
IRQ_CONNECT(SMARTBOND_IRQN, SMARTBOND_IRQ_PRIO, smartbond_display_isr, |
|
DEVICE_DT_INST_GET(0), 0); |
|
|
|
/* |
|
* Currently, there is no API to explicitly enable/disable the display controller. |
|
* At the same time, the controller is set to continuous mode meaning that |
|
* as long as a display panel is turned on, frame updates should happen all |
|
* the time (otherwise contents on the display pane will be lost as the latter |
|
* does not integrate an SDRAM memory to keep its frame). |
|
* As such, resume/suspend operations are bound to blanking operations. |
|
* That is, when the display is blanked on we can safely consider that display |
|
* is no longer functional and thus, the controller can be suspended (allowing the |
|
* SoC to enter the sleep state). Once the display is blanked off, then we consider |
|
* that the controller should be resumed and sleep should be prevented at all |
|
* (this is because the controller is powered by the same power domain used to |
|
* power the application core). Side effect of the above is that the controller |
|
* should be configured at initialization phase as display operations might |
|
* be requested before the display is blanked off for the very first time. |
|
*/ |
|
ret = display_smartbond_resume(dev); |
|
if (ret == 0) { |
|
/* Display port should be enabled at this moment and so sleep is not allowed. */ |
|
lcdc_smartbond_pm_policy_state_lock_get(data); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int display_smartbond_blanking_on(const struct device *dev) |
|
{ |
|
const struct display_smartbond_config *config = dev->config; |
|
struct display_smartbond_data *data = dev->data; |
|
int ret = 0; |
|
|
|
k_sem_take(&data->device_sem, K_FOREVER); |
|
|
|
/* |
|
* This bit will force LCD controller's output to blank that is, |
|
* the controller will keep operating without outputting any |
|
* pixel data. |
|
*/ |
|
LCDC->LCDC_MODE_REG |= LCDC_LCDC_MODE_REG_LCDC_FORCE_BLANK_Msk; |
|
|
|
/* If enabled, disable display port. */ |
|
if (gpio_is_ready_dt(&config->disp)) { |
|
ret = gpio_pin_configure_dt(&config->disp, GPIO_OUTPUT_INACTIVE); |
|
if (ret < 0) { |
|
LOG_WRN("Display port could not be de-activated"); |
|
} |
|
} |
|
|
|
/* |
|
* At this moment the display panel should be turned off and so the device |
|
* can enter the suspend state. |
|
*/ |
|
lcdc_smartbond_pm_policy_state_lock_put(data); |
|
|
|
k_sem_give(&data->device_sem); |
|
|
|
return ret; |
|
} |
|
|
|
static int display_smartbond_blanking_off(const struct device *dev) |
|
{ |
|
const struct display_smartbond_config *config = dev->config; |
|
struct display_smartbond_data *data = dev->data; |
|
int ret = 0; |
|
|
|
k_sem_take(&data->device_sem, K_FOREVER); |
|
|
|
/* If used, enable display port */ |
|
if (gpio_is_ready_dt(&config->disp)) { |
|
ret = gpio_pin_configure_dt(&config->disp, GPIO_OUTPUT_ACTIVE); |
|
if (ret < 0) { |
|
LOG_WRN("Display port could not be activated"); |
|
} |
|
} |
|
|
|
/* |
|
* This bit will force LCD controller's output to blank that is, |
|
* the controller will keep operating without outputting any |
|
* pixel data. |
|
*/ |
|
LCDC->LCDC_MODE_REG &= ~LCDC_LCDC_MODE_REG_LCDC_FORCE_BLANK_Msk; |
|
|
|
/* |
|
* At this moment the display should be turned on and so the device |
|
* cannot enter the suspend state. |
|
*/ |
|
lcdc_smartbond_pm_policy_state_lock_get(data); |
|
|
|
k_sem_give(&data->device_sem); |
|
|
|
return ret; |
|
} |
|
|
|
static void *display_smartbond_get_framebuffer(const struct device *dev) |
|
{ |
|
struct display_smartbond_data *data = dev->data; |
|
|
|
return ((void *)data->buffer); |
|
} |
|
|
|
static void display_smartbond_get_capabilities(const struct device *dev, |
|
struct display_capabilities *capabilities) |
|
{ |
|
memset(capabilities, 0, sizeof(*capabilities)); |
|
|
|
/* |
|
* Multiple color formats should be supported by LCDC. Currently, RGB56 and ARGB888 |
|
* exposed by display API are supported. In the future we should consider supporting |
|
* more color formats which should require changes in LVGL porting. |
|
* Here, only one color format should be supported as the frame buffer is accessed |
|
* directly by LCDC and is allocated statically during device initialization. The color |
|
* format is defined based on the pixel-format property dictated by lcd-controller |
|
* bindings. |
|
*/ |
|
capabilities->supported_pixel_formats = DT_INST_PROP(0, pixel_format); |
|
capabilities->current_orientation = DISPLAY_ORIENTATION_NORMAL; |
|
capabilities->current_pixel_format = DT_INST_PROP(0, pixel_format); |
|
capabilities->x_resolution = DT_INST_PROP(0, width); |
|
capabilities->y_resolution = DT_INST_PROP(0, height); |
|
} |
|
|
|
static int display_smartbond_read(const struct device *dev, |
|
const uint16_t x, const uint16_t y, |
|
const struct display_buffer_descriptor *desc, |
|
void *buf) |
|
{ |
|
struct display_smartbond_data *data = dev->data; |
|
const struct display_smartbond_config *config = dev->config; |
|
uint8_t *dst = buf; |
|
const uint8_t *src = data->buffer; |
|
|
|
k_sem_take(&data->device_sem, K_FOREVER); |
|
|
|
/* pointer to upper left pixel of the rectangle */ |
|
src += (x * config->pixel_size); |
|
src += (y * data->layer.stride); |
|
|
|
data->dma_block_cfg.block_size = desc->width * config->pixel_size; |
|
/* |
|
* Source and destination base address is word aligned. |
|
* Data size should be selected based on color depth as |
|
* cursor is shifted multiple of pixel color depth. |
|
*/ |
|
data->dma_cfg.source_data_size = data->dma_cfg.dest_data_size = |
|
!(config->pixel_size & 3) ? 4 : |
|
!(config->pixel_size & 1) ? 2 : 1; |
|
|
|
data->dma_cfg.dest_burst_length = data->dma_cfg.source_burst_length = |
|
!((data->dma_block_cfg.block_size / data->dma_cfg.source_data_size) & 7) ? 8 : |
|
!((data->dma_block_cfg.block_size / data->dma_cfg.source_data_size) & 3) ? 4 : 1; |
|
|
|
for (int row = 0; row < desc->height; row++) { |
|
|
|
data->dma_block_cfg.dest_address = (uint32_t)dst; |
|
data->dma_block_cfg.source_address = (uint32_t)src; |
|
|
|
if (dma_config(data->dma, data->dma_channel, &data->dma_cfg)) { |
|
LOG_ERR("Could not configure DMA"); |
|
k_sem_give(&data->device_sem); |
|
return -EIO; |
|
} |
|
|
|
if (dma_start(data->dma, data->dma_channel)) { |
|
LOG_ERR("Could not start DMA"); |
|
k_sem_give(&data->device_sem); |
|
return -EIO; |
|
} |
|
|
|
k_sem_take(&data->dma_sync_sem, K_FOREVER); |
|
|
|
src += data->layer.stride; |
|
dst += (desc->pitch * config->pixel_size); |
|
} |
|
|
|
if (dma_stop(data->dma, data->dma_channel)) { |
|
LOG_WRN("Could not stop DMA"); |
|
} |
|
|
|
k_sem_give(&data->device_sem); |
|
|
|
return 0; |
|
} |
|
|
|
static int display_smartbond_write(const struct device *dev, |
|
const uint16_t x, const uint16_t y, |
|
const struct display_buffer_descriptor *desc, |
|
const void *buf) |
|
{ |
|
struct display_smartbond_data *data = dev->data; |
|
const struct display_smartbond_config *config = dev->config; |
|
uint8_t *dst = data->buffer; |
|
const uint8_t *src = buf; |
|
|
|
k_sem_take(&data->device_sem, K_FOREVER); |
|
|
|
/* pointer to upper left pixel of the rectangle */ |
|
dst += (x * config->pixel_size); |
|
dst += (y * data->layer.stride); |
|
|
|
/* |
|
* Wait for the current frame to finish. Do not disable continuous mode as this |
|
* will have visual artifacts. |
|
*/ |
|
LCDC->LCDC_INTERRUPT_REG |= LCDC_LCDC_INTERRUPT_REG_LCDC_VSYNC_IRQ_EN_Msk; |
|
k_sem_take(&data->sync_sem, K_FOREVER); |
|
|
|
data->dma_block_cfg.block_size = desc->width * config->pixel_size; |
|
/* |
|
* Source and destination base address is word aligned. |
|
* Data size should be selected based on color depth as |
|
* cursor is shifted multiple of pixel color depth. |
|
*/ |
|
data->dma_cfg.source_data_size = data->dma_cfg.dest_data_size = |
|
!(config->pixel_size & 3) ? 4 : |
|
!(config->pixel_size & 1) ? 2 : 1; |
|
|
|
data->dma_cfg.dest_burst_length = data->dma_cfg.source_burst_length = |
|
!((data->dma_block_cfg.block_size / data->dma_cfg.source_data_size) & 7) ? 8 : |
|
!((data->dma_block_cfg.block_size / data->dma_cfg.source_data_size) & 3) ? 4 : 1; |
|
|
|
for (int row = 0; row < desc->height; row++) { |
|
|
|
data->dma_block_cfg.dest_address = (uint32_t)dst; |
|
data->dma_block_cfg.source_address = (uint32_t)src; |
|
|
|
if (dma_config(data->dma, data->dma_channel, &data->dma_cfg)) { |
|
LOG_ERR("Could not configure DMA"); |
|
k_sem_give(&data->device_sem); |
|
return -EIO; |
|
} |
|
|
|
if (dma_start(data->dma, data->dma_channel)) { |
|
LOG_ERR("Could not start DMA"); |
|
k_sem_give(&data->device_sem); |
|
return -EIO; |
|
} |
|
|
|
k_sem_take(&data->dma_sync_sem, K_FOREVER); |
|
|
|
dst += data->layer.stride; |
|
src += (desc->pitch * config->pixel_size); |
|
} |
|
|
|
if (dma_stop(data->dma, data->dma_channel)) { |
|
LOG_WRN("Could not stop DMA"); |
|
} |
|
|
|
k_sem_give(&data->device_sem); |
|
|
|
return 0; |
|
} |
|
|
|
#if defined(CONFIG_PM_DEVICE) |
|
static int display_smartbond_pm_action(const struct device *dev, enum pm_device_action action) |
|
{ |
|
int ret = 0; |
|
|
|
switch (action) { |
|
case PM_DEVICE_ACTION_SUSPEND: |
|
/* A non-zero value should not affect sleep */ |
|
(void)display_smartbond_suspend(dev); |
|
break; |
|
case PM_DEVICE_ACTION_RESUME: |
|
/* |
|
* The resume error code should not be taken into consideration |
|
* by the PM subsystem |
|
*/ |
|
ret = display_smartbond_resume(dev); |
|
break; |
|
default: |
|
ret = -ENOTSUP; |
|
} |
|
|
|
return ret; |
|
} |
|
#endif |
|
|
|
static DEVICE_API(display, display_smartbond_driver_api) = { |
|
.write = display_smartbond_write, |
|
.read = display_smartbond_read, |
|
.get_framebuffer = display_smartbond_get_framebuffer, |
|
.get_capabilities = display_smartbond_get_capabilities, |
|
.blanking_off = display_smartbond_blanking_off, |
|
.blanking_on = display_smartbond_blanking_on |
|
}; |
|
|
|
#define SMARTBOND_DISPLAY_INIT(inst) \ |
|
PINCTRL_DT_INST_DEFINE(inst); \ |
|
PM_DEVICE_DT_INST_DEFINE(inst, display_smartbond_pm_action); \ |
|
\ |
|
__aligned(4) static uint8_t buffer_ ## inst[(((DT_INST_PROP(inst, width) * \ |
|
DISPLAY_SMARTBOND_PIXEL_SIZE(inst)) + 0x3) & ~0x3) * \ |
|
DT_INST_PROP(inst, height)] DISPLAY_BUFFER_LINKER_SECTION; \ |
|
\ |
|
static const struct display_smartbond_config display_smartbond_config_## inst = { \ |
|
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \ |
|
.disp = GPIO_DT_SPEC_INST_GET_OR(inst, disp_gpios, {}), \ |
|
.timing_cfg.vsync_len = \ |
|
DT_PROP(DT_INST_CHILD(inst, display_timings), vsync_len), \ |
|
.timing_cfg.hsync_len = \ |
|
DT_PROP(DT_INST_CHILD(inst, display_timings), hsync_len), \ |
|
.timing_cfg.hfront_porch = \ |
|
DT_PROP(DT_INST_CHILD(inst, display_timings), hfront_porch), \ |
|
.timing_cfg.vfront_porch = \ |
|
DT_PROP(DT_INST_CHILD(inst, display_timings), vfront_porch), \ |
|
.timing_cfg.hback_porch = \ |
|
DT_PROP(DT_INST_CHILD(inst, display_timings), hback_porch), \ |
|
.timing_cfg.vback_porch = \ |
|
DT_PROP(DT_INST_CHILD(inst, display_timings), vback_porch), \ |
|
.bgcolor_cfg = {0xFF, 0xFF, 0xFF, 0}, \ |
|
.x_res = DT_INST_PROP(inst, width), \ |
|
.y_res = DT_INST_PROP(inst, height), \ |
|
.pixel_size = DISPLAY_SMARTBOND_PIXEL_SIZE(inst), \ |
|
.pixel_format = DT_INST_PROP(0, pixel_format), \ |
|
.mode.vsync_pol = \ |
|
DT_PROP(DT_INST_CHILD(inst, display_timings), vsync_active) ? 0 : 1, \ |
|
.mode.hsync_pol = \ |
|
DT_PROP(DT_INST_CHILD(inst, display_timings), vsync_active) ? 0 : 1, \ |
|
.mode.de_pol = \ |
|
DT_PROP(DT_INST_CHILD(inst, display_timings), de_active) ? 0 : 1, \ |
|
.mode.pixelclk_pol = \ |
|
DT_PROP(DT_INST_CHILD(inst, display_timings), pixelclk_active) ? 0 : 1, \ |
|
}; \ |
|
\ |
|
static struct display_smartbond_data display_smartbond_data_## inst = { \ |
|
.buffer = buffer_ ##inst, \ |
|
.layer.start_x = 0, \ |
|
.layer.start_y = 0, \ |
|
.layer.size_x = DT_INST_PROP(inst, width), \ |
|
.layer.size_y = DT_INST_PROP(inst, height), \ |
|
.layer.frame_buf = (uint32_t)buffer_ ## inst, \ |
|
}; \ |
|
\ |
|
\ |
|
DEVICE_DT_INST_DEFINE(inst, display_smartbond_init, PM_DEVICE_DT_INST_GET(inst), \ |
|
&display_smartbond_data_## inst, \ |
|
&display_smartbond_config_## inst, \ |
|
POST_KERNEL, \ |
|
CONFIG_DISPLAY_INIT_PRIORITY, \ |
|
&display_smartbond_driver_api); |
|
|
|
SMARTBOND_DISPLAY_INIT(0);
|
|
|