From 5366dcae7d9e47e494d971448dded5a964086aa0 Mon Sep 17 00:00:00 2001 From: Andy Yan Date: Thu, 12 Sep 2024 16:53:21 +0800 Subject: [PATCH 1/9] drm/rockchip: Add tracepoints Enable: echo 1 > sys/kernel/tracing/tracing_on echo 1 > sys/kernel/tracing/events/rockchipdrm/enable echo 8192 > sys/kernel/tracing/buffer_size_kb cat /sys/kernel/tracing/trace Signed-off-by: Andy Yan Change-Id: Ib3936d6093cb6dcb3a19ee5f74867be1f4befa94 --- drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 21 +++++-- drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 1 + drivers/gpu/drm/rockchip/rockchip_drm_trace.h | 60 +++++++++++++++++++ 3 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_trace.h diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c index 19661accdc50..4296c2f58e53 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c @@ -47,6 +47,9 @@ #include "../drm_crtc_internal.h" +#define CREATE_TRACE_POINTS +#include "rockchip_drm_trace.h" + #define DRIVER_NAME "rockchip" #define DRIVER_DESC "RockChip Soc DRM" #define DRIVER_DATE "20140818" @@ -85,17 +88,23 @@ void rockchip_drm_dbg(const struct device *dev, enum rockchip_drm_debug_category struct va_format vaf; va_list args; - if (!rockchip_drm_debug_enabled(category)) - return; - va_start(args, format); vaf.fmt = format; vaf.va = &args; - if (dev) - dev_printk(KERN_DEBUG, dev, "%pV", &vaf); + if (rockchip_drm_debug_enabled(category)) { + if (dev) + dev_printk(KERN_DEBUG, dev, "%pV", &vaf); + else + printk(KERN_DEBUG "%pV", &vaf); + } + + if (category == VOP_DEBUG_VSYNC) + trace_rockchip_drm_dbg_vsync(&vaf); + else if (category == VOP_DEBUG_IOMMU_MAP) + trace_rockchip_drm_dbg_iommu(&vaf); else - printk(KERN_DEBUG "%pV", &vaf); + trace_rockchip_drm_dbg_common(&vaf); va_end(args); } diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index 14255b58058c..32abec20fb99 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -75,6 +75,7 @@ enum rockchip_drm_debug_category { VOP_DEBUG_WB = BIT(2), VOP_DEBUG_CFG_DONE = BIT(3), VOP_DEBUG_CLK = BIT(4), + VOP_DEBUG_IOMMU_MAP = BIT(5), VOP_DEBUG_VSYNC = BIT(7), VOP_DEBUG_PIXEL_SHIFT = BIT(8), }; diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_trace.h b/drivers/gpu/drm/rockchip/rockchip_drm_trace.h new file mode 100644 index 000000000000..5e625d48639b --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_trace.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM rockchipdrm + +#if !defined(_ROCKCHIP_DRM_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _ROCKCHIP_DRM_TRACE_H_ + +#include + +#define ROCKCHIP_DRM_MAX_MSG_LEN 256 + +DECLARE_EVENT_CLASS(rockchipdrm_dbg, + + TP_PROTO(struct va_format *vaf), + + TP_ARGS(vaf), + + TP_STRUCT__entry( + __dynamic_array(char, msg, ROCKCHIP_DRM_MAX_MSG_LEN) + ), + + TP_fast_assign( + WARN_ON_ONCE(vsnprintf(__get_dynamic_array(msg), + ROCKCHIP_DRM_MAX_MSG_LEN, + vaf->fmt, + *vaf->va) >= ROCKCHIP_DRM_MAX_MSG_LEN); + ), + + TP_printk("%s", __get_str(msg)) +); + +DEFINE_EVENT(rockchipdrm_dbg, rockchip_drm_dbg_common, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(rockchipdrm_dbg, rockchip_drm_dbg_iommu, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(rockchipdrm_dbg, rockchip_drm_dbg_vsync, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +#endif /* _ROCKCHIP_DRM_TRACE_H_ || TRACE_HEADER_MULTI_READ */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH ../../drivers/gpu/drm/rockchip +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE rockchip_drm_trace + +/* This part must be outside protection */ +#include From 593488826a78aa29c27892d3fb896e14209a6b41 Mon Sep 17 00:00:00 2001 From: Andy Yan Date: Fri, 13 Sep 2024 08:47:40 +0800 Subject: [PATCH 2/9] drm/rockchip: omit \n when call rockchip_drm_dbg() trace(TP_printk) will add \n by it's self. Signed-off-by: Andy Yan Change-Id: Idcc0bbd801053222fbf67085cc218596f7d6c84e --- drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 4 +-- drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 8 +++--- drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 26 ++++++++++---------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c index 4296c2f58e53..fbec7d884219 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c @@ -94,9 +94,9 @@ void rockchip_drm_dbg(const struct device *dev, enum rockchip_drm_debug_category if (rockchip_drm_debug_enabled(category)) { if (dev) - dev_printk(KERN_DEBUG, dev, "%pV", &vaf); + dev_printk(KERN_DEBUG, dev, "%pV\n", &vaf); else - printk(KERN_DEBUG "%pV", &vaf); + printk(KERN_DEBUG "%pV\n", &vaf); } if (category == VOP_DEBUG_VSYNC) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index d0baf6edf87e..8c94d53d1142 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -1993,7 +1993,7 @@ static void vop_plane_atomic_disable(struct drm_plane *plane, if (!old_state->crtc) return; - rockchip_drm_dbg(vop->dev, VOP_DEBUG_PLANE, "disable win%d-area%d by %s\n", + rockchip_drm_dbg(vop->dev, VOP_DEBUG_PLANE, "disable win%d-area%d by %s", win->win_id, win->area_id, current->comm); spin_lock(&vop->reg_lock); @@ -2250,7 +2250,7 @@ static void vop_plane_atomic_update(struct drm_plane *plane, if (rockchip_afbc(plane, fb->modifier)) afbc_en = true; rockchip_drm_dbg(vop->dev, VOP_DEBUG_PLANE, - "update win%d-area%d [%dx%d->%dx%d@(%d, %d)] zpos:%d fmt[%p4cc%s] addr[%pad] by %s\n", + "update win%d-area%d [%dx%d->%dx%d@(%d, %d)] zpos:%d fmt[%p4cc%s] addr[%pad] by %s", win->win_id, win->area_id, actual_w, actual_h, dsp_w, dsp_h, dest->x1, dest->y1, vop_plane_state->zpos, &fb->format->format, afbc_en ? "[AFBC]" : "", @@ -4363,7 +4363,7 @@ static void vop_crtc_atomic_flush(struct drm_crtc *crtc, spin_lock_irqsave(&vop->irq_lock, flags); vop->pre_overlay = s->hdr.pre_overlay; vop_cfg_done(vop); - rockchip_drm_dbg(vop->dev, VOP_DEBUG_CFG_DONE, "cfg_done\n\n"); + rockchip_drm_dbg(vop->dev, VOP_DEBUG_CFG_DONE, "cfg_done\n"); /* * rk322x and rk332x odd-even field will mistake when in interlace mode. * we must switch to frame effect before switch screen and switch to @@ -4730,7 +4730,7 @@ static irqreturn_t vop_isr(int irq, void *data) * frame effective, but actually it's effective immediately, so * we config this register at frame start. */ - rockchip_drm_dbg(vop->dev, VOP_DEBUG_VSYNC, "vsync\n"); + rockchip_drm_dbg(vop->dev, VOP_DEBUG_VSYNC, "vsync"); spin_lock_irqsave(&vop->irq_lock, flags); VOP_CTRL_SET(vop, level2_overlay_en, vop->pre_overlay); VOP_CTRL_SET(vop, alpha_hard_calc, vop->pre_overlay); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index 2031c8c5e70b..6d72dc006d5e 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -1385,7 +1385,7 @@ static int vop2_set_aclk_rate(struct drm_crtc *crtc, if (csu_div != vop2->csu_div) { rockchip_csu_set_div(vop2->csu_aclk, csu_div); rockchip_drm_dbg(vop2->dev, VOP_DEBUG_CLK, - "Set aclk auto cs div from %d to %d, aclk rate:%ld, aclk mode:%d\n", + "Set aclk auto cs div from %d to %d, aclk rate:%ld, aclk mode:%d", vop2->csu_div, csu_div, clk_get_rate(vop2->aclk), vop2->aclk_mode); } @@ -1763,7 +1763,7 @@ static inline void rk3568_vop2_cfg_done(struct drm_crtc *crtc) */ val |= vop2_readl(vop2, RK3568_REG_CFG_DONE) & 0x7; - rockchip_drm_dbg(vop2->dev, VOP_DEBUG_CFG_DONE, "cfg_done: 0x%x\n\n", val); + rockchip_drm_dbg(vop2->dev, VOP_DEBUG_CFG_DONE, "cfg_done: 0x%x\n", val); vop2_writel(vop2, 0, val); @@ -1790,7 +1790,7 @@ static inline void rk3588_vop2_cfg_done(struct drm_crtc *crtc) if (vcstate->splice_mode) val |= BIT(vp_data->splice_vp_id) | (BIT(vp_data->splice_vp_id) << 16); - rockchip_drm_dbg(vop2->dev, VOP_DEBUG_CFG_DONE, "cfg_done: 0x%x\n\n", val); + rockchip_drm_dbg(vop2->dev, VOP_DEBUG_CFG_DONE, "cfg_done: 0x%x\n", val); vop2_writel(vop2, 0, val); } @@ -3752,7 +3752,7 @@ static void vop2_wb_commit(struct drm_crtc *crtc) struct drm_framebuffer *fb = conn_state->writeback_job->fb; rockchip_drm_dbg(vop2->dev, VOP_DEBUG_WB, - "Enable wb %ux%u fmt: %u pitches: %d addr: %pad\n", + "Enable wb %ux%u fmt: %u pitches: %d addr: %pad", fb->width, fb->height, wb_state->format, fb->pitches[0], &wb_state->yrgb_addr); @@ -5690,7 +5690,7 @@ static void vop2_plane_atomic_disable(struct drm_plane *plane, struct drm_atomic struct drm_crtc *crtc; struct vop2_video_port *vp; - rockchip_drm_dbg(vop2->dev, VOP_DEBUG_PLANE, "%s disable %s\n", + rockchip_drm_dbg(vop2->dev, VOP_DEBUG_PLANE, "%s disable %s", win->name, current->comm); if (state) @@ -6113,7 +6113,7 @@ static void vop2_win_atomic_update(struct vop2_win *win, struct drm_rect *src, s vop2_win_enable(win); spin_lock(&vop2->reg_lock); rockchip_drm_dbg(vop2->dev, VOP_DEBUG_PLANE, - "vp%d update %s[%dx%d@(%d, %d)->%dx%d@(%d, %d)] zpos[%d] fmt[%p4cc%s] addr[%pad] fb_size[0x%zx] by %s\n", + "vp%d update %s[%dx%d@(%d, %d)->%dx%d@(%d, %d)] zpos[%d] fmt[%p4cc%s] addr[%pad] fb_size[0x%zx] by %s", vp->id, win->name, actual_w, actual_h, src->x1 >> 16, src->y1 >> 16, dsp_w, dsp_h, dsp_stx, dsp_sty, vpstate->zpos, @@ -6363,7 +6363,7 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_ if (vcstate->splice_mode) { rockchip_drm_dbg(vop2->dev, VOP_DEBUG_PLANE, - "vp%d update %s[%dx%d@(%d, %d)->%dx%d@(%d, %d)] zpos[%d] fmt[%p4cc%s] addr[%pad] fb_size[0x%zx] by %s\n", + "vp%d update %s[%dx%d@(%d, %d)->%dx%d@(%d, %d)] zpos[%d] fmt[%p4cc%s] addr[%pad] fb_size[0x%zx] by %s", vp->id, win->name, drm_rect_width(&vpstate->src) >> 16, drm_rect_height(&vpstate->src) >> 16, @@ -7490,7 +7490,7 @@ static void rockchip_drm_vop2_pixel_shift_commit(struct drm_device *dev, struct int vp_id = vp->id; if (vop2->active_vp_mask & BIT(vp_id)) { - rockchip_drm_dbg(vop2->dev, VOP_DEBUG_PIXEL_SHIFT, "vop2 pixel shift x:%d, y:%d\n", + rockchip_drm_dbg(vop2->dev, VOP_DEBUG_PIXEL_SHIFT, "vop2 pixel shift x:%d, y:%d", vcstate->shift_x, vcstate->shift_y); rockchip_drm_vop2_pixel_shift_duplicate_commit(dev); } @@ -11225,7 +11225,7 @@ static void vop2_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_atomic_stat vop2_zpos[nr_layers].zpos = vpstate->zpos; vop2_zpos[nr_layers].plane = plane; - rockchip_drm_dbg(vop2->dev, VOP_DEBUG_OVERLAY, "%s active zpos:%d for vp%d from vp%d\n", + rockchip_drm_dbg(vop2->dev, VOP_DEBUG_OVERLAY, "%s active zpos:%d for vp%d from vp%d", win->name, vpstate->zpos, vp->id, old_vp->id); /* left and right win may have different number */ if (vcstate->splice_mode && vop2_zpos_splice) { @@ -11259,7 +11259,7 @@ static void vop2_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_atomic_stat } vp->hdr10_at_splice_mode = hdr10_at_splice_mode; - rockchip_drm_dbg(vop2->dev, VOP_DEBUG_OVERLAY, "vp%d: %d windows, active layers %d\n", + rockchip_drm_dbg(vop2->dev, VOP_DEBUG_OVERLAY, "vp%d: %d windows, active layers %d", vp->id, hweight32(vp->win_mask), nr_layers); if (nr_layers) { vp->nr_layers = nr_layers; @@ -12619,7 +12619,7 @@ static irqreturn_t vop2_isr(int irq, void *data) } if (active_irqs & FS_FIELD_INTR) { - rockchip_drm_dbg(vop2->dev, VOP_DEBUG_VSYNC, "vsync_vp%d\n", vp->id); + rockchip_drm_dbg(vop2->dev, VOP_DEBUG_VSYNC, "vsync_vp%d", vp->id); vop2_wb_handler(vp); if (likely(!vp->skip_vsync) || (vp->layer_sel_update == false)) { drm_crtc_handle_vblank(crtc); @@ -12777,7 +12777,7 @@ static irqreturn_t vop3_vp_isr(int irq, void *data) } if (active_irqs & FS_FIELD_INTR) { - rockchip_drm_dbg(vop2->dev, VOP_DEBUG_VSYNC, "vsync_vp%d\n", vp->id); + rockchip_drm_dbg(vop2->dev, VOP_DEBUG_VSYNC, "vsync_vp%d", vp->id); vop2_wb_handler(vp); drm_crtc_handle_vblank(crtc); vop2_handle_vblank(vop2, crtc); @@ -14077,7 +14077,7 @@ static int vop2_devfreq_target(struct device *dev, unsigned long *freq, ret = dev_pm_opp_set_rate(dev, *freq); if (!ret) { rockchip_drm_dbg(vop2->dev, VOP_DEBUG_CLK, - "Set VOP aclk from %ld to %ld\n", vop2->aclk_current_freq, *freq); + "Set VOP aclk from %ld to %ld", vop2->aclk_current_freq, *freq); vop2->aclk_current_freq = *freq; vop2->devfreq->last_status.current_frequency = *freq; } From d233408e363a72cb06d6f851b2bdb154c46ca3b9 Mon Sep 17 00:00:00 2001 From: Andy Yan Date: Fri, 13 Sep 2024 14:22:41 +0800 Subject: [PATCH 3/9] drm/rockchip: Add rockchip_drm_dbg_thread_info() function Support print thread info. Change-Id: I56111c8bb438ad354553276881d74fc7a6186340 Signed-off-by: Andy Yan --- drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 57 +++++++++++++++----- drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 7 ++- drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 16 +++--- drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 36 ++++++------- 4 files changed, 76 insertions(+), 40 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c index fbec7d884219..6c3f083d4b8e 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c @@ -81,8 +81,34 @@ static inline bool rockchip_drm_debug_enabled(enum rockchip_drm_debug_category c return unlikely(drm_debug & category); } +static void rockchip_drm_dbg_print(const struct device *dev, enum rockchip_drm_debug_category category, + bool show_thread, struct va_format *vaf) +{ + if (rockchip_drm_debug_enabled(category)) { + if (dev) { + if (show_thread) + dev_printk(KERN_DEBUG, dev, "%s %pV\n", current->comm, vaf); + else + dev_printk(KERN_DEBUG, dev, "%pV\n", vaf); + } else { + if (show_thread) + printk(KERN_DEBUG "%s %pV\n", current->comm, vaf); + else + printk(KERN_DEBUG "%pV\n", vaf); + } + } + + if (category == VOP_DEBUG_VSYNC) + trace_rockchip_drm_dbg_vsync(vaf); + else if (category == VOP_DEBUG_IOMMU_MAP) + trace_rockchip_drm_dbg_iommu(vaf); + else + trace_rockchip_drm_dbg_common(vaf); +} + __printf(3, 4) -void rockchip_drm_dbg(const struct device *dev, enum rockchip_drm_debug_category category, +void rockchip_drm_dbg(const struct device *dev, + enum rockchip_drm_debug_category category, const char *format, ...) { struct va_format vaf; @@ -92,19 +118,24 @@ void rockchip_drm_dbg(const struct device *dev, enum rockchip_drm_debug_category vaf.fmt = format; vaf.va = &args; - if (rockchip_drm_debug_enabled(category)) { - if (dev) - dev_printk(KERN_DEBUG, dev, "%pV\n", &vaf); - else - printk(KERN_DEBUG "%pV\n", &vaf); - } + rockchip_drm_dbg_print(dev, category, false, &vaf); - if (category == VOP_DEBUG_VSYNC) - trace_rockchip_drm_dbg_vsync(&vaf); - else if (category == VOP_DEBUG_IOMMU_MAP) - trace_rockchip_drm_dbg_iommu(&vaf); - else - trace_rockchip_drm_dbg_common(&vaf); + va_end(args); +} + +__printf(3, 4) +void rockchip_drm_dbg_thread_info(const struct device *dev, + enum rockchip_drm_debug_category category, + const char *format, ...) +{ + struct va_format vaf; + va_list args; + + va_start(args, format); + vaf.fmt = format; + vaf.va = &args; + + rockchip_drm_dbg_print(dev, category, true, &vaf); va_end(args); } diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index 32abec20fb99..1a7837c8fe6f 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -649,8 +649,13 @@ bool rockchip_drm_is_rfbc(struct drm_plane *plane, u64 modifier); const char *rockchip_drm_modifier_to_string(uint64_t modifier); __printf(3, 4) -void rockchip_drm_dbg(const struct device *dev, enum rockchip_drm_debug_category category, +void rockchip_drm_dbg(const struct device *dev, + enum rockchip_drm_debug_category category, const char *format, ...); +__printf(3, 4) +void rockchip_drm_dbg_thread_info(const struct device *dev, + enum rockchip_drm_debug_category category, + const char *format, ...); extern struct platform_driver cdn_dp_driver; extern struct platform_driver dw_hdmi_rockchip_pltfm_driver; diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index 8c94d53d1142..bd70cfa3e5b4 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -1993,8 +1993,8 @@ static void vop_plane_atomic_disable(struct drm_plane *plane, if (!old_state->crtc) return; - rockchip_drm_dbg(vop->dev, VOP_DEBUG_PLANE, "disable win%d-area%d by %s", - win->win_id, win->area_id, current->comm); + rockchip_drm_dbg_thread_info(vop->dev, VOP_DEBUG_PLANE, "disable win%d-area%d", + win->win_id, win->area_id); spin_lock(&vop->reg_lock); @@ -2249,12 +2249,12 @@ static void vop_plane_atomic_update(struct drm_plane *plane, if (rockchip_afbc(plane, fb->modifier)) afbc_en = true; - rockchip_drm_dbg(vop->dev, VOP_DEBUG_PLANE, - "update win%d-area%d [%dx%d->%dx%d@(%d, %d)] zpos:%d fmt[%p4cc%s] addr[%pad] by %s", - win->win_id, win->area_id, actual_w, actual_h, - dsp_w, dsp_h, dest->x1, dest->y1, vop_plane_state->zpos, &fb->format->format, - afbc_en ? "[AFBC]" : "", - &vop_plane_state->yrgb_mst, current->comm); + rockchip_drm_dbg_thread_info(vop->dev, VOP_DEBUG_PLANE, + "update win%d-area%d [%dx%d->%dx%d@(%d, %d)] zpos:%d fmt[%p4cc%s] addr[%pad]", + win->win_id, win->area_id, actual_w, actual_h, + dsp_w, dsp_h, dest->x1, dest->y1, vop_plane_state->zpos, &fb->format->format, + afbc_en ? "[AFBC]" : "", + &vop_plane_state->yrgb_mst); /* * spi interface(vop_plane_state->yrgb_kvaddr, fb->pixel_format, * actual_w, actual_h) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index 6d72dc006d5e..7722c2d9be47 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -6112,13 +6112,13 @@ static void vop2_win_atomic_update(struct vop2_win *win, struct drm_rect *src, s vop2_win_enable(win); spin_lock(&vop2->reg_lock); - rockchip_drm_dbg(vop2->dev, VOP_DEBUG_PLANE, - "vp%d update %s[%dx%d@(%d, %d)->%dx%d@(%d, %d)] zpos[%d] fmt[%p4cc%s] addr[%pad] fb_size[0x%zx] by %s", - vp->id, win->name, - actual_w, actual_h, src->x1 >> 16, src->y1 >> 16, - dsp_w, dsp_h, dsp_stx, dsp_sty, vpstate->zpos, - &fb->format->format, rockchip_drm_modifier_to_string(fb->modifier), - &vpstate->yrgb_mst, vpstate->fb_size, current->comm); + rockchip_drm_dbg_thread_info(vop2->dev, VOP_DEBUG_PLANE, + "vp%d update %s[%dx%d@(%d, %d)->%dx%d@(%d, %d)] zpos[%d] fmt[%p4cc%s] addr[%pad] fb_size[0x%zx]", + vp->id, win->name, + actual_w, actual_h, src->x1 >> 16, src->y1 >> 16, + dsp_w, dsp_h, dsp_stx, dsp_sty, vpstate->zpos, + &fb->format->format, rockchip_drm_modifier_to_string(fb->modifier), + &vpstate->yrgb_mst, vpstate->fb_size); if (vop2->version != VOP_VERSION_RK3568) rk3588_vop2_win_cfg_axi(win); @@ -6362,17 +6362,17 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_ } if (vcstate->splice_mode) { - rockchip_drm_dbg(vop2->dev, VOP_DEBUG_PLANE, - "vp%d update %s[%dx%d@(%d, %d)->%dx%d@(%d, %d)] zpos[%d] fmt[%p4cc%s] addr[%pad] fb_size[0x%zx] by %s", - vp->id, win->name, - drm_rect_width(&vpstate->src) >> 16, - drm_rect_height(&vpstate->src) >> 16, - vpstate->src.x1 >> 16, vpstate->src.y1 >> 16, - drm_rect_width(&vpstate->dest), drm_rect_height(&vpstate->dest), - vpstate->dest.x1, vpstate->dest.y1, vpstate->zpos, - &fb->format->format, - rockchip_drm_modifier_to_string(fb->modifier), &vpstate->yrgb_mst, - vpstate->fb_size, current->comm); + rockchip_drm_dbg_thread_info(vop2->dev, VOP_DEBUG_PLANE, + "vp%d update %s[%dx%d@(%d, %d)->%dx%d@(%d, %d)] zpos[%d] fmt[%p4cc%s] addr[%pad] fb_size[0x%zx]", + vp->id, win->name, + drm_rect_width(&vpstate->src) >> 16, + drm_rect_height(&vpstate->src) >> 16, + vpstate->src.x1 >> 16, vpstate->src.y1 >> 16, + drm_rect_width(&vpstate->dest), drm_rect_height(&vpstate->dest), + vpstate->dest.x1, vpstate->dest.y1, vpstate->zpos, + &fb->format->format, + rockchip_drm_modifier_to_string(fb->modifier), &vpstate->yrgb_mst, + vpstate->fb_size); vop2_calc_drm_rect_for_splice(vpstate, &wsrc, &wdst, &right_wsrc, &right_wdst); splice_win = win->splice_win; From 4ada27f28382fab226cb04fae2dc26b1e30db86b Mon Sep 17 00:00:00 2001 From: Andy Yan Date: Fri, 13 Sep 2024 09:33:28 +0800 Subject: [PATCH 4/9] drm/rockchip: Add log for iommu map/unmap Signed-off-by: Andy Yan Change-Id: Ieefd2e6e113da45cfdfe9578ab5580d16402c3d6 --- drivers/gpu/drm/rockchip/rockchip_drm_gem.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_gem.c b/drivers/gpu/drm/rockchip/rockchip_drm_gem.c index 25e5f4818e08..7b52a92bdd6a 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_gem.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_gem.c @@ -53,6 +53,9 @@ static int rockchip_gem_iommu_map(struct rockchip_gem_object *rk_obj) rk_obj->dma_addr = rk_obj->mm.start; + rockchip_drm_dbg(drm->dev, VOP_DEBUG_IOMMU_MAP, "iommu map: iova: %pad size: 0x%zx", + &rk_obj->dma_addr, rk_obj->base.size); + ret = iommu_map_sgtable(private->domain, rk_obj->dma_addr, rk_obj->sgt, prot); if (ret < (ssize_t)rk_obj->base.size) { @@ -81,6 +84,9 @@ static int rockchip_gem_iommu_unmap(struct rockchip_gem_object *rk_obj) struct drm_device *drm = rk_obj->base.dev; struct rockchip_drm_private *private = drm->dev_private; + rockchip_drm_dbg(drm->dev, VOP_DEBUG_IOMMU_MAP, "iommu unmap: iova: %pad size: %zx", + &rk_obj->dma_addr, rk_obj->size); + iommu_unmap(private->domain, rk_obj->dma_addr, rk_obj->size); mutex_lock(&private->mm_lock); From b430f82a6f7ee4726c61f263466e7222db48bb2e Mon Sep 17 00:00:00 2001 From: Yu Qiaowei Date: Sat, 14 Sep 2024 17:52:02 +0800 Subject: [PATCH 5/9] video: rockchip: rga3: fix irq_handler crash when hardware reset Signed-off-by: Yu Qiaowei Change-Id: Iee7f20510172fc89c6640503926584a982d2837c --- drivers/video/rockchip/rga3/rga2_reg_info.c | 14 +++++++------- drivers/video/rockchip/rga3/rga3_reg_info.c | 8 ++++---- drivers/video/rockchip/rga3/rga_drv.c | 19 +++++++++++++++++-- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/drivers/video/rockchip/rga3/rga2_reg_info.c b/drivers/video/rockchip/rga3/rga2_reg_info.c index 0b46b5813600..c2af876a98db 100644 --- a/drivers/video/rockchip/rga3/rga2_reg_info.c +++ b/drivers/video/rockchip/rga3/rga2_reg_info.c @@ -3124,6 +3124,13 @@ static int rga2_irq(struct rga_scheduler_t *scheduler) { struct rga_job *job = scheduler->running_job; + /*clear INTR */ + rga_write(rga_read(RGA2_INT, scheduler) | + (m_RGA2_INT_ERROR_CLEAR_MASK | + m_RGA2_INT_ALL_CMD_DONE_INT_CLEAR | m_RGA2_INT_NOW_CMD_DONE_INT_CLEAR | + m_RGA2_INT_LINE_RD_CLEAR | m_RGA2_INT_LINE_WR_CLEAR), + RGA2_INT, scheduler); + /* The hardware interrupt top-half don't need to lock the scheduler. */ if (job == NULL) return IRQ_HANDLED; @@ -3151,13 +3158,6 @@ static int rga2_irq(struct rga_scheduler_t *scheduler) scheduler->ops->soft_reset(scheduler); } - /*clear INTR */ - rga_write(rga_read(RGA2_INT, scheduler) | - (m_RGA2_INT_ERROR_CLEAR_MASK | - m_RGA2_INT_ALL_CMD_DONE_INT_CLEAR | m_RGA2_INT_NOW_CMD_DONE_INT_CLEAR | - m_RGA2_INT_LINE_RD_CLEAR | m_RGA2_INT_LINE_WR_CLEAR), - RGA2_INT, scheduler); - return IRQ_WAKE_THREAD; } diff --git a/drivers/video/rockchip/rga3/rga3_reg_info.c b/drivers/video/rockchip/rga3/rga3_reg_info.c index 9df753f696ba..83c3cc2e0d8e 100644 --- a/drivers/video/rockchip/rga3/rga3_reg_info.c +++ b/drivers/video/rockchip/rga3/rga3_reg_info.c @@ -2155,6 +2155,10 @@ static int rga3_irq(struct rga_scheduler_t *scheduler) { struct rga_job *job = scheduler->running_job; + /*clear INTR */ + rga_write(m_RGA3_INT_FRM_DONE | m_RGA3_INT_CMD_LINE_FINISH | m_RGA3_INT_ERROR_MASK, + RGA3_INT_CLR, scheduler); + if (job == NULL) return IRQ_HANDLED; @@ -2179,10 +2183,6 @@ static int rga3_irq(struct rga_scheduler_t *scheduler) scheduler->ops->soft_reset(scheduler); } - /*clear INTR */ - rga_write(m_RGA3_INT_FRM_DONE | m_RGA3_INT_CMD_LINE_FINISH | m_RGA3_INT_ERROR_MASK, - RGA3_INT_CLR, scheduler); - return IRQ_WAKE_THREAD; } diff --git a/drivers/video/rockchip/rga3/rga_drv.c b/drivers/video/rockchip/rga3/rga_drv.c index 203d10ee938c..fc162f3abca1 100644 --- a/drivers/video/rockchip/rga3/rga_drv.c +++ b/drivers/video/rockchip/rga3/rga_drv.c @@ -1123,11 +1123,26 @@ static irqreturn_t rga_irq_handler(int irq, void *data) { irqreturn_t irq_ret = IRQ_NONE; struct rga_scheduler_t *scheduler = data; + ktime_t timestamp = ktime_get(); - scheduler->running_job->timestamp.hw_done = ktime_get(); + spin_lock(&scheduler->irq_lock); - if (scheduler->ops->irq) + if (scheduler->ops->irq) { irq_ret = scheduler->ops->irq(scheduler); + if (irq_ret == IRQ_HANDLED) { + spin_unlock(&scheduler->irq_lock); + return irq_ret; + } + } + + if (scheduler->running_job == NULL) { + spin_unlock(&scheduler->irq_lock); + return IRQ_HANDLED; + } + + scheduler->running_job->timestamp.hw_done = timestamp; + + spin_unlock(&scheduler->irq_lock); return irq_ret; } From 263734a86205247a76dead320eea6c141a6bb4f7 Mon Sep 17 00:00:00 2001 From: Yu Qiaowei Date: Fri, 6 May 2022 15:33:40 +0800 Subject: [PATCH 6/9] video: rockchip: rga3: Add drv_shutdown callback Need to reset hardware when driver exits. Update driver version to 1.3.6 Signed-off-by: Yu Qiaowei Change-Id: I4c6cb394a1339cf689d2d0d011d5df3fb24f0425 --- drivers/video/rockchip/rga3/include/rga_drv.h | 5 +- drivers/video/rockchip/rga3/include/rga_job.h | 1 + drivers/video/rockchip/rga3/rga_drv.c | 35 +++++++++++++ drivers/video/rockchip/rga3/rga_job.c | 50 +++++++++++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) diff --git a/drivers/video/rockchip/rga3/include/rga_drv.h b/drivers/video/rockchip/rga3/include/rga_drv.h index 37e6bd8bb9b3..9c5688107542 100644 --- a/drivers/video/rockchip/rga3/include/rga_drv.h +++ b/drivers/video/rockchip/rga3/include/rga_drv.h @@ -88,7 +88,7 @@ #define DRIVER_MAJOR_VERISON 1 #define DRIVER_MINOR_VERSION 3 -#define DRIVER_REVISION_VERSION 5 +#define DRIVER_REVISION_VERSION 6 #define DRIVER_PATCH_VERSION #define DRIVER_VERSION (STR(DRIVER_MAJOR_VERISON) "." STR(DRIVER_MINOR_VERSION) \ @@ -450,6 +450,9 @@ struct rga_drvdata_t { #ifdef CONFIG_ROCKCHIP_RGA_DEBUGGER struct rga_debugger *debugger; #endif + + bool shutdown; + struct rw_semaphore rwsem; }; struct rga_irqs_data_t { diff --git a/drivers/video/rockchip/rga3/include/rga_job.h b/drivers/video/rockchip/rga3/include/rga_job.h index 2bdf1a9258d0..bdd9ab9ed74a 100644 --- a/drivers/video/rockchip/rga3/include/rga_job.h +++ b/drivers/video/rockchip/rga3/include/rga_job.h @@ -38,6 +38,7 @@ struct rga_request *rga_request_lookup(struct rga_pending_request_manager *reque uint32_t id); int rga_request_commit(struct rga_request *user_request); +void rga_request_scheduler_shutdown(struct rga_scheduler_t *scheduler); void rga_request_scheduler_abort(struct rga_scheduler_t *scheduler); void rga_request_session_destroy_abort(struct rga_session *session); int rga_request_put(struct rga_request *request); diff --git a/drivers/video/rockchip/rga3/rga_drv.c b/drivers/video/rockchip/rga3/rga_drv.c index fc162f3abca1..37d27b7d6dc9 100644 --- a/drivers/video/rockchip/rga3/rga_drv.c +++ b/drivers/video/rockchip/rga3/rga_drv.c @@ -927,6 +927,15 @@ static long rga_ioctl(struct file *file, uint32_t cmd, unsigned long arg) if (DEBUGGER_EN(NONUSE)) return 0; + down_read(&rga_drvdata->rwsem); + + if (rga_drvdata->shutdown) { + rga_log("driver has been shutdown\n"); + up_read(&rga_drvdata->rwsem); + + return -EBUSY; + } + switch (cmd) { case RGA_BLIT_SYNC: case RGA_BLIT_ASYNC: @@ -1053,6 +1062,8 @@ static long rga_ioctl(struct file *file, uint32_t cmd, unsigned long arg) break; } + up_read(&rga_drvdata->rwsem); + return ret; } @@ -1442,17 +1453,38 @@ pm_disable: static int rga_drv_remove(struct platform_device *pdev) { + struct rga_scheduler_t *scheduler = NULL; + + down_write(&rga_drvdata->rwsem); + rga_drvdata->shutdown = true; + + scheduler = (struct rga_scheduler_t *)platform_get_drvdata(pdev); + if (scheduler) + rga_request_scheduler_shutdown(scheduler); + #ifndef RGA_DISABLE_PM device_init_wakeup(&pdev->dev, false); pm_runtime_disable(&pdev->dev); #endif /* #ifndef RGA_DISABLE_PM */ + up_write(&rga_drvdata->rwsem); + return 0; } +static void rga_drv_shutdown(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + rga_drv_remove(pdev); + + dev_info(dev, "shutdown success\n"); +} + static struct platform_driver rga3_driver = { .probe = rga_drv_probe, .remove = rga_drv_remove, + .shutdown = rga_drv_shutdown, .driver = { .name = "rga3", .of_match_table = of_match_ptr(rga3_dt_ids), @@ -1462,6 +1494,7 @@ static struct platform_driver rga3_driver = { static struct platform_driver rga2_driver = { .probe = rga_drv_probe, .remove = rga_drv_remove, + .shutdown = rga_drv_shutdown, .driver = { .name = "rga2", .of_match_table = of_match_ptr(rga2_dt_ids), @@ -1479,6 +1512,8 @@ static int __init rga_init(void) } mutex_init(&rga_drvdata->lock); + init_rwsem(&rga_drvdata->rwsem); + rga_drvdata->shutdown = false; ret = platform_driver_register(&rga3_driver); if (ret != 0) { diff --git a/drivers/video/rockchip/rga3/rga_job.c b/drivers/video/rockchip/rga3/rga_job.c index 25e1b978f2e9..dd184821b373 100644 --- a/drivers/video/rockchip/rga3/rga_job.c +++ b/drivers/video/rockchip/rga3/rga_job.c @@ -633,6 +633,56 @@ struct rga_request *rga_request_lookup(struct rga_pending_request_manager *manag return request; } +void rga_request_scheduler_shutdown(struct rga_scheduler_t *scheduler) +{ + struct rga_job *job, *job_q; + unsigned long flags; + + rga_power_enable(scheduler); + + spin_lock_irqsave(&scheduler->irq_lock, flags); + + job = scheduler->running_job; + if (job) { + if (test_bit(RGA_JOB_STATE_INTR_ERR, &job->state) || + test_bit(RGA_JOB_STATE_FINISH, &job->state)) { + spin_unlock_irqrestore(&scheduler->irq_lock, flags); + goto finish; + } + + scheduler->running_job = NULL; + scheduler->status = RGA_SCHEDULER_ABORT; + scheduler->ops->soft_reset(scheduler); + + spin_unlock_irqrestore(&scheduler->irq_lock, flags); + + rga_mm_unmap_job_info(job); + + job->ret = -EBUSY; + rga_request_release_signal(scheduler, job); + + /* + * Since the running job was abort, turn off the power here that + * should have been turned off after job done (corresponds to + * power_enable in rga_job_run()). + */ + rga_power_disable(scheduler); + } else { + /* Clean up the jobs in the todo list that need to be free. */ + list_for_each_entry_safe(job, job_q, &scheduler->todo_list, head) { + rga_mm_unmap_job_info(job); + + job->ret = -EBUSY; + rga_request_release_signal(scheduler, job); + } + + spin_unlock_irqrestore(&scheduler->irq_lock, flags); + } + +finish: + rga_power_disable(scheduler); +} + void rga_request_scheduler_abort(struct rga_scheduler_t *scheduler) { struct rga_job *job; From 15d421f84d97e69aa43f4cfa6a420fafcc9c5179 Mon Sep 17 00:00:00 2001 From: Tao Huang Date: Fri, 1 Nov 2024 19:26:23 +0800 Subject: [PATCH 7/9] MALI: bifrost: Fix hibernation Fix following panic after hibernation resume on rk3588: [ 8.170297][ T2089] Restarting tasks ... done. [ 8.183038][ T2089] PM: hibernation: hibernation exit ... [ 10.056856][ T391] rockchip-pm-domain fd8d8000.power-management:power-controller: failed to get ack on domain 'gpu', target_idle = 0, target_ack = 0, val=0x9ff1 [ 10.056912][ T391] Kernel panic - not syncing: panic_on_set_idle set ... [ 10.058770][ T391] CPU: 7 PID: 391 Comm: mali-gpuq-kthre Tainted: G O 6.1.99 #5 [ 10.059554][ T391] Hardware name: Rockchip RK3588 EVB1 LP4 V10 Board (DT) [ 10.060169][ T391] Call trace: [ 10.060462][ T391] dump_backtrace+0xf4/0x114 [ 10.060887][ T391] show_stack+0x18/0x24 [ 10.061262][ T391] dump_stack_lvl+0x6c/0x90 [ 10.061678][ T391] dump_stack+0x18/0x38 [ 10.062053][ T391] panic+0x14c/0x340 [ 10.062402][ T391] rockchip_pmu_set_idle_request+0x274/0x278 [ 10.062931][ T391] rockchip_pd_power+0x1a8/0x350 [ 10.063371][ T391] rockchip_pd_power_on+0x24/0x30 [ 10.063811][ T391] genpd_power_on+0x1d4/0x2ec [ 10.064229][ T391] genpd_runtime_resume+0xb0/0x384 [ 10.064689][ T391] __rpm_callback+0x7c/0x3c8 [ 10.065096][ T391] rpm_resume+0x404/0x5cc [ 10.065477][ T391] __pm_runtime_resume+0x4c/0x90 [ 10.065916][ T391] rk_pm_callback_power_on+0x16c/0x278 [ 10.066407][ T391] kbase_pm_clock_on+0x110/0x3a8 [ 10.066852][ T391] kbase_pm_do_poweron+0x20/0x68 [ 10.067293][ T391] kbase_pm_update_active+0x90/0x1c8 [ 10.067762][ T391] kbase_hwaccess_pm_gpu_active+0x10/0x1c [ 10.068264][ T391] kbase_pm_context_active_handle_suspend_locked+0x8c/0x1a0 [ 10.068906][ T391] scheduler_pm_active_handle_suspend+0x6c/0x100 [ 10.069473][ T391] scheduler_wakeup+0x4c/0x1ac [ 10.069898][ T391] kbase_csf_scheduler_queue_start+0x330/0x710 [ 10.070451][ T391] kbase_csf_process_queue_kick+0x58/0x1b0 [ 10.070969][ T391] kbase_csf_scheduler_kthread+0x9d4/0x13ec [ 10.071499][ T391] kthread+0xec/0x1b8 [ 10.071860][ T391] ret_from_fork+0x10/0x20 Signed-off-by: Tao Huang Change-Id: I416e38313c633c22e4c6675ddf68ce348b4f63e9 --- drivers/gpu/arm/bifrost/mali_kbase_core_linux.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/arm/bifrost/mali_kbase_core_linux.c b/drivers/gpu/arm/bifrost/mali_kbase_core_linux.c index 9f88d4f3fbc6..9cd02fec4a10 100644 --- a/drivers/gpu/arm/bifrost/mali_kbase_core_linux.c +++ b/drivers/gpu/arm/bifrost/mali_kbase_core_linux.c @@ -5948,8 +5948,8 @@ static int kbase_device_runtime_idle(struct device *dev) /* The power management operations for the platform driver. */ static const struct dev_pm_ops kbase_pm_ops = { - .suspend = kbase_device_suspend, - .resume = kbase_device_resume, + SYSTEM_SLEEP_PM_OPS(kbase_device_suspend, + kbase_device_resume) #ifdef KBASE_PM_RUNTIME .runtime_suspend = kbase_device_runtime_suspend, .runtime_resume = kbase_device_runtime_resume, From 3f8d95174b31d15f1e530fdbaef31f9fda4a8875 Mon Sep 17 00:00:00 2001 From: Sugar Zhang Date: Wed, 12 Jun 2024 15:04:21 +0800 Subject: [PATCH 8/9] dmaengine: Add support for rockchip dma Signed-off-by: Sugar Zhang Change-Id: Ib37e0188b26b9212b1f48aebb903709c0e20843d --- drivers/dma/Kconfig | 8 + drivers/dma/Makefile | 1 + drivers/dma/rockchip-dma.c | 1328 ++++++++++++++++++++++++++++++++++++ 3 files changed, 1337 insertions(+) create mode 100644 drivers/dma/rockchip-dma.c diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 66ef0a111484..c2617a6a3bf0 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -558,6 +558,14 @@ config PLX_DMA These are exposed via extra functions on the switch's upstream port. Each function exposes one DMA channel. +config ROCKCHIP_DMA + tristate "Rockchip DMA support" + depends on ARCH_ROCKCHIP || COMPILE_TEST + select DMA_ENGINE + select DMA_VIRTUAL_CHANNELS + help + Support the DMA engine for Rockchip family platform devices. + config STE_DMA40 bool "ST-Ericsson DMA40 support" depends on ARCH_U8500 diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index 10f7d4241001..840115d04e61 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -65,6 +65,7 @@ obj-$(CONFIG_PLX_DMA) += plx_dma.o obj-$(CONFIG_PPC_BESTCOMM) += bestcomm/ obj-$(CONFIG_PXA_DMA) += pxa_dma.o obj-$(CONFIG_RENESAS_DMA) += sh/ +obj-$(CONFIG_ROCKCHIP_DMA) += rockchip-dma.o obj-$(CONFIG_SF_PDMA) += sf-pdma/ obj-$(CONFIG_STE_DMA40) += ste_dma40.o ste_dma40_ll.o obj-$(CONFIG_STM32_DMA) += stm32-dma.o diff --git a/drivers/dma/rockchip-dma.c b/drivers/dma/rockchip-dma.c new file mode 100644 index 000000000000..e7e52979c1b7 --- /dev/null +++ b/drivers/dma/rockchip-dma.c @@ -0,0 +1,1328 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2024 Rockchip Electronics Co. Ltd. + * Author: Sugar Zhang + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "virt-dma.h" + +#define DRIVER_NAME "rk-dma" +#define DMA_MAX_SIZE (0x1000000) +#define LLI_BLOCK_SIZE (SZ_4K) + +#define RK_MAX_BURST_LEN 16 +#define RK_DMA_BUSWIDTHS \ + (BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | \ + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \ + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES) | \ + BIT(DMA_SLAVE_BUSWIDTH_8_BYTES) | \ + BIT(DMA_SLAVE_BUSWIDTH_16_BYTES)) + +#define HIWORD_UPDATE(v, h, l) (((v) << (l)) | (GENMASK((h), (l)) << 16)) +#define GENMASK_VAL(v, h, l) (((v) & GENMASK(h, l)) >> l) + +#define RK_DMA_CMN_GROUP_SIZE 0x40 +#define RK_DMA_LCH_GROUP_SIZE 0x40 +#define RK_DMA_PCH_GROUP_SIZE 0x40 + +#define RK_DMA_CMN_REG(x) (d->base + (x)) +#define RK_DMA_LCH_REG(x) (l->base + (x)) +#define RK_DMA_LCHn_REG(n, x) (d->base + RK_DMA_CMN_GROUP_SIZE + \ + (RK_DMA_LCH_GROUP_SIZE * (n)) + (x)) +#define RK_DMA_PCHn_REG(n, x) (d->base + RK_DMA_CMN_GROUP_SIZE + \ + (RK_DMA_LCH_GROUP_SIZE * (d->dma_channels)) + \ + (RK_DMA_PCH_GROUP_SIZE * (n)) + (x)) + +/* RK_DMA Common Register Define */ +#define RK_DMA_CMN_VER RK_DMA_CMN_REG(0x0000) /* Address Offset: 0x0000 */ +#define RK_DMA_CMN_CFG RK_DMA_CMN_REG(0x0004) /* Address Offset: 0x0004 */ +#define RK_DMA_CMN_CTL0 RK_DMA_CMN_REG(0x0008) /* Address Offset: 0x0008 */ +#define RK_DMA_CMN_CTL1 RK_DMA_CMN_REG(0x000c) /* Address Offset: 0x000C */ +#define RK_DMA_CMN_AXICTL RK_DMA_CMN_REG(0x0010) /* Address Offset: 0x0010 */ +#define RK_DMA_CMN_DYNCTL RK_DMA_CMN_REG(0x0014) /* Address Offset: 0x0014 */ +#define RK_DMA_CMN_IS0 RK_DMA_CMN_REG(0x0018) /* Address Offset: 0x0018 */ +#define RK_DMA_CMN_IS1 RK_DMA_CMN_REG(0x001c) /* Address Offset: 0x001C */ +#define RK_DMA_CMN_PERISEL0 RK_DMA_CMN_REG(0x0020) /* Address Offset: 0x0020 */ +#define RK_DMA_CMN_PERISEL1 RK_DMA_CMN_REG(0x0024) /* Address Offset: 0x0024 */ +#define RK_DMA_CMN_CAP0 RK_DMA_CMN_REG(0x0030) /* Address Offset: 0x0030 */ +#define RK_DMA_CMN_CAP1 RK_DMA_CMN_REG(0x0034) /* Address Offset: 0x0034 */ + +/* RK_DMA_Logic Channel Register Define */ +#define RK_DMA_LCH_CTL RK_DMA_LCH_REG(0x0000) /* Address Offset: 0x0000 */ +#define RK_DMA_LCH_BIND RK_DMA_LCH_REG(0x0004) /* Address Offset: 0x0004 */ +#define RK_DMA_LCH_CMDBA RK_DMA_LCH_REG(0x0008) /* Address Offset: 0x0008 */ +#define RK_DMA_LCH_TRF_CMD RK_DMA_LCH_REG(0x000c) /* Address Offset: 0x000C */ +#define RK_DMA_LCH_TRF_PARA RK_DMA_LCH_REG(0x0010) /* Address Offset: 0x0010 */ +#define RK_DMA_LCH_IS RK_DMA_LCH_REG(0x0014) /* Address Offset: 0x0014 */ +#define RK_DMA_LCH_IE RK_DMA_LCH_REG(0x0018) /* Address Offset: 0x0018 */ +#define RK_DMA_LCH_DBG RK_DMA_LCH_REG(0x001c) /* Address Offset: 0x001C */ + +/* RK_DMA_Logic Channel-N Register Define */ +#define RK_DMA_LCHn_CTL(n) RK_DMA_LCHn_REG(n, 0x0000) /* Address Offset: 0x0000 */ +#define RK_DMA_LCHn_BIND(n) RK_DMA_LCHn_REG(n, 0x0004) /* Address Offset: 0x0004 */ +#define RK_DMA_LCHn_CMDBA(n) RK_DMA_LCHn_REG(n, 0x0008) /* Address Offset: 0x0008 */ +#define RK_DMA_LCHn_TRF_CMD(n) RK_DMA_LCHn_REG(n, 0x000c) /* Address Offset: 0x000C */ +#define RK_DMA_LCHn_TRF_PARA(n) RK_DMA_LCHn_REG(n, 0x0010) /* Address Offset: 0x0010 */ +#define RK_DMA_LCHn_IS(n) RK_DMA_LCHn_REG(n, 0x0014) /* Address Offset: 0x0014 */ +#define RK_DMA_LCHn_IE(n) RK_DMA_LCHn_REG(n, 0x0018) /* Address Offset: 0x0018 */ +#define RK_DMA_LCHn_DBG(n) RK_DMA_LCHn_REG(n, 0x001c) /* Address Offset: 0x001C */ + +/* RK_DMA_Phycial Channel Register Define */ +#define RK_DMA_PCHn_CTL(n) RK_DMA_PCHn_REG(n, 0x0000) /* Address Offset: 0x0000 */ +#define RK_DMA_PCHn_BIND(n) RK_DMA_PCHn_REG(n, 0x0004) /* Address Offset: 0x0004 */ +#define RK_DMA_PCHn_CMDBA(n) RK_DMA_PCHn_REG(n, 0x0008) /* Address Offset: 0x0008 */ +#define RK_DMA_PCHn_DBG(n) RK_DMA_PCHn_REG(n, 0x000c) /* Address Offset: 0x000C */ +#define RK_DMA_PCHn_TRF_CTL0(n) RK_DMA_PCHn_REG(n, 0x0010) /* Address Offset: 0x0010 */ +#define RK_DMA_PCHn_TRF_CTL1(n) RK_DMA_PCHn_REG(n, 0x0014) /* Address Offset: 0x0014 */ +#define RK_DMA_PCHn_SAR(n) RK_DMA_PCHn_REG(n, 0x0018) /* Address Offset: 0x0018 */ +#define RK_DMA_PCHn_DAR(n) RK_DMA_PCHn_REG(n, 0x001c) /* Address Offset: 0x001C */ +#define RK_DMA_PCHn_BLOCK_TS(n) RK_DMA_PCHn_REG(n, 0x0020) /* Address Offset: 0x0020 */ +#define RK_DMA_PCHn_LLP_NXT(n) RK_DMA_PCHn_REG(n, 0x0024) /* Address Offset: 0x0024 */ +#define RK_DMA_PCHn_LLP_CUR(n) RK_DMA_PCHn_REG(n, 0x0028) /* Address Offset: 0x0028 */ +#define RK_DMA_PCHn_TRF_CFG(n) RK_DMA_PCHn_REG(n, 0x002c) /* Address Offset: 0x002C */ +#define RK_DMA_PCHn_BLOCK_CS(n) RK_DMA_PCHn_REG(n, 0x0030) /* Address Offset: 0x0030 */ +#define RK_DMA_PCHn_PCH_RSV1(n) RK_DMA_PCHn_REG(n, 0x0034) /* Address Offset: 0x0034 */ +#define RK_DMA_PCHn_SAR_RELOAD(n) RK_DMA_PCHn_REG(n, 0x0038) /* Address Offset: 0x0038 */ +#define RK_DMA_PCHn_DAR_RELOAD(n) RK_DMA_PCHn_REG(n, 0x003c) /* Address Offset: 0x003C */ + +/* CMN_VER */ +#define CMN_VER_MAJOR(v) GENMASK_VAL(v, 31, 16) +#define CMN_VER_MINOR(v) GENMASK_VAL(v, 15, 0) + +/* CMN_CFG */ +#define CMN_CFG_EN HIWORD_UPDATE(1, 0, 0) +#define CMN_CFG_DIS HIWORD_UPDATE(0, 0, 0) +#define CMN_CFG_SRST HIWORD_UPDATE(1, 1, 1) +#define CMN_CFG_IE_EN HIWORD_UPDATE(1, 2, 2) +#define CMN_CFG_IE_DIS HIWORD_UPDATE(0, 2, 2) + +/* CMN_AXICTL */ +#define CMN_AXICTL_AWLOCK_MASK GENMASK(1, 0) +#define CMN_AXICTL_AWCACHE_MASK GENMASK(5, 2) +#define CMN_AXICTL_AWPROT_MASK GENMASK(8, 6) +#define CMN_AXICTL_ARLOCK_MASK GENMASK(17, 16) +#define CMN_AXICTL_ARCACHE_MASK GENMASK(21, 18) +#define CMN_AXICTL_ARPROT_MASK GENMASK(24, 22) + +/* CMN_DYNCTL */ +#define CMN_DYNCTL_LCH_DYNEN_MASK BIT(0) +#define CMN_DYNCTL_LCH_DYNEN_EN BIT(0) +#define CMN_DYNCTL_LCH_DYNEN_DIS 0 +#define CMN_DYNCTL_PCH_DYNEN_MASK BIT(1) +#define CMN_DYNCTL_PCH_DYNEN_EN BIT(1) +#define CMN_DYNCTL_PCH_DYNEN_DIS 0 +#define CMN_DYNCTL_AXI_DYNEN_MASK BIT(2) +#define CMN_DYNCTL_AXI_DYNEN_EN BIT(2) +#define CMN_DYNCTL_AXI_DYNEN_DIS 0 +#define CMN_DYNCTL_RREQ_ARB_DYNEN_MASK BIT(3) +#define CMN_DYNCTL_RREQ_ARB_DYNEN_EN BIT(3) +#define CMN_DYNCTL_RREQ_ARB_DYNEN_DIS 0 +#define CMN_DYNCTL_WREQ_ARB_DYNEN_MASK BIT(4) +#define CMN_DYNCTL_WREQ_ARB_DYNEN_EN BIT(4) +#define CMN_DYNCTL_WREQ_ARB_DYNEN_DIS 0 +#define CMN_DYNCTL_BIND_ARB_DYNEN_MASK BIT(5) +#define CMN_DYNCTL_BIND_ARB_DYNEN_EN BIT(5) +#define CMN_DYNCTL_BIND_ARB_DYNEN_DIS 0 + +/* CMN_CAP0 */ +#define CMN_LCH_NUM(v) (GENMASK_VAL(v, 5, 0) + 1) +#define CMN_PCH_NUM(v) (GENMASK_VAL(v, 11, 6) + 1) + +/* CMN_CAP1 */ +#define CMN_AXI_SIZE(v) (1 << GENMASK_VAL(v, 2, 0)) +#define CMN_AXI_LEN(v) (GENMASK_VAL(v, 10, 3) + 1) + +/* LCH_CTL */ +#define LCH_CTL_CH_EN HIWORD_UPDATE(1, 0, 0) +#define LCH_CTL_CH_DIS HIWORD_UPDATE(0, 0, 0) +#define LCH_CTL_CH_SRST HIWORD_UPDATE(1, 1, 1) + +/* LCH_BIND */ +#define LCH_BIND_BIND_VLD_MASK BIT(0) +#define LCH_BIND_BIND_VLD_BIND BIT(0) +#define LCH_BIND_BIND_VLD_UNBIND 0 +#define LCH_BIND_BIND_PIDX_MASK GENMASK(4, 1) +#define LCH_BIND_BIND_PRIOR_MASK GENMASK(17, 16) +#define LCH_BIND_BIND_PRIOR(x) ((x) << 16) +#define LCH_BIND_BIND_PIDX_S_MASK GENMASK(27, 24) +#define LCH_BIND_BIND_PIDX_S(x) ((x) << 24) +#define LCH_BIND_BIND_SEN_MASK BIT(31) +#define LCH_BIND_BIND_SEN_EN BIT(31) +#define LCH_BIND_BIND_SEN_DIS 0 + +/* LCH_TRF_CMD */ +#define LCH_TRF_CMD_DMA_START HIWORD_UPDATE(1, 0, 0) +#define LCH_TRF_CMD_DMA_KILL HIWORD_UPDATE(1, 1, 1) +#define LCH_TRF_CMD_DMA_RESUME HIWORD_UPDATE(1, 2, 2) +#define LCH_TRF_CMD_DMA_FLUSH HIWORD_UPDATE(1, 3, 3) +#define LCH_TRF_CMD_P2P_INFI_EN HIWORD_UPDATE(1, 9, 9) +#define LCH_TRF_CMD_P2P_INFI_DIS HIWORD_UPDATE(0, 9, 9) +#define LCH_TRF_CMD_SRC_MT(x) HIWORD_UPDATE(x, 11, 10) +#define LCH_TRF_CMD_DST_MT(x) HIWORD_UPDATE(x, 13, 12) +#define LCH_TRF_CMD_TT_FC(x) HIWORD_UPDATE(x, 15, 14) + +/* LCH_TRF_PARA */ +#define LCH_TRF_PARA_LLI_VALID_MASK BIT(0) +#define LCH_TRF_PARA_LLI_VALID BIT(0) +#define LCH_TRF_PARA_LLI_INVALID 0 +#define LCH_TRF_PARA_LLI_LAST_MASK BIT(1) +#define LCH_TRF_PARA_LLI_LAST BIT(1) +#define LCH_TRF_PARA_LLI_WAIT_MASK BIT(2) +#define LCH_TRF_PARA_LLI_WAIT_EN BIT(2) +#define LCH_TRF_PARA_IOC_MASK BIT(3) +#define LCH_TRF_PARA_IOC_EN BIT(3) + +/* LCH_IS */ +#define LCH_IS_DMA_DONE_IS BIT(0) +#define LCH_IS_BLOCK_DONE_IS BIT(1) +#define LCH_IS_SRC_BTRANS_DONE_IS BIT(2) +#define LCH_IS_DST_BTRANS_DONE_IS BIT(3) +#define LCH_IS_SRC_STRANS_DONE_IS BIT(4) +#define LCH_IS_DST_STRANS_DONE_IS BIT(5) +#define LCH_IS_SRC_RD_ERR_IS BIT(6) +#define LCH_IS_DST_WR_ERR_IS BIT(7) +#define LCH_IS_CMD_RD_ERR_IS BIT(8) +#define LCH_IS_CMD_WR_ERR_IS BIT(9) +#define LCH_IS_LLI_RD_ERR_IS BIT(10) +#define LCH_IS_LLI_INVALID_IS BIT(11) +#define LCH_IS_LLI_WAIT_IS BIT(12) + +/* LCH_IE */ +#define LCH_IE_DMA_DONE_IE_MASK BIT(0) +#define LCH_IE_DMA_DONE_IE_EN BIT(0) +#define LCH_IE_DMA_DONE_IE_DIS 0 +#define LCH_IE_BLOCK_DONE_IE_MASK BIT(1) +#define LCH_IE_BLOCK_DONE_IE_EN BIT(1) +#define LCH_IE_BLOCK_DONE_IE_DIS 0 +#define LCH_IE_SRC_BTRANS_DONE_IE_MASK BIT(2) +#define LCH_IE_SRC_BTRANS_DONE_IE_EN BIT(2) +#define LCH_IE_SRC_BTRANS_DONE_IE_DIS 0 +#define LCH_IE_DST_BTRANS_DONE_IE_MASK BIT(3) +#define LCH_IE_DST_BTRANS_DONE_IE_EN BIT(3) +#define LCH_IE_DST_BTRANS_DONE_IE_DIS 0 +#define LCH_IE_SRC_STRANS_DONE_IE_MASK BIT(4) +#define LCH_IE_SRC_STRANS_DONE_IE_EN BIT(4) +#define LCH_IE_SRC_STRANS_DONE_IE_DIS 0 +#define LCH_IE_DST_STRANS_DONE_IE_MASK BIT(5) +#define LCH_IE_DST_STRANS_DONE_IE_EN BIT(5) +#define LCH_IE_DST_STRANS_DONE_IE_DIS 0 +#define LCH_IE_SRC_RD_ERR_IE_MASK BIT(6) +#define LCH_IE_SRC_RD_ERR_IE_EN BIT(6) +#define LCH_IE_SRC_RD_ERR_IE_DIS 0 +#define LCH_IE_DST_WR_ERR_IE_MASK BIT(7) +#define LCH_IE_DST_WR_ERR_IE_EN BIT(7) +#define LCH_IE_DST_WR_ERR_IE_DIS 0 +#define LCH_IE_CMD_RD_ERR_IE_MASK BIT(8) +#define LCH_IE_CMD_RD_ERR_IE_EN BIT(8) +#define LCH_IE_CMD_RD_ERR_IE_DIS 0 +#define LCH_IE_CMD_WR_ERR_IE_MASK BIT(9) +#define LCH_IE_CMD_WR_ERR_IE_EN BIT(9) +#define LCH_IE_CMD_WR_ERR_IE_DIS 0 +#define LCH_IE_LLI_RD_ERR_IE_MASK BIT(10) +#define LCH_IE_LLI_RD_ERR_IE_EN BIT(10) +#define LCH_IE_LLI_RD_ERR_IE_DIS 0 +#define LCH_IE_LLI_INVALID_IE_MASK BIT(11) +#define LCH_IE_LLI_INVALID_IE_EN BIT(11) +#define LCH_IE_LLI_INVALID_IE_DIS 0 +#define LCH_IE_LLI_WAIT_IE_MASK BIT(12) +#define LCH_IE_LLI_WAIT_IE_EN BIT(12) +#define LCH_IE_LLI_WAIT_IE_DIS 0 + +/* LCH_DBG */ +#define LCH_DBG_DMA_CS_MASK GENMASK(3, 0) + +/* PCH_CTL */ +#define PCH_CTL_CH_EN HIWORD_UPDATE(1, 0, 0) +#define PCH_CTL_CH_DIS HIWORD_UPDATE(0, 0, 0) +#define PCH_CTL_CH_SRST HIWORD_UPDATE(1, 1, 1) + +/* PCH_BIND */ +#define PCH_BIND_BIND_VLD_MASK BIT(0) +#define PCH_BIND_BIND_VLD_BIND BIT(0) +#define PCH_BIND_BIND_VLD_UNBIND 0 +#define PCH_BIND_BIND_LIDX_MASK GENMASK(6, 1) +#define PCH_BIND_BIND_SBURST_MASK BIT(7) +#define PCH_BIND_BIND_DBURST_MASK BIT(8) +#define PCH_BIND_BIND_SEN_MASK BIT(31) +#define PCH_BIND_BIND_SEN_EN BIT(31) +#define PCH_BIND_BIND_SEN_DIS 0 + +/* PCH_DBG */ +#define PCH_DBG_SRC_CS_MASK GENMASK(2, 0) +#define PCH_DBG_DST_CS_MASK GENMASK(5, 3) +#define PCH_DBG_CMD_CS_MASK GENMASK(8, 6) + +/* TRF_CTL0 */ +#define PCH_TRF_CTL0_LLI_VALID_MASK BIT(0) +#define PCH_TRF_CTL0_LLI_VALID BIT(0) +#define PCH_TRF_CTL0_LLI_INVALID 0 +#define PCH_TRF_CTL0_LLI_LAST_MASK BIT(1) +#define PCH_TRF_CTL0_LLI_LAST BIT(1) +#define PCH_TRF_CTL0_LLI_WAIT_MASK BIT(2) +#define PCH_TRF_CTL0_LLI_WAIT_EN BIT(2) +#define PCH_TRF_CTL0_IOC_MASK BIT(3) +#define PCH_TRF_CTL0_IOC_EN BIT(3) +#define PCH_TRF_CTL0_MSIZE_MASK GENMASK(31, 15) +#define PCH_TRF_CTL0_MSIZE(x) ((x) << 15) + +/* TRF_CTL1 */ +#define PCH_TRF_CTL1_ARBURST_MASK BIT(0) +#define PCH_TRF_CTL1_ARBURST_INCR BIT(0) +#define PCH_TRF_CTL1_ARBURST_FIXED 0 +#define PCH_TRF_CTL1_ARSIZE_MASK GENMASK(4, 2) +#define PCH_TRF_CTL1_ARSIZE(x) ((x) << 2) +#define PCH_TRF_CTL1_ARLEN_MASK GENMASK(8, 5) +#define PCH_TRF_CTL1_ARLEN(x) ((x - 1) << 5) +#define PCH_TRF_CTL1_AROSR_MASK GENMASK(15, 11) +#define PCH_TRF_CTL1_AROSR(x) ((x) << 11) +#define PCH_TRF_CTL1_AWBURST_MASK BIT(16) +#define PCH_TRF_CTL1_AWBURST_INCR BIT(16) +#define PCH_TRF_CTL1_AWBURST_FIXED 0 +#define PCH_TRF_CTL1_AWSIZE_MASK GENMASK(20, 18) +#define PCH_TRF_CTL1_AWSIZE(x) ((x) << 18) +#define PCH_TRF_CTL1_AWLEN_MASK GENMASK(24, 21) +#define PCH_TRF_CTL1_AWLEN(x) ((x - 1) << 21) +#define PCH_TRF_CTL1_AWOSR_MASK GENMASK(31, 27) +#define PCH_TRF_CTL1_AWOSR(x) ((x) << 27) + +/* BLOCK_TS */ +#define PCH_BLOCK_TS_MASK GENMASK(24, 0) +#define PCH_BLOCK_TS(x) ((x) & GENMASK(24, 0)) + +/* TRF_CFG */ +#define PCH_TRF_CFG_SRC_MT_MASK GENMASK(1, 0) +#define PCH_TRF_CFG_SRC_MT(x) ((x) << 0) +#define PCH_TRF_CFG_DST_MT_MASK GENMASK(5, 4) +#define PCH_TRF_CFG_DST_MT(x) ((x) << 4) +#define PCH_TRF_CFG_TT_FC_MASK GENMASK(9, 8) +#define PCH_TRF_CFG_TT_FC(x) ((x) << 8) +#define PCH_TRF_CFG_P2P_INFI_MASK BIT(13) +#define PCH_TRF_CFG_P2P_INFI_EN BIT(13) +#define PCH_TRF_CFG_P2P_INFI_DIS 0 +#define PCH_TRF_CFG_STRIDE_EN_MASK BIT(14) +#define PCH_TRF_CFG_STRIDE_EN BIT(14) +#define PCH_TRF_CFG_STRIDE_DIS 0 +#define PCH_TRF_CFG_STRIDE_INC_MASK GENMASK(31, 15) +#define PCH_TRF_CFG_STRIDE_INC(x) ((x) << 15) + +/* BLOCK_CS */ +#define PCH_BLOCK_CS_MASK GENMASK(24, 0) +#define PCH_BLOCK_CS(x) ((x) & GENMASK(24, 0)) +#define PCH_BLOCK_CS_ON_MASK BIT(31) +#define PCH_BLOCK_CS_ON BIT(31) +#define PCH_BLOCK_CS_IDLE 0 + +#define to_rk_dma(dmadev) container_of(dmadev, struct rk_dma_dev, slave) + +enum rk_dma_mt_transfer_type { + DMA_MT_TRANSFER_CONTIGUOUS, + DMA_MT_TRANSFER_AUTO_RELOAD, + DMA_MT_TRANSFER_LINK_LIST, +}; + +enum rk_dma_lch_state { + DMA_LCH_IDLE, + DMA_LCH_BLOCK_START, + DMA_LCH_PERI_WAIT, + DMA_LCH_BIND_REQ, + DMA_LCH_TRANS, + DMA_LCH_MEM_BDONE, + DMA_LCH_PERI_BDONE, + DMA_LCH_PERI_BNDONE, + DMA_LCH_BLOCK_DONE, + DMA_LCH_SUSPD, + DMA_LCH_ERR, + DMA_LCH_DONE, +}; + +enum rk_dma_pch_trans_state { + DMA_PCH_TRANS_IDLE, + DMA_PCH_TRANS_CHK_REQ, + DMA_PCH_TRANS_REQ, + DMA_PCH_TRANS_RESP, + DMA_PCH_TRANS_END, + DMA_PCH_TRANS_ERR, +}; + +enum rk_dma_pch_cmd_state { + DMA_PCH_CMD_IDLE, + DMA_PCH_CMD_ENTRY_RD, + DMA_PCH_CMD_LLI_RD, + DMA_PCH_CMD_DAT_TRANS, + DMA_PCH_CMD_ENTRY_WR, + DMA_PCH_CMD_REL, + DMA_PCH_CMD_ERR, +}; + +enum rk_dma_burst_width { + DMA_BURST_WIDTH_1_BYTE, + DMA_BURST_WIDTH_2_BYTES, + DMA_BURST_WIDTH_4_BYTES, + DMA_BURST_WIDTH_8_BYTES, + DMA_BURST_WIDTH_16_BYTES, +}; + +struct rk_desc_hw { + u32 trf_ctl0; + u32 trf_ctl1; + u32 sar; + u32 dar; + u32 block_ts; + u32 llp_nxt; + u32 llp_cur; + u32 trf_cfg; + u32 block_cs; + u32 rsv; + u32 sar_reload; + u32 dar_reload; + u32 rsvd[4]; +} __aligned(32); + +struct rk_dma_desc_sw { + struct virt_dma_desc vd; + struct rk_desc_hw *desc_hw; + dma_addr_t desc_hw_lli; + size_t desc_num; + size_t size; + enum dma_transfer_direction dir; +}; + +struct rk_dma_lch; + +struct rk_dma_chan { + struct virt_dma_chan vc; + struct rk_dma_lch *lch; + struct list_head node; + struct dma_slave_config slave_cfg; + u32 id; /* lch chan id */ + u32 ctl0; + u32 ctl1; + u32 ccfg; + u32 cyclic; + dma_addr_t dev_addr; + enum dma_status status; +}; + +struct rk_dma_lch { + struct rk_dma_chan *vchan; + struct rk_dma_desc_sw *ds_run; + struct rk_dma_desc_sw *ds_done; + void __iomem *base; + u32 id; +}; + +struct rk_dma_dev { + struct dma_device slave; + struct list_head chan_pending; + struct rk_dma_lch *lch; + struct rk_dma_chan *chans; + struct clk *clk; + struct dma_pool *pool; + void __iomem *base; + int irq; + u32 bus_width; + u32 dma_channels; + u32 dma_requests; + u32 version; + spinlock_t lock; /* lock for ch and lch */ +}; + +static struct rk_dma_chan *to_rk_chan(struct dma_chan *chan) +{ + return container_of(chan, struct rk_dma_chan, vc.chan); +} + +static void rk_dma_terminate_chan(struct rk_dma_lch *l, struct rk_dma_dev *d) +{ + writel(LCH_CTL_CH_DIS, RK_DMA_LCH_CTL); + writel(0x0, RK_DMA_LCH_IE); + writel(readl(RK_DMA_LCH_IS), RK_DMA_LCH_IS); +} + +static void rk_dma_set_desc(struct rk_dma_chan *c, struct rk_dma_desc_sw *ds) +{ + struct rk_dma_lch *l = c->lch; + + writel(LCH_CTL_CH_EN, RK_DMA_LCH_CTL); + + if (c->cyclic) + writel(LCH_IE_BLOCK_DONE_IE_EN, RK_DMA_LCH_IE); + else + writel(LCH_IE_DMA_DONE_IE_EN, RK_DMA_LCH_IE); + + writel(ds->desc_hw_lli, RK_DMA_LCH_CMDBA); + writel(LCH_TRF_CMD_DST_MT(DMA_MT_TRANSFER_LINK_LIST) | + LCH_TRF_CMD_SRC_MT(DMA_MT_TRANSFER_LINK_LIST) | + LCH_TRF_CMD_TT_FC(ds->dir) | LCH_TRF_CMD_DMA_START, + RK_DMA_LCH_TRF_CMD); + + dev_dbg(c->vc.chan.device->dev, + "%s: id: %d, desc_sw: %px, desc_hw_lli: %pad\n", + __func__, l->id, ds, &ds->desc_hw_lli); +} + +static u32 rk_dma_get_chan_stat(struct rk_dma_lch *l) +{ + return readl(RK_DMA_LCH_CTL); +} + +static int rk_dma_init(struct rk_dma_dev *d) +{ + struct device *dev = d->slave.dev; + int i, lch, pch, buswidth, maxburst; + u32 cap0, cap1, ver; + + writel(CMN_CFG_EN | CMN_CFG_IE_EN, RK_DMA_CMN_CFG); + + ver = readl(RK_DMA_CMN_VER); + cap0 = readl(RK_DMA_CMN_CAP0); + cap1 = readl(RK_DMA_CMN_CAP1); + + lch = CMN_LCH_NUM(cap0); + pch = CMN_PCH_NUM(cap0); + + buswidth = CMN_AXI_SIZE(cap1); + maxburst = CMN_AXI_LEN(cap1); + + d->version = ver; + d->bus_width = buswidth; + d->dma_channels = CMN_LCH_NUM(cap0); + d->dma_requests = CMN_LCH_NUM(cap0); + + writel(0xffffffff, RK_DMA_CMN_DYNCTL); + writel(0xffffffff, RK_DMA_CMN_IS0); + writel(0xffffffff, RK_DMA_CMN_IS1); + + for (i = 0; i < pch; i++) + writel(PCH_CTL_CH_EN, RK_DMA_PCHn_CTL(i)); + + dev_info(dev, "NR_LCH-%d NR_PCH-%d AXI_SIZE-%dBytes AXI_LEN-%d V%lu.%lu\n", + lch, pch, buswidth, maxburst, + CMN_VER_MAJOR(ver), CMN_VER_MINOR(ver)); + + return 0; +} + +static int rk_dma_start_txd(struct rk_dma_chan *c) +{ + struct virt_dma_desc *vd = vchan_next_desc(&c->vc); + + if (!c->lch) + return -EAGAIN; + + if (BIT(c->lch->id) & rk_dma_get_chan_stat(c->lch)) + return -EAGAIN; + + if (vd) { + struct rk_dma_desc_sw *ds = container_of(vd, struct rk_dma_desc_sw, vd); + /* + * fetch and remove request from vc->desc_issued + * so vc->desc_issued only contains desc pending + */ + list_del(&ds->vd.node); + c->lch->ds_run = ds; + c->lch->ds_done = NULL; + /* start dma */ + rk_dma_set_desc(c, ds); + return 0; + } + + c->lch->ds_done = NULL; + c->lch->ds_run = NULL; + + return -EAGAIN; +} + +static void rk_dma_task(struct rk_dma_dev *d) +{ + struct rk_dma_lch *l; + struct rk_dma_chan *c, *cn; + unsigned int i = 0, lch_alloc = 0; + unsigned long flags; + + /* check new dma request of running channel in vc->desc_issued */ + list_for_each_entry_safe(c, cn, &d->slave.channels, vc.chan.device_node) { + spin_lock_irqsave(&c->vc.lock, flags); + l = c->lch; + if (l && l->ds_done && rk_dma_start_txd(c)) { + dev_dbg(d->slave.dev, "lch-%u: free\n", l->id); + rk_dma_terminate_chan(l, d); + c->lch = NULL; + l->vchan = NULL; + } + spin_unlock_irqrestore(&c->vc.lock, flags); + } + + /* check new channel request in d->chan_pending */ + spin_lock_irqsave(&d->lock, flags); + while (!list_empty(&d->chan_pending)) { + c = list_first_entry(&d->chan_pending, struct rk_dma_chan, node); + l = &d->lch[c->id]; + if (!l->vchan) { + list_del_init(&c->node); + lch_alloc |= 1 << c->id; + l->vchan = c; + c->lch = l; + } else { + dev_dbg(d->slave.dev, "lch-%u: busy\n", l->id); + } + } + spin_unlock_irqrestore(&d->lock, flags); + + for (i = 0; i < d->dma_channels; i++) { + if (lch_alloc & (1 << i)) { + l = &d->lch[i]; + c = l->vchan; + if (c) { + spin_lock_irqsave(&c->vc.lock, flags); + rk_dma_start_txd(c); + spin_unlock_irqrestore(&c->vc.lock, flags); + } + } + } +} + +static irqreturn_t rk_dma_irq_handler(int irq, void *dev_id) +{ + struct rk_dma_dev *d = (struct rk_dma_dev *)dev_id; + struct rk_dma_lch *l; + struct rk_dma_chan *c; + u64 is = 0, is_raw = 0; + u32 i = 0, task = 0; + + is = readq(RK_DMA_CMN_IS0); + is_raw = is; + while (is) { + i = __ffs64(is); + is &= ~BIT(i); + l = &d->lch[i]; + c = l->vchan; + if (c) { + spin_lock(&c->vc.lock); + if (c->cyclic) { + vchan_cyclic_callback(&l->ds_run->vd); + } else { + vchan_cookie_complete(&l->ds_run->vd); + l->ds_done = l->ds_run; + task = 1; + } + spin_unlock(&c->vc.lock); + writel(readl(RK_DMA_LCH_IS), RK_DMA_LCH_IS); + } + } + + writeq(is_raw, RK_DMA_CMN_IS0); + + if (task) + rk_dma_task(d); + + return IRQ_HANDLED; +} + +static void rk_dma_free_chan_resources(struct dma_chan *chan) +{ + struct rk_dma_chan *c = to_rk_chan(chan); + struct rk_dma_dev *d = to_rk_dma(chan->device); + unsigned long flags; + + spin_lock_irqsave(&d->lock, flags); + list_del_init(&c->node); + spin_unlock_irqrestore(&d->lock, flags); + + vchan_free_chan_resources(&c->vc); + c->ccfg = 0; + c->ctl0 = 0; + c->ctl1 = 0; +} + +static int rk_dma_lch_get_bytes_xfered(struct rk_dma_lch *l) +{ + struct rk_dma_desc_sw *ds = l->ds_run; + int bytes = 0; + + if (!ds) + return 0; + + /* cmd_entry holds the current LLI being processed */ + if (ds->dir == DMA_MEM_TO_DEV) + bytes = ds->desc_hw[0].sar - ds->desc_hw[1].sar; + else + bytes = ds->desc_hw[0].dar - ds->desc_hw[1].dar; + + return bytes; +} + +static enum dma_status rk_dma_tx_status(struct dma_chan *chan, + dma_cookie_t cookie, + struct dma_tx_state *state) +{ + struct rk_dma_chan *c = to_rk_chan(chan); + struct rk_dma_lch *l; + struct virt_dma_desc *vd; + unsigned long flags; + enum dma_status ret; + size_t bytes = 0; + + ret = dma_cookie_status(&c->vc.chan, cookie, state); + if (ret == DMA_COMPLETE || !state) + return ret; + + spin_lock_irqsave(&c->vc.lock, flags); + l = c->lch; + ret = c->status; + + /* + * If the cookie is on our issue queue, then the residue is + * its total size. + */ + vd = vchan_find_desc(&c->vc, cookie); + if (vd) { + bytes = container_of(vd, struct rk_dma_desc_sw, vd)->size; + } else if ((!l) || (!l->ds_run)) { + bytes = 0; + } else { + bytes = l->ds_run->size - rk_dma_lch_get_bytes_xfered(l); + } + spin_unlock_irqrestore(&c->vc.lock, flags); + dma_set_residue(state, bytes); + + return ret; +} + +static void rk_dma_issue_pending(struct dma_chan *chan) +{ + struct rk_dma_chan *c = to_rk_chan(chan); + struct rk_dma_dev *d = to_rk_dma(chan->device); + unsigned long flags; + int issue = 0; + + spin_lock_irqsave(&c->vc.lock, flags); + /* add request to vc->desc_issued */ + if (vchan_issue_pending(&c->vc)) { + spin_lock(&d->lock); + if (!c->lch && list_empty(&c->node)) { + /* if new channel, add chan_pending */ + list_add_tail(&c->node, &d->chan_pending); + issue = 1; + dev_dbg(d->slave.dev, "vch-%px: id-%u issued\n", &c->vc, c->id); + } + spin_unlock(&d->lock); + } else { + dev_dbg(d->slave.dev, "vch-%px: nothing to issue\n", &c->vc); + } + spin_unlock_irqrestore(&c->vc.lock, flags); + + if (issue) + rk_dma_task(d); +} + +static void rk_dma_fill_desc(struct rk_dma_desc_sw *ds, dma_addr_t dst, + dma_addr_t src, size_t len, u32 num, u32 cc0, u32 cc1, u32 ccfg) +{ + /* assign llp_nxt for cmd_entry */ + if (num == 0) { + ds->desc_hw[0].llp_nxt = ds->desc_hw_lli + sizeof(struct rk_desc_hw); + ds->desc_hw[0].trf_cfg = ccfg; + + return; + } + + if ((num + 1) < ds->desc_num) + ds->desc_hw[num].llp_nxt = ds->desc_hw_lli + (num + 1) * sizeof(struct rk_desc_hw); + + ds->desc_hw[num].sar = src; + ds->desc_hw[num].dar = dst; + ds->desc_hw[num].block_ts = len; + ds->desc_hw[num].trf_ctl0 = cc0; + ds->desc_hw[num].trf_ctl1 = cc1; + ds->desc_hw[num].trf_cfg = ccfg; +} + +static struct rk_dma_desc_sw *rk_alloc_desc_resource(int num, struct dma_chan *chan) +{ + struct rk_dma_chan *c = to_rk_chan(chan); + struct rk_dma_desc_sw *ds; + struct rk_dma_dev *d = to_rk_dma(chan->device); + int lli_limit = LLI_BLOCK_SIZE / sizeof(struct rk_desc_hw); + + if (num > lli_limit) { + dev_err(chan->device->dev, "vch-%px: sg num %d exceed max %d\n", + &c->vc, num, lli_limit); + return NULL; + } + + ds = kzalloc(sizeof(*ds), GFP_ATOMIC); + if (!ds) + return NULL; + + ds->desc_hw = dma_pool_zalloc(d->pool, GFP_NOWAIT, &ds->desc_hw_lli); + if (!ds->desc_hw) { + dev_err(chan->device->dev, "vch-%px: dma alloc fail\n", &c->vc); + kfree(ds); + return NULL; + } + ds->desc_num = num; + + dev_dbg(chan->device->dev, "vch-%px, desc_sw: %px, desc_hw_lli: %pad\n", + &c->vc, ds, &ds->desc_hw_lli); + + return ds; +} + +static enum rk_dma_burst_width rk_dma_burst_width(enum dma_slave_buswidth width) +{ + switch (width) { + case DMA_SLAVE_BUSWIDTH_1_BYTE: + case DMA_SLAVE_BUSWIDTH_2_BYTES: + case DMA_SLAVE_BUSWIDTH_4_BYTES: + case DMA_SLAVE_BUSWIDTH_8_BYTES: + return ffs(width) - 1; + default: + return DMA_BURST_WIDTH_4_BYTES; + } +} + +static int rk_dma_set_perisel(struct dma_chan *chan, enum dma_transfer_direction dir) +{ + struct rk_dma_chan *c = to_rk_chan(chan); + struct rk_dma_dev *d = to_rk_dma(chan->device); + u64 perisel; + + perisel = readq(RK_DMA_CMN_PERISEL0); + + if (dir == DMA_MEM_TO_DEV) + perisel |= BIT(c->id); + else + perisel &= ~BIT(c->id); + + writeq(perisel, RK_DMA_CMN_PERISEL0); + + return 0; +} + +static int rk_pre_config(struct dma_chan *chan, enum dma_transfer_direction dir) +{ + struct rk_dma_chan *c = to_rk_chan(chan); + struct rk_dma_dev *d = to_rk_dma(chan->device); + struct dma_slave_config *cfg = &c->slave_cfg; + enum rk_dma_burst_width src_width; + enum rk_dma_burst_width dst_width; + u32 maxburst = 0; + + rk_dma_set_perisel(chan, dir); + + switch (dir) { + case DMA_MEM_TO_MEM: + /* DMAC use the min(addr_align, bus_width, len) automatically */ + src_width = rk_dma_burst_width(d->bus_width); + c->ctl0 = PCH_TRF_CTL0_LLI_VALID | PCH_TRF_CTL0_MSIZE(0); + c->ctl1 = PCH_TRF_CTL1_AROSR(4) | + PCH_TRF_CTL1_AWOSR(4) | + PCH_TRF_CTL1_ARLEN(RK_MAX_BURST_LEN) | + PCH_TRF_CTL1_AWLEN(RK_MAX_BURST_LEN) | + PCH_TRF_CTL1_ARSIZE(src_width) | + PCH_TRF_CTL1_AWSIZE(src_width) | + PCH_TRF_CTL1_ARBURST_INCR | + PCH_TRF_CTL1_AWBURST_INCR; + c->ccfg = PCH_TRF_CFG_TT_FC(DMA_MEM_TO_MEM) | + PCH_TRF_CFG_DST_MT(DMA_MT_TRANSFER_LINK_LIST) | + PCH_TRF_CFG_SRC_MT(DMA_MT_TRANSFER_LINK_LIST); + break; + case DMA_MEM_TO_DEV: + c->dev_addr = cfg->dst_addr; + /* dst len is calculated from src width, len and dst width. + * We need make sure dst len not exceed MAX LEN. + * Trailing single transaction that does not fill a full + * burst also require identical src/dst data width. + */ + dst_width = rk_dma_burst_width(cfg->dst_addr_width); + maxburst = cfg->dst_maxburst; + maxburst = maxburst < RK_MAX_BURST_LEN ? maxburst : RK_MAX_BURST_LEN; + + c->ctl0 = PCH_TRF_CTL0_MSIZE(maxburst * cfg->dst_addr_width) | + PCH_TRF_CTL0_LLI_VALID; + if (c->cyclic) + c->ctl0 |= PCH_TRF_CTL0_IOC_EN; + c->ctl1 = PCH_TRF_CTL1_AROSR(4) | + PCH_TRF_CTL1_AWOSR(4) | + PCH_TRF_CTL1_ARLEN(maxburst) | + PCH_TRF_CTL1_AWLEN(maxburst) | + PCH_TRF_CTL1_ARSIZE(dst_width) | + PCH_TRF_CTL1_AWSIZE(dst_width) | + PCH_TRF_CTL1_ARBURST_INCR | + PCH_TRF_CTL1_AWBURST_FIXED; + c->ccfg = PCH_TRF_CFG_TT_FC(DMA_MEM_TO_DEV) | + PCH_TRF_CFG_DST_MT(DMA_MT_TRANSFER_LINK_LIST) | + PCH_TRF_CFG_SRC_MT(DMA_MT_TRANSFER_LINK_LIST); + break; + case DMA_DEV_TO_MEM: + c->dev_addr = cfg->src_addr; + src_width = rk_dma_burst_width(cfg->src_addr_width); + maxburst = cfg->src_maxburst; + maxburst = maxburst < RK_MAX_BURST_LEN ? + maxburst : RK_MAX_BURST_LEN; + + c->ctl0 = PCH_TRF_CTL0_MSIZE(maxburst * cfg->src_addr_width) | + PCH_TRF_CTL0_LLI_VALID; + if (c->cyclic) + c->ctl0 |= PCH_TRF_CTL0_IOC_EN; + c->ctl1 = PCH_TRF_CTL1_AROSR(4) | + PCH_TRF_CTL1_AWOSR(4) | + PCH_TRF_CTL1_ARLEN(maxburst) | + PCH_TRF_CTL1_AWLEN(maxburst) | + PCH_TRF_CTL1_ARSIZE(src_width) | + PCH_TRF_CTL1_AWSIZE(src_width) | + PCH_TRF_CTL1_ARBURST_FIXED | + PCH_TRF_CTL1_AWBURST_INCR; + c->ccfg = PCH_TRF_CFG_TT_FC(DMA_DEV_TO_MEM) | + PCH_TRF_CFG_DST_MT(DMA_MT_TRANSFER_LINK_LIST) | + PCH_TRF_CFG_SRC_MT(DMA_MT_TRANSFER_LINK_LIST); + break; + default: + return -EINVAL; + } + return 0; +} + +static struct dma_async_tx_descriptor *rk_dma_prep_memcpy( + struct dma_chan *chan, dma_addr_t dst, dma_addr_t src, + size_t len, unsigned long flags) +{ + struct rk_dma_chan *c = to_rk_chan(chan); + struct rk_dma_desc_sw *ds; + size_t copy = 0; + int num = 0; + + if (!len) + return NULL; + + if (rk_pre_config(chan, DMA_MEM_TO_MEM)) + return NULL; + + num = DIV_ROUND_UP(len, DMA_MAX_SIZE); + + /* one more for cmd entry */ + num++; + + ds = rk_alloc_desc_resource(num, chan); + if (!ds) + return NULL; + + ds->size = len; + ds->dir = DMA_MEM_TO_MEM; + + /* the first one used as cmd entry */ + rk_dma_fill_desc(ds, dst, src, copy, 0, c->ctl0, c->ctl1, c->ccfg); + num = 1; + + do { + copy = min_t(size_t, len, DMA_MAX_SIZE); + rk_dma_fill_desc(ds, dst, src, copy, num++, c->ctl0, c->ctl1, c->ccfg); + + src += copy; + dst += copy; + len -= copy; + } while (len); + + ds->desc_hw[num - 1].llp_nxt = 0; + ds->desc_hw[num - 1].trf_ctl0 |= PCH_TRF_CTL0_LLI_LAST; + + c->cyclic = 0; + + return vchan_tx_prep(&c->vc, &ds->vd, flags); +} + +static struct dma_async_tx_descriptor * +rk_dma_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, unsigned int sglen, + enum dma_transfer_direction dir, unsigned long flags, void *context) +{ + struct rk_dma_chan *c = to_rk_chan(chan); + struct rk_dma_desc_sw *ds; + size_t len, avail, total = 0; + struct scatterlist *sg; + dma_addr_t addr, src = 0, dst = 0; + int num = sglen, i; + + if (!sgl) + return NULL; + + if (rk_pre_config(chan, dir)) + return NULL; + + for_each_sg(sgl, sg, sglen, i) { + avail = sg_dma_len(sg); + if (avail > DMA_MAX_SIZE) + num += DIV_ROUND_UP(avail, DMA_MAX_SIZE) - 1; + } + + ds = rk_alloc_desc_resource(num + 1, chan); + if (!ds) + return NULL; + + c->cyclic = 0; + /* the first one used as cmd entry */ + rk_dma_fill_desc(ds, dst, src, 0, 0, c->ctl0, c->ctl1, c->ccfg); + num = 1; + + for_each_sg(sgl, sg, sglen, i) { + addr = sg_dma_address(sg); + avail = sg_dma_len(sg); + total += avail; + + do { + len = min_t(size_t, avail, DMA_MAX_SIZE); + + if (dir == DMA_MEM_TO_DEV) { + src = addr; + dst = c->dev_addr; + } else if (dir == DMA_DEV_TO_MEM) { + src = c->dev_addr; + dst = addr; + } + + rk_dma_fill_desc(ds, dst, src, len, num++, c->ctl0, c->ctl1, c->ccfg); + + addr += len; + avail -= len; + } while (avail); + } + + ds->desc_hw[num - 1].llp_nxt = 0; /* end of link */ + ds->desc_hw[num - 1].trf_ctl0 |= PCH_TRF_CTL0_LLI_LAST; + ds->size = total; + ds->dir = dir; + return vchan_tx_prep(&c->vc, &ds->vd, flags); +} + +static struct dma_async_tx_descriptor * +rk_dma_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t dma_addr, size_t buf_len, + size_t period_len, enum dma_transfer_direction dir, + unsigned long flags) +{ + struct rk_dma_chan *c = to_rk_chan(chan); + struct rk_dma_desc_sw *ds; + dma_addr_t src = 0, dst = 0; + int num_periods = buf_len / period_len; + int buf = 0, num = 0; + + if (period_len > DMA_MAX_SIZE) { + dev_err(chan->device->dev, "maximum period size exceeded\n"); + return NULL; + } + + c->cyclic = 1; + if (rk_pre_config(chan, dir)) + return NULL; + + ds = rk_alloc_desc_resource(num_periods + 1, chan); + if (!ds) + return NULL; + + /* the first one used as cmd entry */ + rk_dma_fill_desc(ds, dst, src, 0, 0, c->ctl0, c->ctl1, c->ccfg); + num = 1; + + while (buf < buf_len) { + if (dir == DMA_MEM_TO_DEV) { + src = dma_addr; + dst = c->dev_addr; + } else if (dir == DMA_DEV_TO_MEM) { + src = c->dev_addr; + dst = dma_addr; + } + rk_dma_fill_desc(ds, dst, src, period_len, num++, c->ctl0, c->ctl1, c->ccfg); + dma_addr += period_len; + buf += period_len; + } + + ds->desc_hw[num - 1].llp_nxt = ds->desc_hw_lli + sizeof(struct rk_desc_hw); + ds->size = buf_len; + ds->dir = dir; + return vchan_tx_prep(&c->vc, &ds->vd, flags); +} + +static int rk_dma_config(struct dma_chan *chan, struct dma_slave_config *cfg) +{ + struct rk_dma_chan *c = to_rk_chan(chan); + + if (!cfg) + return -EINVAL; + + memcpy(&c->slave_cfg, cfg, sizeof(*cfg)); + + return 0; +} + +static int rk_dma_terminate_all(struct dma_chan *chan) +{ + struct rk_dma_chan *c = to_rk_chan(chan); + struct rk_dma_dev *d = to_rk_dma(chan->device); + struct rk_dma_lch *l = c->lch; + unsigned long flags; + LIST_HEAD(head); + + dev_dbg(d->slave.dev, "vch-%px: terminate all\n", &c->vc); + + spin_lock_irqsave(&d->lock, flags); + list_del_init(&c->node); + spin_unlock_irqrestore(&d->lock, flags); + + spin_lock_irqsave(&c->vc.lock, flags); + if (l) { + rk_dma_terminate_chan(l, d); + if (l->ds_run) + vchan_terminate_vdesc(&l->ds_run->vd); + + c->lch = NULL; + l->vchan = NULL; + l->ds_run = NULL; + l->ds_done = NULL; + } + vchan_get_all_descriptors(&c->vc, &head); + spin_unlock_irqrestore(&c->vc.lock, flags); + + vchan_dma_desc_free_list(&c->vc, &head); + + return 0; +} + +static int rk_dma_transfer_pause(struct dma_chan *chan) +{ + //TBD + return 0; +} + +static int rk_dma_transfer_resume(struct dma_chan *chan) +{ + struct rk_dma_chan *c = to_rk_chan(chan); + struct rk_dma_lch *l = c->lch; + + writel(LCH_TRF_CMD_DMA_RESUME, RK_DMA_LCH_TRF_CMD); + + return 0; +} + +static void rk_dma_free_desc(struct virt_dma_desc *vd) +{ + struct rk_dma_dev *d = to_rk_dma(vd->tx.chan->device); + struct rk_dma_desc_sw *ds = container_of(vd, struct rk_dma_desc_sw, vd); + + dev_dbg(d->slave.dev, "desc_sw: %px free\n", ds); + + dma_pool_free(d->pool, ds->desc_hw, ds->desc_hw_lli); + kfree(ds); +} + +static const struct of_device_id rk_dma_dt_ids[] = { + { .compatible = "rockchip,dma", }, + {} +}; +MODULE_DEVICE_TABLE(of, rk_dma_dt_ids); + +static struct dma_chan *rk_of_dma_simple_xlate(struct of_phandle_args *dma_spec, + struct of_dma *ofdma) +{ + struct rk_dma_dev *d = ofdma->of_dma_data; + unsigned int request = dma_spec->args[0]; + struct dma_chan *chan; + struct rk_dma_chan *c; + + if (request >= d->dma_requests) + return NULL; + + chan = dma_get_any_slave_channel(&d->slave); + if (!chan) { + dev_err(d->slave.dev, "Failed to get chan for req %u\n", request); + return NULL; + } + + c = to_rk_chan(chan); + c->id = request; + + dev_dbg(d->slave.dev, "Xlate lch-%u for req-%u\n", c->id, request); + + return chan; +} + +static int rk_dma_probe(struct platform_device *pdev) +{ + struct rk_dma_dev *d; + int i, ret; + + d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL); + if (!d) + return -ENOMEM; + + d->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(d->base)) + return PTR_ERR(d->base); + + d->clk = devm_clk_get(&pdev->dev, "aclk"); + if (IS_ERR(d->clk)) { + dev_err(&pdev->dev, "Failed to get aclk\n"); + return PTR_ERR(d->clk); + } + + d->irq = platform_get_irq(pdev, 0); + ret = devm_request_irq(&pdev->dev, d->irq, rk_dma_irq_handler, 0, dev_name(&pdev->dev), d); + if (ret) + return ret; + + /* A DMA memory pool for LLIs, align on 4k-bytes boundary */ + d->pool = dmam_pool_create(DRIVER_NAME, &pdev->dev, LLI_BLOCK_SIZE, SZ_4K, 0); + if (!d->pool) + return -ENOMEM; + + spin_lock_init(&d->lock); + INIT_LIST_HEAD(&d->chan_pending); + INIT_LIST_HEAD(&d->slave.channels); + dma_cap_set(DMA_SLAVE, d->slave.cap_mask); + dma_cap_set(DMA_MEMCPY, d->slave.cap_mask); + dma_cap_set(DMA_CYCLIC, d->slave.cap_mask); + dma_cap_set(DMA_PRIVATE, d->slave.cap_mask); + d->slave.dev = &pdev->dev; + d->slave.device_free_chan_resources = rk_dma_free_chan_resources; + d->slave.device_tx_status = rk_dma_tx_status; + d->slave.device_prep_dma_memcpy = rk_dma_prep_memcpy; + d->slave.device_prep_slave_sg = rk_dma_prep_slave_sg; + d->slave.device_prep_dma_cyclic = rk_dma_prep_dma_cyclic; + d->slave.device_issue_pending = rk_dma_issue_pending; + d->slave.device_config = rk_dma_config; + d->slave.device_terminate_all = rk_dma_terminate_all; + d->slave.device_pause = rk_dma_transfer_pause; + d->slave.device_resume = rk_dma_transfer_resume; + d->slave.src_addr_widths = RK_DMA_BUSWIDTHS; + d->slave.dst_addr_widths = RK_DMA_BUSWIDTHS; + d->slave.directions = BIT(DMA_MEM_TO_MEM) | BIT(DMA_MEM_TO_DEV) | BIT(DMA_DEV_TO_MEM); + d->slave.residue_granularity = DMA_RESIDUE_GRANULARITY_BURST; + + platform_set_drvdata(pdev, d); + + /* Enable clock before access registers */ + ret = clk_prepare_enable(d->clk); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to enable clk: %d\n", ret); + return ret; + } + + rk_dma_init(d); + + /* init lch channel */ + d->lch = devm_kcalloc(&pdev->dev, d->dma_channels, sizeof(struct rk_dma_lch), GFP_KERNEL); + if (!d->lch) { + ret = -ENOMEM; + goto err_disable_clk; + } + + for (i = 0; i < d->dma_channels; i++) { + struct rk_dma_lch *l = &d->lch[i]; + + l->id = i; + l->base = RK_DMA_LCHn_REG(i, 0); + } + + /* init virtual channel */ + d->chans = devm_kcalloc(&pdev->dev, d->dma_requests, + sizeof(struct rk_dma_chan), GFP_KERNEL); + if (!d->chans) { + ret = -ENOMEM; + goto err_disable_clk; + } + + for (i = 0; i < d->dma_requests; i++) { + struct rk_dma_chan *c = &d->chans[i]; + + c->status = DMA_IN_PROGRESS; + c->id = i; + INIT_LIST_HEAD(&c->node); + c->vc.desc_free = rk_dma_free_desc; + vchan_init(&c->vc, &d->slave); + } + + ret = dmaenginem_async_device_register(&d->slave); + if (ret) + goto err_disable_clk; + + ret = of_dma_controller_register((&pdev->dev)->of_node, rk_of_dma_simple_xlate, d); + if (ret) + goto err_disable_clk; + + return 0; + +err_disable_clk: + clk_disable_unprepare(d->clk); + + return ret; +} + +static int rk_dma_remove(struct platform_device *pdev) +{ + struct rk_dma_chan *c, *cn; + struct rk_dma_dev *d = platform_get_drvdata(pdev); + + of_dma_controller_free((&pdev->dev)->of_node); + + list_for_each_entry_safe(c, cn, &d->slave.channels, vc.chan.device_node) { + list_del(&c->vc.chan.device_node); + } + clk_disable_unprepare(d->clk); + + return 0; +} + +static int rk_dma_suspend_dev(struct device *dev) +{ + struct rk_dma_dev *d = dev_get_drvdata(dev); + + //TBD dma all chan idle + clk_disable_unprepare(d->clk); + + return 0; +} + +static int rk_dma_resume_dev(struct device *dev) +{ + struct rk_dma_dev *d = dev_get_drvdata(dev); + int ret = 0; + + ret = clk_prepare_enable(d->clk); + if (ret < 0) { + dev_err(d->slave.dev, "Failed to enable clk: %d\n", ret); + return ret; + } + + rk_dma_init(d); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(rk_dma_pmops, rk_dma_suspend_dev, rk_dma_resume_dev); + +static struct platform_driver rk_pdma_driver = { + .driver = { + .name = DRIVER_NAME, + .pm = pm_sleep_ptr(&rk_dma_pmops), + .of_match_table = rk_dma_dt_ids, + }, + .probe = rk_dma_probe, + .remove = rk_dma_remove, +}; + +module_platform_driver(rk_pdma_driver); + +MODULE_DESCRIPTION("Rockchip DMA Driver"); +MODULE_AUTHOR("Sugar.Zhang@rock-chips.com"); +MODULE_LICENSE("GPL"); From 4d9d171391950dabb8faa7a7c27a930ba8f72ec6 Mon Sep 17 00:00:00 2001 From: Andy Yan Date: Thu, 31 Oct 2024 11:31:51 +0800 Subject: [PATCH 9/9] Revert "phy: Add support for INNO MIPI D-PHY" This reverts commit 35a788ded3c4c8f0f57b2d19a13045b4ee737fd0. This driver is unused since linux 5.10, drop it. Change-Id: I5cafde24e924034ed9daed37f64c2bf370603732 Signed-off-by: Andy Yan --- .../phy/phy-rockchip-inno-mipi-dphy.txt | 33 - .../rockchip/phy-rockchip-inno-mipi-dphy.c | 853 ------------------ 2 files changed, 886 deletions(-) delete mode 100644 Documentation/devicetree/bindings/phy/phy-rockchip-inno-mipi-dphy.txt delete mode 100644 drivers/phy/rockchip/phy-rockchip-inno-mipi-dphy.c diff --git a/Documentation/devicetree/bindings/phy/phy-rockchip-inno-mipi-dphy.txt b/Documentation/devicetree/bindings/phy/phy-rockchip-inno-mipi-dphy.txt deleted file mode 100644 index 3fa35647c5a5..000000000000 --- a/Documentation/devicetree/bindings/phy/phy-rockchip-inno-mipi-dphy.txt +++ /dev/null @@ -1,33 +0,0 @@ -ROCKCHIP MIPI DPHY WITH INNO IP BLOCK - -Required properties: - - compatible : must be one of: - "rockchip,rk1808-mipi-dphy"; - "rockchip,rv1126-mipi-dphy"; - - reg : the address offset of register for mipi-dphy configuration. - - #phy-cells : must be 0. See ./phy-bindings.txt for details. - - clocks and clock-names: - - the "pclk" clock is required by the phy module, used to register - configuration - - the "ref" clock is used to get the rate of the reference clock - provided to the PHY module - - clock-output-names: from common clock binding. - See ../clocks/clock-bindings.txt for details. - - #clock-cells : from common clock binding; shall be set to 0. - - resets : phandle to the reset of MIPI DSI PHY APB clock. - - reset-names : should be "apb". - -Example: - mipi_dphy: mipi-dphy@ff370000 { - compatible = "rockchip,rk1808-mipi-dphy"; - reg = <0x0 0xff370000 0x0 0x500>; - clocks = <&cru SCLK_MIPIDSIPHY_REF>, <&cru PCLK_MIPIDSIPHY>; - clock-names = "ref", "pclk"; - clock-output-names = "mipi_dphy_pll"; - #clock-cells = <0>; - resets = <&cru SRST_MIPIDSIPHY_P>; - reset-names = "apb"; - #phy-cells = <0>; - rockchip,grf = <&grf>; - status = "disabled"; - }; diff --git a/drivers/phy/rockchip/phy-rockchip-inno-mipi-dphy.c b/drivers/phy/rockchip/phy-rockchip-inno-mipi-dphy.c deleted file mode 100644 index b49b1aad2a09..000000000000 --- a/drivers/phy/rockchip/phy-rockchip-inno-mipi-dphy.c +++ /dev/null @@ -1,853 +0,0 @@ -/* - * Copyright (c) 2017 Rockchip Electronics Co. Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define UPDATE(x, h, l) (((x) << (l)) & GENMASK((h), (l))) - -/* - * The offset address[7:0] is distributed two parts, one from the bit7 to bit5 - * is the first address, the other from the bit4 to bit0 is the second address. - * when you configure the registers, you must set both of them. The Clock Lane - * and Data Lane use the same registers with the same second address, but the - * first address is different. - */ -#define FIRST_ADDRESS(x) (((x) & 0x7) << 5) -#define SECOND_ADDRESS(x) (((x) & 0x1f) << 0) -#define INNO_PHY_REG(first, second) (FIRST_ADDRESS(first) | \ - SECOND_ADDRESS(second)) - -/* Analog Register Part: reg00 */ -#define BANDGAP_POWER_MASK BIT(7) -#define BANDGAP_POWER_DOWN BIT(7) -#define BANDGAP_POWER_ON 0 -#define LANE_EN_MASK GENMASK(6, 2) -#define LANE_EN_CK BIT(6) -#define LANE_EN_3 BIT(5) -#define LANE_EN_2 BIT(4) -#define LANE_EN_1 BIT(3) -#define LANE_EN_0 BIT(2) -#define POWER_WORK_MASK GENMASK(1, 0) -#define POWER_WORK_ENABLE UPDATE(1, 1, 0) -#define POWER_WORK_DISABLE UPDATE(2, 1, 0) -/* Analog Register Part: reg01 */ -#define REG_SYNCRST_MASK BIT(2) -#define REG_SYNCRST_RESET BIT(2) -#define REG_SYNCRST_NORMAL 0 -#define REG_LDOPD_MASK BIT(1) -#define REG_LDOPD_POWER_DOWN BIT(1) -#define REG_LDOPD_POWER_ON 0 -#define REG_PLLPD_MASK BIT(0) -#define REG_PLLPD_POWER_DOWN BIT(0) -#define REG_PLLPD_POWER_ON 0 -/* Analog Register Part: reg03 */ -#define REG_FBDIV_HI_MASK BIT(5) -#define REG_FBDIV_HI(x) UPDATE(x, 5, 5) -#define REG_PREDIV_MASK GENMASK(4, 0) -#define REG_PREDIV(x) UPDATE(x, 4, 0) -/* Analog Register Part: reg04 */ -#define REG_FBDIV_LO_MASK GENMASK(7, 0) -#define REG_FBDIV_LO(x) UPDATE(x, 7, 0) -/* Analog Register Part: reg05 */ -#define CLK_LANE_SKEW_PHASE_SET_MASK GENMASK(2, 0) -#define CLK_LANE_SKEW_PHASE_SET(x) UPDATE(x, 2, 0) -/* Analog Register Part: reg06 */ -#define LDO_OUTPUT_SET_HI_MASK BIT(7) -#define LDO_OUTPUT_SET_HI(x) UPDATE(x, 7, 7) -#define LANE_3_SKEW_PHASE_SET_MASK GENMASK(6, 4) -#define LANE_3_SKEW_PHASE_SET(x) UPDATE(x, 6, 4) -#define LDO_OUTPUT_SET_LO_MASK BIT(3) -#define LDO_OUTPUT_SET_LO(x) UPDATE(x, 3, 3) -#define LANE_2_SKEW_PHASE_SET_MASK GENMASK(2, 0) -#define LANE_2_SKEW_PHASE_SET(x) UPDATE(x, 2, 0) -/* Analog Register Part: reg07 */ -#define PRE_EMPHASIS_RANGE_SET_HI_MASK BIT(7) -#define PRE_EMPHASIS_RANGE_SET_HI(x) UPDATE(x, 7, 7) -#define LANE_1_SKEW_PHASE_SET_MASK GENMASK(6, 4) -#define LANE_1_SKEW_PHASE_SET(x) UPDATE(x, 6, 4) -#define PRE_EMPHASIS_RANGE_SET_LO_MASK BIT(3) -#define PRE_EMPHASIS_RANGE_SET_LO(x) UPDATE(x, 3, 3) -#define LANE_0_SKEW_PHASE_SET_MASK GENMASK(2, 0) -#define LANE_0_SKEW_PHASE_SET(x) UPDATE(x, 2, 0) -/* Analog Register Part: reg08 */ -#define PRE_EMPHASIS_ENABLE_MASK BIT(7) -#define PRE_EMPHASIS_ENABLE BIT(7) -#define PRE_EMPHASIS_DISABLE 0 -#define PLL_POST_DIV_ENABLE_MASK BIT(5) -#define PLL_POST_DIV_ENABLE BIT(5) -#define PLL_POST_DIV_DISABLE 0 -#define DATA_LANE_VOD_RANGE_SET_MASK GENMASK(3, 0) -#define DATA_LANE_VOD_RANGE_SET(x) UPDATE(x, 3, 0) -/* Analog Register Part: reg0b */ -#define CLOCK_LANE_VOD_RANGE_SET_MASK GENMASK(3, 0) -#define CLOCK_LANE_VOD_RANGE_SET(x) UPDATE(x, 3, 0) -#define VOD_MIN_RANGE 0x1 -#define VOD_MID_RANGE 0x3 -#define VOD_BIG_RANGE 0x7 -#define VOD_MAX_RANGE 0xf -/* Analog Register Part: reg11 */ -#define DATA_SAMPLE_PHASE_SET_MASK GENMASK(7, 6) -#define DATA_SAMPLE_PHASE_SET(x) UPDATE(x, 7, 6) -/* Digital Register Part: reg00 */ -#define REG_DIG_RSTN_MASK BIT(0) -#define REG_DIG_RSTN_NORMAL BIT(0) -#define REG_DIG_RSTN_RESET 0 -/* Digital Register Part: reg01 */ -#define INV_PIN_TXCLKESC_0_ENABLE_MASK BIT(1) -#define INV_PIN_TXCLKESC_0_ENABLE BIT(1) -#define INV_PIN_TXCLKESC_0_DISABLE 0 -#define INV_PIN_TXBYTECLKHS_ENABLE_MASK BIT(0) -#define INV_PIN_TXBYTECLKHS_ENABLE BIT(0) -#define INV_PIN_TXBYTECLKHS_DISABLE 0 -/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg00 */ -#define DIFF_SIGNAL_SWAP_ENABLE_MASK BIT(4) -#define DIFF_SIGNAL_SWAP_ENABLE BIT(4) -#define DIFF_SIGNAL_SWAP_DISABLE 0 -/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg05 */ -#define T_LPX_CNT_MASK GENMASK(5, 0) -#define T_LPX_CNT(x) UPDATE(x, 5, 0) -/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg06 */ -#define T_HS_ZERO_CNT_HI_MASK BIT(7) -#define T_HS_ZERO_CNT_HI(x) UPDATE(x, 7, 7) -#define T_HS_PREPARE_CNT_MASK GENMASK(6, 0) -#define T_HS_PREPARE_CNT(x) UPDATE(x, 6, 0) -/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg07 */ -#define T_HS_ZERO_CNT_LO_MASK GENMASK(5, 0) -#define T_HS_ZERO_CNT_LO(x) UPDATE(x, 5, 0) -/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg08 */ -#define T_HS_TRAIL_CNT_MASK GENMASK(6, 0) -#define T_HS_TRAIL_CNT(x) UPDATE(x, 6, 0) -/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg09 */ -#define T_HS_EXIT_CNT_LO_MASK GENMASK(4, 0) -#define T_HS_EXIT_CNT_LO(x) UPDATE(x, 4, 0) -/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg0a */ -#define T_CLK_POST_CNT_LO_MASK GENMASK(3, 0) -#define T_CLK_POST_CNT_LO(x) UPDATE(x, 3, 0) -/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg0c */ -#define LPDT_TX_PPI_SYNC_ENABLE_MASK BIT(2) -#define LPDT_TX_PPI_SYNC_ENABLE BIT(2) -#define LPDT_TX_PPI_SYNC_DISABLE 0 -#define T_WAKEUP_CNT_HI_MASK GENMASK(1, 0) -#define T_WAKEUP_CNT_HI(x) UPDATE(x, 1, 0) -/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg0d */ -#define T_WAKEUP_CNT_LO_MASK GENMASK(7, 0) -#define T_WAKEUP_CNT_LO(x) UPDATE(x, 7, 0) -/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg0e */ -#define T_CLK_PRE_CNT_MASK GENMASK(3, 0) -#define T_CLK_PRE_CNT(x) UPDATE(x, 3, 0) -/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg10 */ -#define T_CLK_POST_HI_MASK GENMASK(7, 6) -#define T_CLK_POST_HI(x) UPDATE(x, 7, 6) -#define T_TA_GO_CNT_MASK GENMASK(5, 0) -#define T_TA_GO_CNT(x) UPDATE(x, 5, 0) -/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg11 */ -#define T_HS_EXIT_CNT_HI_MASK BIT(6) -#define T_HS_EXIT_CNT_HI(x) UPDATE(x, 6, 6) -#define T_TA_SURE_CNT_MASK GENMASK(5, 0) -#define T_TA_SURE_CNT(x) UPDATE(x, 5, 0) -/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg12 */ -#define T_TA_WAIT_CNT_MASK GENMASK(5, 0) -#define T_TA_WAIT_CNT(x) UPDATE(x, 5, 0) - -#define PSEC_PER_NSEC 1000L -#define PSECS_PER_SEC 1000000000000LL - -enum inno_video_phy_functions { - INNO_PHY_PADCTL_FUNC_MIPI, - INNO_PHY_PADCTL_FUNC_LVDS, - INNO_PHY_PADCTL_FUNC_TTL, - INNO_PHY_PADCTL_FUNC_IDLE, -}; - -struct mipi_dphy_timing { - unsigned int clkmiss; - unsigned int clkpost; - unsigned int clkpre; - unsigned int clkprepare; - unsigned int clksettle; - unsigned int clktermen; - unsigned int clktrail; - unsigned int clkzero; - unsigned int dtermen; - unsigned int eot; - unsigned int hsexit; - unsigned int hsprepare; - unsigned int hszero; - unsigned int hssettle; - unsigned int hsskip; - unsigned int hstrail; - unsigned int init; - unsigned int lpx; - unsigned int taget; - unsigned int tago; - unsigned int tasure; - unsigned int wakeup; -}; - -struct inno_mipi_dphy_timing { - unsigned int max_lane_mbps; - u8 lpx; - u8 hs_prepare; - u8 clk_lane_hs_zero; - u8 data_lane_hs_zero; - u8 hs_trail; -}; - -struct inno_mipi_dphy { - struct device *dev; - struct clk *ref_clk; - struct clk *pclk; - struct regmap *regmap; - struct reset_control *rst; - struct regmap *grf; - - unsigned int lanes; - unsigned long lane_rate; - - struct { - struct clk_hw hw; - u8 prediv; - u16 fbdiv; - } pll; -}; - -enum { - REGISTER_PART_ANALOG, - REGISTER_PART_DIGITAL, - REGISTER_PART_CLOCK_LANE, - REGISTER_PART_DATA0_LANE, - REGISTER_PART_DATA1_LANE, - REGISTER_PART_DATA2_LANE, - REGISTER_PART_DATA3_LANE, -}; - -static const -struct inno_mipi_dphy_timing inno_mipi_dphy_timing_table[] = { - { 110, 0x02, 0x7f, 0x16, 0x02, 0x02}, - { 150, 0x02, 0x7f, 0x16, 0x03, 0x02}, - { 200, 0x02, 0x7f, 0x17, 0x04, 0x02}, - { 250, 0x02, 0x7f, 0x17, 0x05, 0x04}, - { 300, 0x02, 0x7f, 0x18, 0x06, 0x04}, - { 400, 0x03, 0x7e, 0x19, 0x07, 0x04}, - { 500, 0x03, 0x7c, 0x1b, 0x07, 0x08}, - { 600, 0x03, 0x70, 0x1d, 0x08, 0x10}, - { 700, 0x05, 0x40, 0x1e, 0x08, 0x30}, - { 800, 0x05, 0x02, 0x1f, 0x09, 0x30}, - {1000, 0x05, 0x08, 0x20, 0x09, 0x30}, - {1200, 0x06, 0x03, 0x32, 0x14, 0x0f}, - {1400, 0x09, 0x03, 0x32, 0x14, 0x0f}, - {1600, 0x0d, 0x42, 0x36, 0x0e, 0x0f}, - {1800, 0x0e, 0x47, 0x7a, 0x0e, 0x0f}, - {2000, 0x11, 0x64, 0x7a, 0x0e, 0x0b}, - {2200, 0x13, 0x64, 0x7e, 0x15, 0x0b}, - {2400, 0x13, 0x33, 0x7f, 0x15, 0x6a}, - {2500, 0x15, 0x54, 0x7f, 0x15, 0x6a}, -}; - -static inline struct inno_mipi_dphy *hw_to_inno(struct clk_hw *hw) -{ - return container_of(hw, struct inno_mipi_dphy, pll.hw); -} - -static void inno_update_bits(struct inno_mipi_dphy *inno, u8 first, u8 second, - u8 mask, u8 val) -{ - u32 reg = INNO_PHY_REG(first, second) << 2; - - regmap_update_bits(inno->regmap, reg, mask, val); -} - -static void inno_mipi_dphy_reset(struct inno_mipi_dphy *inno) -{ - /* Reset analog */ - inno_update_bits(inno, REGISTER_PART_ANALOG, 0x01, - REG_SYNCRST_MASK, REG_SYNCRST_RESET); - udelay(1); - inno_update_bits(inno, REGISTER_PART_ANALOG, 0x01, - REG_SYNCRST_MASK, REG_SYNCRST_NORMAL); - /* Reset digital */ - inno_update_bits(inno, REGISTER_PART_DIGITAL, 0x00, - REG_DIG_RSTN_MASK, REG_DIG_RSTN_RESET); - udelay(1); - inno_update_bits(inno, REGISTER_PART_DIGITAL, 0x00, - REG_DIG_RSTN_MASK, REG_DIG_RSTN_NORMAL); -} - -static void inno_mipi_dphy_power_work_enable(struct inno_mipi_dphy *inno) -{ - inno_update_bits(inno, REGISTER_PART_ANALOG, 0x00, - POWER_WORK_MASK, POWER_WORK_ENABLE); -} - -static void inno_mipi_dphy_power_work_disable(struct inno_mipi_dphy *inno) -{ - inno_update_bits(inno, REGISTER_PART_ANALOG, 0x00, - POWER_WORK_MASK, POWER_WORK_DISABLE); -} - -static void inno_mipi_dphy_bandgap_power_enable(struct inno_mipi_dphy *inno) -{ - inno_update_bits(inno, REGISTER_PART_ANALOG, 0x00, - BANDGAP_POWER_MASK, BANDGAP_POWER_ON); -} - -static void inno_mipi_dphy_bandgap_power_disable(struct inno_mipi_dphy *inno) -{ - inno_update_bits(inno, REGISTER_PART_ANALOG, 0x00, - BANDGAP_POWER_MASK, BANDGAP_POWER_DOWN); -} - -static void inno_mipi_dphy_lane_enable(struct inno_mipi_dphy *inno) -{ - u8 val = LANE_EN_CK; - - switch (inno->lanes) { - case 1: - val |= LANE_EN_0; - break; - case 2: - val |= LANE_EN_1 | LANE_EN_0; - break; - case 3: - val |= LANE_EN_2 | LANE_EN_1 | LANE_EN_0; - break; - case 4: - default: - val |= LANE_EN_3 | LANE_EN_2 | LANE_EN_1 | LANE_EN_0; - break; - } - - inno_update_bits(inno, REGISTER_PART_ANALOG, 0x00, LANE_EN_MASK, val); -} - -static void inno_mipi_dphy_lane_disable(struct inno_mipi_dphy *inno) -{ - inno_update_bits(inno, REGISTER_PART_ANALOG, 0x00, LANE_EN_MASK, 0); -} - -static void inno_mipi_dphy_pll_enable(struct inno_mipi_dphy *inno) -{ - inno_update_bits(inno, REGISTER_PART_ANALOG, 0x03, - REG_PREDIV_MASK, REG_PREDIV(inno->pll.prediv)); - inno_update_bits(inno, REGISTER_PART_ANALOG, 0x03, - REG_FBDIV_HI_MASK, REG_FBDIV_HI(inno->pll.fbdiv >> 8)); - inno_update_bits(inno, REGISTER_PART_ANALOG, 0x04, - REG_FBDIV_LO_MASK, REG_FBDIV_LO(inno->pll.fbdiv)); - inno_update_bits(inno, REGISTER_PART_ANALOG, 0x08, - PLL_POST_DIV_ENABLE_MASK, PLL_POST_DIV_ENABLE); - inno_update_bits(inno, REGISTER_PART_ANALOG, 0x0b, - CLOCK_LANE_VOD_RANGE_SET_MASK, - CLOCK_LANE_VOD_RANGE_SET(VOD_MAX_RANGE)); - inno_update_bits(inno, REGISTER_PART_ANALOG, 0x01, - REG_LDOPD_MASK | REG_PLLPD_MASK, - REG_LDOPD_POWER_ON | REG_PLLPD_POWER_ON); -} - -static void inno_mipi_dphy_pll_disable(struct inno_mipi_dphy *inno) -{ - inno_update_bits(inno, REGISTER_PART_ANALOG, 0x01, - REG_LDOPD_MASK | REG_PLLPD_MASK, - REG_LDOPD_POWER_DOWN | REG_PLLPD_POWER_DOWN); -} - -static void mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing, - unsigned long period) -{ - /* Global Operation Timing Parameters */ - timing->clkmiss = 0; - /* - * The D-PHY spec define the clk post min time is 60ns + 52UI and - * no define max time, so we set 200 + 52UI leave move margin. - */ - timing->clkpost = 200 + 52 * period / PSEC_PER_NSEC; - timing->clkpre = 8 * period / PSEC_PER_NSEC; - timing->clkprepare = 65; - timing->clksettle = 95; - timing->clktermen = 0; - timing->clktrail = 80; - timing->clkzero = 260; - timing->dtermen = 0; - timing->eot = 0; - timing->hsexit = 120; - timing->hsprepare = 65 + 4 * period / PSEC_PER_NSEC; - timing->hszero = 145 + 6 * period / PSEC_PER_NSEC; - timing->hssettle = 85 + 6 * period / PSEC_PER_NSEC; - timing->hsskip = 40; - timing->hstrail = max(8 * period / PSEC_PER_NSEC, - 60 + 4 * period / PSEC_PER_NSEC); - timing->init = 100000; - timing->lpx = 60; - timing->taget = 5 * timing->lpx; - timing->tago = 4 * timing->lpx; - timing->tasure = 2 * timing->lpx; - timing->wakeup = 1000000; -} - -static const struct inno_mipi_dphy_timing * -inno_mipi_dphy_get_timing(struct inno_mipi_dphy *inno) -{ - const struct inno_mipi_dphy_timing *timings; - unsigned int num_timings; - unsigned int lane_mbps = inno->lane_rate / USEC_PER_SEC; - unsigned int i; - - timings = inno_mipi_dphy_timing_table; - num_timings = ARRAY_SIZE(inno_mipi_dphy_timing_table); - - for (i = 0; i < num_timings; i++) - if (lane_mbps <= timings[i].max_lane_mbps) - break; - - if (i == num_timings) - --i; - - return &timings[i]; -} - -static void inno_mipi_dphy_timing_init(struct inno_mipi_dphy *inno) -{ - struct mipi_dphy_timing gotp; - const struct inno_mipi_dphy_timing *timing; - unsigned long txbyteclk, txclkesc, ui, sys_clk; - unsigned int esc_clk_div; - u32 hs_exit, clk_post, clk_pre, wakeup, lpx, ta_go, ta_sure, ta_wait; - u32 hs_prepare, hs_trail, hs_zero; - unsigned int i; - - memset(&gotp, 0, sizeof(gotp)); - - txbyteclk = inno->lane_rate / 8; - sys_clk = clk_get_rate(inno->pclk); - esc_clk_div = DIV_ROUND_UP(txbyteclk, 20000000); - txclkesc = txbyteclk / esc_clk_div; - ui = DIV_ROUND_CLOSEST_ULL(PSECS_PER_SEC, inno->lane_rate); - - dev_dbg(inno->dev, "txbyteclk=%ld, ui=%ld, sys_clk=%ld\n", - txbyteclk, ui, sys_clk); - - mipi_dphy_timing_get_default(&gotp, ui); - timing = inno_mipi_dphy_get_timing(inno); - - hs_exit = DIV_ROUND_UP(gotp.hsexit * txbyteclk, NSEC_PER_SEC); - clk_post = DIV_ROUND_UP(gotp.clkpost * txbyteclk, NSEC_PER_SEC); - clk_pre = DIV_ROUND_UP(gotp.clkpre * txbyteclk, NSEC_PER_SEC); - - wakeup = DIV_ROUND_UP(gotp.wakeup * sys_clk, NSEC_PER_SEC); - if (wakeup > 0x3ff) - wakeup = 0x3ff; - - ta_go = DIV_ROUND_UP(gotp.tago * txclkesc, NSEC_PER_SEC); - ta_sure = DIV_ROUND_UP(gotp.tasure * txclkesc, NSEC_PER_SEC); - ta_wait = DIV_ROUND_UP(gotp.taget * txclkesc, NSEC_PER_SEC); - - lpx = timing->lpx; - hs_prepare = timing->hs_prepare; - hs_trail = timing->hs_trail; - - for (i = REGISTER_PART_CLOCK_LANE; i <= REGISTER_PART_DATA3_LANE; i++) { - if (i == REGISTER_PART_CLOCK_LANE) - hs_zero = timing->clk_lane_hs_zero; - else - hs_zero = timing->data_lane_hs_zero; - - dev_dbg(inno->dev, "lpx=%x\n", lpx); - dev_dbg(inno->dev, - "hs_trail=%x, hs_exit=%x, hs_prepare=%x, hs_zero=%x\n", - hs_trail, hs_exit, hs_prepare, hs_zero); - dev_dbg(inno->dev, "clk_pre=%x, clk_post=%x\n", - clk_pre, clk_post); - dev_dbg(inno->dev, "ta_go=%x, ta_sure=%x, ta_wait=%x\n", - ta_go, ta_sure, ta_wait); - - inno_update_bits(inno, i, 0x05, T_LPX_CNT_MASK, - T_LPX_CNT(lpx)); - inno_update_bits(inno, i, 0x06, T_HS_PREPARE_CNT_MASK, - T_HS_PREPARE_CNT(hs_prepare)); - inno_update_bits(inno, i, 0x06, T_HS_ZERO_CNT_HI_MASK, - T_HS_ZERO_CNT_HI(hs_zero >> 6)); - inno_update_bits(inno, i, 0x07, T_HS_ZERO_CNT_LO_MASK, - T_HS_ZERO_CNT_LO(hs_zero)); - inno_update_bits(inno, i, 0x08, T_HS_TRAIL_CNT_MASK, - T_HS_TRAIL_CNT(hs_trail)); - inno_update_bits(inno, i, 0x11, T_HS_EXIT_CNT_HI_MASK, - T_HS_EXIT_CNT_HI(hs_exit >> 5)); - inno_update_bits(inno, i, 0x09, T_HS_EXIT_CNT_LO_MASK, - T_HS_EXIT_CNT_LO(hs_exit)); - inno_update_bits(inno, i, 0x10, T_CLK_POST_HI_MASK, - T_CLK_POST_HI(clk_post >> 4)); - inno_update_bits(inno, i, 0x0a, T_CLK_POST_CNT_LO_MASK, - T_CLK_POST_CNT_LO(clk_post)); - inno_update_bits(inno, i, 0x0e, T_CLK_PRE_CNT_MASK, - T_CLK_PRE_CNT(clk_pre)); - inno_update_bits(inno, i, 0x0c, T_WAKEUP_CNT_HI_MASK, - T_WAKEUP_CNT_HI(wakeup >> 8)); - inno_update_bits(inno, i, 0x0d, T_WAKEUP_CNT_LO_MASK, - T_WAKEUP_CNT_LO(wakeup)); - inno_update_bits(inno, i, 0x10, T_TA_GO_CNT_MASK, - T_TA_GO_CNT(ta_go)); - inno_update_bits(inno, i, 0x11, T_TA_SURE_CNT_MASK, - T_TA_SURE_CNT(ta_sure)); - inno_update_bits(inno, i, 0x12, T_TA_WAIT_CNT_MASK, - T_TA_WAIT_CNT(ta_wait)); - } -} - -static unsigned long inno_mipi_dphy_pll_round_rate(struct inno_mipi_dphy *inno, - unsigned long prate, - unsigned long rate, - u8 *prediv, u16 *fbdiv) -{ - const struct inno_mipi_dphy_timing *timings; - unsigned int num_timings; - unsigned long best_freq = 0; - unsigned int fin, fout, max_fout; - u8 min_prediv, max_prediv; - u8 _prediv, best_prediv = 1; - u16 _fbdiv, best_fbdiv = 1; - u32 min_delta = UINT_MAX; - - timings = inno_mipi_dphy_timing_table; - num_timings = ARRAY_SIZE(inno_mipi_dphy_timing_table); - - /* - * The PLL output frequency can be calculated using a simple formula: - * PLL_Output_Frequency = (FREF / PREDIV * FBDIV) / 2 - * PLL_Output_Frequency: it is equal to DDR-Clock-Frequency * 2 - */ - fin = prate / USEC_PER_SEC; - fout = 2 * (rate / USEC_PER_SEC); - max_fout = 2 * timings[num_timings - 1].max_lane_mbps; - if (fout > max_fout) - fout = max_fout; - - /* constraint: 5Mhz < Fref / prediv < 40MHz */ - min_prediv = DIV_ROUND_UP(fin, 40); - max_prediv = fin / 5; - - for (_prediv = min_prediv; _prediv <= max_prediv; _prediv++) { - u32 delta, tmp; - - _fbdiv = fout * _prediv / fin; - /* - * The all possible settings of feedback divider are - * 12, 13, 14, 16, ~ 511 - */ - if ((_fbdiv == 15) || (_fbdiv < 12) || (_fbdiv > 511)) - continue; - - tmp = _fbdiv * fin / _prediv; - delta = abs(fout - tmp); - if (delta < min_delta) { - best_prediv = _prediv; - best_fbdiv = _fbdiv; - min_delta = delta; - best_freq = tmp * USEC_PER_SEC; - } - } - - if (best_freq) { - *prediv = best_prediv; - *fbdiv = best_fbdiv; - } - - return best_freq / 2; -} - -static int inno_mipi_dphy_power_on(struct phy *phy) -{ - struct inno_mipi_dphy *inno = phy_get_drvdata(phy); - - clk_prepare_enable(inno->pclk); - pm_runtime_get_sync(inno->dev); - inno_mipi_dphy_bandgap_power_enable(inno); - inno_mipi_dphy_power_work_enable(inno); - inno_mipi_dphy_pll_enable(inno); - inno_mipi_dphy_lane_enable(inno); - inno_mipi_dphy_reset(inno); - inno_mipi_dphy_timing_init(inno); - udelay(1); - - return 0; -} - -static int inno_mipi_dphy_power_off(struct phy *phy) -{ - struct inno_mipi_dphy *inno = phy_get_drvdata(phy); - - inno_mipi_dphy_lane_disable(inno); - inno_mipi_dphy_pll_disable(inno); - inno_mipi_dphy_power_work_disable(inno); - inno_mipi_dphy_bandgap_power_disable(inno); - pm_runtime_put(inno->dev); - clk_disable_unprepare(inno->pclk); - - return 0; -} - -static const struct phy_ops inno_mipi_dphy_ops = { - .power_on = inno_mipi_dphy_power_on, - .power_off = inno_mipi_dphy_power_off, - .owner = THIS_MODULE, -}; - -static long inno_mipi_dphy_pll_clk_round_rate(struct clk_hw *hw, - unsigned long rate, - unsigned long *prate) -{ - struct inno_mipi_dphy *inno = hw_to_inno(hw); - unsigned long fin = *prate; - unsigned long fout; - u16 fbdiv = 1; - u8 prediv = 1; - - fout = inno_mipi_dphy_pll_round_rate(inno, fin, rate, - &prediv, &fbdiv); - - dev_dbg(inno->dev, "%s: fin=%lu, req_rate=%lu\n", - __func__, *prate, rate); - dev_dbg(inno->dev, "%s: fout=%lu, prediv=%u, fbdiv=%u\n", - __func__, fout, prediv, fbdiv); - - inno->pll.prediv = prediv; - inno->pll.fbdiv = fbdiv; - - return fout; -} - -static int inno_mipi_dphy_pll_clk_set_rate(struct clk_hw *hw, - unsigned long rate, - unsigned long parent_rate) -{ - struct inno_mipi_dphy *inno = hw_to_inno(hw); - - dev_dbg(inno->dev, "%s: rate: %lu Hz\n", __func__, rate); - - inno->lane_rate = rate; - - return 0; -} - -static unsigned long -inno_mipi_dphy_pll_clk_recalc_rate(struct clk_hw *hw, unsigned long prate) -{ - struct inno_mipi_dphy *inno = hw_to_inno(hw); - - dev_dbg(inno->dev, "%s: rate: %lu Hz\n", __func__, inno->lane_rate); - - return inno->lane_rate; -} - -static const struct clk_ops inno_mipi_dphy_pll_clk_ops = { - .round_rate = inno_mipi_dphy_pll_clk_round_rate, - .set_rate = inno_mipi_dphy_pll_clk_set_rate, - .recalc_rate = inno_mipi_dphy_pll_clk_recalc_rate, -}; - -static int inno_mipi_dphy_pll_register(struct inno_mipi_dphy *inno) -{ - struct device *dev = inno->dev; - struct device_node *np = dev->of_node; - struct clk *clk; - const char *parent_name; - struct clk_init_data init = {}; - int ret; - - parent_name = __clk_get_name(inno->ref_clk); - - ret = of_property_read_string(np, "clock-output-names", &init.name); - if (ret < 0) { - dev_err(dev, "Missing clock-output-names property: %d\n", ret); - return ret; - } - - init.ops = &inno_mipi_dphy_pll_clk_ops; - init.parent_names = (const char * const *)&parent_name; - init.num_parents = 1; - init.flags = 0; - - inno->pll.hw.init = &init; - clk = devm_clk_register(dev, &inno->pll.hw); - if (IS_ERR(clk)) { - ret = PTR_ERR(clk); - dev_err(dev, "failed to register PLL: %d\n", ret); - return ret; - } - - return of_clk_add_provider(np, of_clk_src_simple_get, clk); -} - -static void inno_mipi_dphy_pll_unregister(struct inno_mipi_dphy *inno) -{ - of_clk_del_provider(inno->dev->of_node); -} - -static int inno_mipi_dphy_parse_dt(struct inno_mipi_dphy *inno) -{ - struct device *dev = inno->dev; - - if (of_property_read_u32(dev->of_node, "inno,lanes", &inno->lanes)) - inno->lanes = 4; - - return 0; -} - -static const struct regmap_config inno_mipi_dphy_regmap_config = { - .reg_bits = 32, - .val_bits = 32, - .reg_stride = 4, - .max_register = 0x3ac, -}; - -static int inno_mipi_dphy_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct inno_mipi_dphy *inno; - struct phy_provider *phy_provider; - struct phy *phy; - struct resource *res; - void __iomem *regs; - int ret; - - inno = devm_kzalloc(dev, sizeof(*inno), GFP_KERNEL); - if (!inno) - return -ENOMEM; - - inno->dev = dev; - platform_set_drvdata(pdev, inno); - - ret = inno_mipi_dphy_parse_dt(inno); - if (ret) { - dev_err(dev, "failed to parse DT\n"); - return ret; - } - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - regs = devm_ioremap_resource(dev, res); - if (IS_ERR(regs)) - return PTR_ERR(regs); - - inno->regmap = devm_regmap_init_mmio(dev, regs, - &inno_mipi_dphy_regmap_config); - if (IS_ERR(inno->regmap)) { - ret = PTR_ERR(inno->regmap); - dev_err(dev, "failed to init regmap: %d\n", ret); - return ret; - } - - inno->ref_clk = devm_clk_get(dev, "ref"); - if (IS_ERR(inno->ref_clk)) { - dev_err(dev, "failed to get reference clock\n"); - return PTR_ERR(inno->ref_clk); - } - - inno->pclk = devm_clk_get(dev, "pclk"); - if (IS_ERR(inno->pclk)) { - dev_err(dev, "failed to get pclk\n"); - return PTR_ERR(inno->pclk); - } - - inno->rst = devm_reset_control_get(dev, "apb"); - if (IS_ERR(inno->rst)) { - dev_err(dev, "failed to get system reset control\n"); - return PTR_ERR(inno->rst); - } - - inno->grf = syscon_regmap_lookup_by_phandle(dev->of_node, - "rockchip,grf"); - if (IS_ERR(inno->grf)) { - dev_err(dev, "failed to get grf regmap\n"); - return PTR_ERR(inno->grf); - } - - phy = devm_phy_create(dev, NULL, &inno_mipi_dphy_ops); - if (IS_ERR(phy)) { - dev_err(dev, "failed to create MIPI D-PHY\n"); - return PTR_ERR(phy); - } - - phy_set_drvdata(phy, inno); - - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - if (IS_ERR(phy_provider)) { - dev_err(dev, "failed to register phy provider\n"); - return PTR_ERR(phy_provider); - } - - ret = inno_mipi_dphy_pll_register(inno); - if (ret) - return ret; - - pm_runtime_enable(dev); - - return 0; -} - -static int inno_mipi_dphy_remove(struct platform_device *pdev) -{ - struct inno_mipi_dphy *inno = platform_get_drvdata(pdev); - - inno_mipi_dphy_pll_unregister(inno); - pm_runtime_disable(inno->dev); - - return 0; -} - -static const struct of_device_id inno_mipi_dphy_of_match[] = { - { .compatible = "rockchip,rk1808-mipi-dphy", }, - { .compatible = "rockchip,rk3568-mipi-dphy", }, - { .compatible = "rockchip,rv1126-mipi-dphy", }, - {} -}; -MODULE_DEVICE_TABLE(of, inno_mipi_dphy_of_match); - -static struct platform_driver inno_mipi_dphy_driver = { - .driver = { - .name = "inno-mipi-dphy", - .of_match_table = of_match_ptr(inno_mipi_dphy_of_match), - }, - .probe = inno_mipi_dphy_probe, - .remove = inno_mipi_dphy_remove, -}; - -#ifdef CONFIG_ROCKCHIP_THUNDER_BOOT -static int __init inno_mipi_dphy_driver_init(void) -{ - return platform_driver_register(&inno_mipi_dphy_driver); -} -fs_initcall(inno_mipi_dphy_driver_init); - -static void __exit inno_mipi_dphy_driver_exit(void) -{ - platform_driver_unregister(&inno_mipi_dphy_driver); -} -module_exit(inno_mipi_dphy_driver_exit); -#else -module_platform_driver(inno_mipi_dphy_driver); -#endif - -MODULE_AUTHOR("Wyon Bi "); -MODULE_DESCRIPTION("Innosilicon MIPI D-PHY Driver"); -MODULE_LICENSE("GPL v2");