Files
kernel-zhihe-a210/drivers/gpu/drm/verisilicon/auxdisp_crtc.c
2026-01-30 17:09:03 +08:00

1286 lines
42 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2025 Zhihe Computing Limited.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/component.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/pm_runtime.h>
#include <drm/drm_device.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_plane_helper.h>
#include <drm/drm_gem.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include <drm/drm_vblank.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_plane.h>
#include <drm/drm_print.h>
#include <drm/drm_file.h>
#include <drm/drm_ioctl.h>
#include <drm/drm_framebuffer.h>
#include "vs_gem.h"
#include "vs_fb.h"
#include "vs_drv.h"
/* AuxDisp 寄存器偏移量 (A210基地址: 0x06740000) */
#define AUXDISP_EN 0x00 /* 显示输出使能 */
#define AUXDISP_DTG0 0x04 /* htotal, vtotal */
#define AUXDISP_DTG1 0x08 /* hactive, vactive */
#define AUXDISP_DTG2 0x0c /* hbp, hsw */
#define AUXDISP_DTG3 0x10 /* hfp */
#define AUXDISP_DTG4 0x14 /* vbp, vsw */
#define AUXDISP_DTG5 0x18 /* vfp */
#define AUXDISP_MODE 0x1c /* 显示模式 */
#define AUXDISP_NEXT_CFG 0x20 /* next_cfg vdelay */
#define AUXDISP_POLARITY 0x24 /* 极性控制 */
#define AUXDISP_PATTERN 0x2c /* 测试图案 */
#define AUXDISP_CFG_DONE 0x3c /* 配置完成标志 */
#define AUXDISP_IN_FMT 0x40 /* 视频格式 */
#define AUXDISP_RES 0x44 /* 视频分辨率 */
#define AUXDISP_ADDR0 0x48 /* Y/RGB 基地址 */
#define AUXDISP_ADDR1 0x4c /* UV 基地址 */
#define AUXDISP_STRIDE0 0x58 /* Y/RGB stride */
#define AUXDISP_STRIDE1 0x5c /* UV stride */
#define AUXDISP_BEAT 0x60 /* AXI beats */
#define AUXDISP_OFFSET 0x64 /* 水平裁剪偏移 */
#define AUXDISP_ABNORMAL_CTRL 0x70 /* AXI 复位控制 */
#define AUXDISP_IDLE 0x74 /* AXI 空闲状态 */
#define AUXDISP_AXI_CFG 0x80 /* AXI 参数 */
#define AUXDISP_INT_ENABLE 0x100 /* 中断使能 */
#define AUXDISP_INT_CLEAR 0x104 /* 中断清除 */
#define AUXDISP_INT_RAW 0x108 /* 原始中断状态 */
#define AUXDISP_INT_STATUS 0x10c /* 中断状态 */
/* 中断位定义 (INT_STATUS 寄存器位定义) */
#define AUXDISP_INT_VSYNC BIT(0) /* Bit 0: disp vsync (VBlank) */
#define AUXDISP_INT_NEXT_CFG BIT(1) /* Bit 1: next cfg */
#define AUXDISP_INT_UNDERFLOW BIT(2) /* Bit 2: underflow */
#define AUXDISP_INT_AXI_ERR BIT(3) /* Bit 3: axi read resp error */
#define AUXDISP_INT_FRAME_DONE BIT(5) /* Bit 5: axi read one frame done */
/* 已知/已定义的中断掩码 */
#define AUXDISP_INT_ALL_KNOWN (AUXDISP_INT_VSYNC | AUXDISP_INT_NEXT_CFG | \
AUXDISP_INT_UNDERFLOW | AUXDISP_INT_AXI_ERR | \
AUXDISP_INT_FRAME_DONE)
/* 为了兼容性,保留旧的 VBLANK 定义 */
#define AUXDISP_INT_VBLANK AUXDISP_INT_VSYNC
/* AUXDISP_EN 寄存器位定义 */
#define AUXDISP_EN_ENABLE BIT(0) /* 显示使能 */
/* AUXDISP_MODE 寄存器位定义 */
#define AUXDISP_MODE_IF_HDMI 0x0 /* 接口类型: HDMI */
#define AUXDISP_MODE_IF_DSI 0x1 /* 接口类型: MIPI DSI */
#define AUXDISP_MODE_IF_DP 0x2 /* 接口类型: DP */
#define AUXDISP_MODE_IF_MASK 0x3
#define AUXDISP_MODE_FMT_RGB10 (0x0 << 4) /* 输出格式: RGB10 */
#define AUXDISP_MODE_FMT_RGB8 (0x1 << 4) /* 输出格式: RGB8 */
#define AUXDISP_MODE_FMT_RGB6 (0x2 << 4) /* 输出格式: RGB6 */
#define AUXDISP_MODE_FMT_RGB565 (0x3 << 4) /* 输出格式: RGB565 */
#define AUXDISP_MODE_FMT_YUV420_10 (0x4 << 4) /* 输出格式: YUV420 10bit */
#define AUXDISP_MODE_FMT_YUV420_8 (0x5 << 4) /* 输出格式: YUV420 8bit */
#define AUXDISP_MODE_FMT_MASK (0xF << 4)
/* AUXDISP_POLARITY 位定义 */
#define AUXDISP_POL_VSYNC_HIGH BIT(0)
#define AUXDISP_POL_HSYNC_HIGH BIT(1)
#define AUXDISP_POL_DE_HIGH BIT(2)
/* AUXDISP_PATTERN 位定义 */
#define AUXDISP_PATTERN_EN BIT(0) /* 图案使能 */
#define AUXDISP_PATTERN_SEL_SHIFT 4
#define AUXDISP_PATTERN_SEL_MASK (0xF << 4)
#define AUXDISP_PATTERN_COLOR_SHIFT 8
/* 图案模式选择 */
#define AUXDISP_PATTERN_FIXED 0 /* 固定颜色 */
#define AUXDISP_PATTERN_GRAY_H 1 /* 灰度条-水平 */
#define AUXDISP_PATTERN_GRAY_V 2 /* 灰度条-垂直 */
#define AUXDISP_PATTERN_COLOR_H 3 /* 彩条-水平 */
#define AUXDISP_PATTERN_COLOR_V 4 /* 彩条-垂直 */
/* AUXDISP_IN_FMT 位定义 */
#define AUXDISP_IN_FMT_FMT_SHIFT 0
#define AUXDISP_IN_FMT_FMT_MASK 0xF
#define AUXDISP_IN_FMT_FMT_YUV420 0x0
#define AUXDISP_IN_FMT_FMT_RGB 0x3
#define AUXDISP_IN_FMT_BPP_SHIFT 4
#define AUXDISP_IN_FMT_BPP_MASK (0xF << 4)
#define AUXDISP_IN_FMT_BPP_8BIT (0x0 << 4)
#define AUXDISP_IN_FMT_BPP_10BIT (0x1 << 4)
#define AUXDISP_IN_FMT_BPP_565 (0x2 << 4)
#define AUXDISP_IN_FMT_REMAP_SHIFT 8
#define AUXDISP_IN_FMT_REMAP_MASK (0xF << 8)
#define AUXDISP_IN_FMT_REMAP_B_LSB (0x0 << 8) /* B in LSB (默认) */
#define AUXDISP_IN_FMT_REMAP_R_LSB (0x1 << 8) /* R in LSB */
/* AUXDISP_ABNORMAL_CTRL 位定义 */
#define AUXDISP_ABNORMAL_STOP BIT(0) /* 停止 AXI 读取 */
#define AUXDISP_ABNORMAL_RESET BIT(1) /* 复位 AXI */
static void auxdisp_set_display_path(struct drm_crtc *crtc, struct drm_atomic_state *state);
/* 时序参数缓存结构 */
struct auxdisp_timing {
/* 时序参数 */
u32 htotal, vtotal;
u32 hactive, vactive;
u32 hsw, hbp, hfp;
u32 vsw, vbp, vfp;
u32 polarity;
unsigned long pixel_clock_hz;
bool valid; /* 时序参数是否有效 */
/* Framebuffer 配置参数(在 mode_set_nofb 中从 crtc->primary 获取) */
bool fb_configured; /* FB 是否已配置 */
u32 fb_addr; /* Y/RGB 基地址 */
u32 fb_addr_uv; /* UV 基地址YUV420 */
u32 fb_width; /* FB 宽度 */
u32 fb_height; /* FB 高度 */
u32 fb_stride; /* Y/RGB stride */
u32 fb_stride_uv; /* UV stride */
u32 fb_in_fmt; /* 输入格式 */
u32 fb_out_fmt; /* 输出格式AUXDISP_MODE 寄存器的格式字段) */
u32 fb_axibeat; /* AXI beats */
bool fb_has_uv; /* 是否有 UV 平面 */
};
/* 设备私有数据结构 */
struct auxdisp_crtc {
struct drm_crtc crtc; /* 嵌入 CRTC 结构体 */
struct drm_plane primary_plane; /* 嵌入 Plane 结构体 */
struct drm_device *drm_dev;
void __iomem *regs; /* 硬件寄存器基地址 */
struct device *dev; /* 平台设备 */
int irq; /* 中断号 */
bool bound; /* 组件绑定状态 */
bool display_enabled; /* 显示输出是否已启用 */
/* 时钟资源 */
struct clk *aclk; /* AXI 总线时钟 */
struct clk *pclk; /* APB 配置时钟 */
struct clk *pixclk; /* 像素时钟 */
struct clk *current_pixclk; /* 当前使用的像素时钟 */
struct clk *hdmi_pixclk; /* HDMI 输出像素时钟 */
struct clk *mipi_pixclk; /* MIPI 输出像素时钟 */
struct clk *dptx_pixclk; /* DPTX 输出像素时钟 */
/* 当前显示路径配置 */
u32 current_interface; /* 当前接口类型: HDMI/DSI/DP */
/* 缓存的时序参数(在 pclk 使能后才写入硬件) */
struct auxdisp_timing pending_timing;
/* 互斥锁保护共享数据pending_timing, display_enabled, current_interface */
struct mutex config_lock;
};
static inline struct auxdisp_crtc *to_auxdisp_crtc(struct drm_crtc *crtc)
{
return container_of(crtc, struct auxdisp_crtc, crtc);
}
static inline struct auxdisp_crtc *to_auxdisp_plane(struct drm_plane *plane)
{
return container_of(plane, struct auxdisp_crtc, primary_plane);
}
static const uint32_t auxdisp_primary_plane_formats[] = {
/* RGB 8bit 格式 */
DRM_FORMAT_XRGB8888,
DRM_FORMAT_ARGB8888,
DRM_FORMAT_XBGR8888,
DRM_FORMAT_ABGR8888,
/* RGB 565 格式 */
DRM_FORMAT_RGB565,
DRM_FORMAT_BGR565,
/* RGB 10bit 格式 */
DRM_FORMAT_XRGB2101010,
DRM_FORMAT_ARGB2101010,
DRM_FORMAT_XBGR2101010,
DRM_FORMAT_ABGR2101010,
/* YUV420 8bit 格式 */
DRM_FORMAT_NV12,
/* YUV420 10bit 格式 */
DRM_FORMAT_P010,
};
static inline void auxdisp_write(struct auxdisp_crtc *auxdisp, u32 reg, u32 value)
{
writel(value, auxdisp->regs + reg);
}
static inline u32 auxdisp_read(struct auxdisp_crtc *auxdisp, u32 reg)
{
return readl(auxdisp->regs + reg);
}
static irqreturn_t auxdisp_irq_handler(int irq, void *data)
{
struct auxdisp_crtc *auxdisp = data;
u32 int_status;
u32 handled_irqs = 0;
/* 读取中断状态 */
int_status = auxdisp_read(auxdisp, AUXDISP_INT_STATUS);
if (!int_status)
return IRQ_NONE;
if (int_status & AUXDISP_INT_VSYNC) {
drm_crtc_handle_vblank(&auxdisp->crtc);
handled_irqs |= AUXDISP_INT_VSYNC;
}
if (int_status & AUXDISP_INT_NEXT_CFG) {
handled_irqs |= AUXDISP_INT_NEXT_CFG;
}
if (int_status & AUXDISP_INT_UNDERFLOW) {
u32 int_raw = auxdisp_read(auxdisp, AUXDISP_INT_RAW);
dev_err(auxdisp->dev, "[IRQ] *** UNDERFLOW interrupt detected! ***\n");
dev_err(auxdisp->dev, "This indicates AXI bandwidth is insufficient!\n");
dev_err(auxdisp->dev, "INT_RAW=0x%08x, INT_STATUS=0x%08x\n", int_raw, int_status);
handled_irqs |= AUXDISP_INT_UNDERFLOW;
}
if (int_status & AUXDISP_INT_AXI_ERR) {
u32 int_raw = auxdisp_read(auxdisp, AUXDISP_INT_RAW);
dev_err(auxdisp->dev, "[IRQ] *** AXI read response ERROR interrupt! ***\n");
dev_err(auxdisp->dev, "AXI bus error detected - check memory address and bus status\n");
dev_err(auxdisp->dev, "INT_RAW=0x%08x, INT_STATUS=0x%08x\n", int_raw, int_status);
handled_irqs |= AUXDISP_INT_AXI_ERR;
}
if (int_status & AUXDISP_INT_FRAME_DONE) {
handled_irqs |= AUXDISP_INT_FRAME_DONE;
}
auxdisp_write(auxdisp, AUXDISP_INT_CLEAR, int_status);
return IRQ_HANDLED;
}
static int auxdisp_get_fb_format_config(u32 format, u32 width, u32 *in_fmt, u32 *out_fmt, u32 *axibeat)
{
switch (format) {
case DRM_FORMAT_XBGR8888:
case DRM_FORMAT_ABGR8888:
*in_fmt = AUXDISP_IN_FMT_FMT_RGB | AUXDISP_IN_FMT_BPP_8BIT | AUXDISP_IN_FMT_REMAP_R_LSB;
*out_fmt = AUXDISP_MODE_FMT_RGB8;
*axibeat = (width + 3) / 4;
break;
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_ARGB8888:
*in_fmt = AUXDISP_IN_FMT_FMT_RGB | AUXDISP_IN_FMT_BPP_8BIT | AUXDISP_IN_FMT_REMAP_B_LSB;
*out_fmt = AUXDISP_MODE_FMT_RGB8;
*axibeat = (width + 3) / 4;
break;
case DRM_FORMAT_XBGR2101010:
case DRM_FORMAT_ABGR2101010:
*in_fmt = AUXDISP_IN_FMT_FMT_RGB | AUXDISP_IN_FMT_BPP_10BIT | AUXDISP_IN_FMT_REMAP_R_LSB;
*out_fmt = AUXDISP_MODE_FMT_RGB10;
*axibeat = (width + 3) / 4;
break;
case DRM_FORMAT_XRGB2101010:
case DRM_FORMAT_ARGB2101010:
*in_fmt = AUXDISP_IN_FMT_FMT_RGB | AUXDISP_IN_FMT_BPP_10BIT | AUXDISP_IN_FMT_REMAP_B_LSB;
*out_fmt = AUXDISP_MODE_FMT_RGB10;
*axibeat = (width + 3) / 4;
break;
case DRM_FORMAT_RGB565:
*in_fmt = AUXDISP_IN_FMT_FMT_RGB | AUXDISP_IN_FMT_BPP_565 | AUXDISP_IN_FMT_REMAP_B_LSB;
*out_fmt = AUXDISP_MODE_FMT_RGB565;
*axibeat = (width + 7) / 8;
break;
case DRM_FORMAT_BGR565:
*in_fmt = AUXDISP_IN_FMT_FMT_RGB | AUXDISP_IN_FMT_BPP_565 | AUXDISP_IN_FMT_REMAP_R_LSB;
*out_fmt = AUXDISP_MODE_FMT_RGB565;
*axibeat = (width + 7) / 8;
break;
case DRM_FORMAT_NV12:
*in_fmt = AUXDISP_IN_FMT_FMT_YUV420 | AUXDISP_IN_FMT_BPP_8BIT | AUXDISP_IN_FMT_REMAP_R_LSB;
*out_fmt = AUXDISP_MODE_FMT_YUV420_8;
*axibeat = (width + 15) / 16;
break;
case DRM_FORMAT_P010:
*in_fmt = AUXDISP_IN_FMT_FMT_YUV420 | AUXDISP_IN_FMT_BPP_10BIT | AUXDISP_IN_FMT_REMAP_B_LSB;
*out_fmt = AUXDISP_MODE_FMT_YUV420_10;
*axibeat = (width + 11) / 12;
break;
default:
return -EINVAL;
}
return 0;
}
static void auxdisp_crtc_mode_set_nofb(struct drm_crtc *crtc)
{
struct auxdisp_crtc *auxdisp = to_auxdisp_crtc(crtc);
struct drm_display_mode *mode = &crtc->state->adjusted_mode;
struct drm_encoder *encoder;
struct drm_plane *primary;
struct drm_framebuffer *fb;
struct vs_gem_object *vs_obj;
struct vs_gem_object *vs_obj_uv;
dma_addr_t dma_addr;
dma_addr_t dma_addr_uv;
u32 htotal, vtotal, hactive, vactive;
u32 hsw, hbp, hfp, vsw, vbp, vfp;
u32 polarity = 0;
u32 width, height, stride;
u32 in_fmt, axibeat, out_fmt;
u32 stride_uv;
unsigned long pixel_clock_hz = mode->clock * 1000;
auxdisp->current_interface = AUXDISP_MODE_IF_HDMI;
drm_for_each_encoder_mask(encoder, auxdisp->drm_dev,
auxdisp->crtc.state->encoder_mask) {
switch (encoder->encoder_type) {
case DRM_MODE_ENCODER_TMDS:
auxdisp->current_interface = AUXDISP_MODE_IF_HDMI;
break;
case DRM_MODE_ENCODER_DSI:
auxdisp->current_interface = AUXDISP_MODE_IF_DSI;
break;
case DRM_MODE_ENCODER_DPMST:
auxdisp->current_interface = AUXDISP_MODE_IF_DP;
break;
default:
break;
}
break;
}
htotal = mode->htotal;
vtotal = mode->vtotal;
hactive = mode->hdisplay;
vactive = mode->vdisplay;
hsw = mode->hsync_end - mode->hsync_start;
hbp = mode->htotal - mode->hsync_end;
hfp = mode->hsync_start - mode->hdisplay;
vsw = mode->vsync_end - mode->vsync_start;
vbp = mode->vtotal - mode->vsync_end;
vfp = mode->vsync_start - mode->vdisplay;
if (mode->flags & DRM_MODE_FLAG_PVSYNC)
polarity |= AUXDISP_POL_VSYNC_HIGH;
if (mode->flags & DRM_MODE_FLAG_PHSYNC)
polarity |= AUXDISP_POL_HSYNC_HIGH;
polarity |= AUXDISP_POL_DE_HIGH;
auxdisp->pending_timing.htotal = htotal;
auxdisp->pending_timing.vtotal = vtotal;
auxdisp->pending_timing.hactive = hactive;
auxdisp->pending_timing.vactive = vactive;
auxdisp->pending_timing.hsw = hsw;
auxdisp->pending_timing.hbp = hbp;
auxdisp->pending_timing.hfp = hfp;
auxdisp->pending_timing.vsw = vsw;
auxdisp->pending_timing.vbp = vbp;
auxdisp->pending_timing.vfp = vfp;
auxdisp->pending_timing.polarity = polarity;
auxdisp->pending_timing.pixel_clock_hz = pixel_clock_hz;
auxdisp->pending_timing.valid = true;
primary = crtc->primary;
if (primary && primary->state && primary->state->fb) {
fb = primary->state->fb;
vs_obj = vs_fb_get_gem_obj(fb, 0);
if (!vs_obj) {
auxdisp->pending_timing.fb_configured = false;
} else {
dma_addr = vs_obj->iova + fb->offsets[0];
width = fb->width;
height = fb->height;
stride = fb->pitches[0];
if (auxdisp_get_fb_format_config(fb->format->format, width,
&in_fmt, &out_fmt, &axibeat) != 0) {
auxdisp->pending_timing.fb_configured = false;
return;
}
auxdisp->pending_timing.fb_configured = true;
auxdisp->pending_timing.fb_addr = (u32)dma_addr;
auxdisp->pending_timing.fb_width = width;
auxdisp->pending_timing.fb_height = height;
auxdisp->pending_timing.fb_stride = stride;
auxdisp->pending_timing.fb_in_fmt = in_fmt;
auxdisp->pending_timing.fb_out_fmt = out_fmt;
auxdisp->pending_timing.fb_axibeat = axibeat;
/* YUV420 格式需要特殊处理像素时钟*/
if (out_fmt == AUXDISP_MODE_FMT_YUV420_8 || out_fmt == AUXDISP_MODE_FMT_YUV420_10) {
auxdisp->pending_timing.pixel_clock_hz = pixel_clock_hz / 2;
}
if (fb->format->num_planes > 1) {
vs_obj_uv = vs_fb_get_gem_obj(fb, 1);
if (!vs_obj_uv) {
auxdisp->pending_timing.fb_has_uv = false;
} else {
dma_addr_uv = vs_obj_uv->iova + fb->offsets[1];
stride_uv = fb->pitches[1];
auxdisp->pending_timing.fb_addr_uv = (u32)dma_addr_uv;
auxdisp->pending_timing.fb_stride_uv = stride_uv;
auxdisp->pending_timing.fb_has_uv = true;
}
} else {
auxdisp->pending_timing.fb_has_uv = false;
}
}
} else {
auxdisp->pending_timing.fb_configured = false;
}
}
static int auxdisp_wait_cfg_done(struct auxdisp_crtc *auxdisp)
{
int timeout = 1000;
u32 cfg_status;
while (timeout-- > 0) {
cfg_status = auxdisp_read(auxdisp, AUXDISP_CFG_DONE);
if (cfg_status == 0) {
return 0;
}
usleep_range(100, 200);
}
return -ETIMEDOUT;
}
static void auxdisp_configure_timing(struct auxdisp_crtc *auxdisp)
{
u32 display_mode;
if (!auxdisp->pending_timing.valid)
return;
auxdisp_write(auxdisp, AUXDISP_DTG0,
(auxdisp->pending_timing.vtotal << 16) | auxdisp->pending_timing.htotal);
auxdisp_write(auxdisp, AUXDISP_DTG1,
(auxdisp->pending_timing.vactive << 16) | auxdisp->pending_timing.hactive);
auxdisp_write(auxdisp, AUXDISP_DTG2,
(auxdisp->pending_timing.hbp << 16) | auxdisp->pending_timing.hsw);
auxdisp_write(auxdisp, AUXDISP_DTG3, auxdisp->pending_timing.hfp);
auxdisp_write(auxdisp, AUXDISP_DTG4,
(auxdisp->pending_timing.vbp << 16) | auxdisp->pending_timing.vsw);
auxdisp_write(auxdisp, AUXDISP_DTG5, auxdisp->pending_timing.vfp);
auxdisp_write(auxdisp, AUXDISP_POLARITY, auxdisp->pending_timing.polarity);
display_mode = auxdisp->current_interface;
if (auxdisp->pending_timing.fb_configured)
display_mode |= auxdisp->pending_timing.fb_out_fmt;
else
display_mode |= AUXDISP_MODE_FMT_RGB8;
auxdisp_write(auxdisp, AUXDISP_MODE, display_mode);
auxdisp_write(auxdisp, AUXDISP_NEXT_CFG, 0x10);
}
static int auxdisp_configure_clocks(struct auxdisp_crtc *auxdisp, unsigned long pixel_clock_hz)
{
int ret;
ret = clk_set_rate(auxdisp->pixclk, pixel_clock_hz);
if (ret) {
dev_err(auxdisp->dev, "Failed to set pixel clock rate: %d\n", ret);
return ret;
}
ret = clk_prepare_enable(auxdisp->pixclk);
if (ret) {
dev_err(auxdisp->dev, "Failed to enable pixclk: %d\n", ret);
return ret;
}
return 0;
}
static void auxdisp_configure_framebuffer(struct auxdisp_crtc *auxdisp)
{
if (!auxdisp->pending_timing.fb_configured) {
return;
}
auxdisp_write(auxdisp, AUXDISP_IN_FMT, auxdisp->pending_timing.fb_in_fmt);
auxdisp_write(auxdisp, AUXDISP_RES,
(auxdisp->pending_timing.fb_height << 16) | auxdisp->pending_timing.fb_width);
auxdisp_write(auxdisp, AUXDISP_ADDR0, auxdisp->pending_timing.fb_addr);
auxdisp_write(auxdisp, AUXDISP_STRIDE0, auxdisp->pending_timing.fb_stride);
auxdisp_write(auxdisp, AUXDISP_BEAT, auxdisp->pending_timing.fb_axibeat);
auxdisp_write(auxdisp, AUXDISP_OFFSET, 0);
if (auxdisp->pending_timing.fb_has_uv) {
auxdisp_write(auxdisp, AUXDISP_ADDR1, auxdisp->pending_timing.fb_addr_uv);
auxdisp_write(auxdisp, AUXDISP_STRIDE1, auxdisp->pending_timing.fb_stride_uv);
}
}
static void auxdisp_configure_axi(struct auxdisp_crtc *auxdisp)
{
u32 axi_cfg;
u32 pixel_count = auxdisp->pending_timing.hactive * auxdisp->pending_timing.vactive;
if (pixel_count >= (3840 * 2160)) {
axi_cfg = 0x10FFF00;
} else if (pixel_count >= (1920 * 1080)) {
axi_cfg = 0x10CC000;
} else {
axi_cfg = 0x808000;
}
auxdisp_write(auxdisp, AUXDISP_AXI_CFG, axi_cfg);
}
static void auxdisp_configure_interrupts(struct auxdisp_crtc *auxdisp)
{
u32 int_enable_mask = AUXDISP_INT_ALL_KNOWN;
auxdisp_write(auxdisp, AUXDISP_INT_CLEAR, 0xFFFFFFFF);
auxdisp_write(auxdisp, AUXDISP_INT_ENABLE, int_enable_mask);
}
static void auxdisp_crtc_enable(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
struct auxdisp_crtc *auxdisp = to_auxdisp_crtc(crtc);
int ret;
if (!auxdisp) {
dev_err(auxdisp->dev, "ERROR: auxdisp pointer is NULL!\n");
return;
}
if (!auxdisp->aclk) {
dev_err(auxdisp->dev, "ERROR: auxdisp->aclk is NULL!\n");
return;
}
ret = pm_runtime_get_sync(auxdisp->dev);
if (ret < 0) {
dev_err(auxdisp->dev, "Failed to resume device via runtime PM: %d\n", ret);
pm_runtime_put_noidle(auxdisp->dev);
return;
}
auxdisp_configure_timing(auxdisp);
ret = auxdisp_configure_clocks(auxdisp, auxdisp->pending_timing.pixel_clock_hz);
if (ret) {
pm_runtime_put(auxdisp->dev);
return;
}
auxdisp_configure_framebuffer(auxdisp);
auxdisp_configure_axi(auxdisp);
auxdisp_configure_interrupts(auxdisp);
drm_crtc_vblank_on(crtc);
auxdisp_write(auxdisp, AUXDISP_CFG_DONE, 1);
auxdisp_write(auxdisp, AUXDISP_EN, AUXDISP_EN_ENABLE);
auxdisp->display_enabled = true;
auxdisp_wait_cfg_done(auxdisp);
}
static void auxdisp_crtc_disable(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
struct auxdisp_crtc *auxdisp = to_auxdisp_crtc(crtc);
auxdisp_write(auxdisp, AUXDISP_INT_ENABLE, 0);
auxdisp_write(auxdisp, AUXDISP_EN, 0);
auxdisp->display_enabled = false;
auxdisp_write(auxdisp, AUXDISP_INT_CLEAR, 0xFFFFFFFF);
auxdisp->pending_timing.valid = false;
auxdisp->pending_timing.fb_configured = false;
drm_crtc_vblank_off(crtc);
spin_lock_irq(&crtc->dev->event_lock);
if (crtc->state->event && !crtc->state->active) {
drm_crtc_send_vblank_event(crtc, crtc->state->event);
crtc->state->event = NULL;
}
spin_unlock_irq(&crtc->dev->event_lock);
}
static void auxdisp_set_display_path(struct drm_crtc *crtc, struct drm_atomic_state *state)
{
struct auxdisp_crtc *auxdisp = to_auxdisp_crtc(crtc);
struct clk *output_clk = NULL;
const char *if_name = NULL;
int ret;
switch (auxdisp->current_interface) {
case AUXDISP_MODE_IF_HDMI:
output_clk = auxdisp->hdmi_pixclk;
if_name = "HDMI";
break;
case AUXDISP_MODE_IF_DSI:
output_clk = auxdisp->mipi_pixclk;
if_name = "MIPI DSI";
break;
case AUXDISP_MODE_IF_DP:
output_clk = auxdisp->dptx_pixclk;
if_name = "DP";
break;
default:
return;
}
auxdisp->current_pixclk = output_clk;
if (auxdisp->current_pixclk == NULL) {
return;
}
ret = clk_set_parent(auxdisp->current_pixclk, auxdisp->pixclk);
if (ret) {
dev_err(auxdisp->dev, "Failed to set %s clock parent: %d\n", if_name, ret);;
}
}
static int auxdisp_crtc_enable_vblank(struct drm_crtc *crtc)
{
struct auxdisp_crtc *auxdisp = to_auxdisp_crtc(crtc);
u32 int_enable_mask = AUXDISP_INT_ALL_KNOWN;
auxdisp_write(auxdisp, AUXDISP_INT_ENABLE, int_enable_mask);
return 0;
}
static void auxdisp_crtc_disable_vblank(struct drm_crtc *crtc)
{
struct auxdisp_crtc *auxdisp = to_auxdisp_crtc(crtc);
auxdisp_write(auxdisp, AUXDISP_INT_ENABLE, 0);
}
static void auxdisp_crtc_atomic_enable(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
struct drm_device *drm_dev = crtc->dev;
struct vs_drm_private *priv = drm_dev->dev_private;
auxdisp_crtc_enable(crtc, state);
auxdisp_set_display_path(crtc, state);
if (priv->dc_clk_held_for_auxdisp && priv->dc_dev) {
priv->dc_clk_held_for_auxdisp = false;
pm_runtime_put(priv->dc_dev);
}
}
static void auxdisp_crtc_atomic_disable(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
struct auxdisp_crtc *auxdisp = to_auxdisp_crtc(crtc);
struct drm_device *drm_dev = crtc->dev;
struct vs_drm_private *priv = drm_dev->dev_private;
struct drm_crtc *other_crtc;
struct drm_crtc_state *old_crtc_state, *new_crtc_state;
bool dc8200_enabling = false;
int i;
for_each_oldnew_crtc_in_state(state, other_crtc, old_crtc_state, new_crtc_state, i) {
if (other_crtc == crtc)
continue;
if (other_crtc->port && other_crtc->port->parent &&
of_device_is_compatible(other_crtc->port->parent, VS_DC8200_COMPATIBLE)) {
if (!old_crtc_state->active && new_crtc_state->active) {
dc8200_enabling = true;
break;
}
}
}
auxdisp_crtc_disable(crtc, state);
if (dc8200_enabling) {
priv->auxdisp_clk_held_for_dc = true;
}else{
pm_runtime_put(auxdisp->dev);
}
}
static void auxdisp_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
struct auxdisp_crtc *auxdisp = to_auxdisp_crtc(crtc);
struct drm_pending_vblank_event *event = crtc->state->event;
struct drm_plane *plane = crtc->primary;
bool has_fb = false;
bool display_enabled;
if (plane && plane->state && plane->state->fb) {
has_fb = true;
}
display_enabled = auxdisp->display_enabled;
if (has_fb) {
/* Re-enable display if it was previously disabled */
auxdisp_write(auxdisp, AUXDISP_CFG_DONE, 1);
if (!display_enabled) {
auxdisp_write(auxdisp, AUXDISP_EN, AUXDISP_EN_ENABLE);
auxdisp->display_enabled = true;
}
auxdisp_wait_cfg_done(auxdisp);
if (event) {
spin_lock_irq(&crtc->dev->event_lock);
drm_crtc_send_vblank_event(crtc, event);
spin_unlock_irq(&crtc->dev->event_lock);
crtc->state->event = NULL;
}
} else {
auxdisp_write(auxdisp, AUXDISP_EN, 0);
auxdisp->display_enabled = false;
auxdisp_write(auxdisp, AUXDISP_CFG_DONE, 1);
if (event) {
spin_lock_irq(&crtc->dev->event_lock);
drm_crtc_send_vblank_event(crtc, event);
spin_unlock_irq(&crtc->dev->event_lock);
crtc->state->event = NULL;
}
}
}
static const struct drm_crtc_helper_funcs auxdisp_crtc_helper_funcs = {
.atomic_enable = auxdisp_crtc_atomic_enable,
.atomic_disable = auxdisp_crtc_atomic_disable,
.atomic_flush = auxdisp_crtc_atomic_flush,
.mode_set_nofb = auxdisp_crtc_mode_set_nofb,
};
static const struct drm_crtc_funcs auxdisp_crtc_funcs = {
.reset = drm_atomic_helper_crtc_reset,
.destroy = drm_crtc_cleanup,
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
.enable_vblank = auxdisp_crtc_enable_vblank,
.disable_vblank = auxdisp_crtc_disable_vblank,
};
static int auxdisp_plane_atomic_check(struct drm_plane *plane,
struct drm_atomic_state *state)
{
struct auxdisp_crtc *auxdisp = to_auxdisp_plane(plane);
struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane);
struct drm_crtc_state *new_crtc_state;
int ret;
if (!new_plane_state->crtc)
return 0;
new_crtc_state = drm_atomic_get_new_crtc_state(state, new_plane_state->crtc);
ret = drm_atomic_helper_check_plane_state(new_plane_state, new_crtc_state,
DRM_PLANE_NO_SCALING,
DRM_PLANE_NO_SCALING,
true, true);
if (ret) {
dev_err(auxdisp->dev, "Plane state check failed: %d\n", ret);
goto end;
}
if (new_plane_state->fb && new_crtc_state) {
struct drm_framebuffer *fb = new_plane_state->fb;
struct drm_display_mode *mode = &new_crtc_state->mode;
int refresh_rate = drm_mode_vrefresh(mode);
/* Check if FB is too small for the display mode */
if (fb->width < mode->hdisplay || fb->height < mode->vdisplay) {
dev_err(auxdisp->dev, "FB too small: fb=%ux%u, mode=%ux%u\n",
fb->width, fb->height,
mode->hdisplay, mode->vdisplay);
ret = -EINVAL;
goto end;
}
/* Warn if FB is larger than mode (will use top-left portion) */
if (fb->width > mode->hdisplay || fb->height > mode->vdisplay) {
dev_warn_once(auxdisp->dev,
"FB larger than mode: fb=%ux%u, mode=%ux%u (using top-left crop)\n",
fb->width, fb->height,
mode->hdisplay, mode->vdisplay);
}
/* NV12 format does not support resolutions > 4K@30 */
if (fb->format->format == DRM_FORMAT_NV12 && refresh_rate > 30) {
if (mode->hdisplay >= 3840 || mode->vdisplay >= 2160) {
dev_err(auxdisp->dev, "NV12 format does not support %dHz at %ux%u (max resolution at 30Hz: 3840x2160)\n",
refresh_rate, mode->hdisplay, mode->vdisplay);
ret = -EINVAL;
goto end;
}
}
}
end:
return ret;
}
static void auxdisp_plane_atomic_update(struct drm_plane *plane,
struct drm_atomic_state *state)
{
struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane);
struct auxdisp_crtc *auxdisp = to_auxdisp_plane(plane);
struct drm_framebuffer *fb = new_state->fb;
struct vs_gem_object *vs_obj;
struct vs_gem_object *vs_obj_uv;
dma_addr_t dma_addr;
dma_addr_t dma_addr_uv;
u32 width, height, stride;
u32 in_fmt, axibeat, out_fmt;
u32 stride_uv;
bool initial_config_done;
if (!fb || !new_state->crtc)
return;
mutex_lock(&auxdisp->config_lock);
initial_config_done = auxdisp->pending_timing.fb_configured &&
(auxdisp->pending_timing.fb_width == fb->width) &&
(auxdisp->pending_timing.fb_height == fb->height) &&
(auxdisp->pending_timing.fb_stride == fb->pitches[0]);
if (initial_config_done) {
vs_obj = vs_fb_get_gem_obj(fb, 0);
if (!vs_obj) {
dev_err(auxdisp->dev, "Failed to get GEM object\n");
goto end;
}
dma_addr = vs_obj->iova + fb->offsets[0];
auxdisp_write(auxdisp, AUXDISP_ADDR0, (u32)dma_addr);
if (fb->format->num_planes > 1) {
vs_obj_uv = vs_fb_get_gem_obj(fb, 1);
if (vs_obj_uv) {
dma_addr_uv = vs_obj_uv->iova + fb->offsets[1];
auxdisp_write(auxdisp, AUXDISP_ADDR1, (u32)dma_addr_uv);
}
}
auxdisp->pending_timing.fb_addr = (u32)dma_addr;
goto end;
}
vs_obj = vs_fb_get_gem_obj(fb, 0);
if (!vs_obj) {
dev_err(auxdisp->dev, "Failed to get GEM object\n");
goto end;
}
dma_addr = vs_obj->iova + fb->offsets[0];
width = fb->width;
height = fb->height;
stride = fb->pitches[0];
if (auxdisp_get_fb_format_config(fb->format->format, width,
&in_fmt, &out_fmt, &axibeat) != 0) {
dev_err(auxdisp->dev, "Unsupported pixel format: %c%c%c%c\n",
fb->format->format & 0xff,
(fb->format->format >> 8) & 0xff,
(fb->format->format >> 16) & 0xff,
(fb->format->format >> 24) & 0xff);
goto end;
}
auxdisp_write(auxdisp, AUXDISP_IN_FMT, in_fmt);
auxdisp_write(auxdisp, AUXDISP_RES, (height << 16) | width);
auxdisp_write(auxdisp, AUXDISP_ADDR0, (u32)dma_addr);
auxdisp_write(auxdisp, AUXDISP_STRIDE0, stride);
auxdisp_write(auxdisp, AUXDISP_BEAT, axibeat);
auxdisp_write(auxdisp, AUXDISP_OFFSET, 0);
if (fb->format->num_planes > 1) {
vs_obj_uv = vs_fb_get_gem_obj(fb, 1);
if (!vs_obj_uv) {
dev_err(auxdisp->dev, "Failed to get GEM object for UV plane\n");
goto end;
}
dma_addr_uv = vs_obj_uv->iova + fb->offsets[1];
stride_uv = fb->pitches[1];
auxdisp_write(auxdisp, AUXDISP_ADDR1, (u32)dma_addr_uv);
auxdisp_write(auxdisp, AUXDISP_STRIDE1, stride_uv);
}
end:
mutex_unlock(&auxdisp->config_lock);
}
static const struct drm_plane_helper_funcs auxdisp_plane_helper_funcs = {
.atomic_check = auxdisp_plane_atomic_check,
.atomic_update = auxdisp_plane_atomic_update,
};
static const struct drm_plane_funcs auxdisp_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = drm_plane_cleanup,
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
};
static int auxdisp_crtc_bind(struct device *dev, struct device *master, void *data)
{
struct drm_device *drm_dev = data;
struct vs_drm_private *priv = drm_dev->dev_private;
struct auxdisp_crtc *auxdisp = dev_get_drvdata(dev);
struct device_node *port;
int ret;
priv->auxdisp_dev = dev;
auxdisp->drm_dev = drm_dev;
ret = drm_universal_plane_init(drm_dev, &auxdisp->primary_plane, 0,
&auxdisp_plane_funcs,
auxdisp_primary_plane_formats,
ARRAY_SIZE(auxdisp_primary_plane_formats),
NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
if (ret) {
dev_err(auxdisp->dev, "Failed to create primary plane: %d\n", ret);
goto err_cleanup;
}
drm_plane_helper_add(&auxdisp->primary_plane, &auxdisp_plane_helper_funcs);
ret = drm_crtc_init_with_planes(drm_dev, &auxdisp->crtc, &auxdisp->primary_plane, NULL,
&auxdisp_crtc_funcs, "AuxDisp");
if (ret) {
dev_err(auxdisp->dev, "Failed to create CRTC: %d\n", ret);
goto err_cleanup_plane;
}
port = of_graph_get_port_by_id(dev->of_node, 0);
if (port) {
auxdisp->crtc.port = port;
}
drm_crtc_helper_add(&auxdisp->crtc, &auxdisp_crtc_helper_funcs);
auxdisp->bound = true;
return 0;
err_cleanup_plane:
drm_plane_cleanup(&auxdisp->primary_plane);
err_cleanup:
return ret;
}
static void auxdisp_crtc_unbind(struct device *dev, struct device *master, void *data)
{
struct auxdisp_crtc *auxdisp = dev_get_drvdata(dev);
if (!auxdisp->bound)
return;
mutex_lock(&auxdisp->config_lock);
if (auxdisp->crtc.port) {
of_node_put(auxdisp->crtc.port);
auxdisp->crtc.port = NULL;
}
auxdisp->bound = false;
auxdisp->drm_dev = NULL;
mutex_unlock(&auxdisp->config_lock);
}
static const struct component_ops auxdisp_crtc_component_ops = {
.bind = auxdisp_crtc_bind,
.unbind = auxdisp_crtc_unbind,
};
static int auxdisp_crtc_probe(struct platform_device *pdev)
{
struct auxdisp_crtc *auxdisp;
struct resource *res;
int ret;
auxdisp = devm_kzalloc(&pdev->dev, sizeof(*auxdisp), GFP_KERNEL);
if (!auxdisp)
return -ENOMEM;
auxdisp->dev = &pdev->dev;
mutex_init(&auxdisp->config_lock);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(auxdisp->dev, "Failed to get memory resource\n");
return -ENXIO;
}
auxdisp->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(auxdisp->regs)) {
ret = PTR_ERR(auxdisp->regs);
dev_err(auxdisp->dev, "Failed to map registers: %d\n", ret);
return ret;
}
auxdisp->irq = platform_get_irq(pdev, 0);
if (auxdisp->irq < 0) {
dev_err(&pdev->dev, "Failed to get IRQ: %d\n", auxdisp->irq);
return auxdisp->irq;
}
ret = devm_request_irq(&pdev->dev, auxdisp->irq, auxdisp_irq_handler,
0, dev_name(&pdev->dev), auxdisp);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to request IRQ %d: %d\n", auxdisp->irq, ret);
return ret;
}
auxdisp->aclk = devm_clk_get(&pdev->dev, "aclk");
if (IS_ERR(auxdisp->aclk)) {
ret = PTR_ERR(auxdisp->aclk);
dev_err(&pdev->dev, "Failed to get aclk: %d\n", ret);
return ret;
}
auxdisp->pclk = devm_clk_get(&pdev->dev, "pclk");
if (IS_ERR(auxdisp->pclk)) {
ret = PTR_ERR(auxdisp->pclk);
dev_err(&pdev->dev, "Failed to get pclk: %d\n", ret);
return ret;
}
auxdisp->pixclk = devm_clk_get(&pdev->dev, "pixclk");
if (IS_ERR(auxdisp->pixclk)) {
ret = PTR_ERR(auxdisp->pixclk);
dev_err(&pdev->dev, "Failed to get pixclk: %d\n", ret);
return ret;
}
auxdisp->hdmi_pixclk = devm_clk_get_optional(&pdev->dev, "hdmi_pixclk");
if (IS_ERR(auxdisp->hdmi_pixclk)) {
ret = PTR_ERR(auxdisp->hdmi_pixclk);
dev_err(&pdev->dev, "Failed to get hdmi_pixclk: %d\n", ret);
return ret;
}
auxdisp->mipi_pixclk = devm_clk_get_optional(&pdev->dev, "mipi_pixclk");
if (IS_ERR(auxdisp->mipi_pixclk)) {
ret = PTR_ERR(auxdisp->mipi_pixclk);
dev_err(&pdev->dev, "Failed to get mipi_pixclk: %d\n", ret);
return ret;
}
auxdisp->dptx_pixclk = devm_clk_get_optional(&pdev->dev, "dptx_pixclk");
if (IS_ERR(auxdisp->dptx_pixclk)) {
ret = PTR_ERR(auxdisp->dptx_pixclk);
dev_err(&pdev->dev, "Failed to get dptx_pixclk: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, auxdisp);
pm_runtime_enable(&pdev->dev);
pm_runtime_set_autosuspend_delay(&pdev->dev, 2000);
pm_runtime_use_autosuspend(&pdev->dev);
ret = component_add(&pdev->dev, &auxdisp_crtc_component_ops);
if (ret) {
dev_err(auxdisp->dev, "Failed to add component: %d\n", ret);
goto err_pm_disable;
}
return 0;
err_pm_disable:
pm_runtime_disable(&pdev->dev);
return ret;
}
static int auxdisp_crtc_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &auxdisp_crtc_component_ops);
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev)) {
pm_runtime_set_suspended(&pdev->dev);
}
return 0;
}
static const struct of_device_id auxdisp_crtc_dt_ids[] = {
{ .compatible = ZH_AUXDISP_COMPATIBLE },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, auxdisp_crtc_dt_ids);
static int auxdisp_crtc_suspend(struct device *dev)
{
struct auxdisp_crtc *auxdisp = dev_get_drvdata(dev);
if (!auxdisp || !auxdisp->bound) {
return 0;
}
if (!auxdisp->display_enabled) {
return 0;
}
auxdisp->pending_timing.valid = false;
if (!__clk_is_enabled(auxdisp->pclk)) {
return 0;
}
auxdisp_write(auxdisp, AUXDISP_INT_ENABLE, 0);
auxdisp_write(auxdisp, AUXDISP_EN, 0);
auxdisp->display_enabled = false;
auxdisp_write(auxdisp, AUXDISP_INT_CLEAR, 0xFFFFFFFF);
if (__clk_is_enabled(auxdisp->pixclk))
clk_disable_unprepare(auxdisp->pixclk);
if (__clk_is_enabled(auxdisp->pclk))
clk_disable_unprepare(auxdisp->pclk);
if (__clk_is_enabled(auxdisp->aclk))
clk_disable_unprepare(auxdisp->aclk);
return 0;
}
static int auxdisp_crtc_resume(struct device *dev)
{
struct auxdisp_crtc *auxdisp = dev_get_drvdata(dev);
if (!auxdisp || !auxdisp->bound) {
return 0;
}
return 0;
}
static int auxdisp_crtc_runtime_suspend(struct device *dev)
{
struct auxdisp_crtc *auxdisp = dev_get_drvdata(dev);
if (auxdisp->current_pixclk) {
clk_disable_unprepare(auxdisp->current_pixclk);
}
clk_disable_unprepare(auxdisp->pclk);
clk_disable_unprepare(auxdisp->aclk);
return 0;
}
static int auxdisp_crtc_runtime_resume(struct device *dev)
{
struct auxdisp_crtc *auxdisp = dev_get_drvdata(dev);
int ret;
ret = clk_prepare_enable(auxdisp->aclk);
if (ret) {
dev_err(dev, "Failed to enable aclk in runtime resume: %d\n", ret);
return ret;
}
ret = clk_prepare_enable(auxdisp->pclk);
if (ret) {
dev_err(dev, "Failed to enable pclk in runtime resume: %d\n", ret);
clk_disable_unprepare(auxdisp->aclk);
return ret;
}
if (auxdisp->current_pixclk) {
ret = clk_prepare_enable(auxdisp->current_pixclk);
if (ret) {
dev_err(dev, "Failed to enable current_pixclk in runtime resume: %d\n", ret);
clk_disable_unprepare(auxdisp->pclk);
clk_disable_unprepare(auxdisp->aclk);
return ret;
}
}
return 0;
}
static const struct dev_pm_ops auxdisp_crtc_pm_ops = {
.suspend = auxdisp_crtc_suspend,
.resume = auxdisp_crtc_resume,
.runtime_suspend = auxdisp_crtc_runtime_suspend,
.runtime_resume = auxdisp_crtc_runtime_resume,
};
struct platform_driver auxdisp_crtc_driver = {
.probe = auxdisp_crtc_probe,
.remove = auxdisp_crtc_remove,
.driver = {
.name = "auxdisp",
.of_match_table = auxdisp_crtc_dt_ids,
.pm = &auxdisp_crtc_pm_ops,
},
};
MODULE_AUTHOR("liqiang.zhang<zhanglq@zhihecomputing.com>");
MODULE_DESCRIPTION("AuxDisp Driver");
MODULE_LICENSE("GPL");