summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/starfive/starfive_drm_lcdc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/starfive/starfive_drm_lcdc.c')
-rw-r--r--drivers/gpu/drm/starfive/starfive_drm_lcdc.c512
1 files changed, 512 insertions, 0 deletions
diff --git a/drivers/gpu/drm/starfive/starfive_drm_lcdc.c b/drivers/gpu/drm/starfive/starfive_drm_lcdc.c
new file mode 100644
index 000000000000..7e9bcbde3092
--- /dev/null
+++ b/drivers/gpu/drm/starfive/starfive_drm_lcdc.c
@@ -0,0 +1,512 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/units.h>
+#include <drm/drm_crtc.h>
+#include "starfive_drm_lcdc.h"
+#include "starfive_drm_vpp.h"
+
+static u32 sf_fb_clkread32(struct starfive_crtc *sf_crtc, u32 reg)
+{
+ return ioread32(sf_crtc->base_clk + reg);
+}
+
+static void sf_fb_clkwrite32(struct starfive_crtc *sf_crtc, u32 reg, u32 val)
+{
+ iowrite32(val, sf_crtc->base_clk + reg);
+}
+
+static u32 sf_fb_lcdcread32(struct starfive_crtc *sf_crtc, u32 reg)
+{
+ return ioread32(sf_crtc->base_lcdc + reg);
+}
+
+static void sf_fb_lcdcwrite32(struct starfive_crtc *sf_crtc, u32 reg, u32 val)
+{
+ iowrite32(val, sf_crtc->base_lcdc + reg);
+}
+
+static u32 starfive_lcdc_rstread32(struct starfive_crtc *sf_crtc, u32 reg)
+{
+ return ioread32(sf_crtc->base_rst + reg);
+}
+
+static void starfive_lcdc_rstwrite32(struct starfive_crtc *sf_crtc, u32 reg, u32 val)
+{
+ iowrite32(val, sf_crtc->base_rst + reg);
+}
+
+static void lcdc_mode_cfg(struct starfive_crtc *sf_crtc, u32 work_mode, int dot_edge,
+ int sync_edge, int r2y_bypass, int src_sel, int int_src, int int_freq)
+{
+ u32 lcdc_en = 0x1;
+ u32 cfg = lcdc_en |
+ work_mode << LCDC_WORK_MODE |
+ dot_edge << LCDC_DOTCLK_P |
+ sync_edge << LCDC_HSYNC_P |
+ sync_edge << LCDC_VSYNC_P |
+ 0x0 << LCDC_DITHER_EN |
+ r2y_bypass << LCDC_R2Y_BPS |
+ src_sel << LCDC_TV_LCD_PATHSEL |
+ int_src << LCDC_INT_SEL |
+ int_freq << LCDC_INT_FREQ;
+
+ sf_fb_lcdcwrite32(sf_crtc, LCDC_GCTRL, cfg);
+ dev_dbg(sf_crtc->dev, "LCDC WorkMode: 0x%x, LCDC Path: %d\n", work_mode, src_sel);
+}
+
+static void lcdc_timing_cfg(struct starfive_crtc *sf_crtc,
+ struct drm_crtc_state *state, int vunit)
+{
+ int hpw, hbk, hfp, vpw, vbk, vfp;
+ u32 htiming, vtiming, hvwid;
+
+ //h-sync
+ int hsync_len = state->adjusted_mode.crtc_hsync_end -
+ state->adjusted_mode.crtc_hsync_start;
+ //h-bp
+ int left_margin = state->adjusted_mode.crtc_htotal -
+ state->adjusted_mode.crtc_hsync_end;
+ //h-fp
+ int right_margin = state->adjusted_mode.crtc_hsync_start -
+ state->adjusted_mode.crtc_hdisplay;
+ //v-sync
+ int vsync_len = state->adjusted_mode.crtc_vsync_end -
+ state->adjusted_mode.crtc_vsync_start;
+ //v-bp
+ int upper_margin = state->adjusted_mode.crtc_vtotal -
+ state->adjusted_mode.crtc_vsync_end;
+ //v-fp
+ int lower_margin = state->adjusted_mode.crtc_vsync_start -
+ state->adjusted_mode.crtc_vdisplay;
+
+ hpw = hsync_len - 1;
+ hbk = hsync_len + left_margin;
+ hfp = right_margin;
+ vpw = vsync_len - 1;
+ vbk = vsync_len + upper_margin;
+ vfp = lower_margin;
+
+ dev_dbg(sf_crtc->dev, "%s: h-sync = %d, h-bp = %d, h-fp = %d", __func__,
+ hsync_len, left_margin, right_margin);
+ dev_dbg(sf_crtc->dev, "%s: v-sync = %d, v-bp = %d, v-fp = %d", __func__,
+ vsync_len, upper_margin, lower_margin);
+
+ htiming = hbk | hfp << LCDC_RGB_HFP;
+ vtiming = vbk | vfp << LCDC_RGB_VFP;
+ hvwid = hpw | vpw << LCDC_RGB_VPW | vunit << LCDC_RGB_UNIT;
+
+ sf_fb_lcdcwrite32(sf_crtc, LCDC_RGB_H_TMG, htiming);
+ sf_fb_lcdcwrite32(sf_crtc, LCDC_RGB_V_TMG, vtiming);
+ sf_fb_lcdcwrite32(sf_crtc, LCDC_RGB_W_TMG, hvwid);
+ dev_dbg(sf_crtc->dev, "LCDC HPW: %d, HBK: %d, HFP: %d\n", hpw, hbk, hfp);
+ dev_dbg(sf_crtc->dev, "LCDC VPW: %d, VBK: %d, VFP: %d\n", vpw, vbk, vfp);
+ dev_dbg(sf_crtc->dev, "LCDC V-Unit: %d, 0-HSYNC and 1-dotClk period\n", vunit);
+}
+
+//? background size
+//lcdc_desize_cfg(sf_dev, sf_dev->display_info.xres-1, sf_dev->display_info.yres-1);
+static void lcdc_desize_cfg(struct starfive_crtc *sf_crtc, struct drm_crtc_state *state)
+{
+ int hsize = state->adjusted_mode.crtc_hdisplay - 1;
+ int vsize = state->adjusted_mode.crtc_vdisplay - 1;
+ u32 sizecfg = hsize | vsize << LCDC_BG_VSIZE;
+
+ sf_fb_lcdcwrite32(sf_crtc, LCDC_BACKGROUND, sizecfg);
+ dev_dbg(sf_crtc->dev, "LCDC Dest H-Size: %d, V-Size: %d\n", hsize, vsize);
+}
+
+static void lcdc_rgb_dclk_cfg(struct starfive_crtc *sf_crtc, int dot_clk_sel)
+{
+ u32 cfg = dot_clk_sel << 16;
+
+ sf_fb_lcdcwrite32(sf_crtc, LCDC_RGB_DCLK, cfg);
+ dev_dbg(sf_crtc->dev, "LCDC Dot_clock_output_sel: 0x%x\n", cfg);
+}
+
+// color table
+//win0, no lock transfer
+//win3, no src_sel and addr_mode, 0 assigned to them
+//lcdc_win_cfgA(sf_dev, win_num, sf_dev->display_info.xres-1, sf_dev->display_info.yres-1,
+// 0x1, 0x0, 0x0, 0x1, 0x0, 0x0);
+static void lcdc_win_cfgA(struct starfive_crtc *sf_crtc, struct drm_crtc_state *state,
+ int win_num, int lay_en, int clor_tab,
+ int color_en, int addr_mode, int lock)
+{
+ int hsize = state->adjusted_mode.crtc_hdisplay - 1;
+ int vsize = state->adjusted_mode.crtc_vdisplay - 1;
+ int src_sel_v = 1;
+ u32 cfg;
+
+ if (sf_crtc->pp_conn_lcdc < 0)
+ src_sel_v = 0;
+
+ cfg = hsize | vsize << LCDC_WIN_VSIZE | lay_en << LCDC_WIN_EN |
+ clor_tab << LCDC_CC_EN | color_en << LCDC_CK_EN |
+ src_sel_v << LCDC_WIN_ISSEL | addr_mode << LCDC_WIN_PM |
+ lock << LCDC_WIN_CLK;
+
+ sf_fb_lcdcwrite32(sf_crtc, LCDC_WIN0_CFG_A + win_num * 0xC, cfg);
+ dev_dbg(sf_crtc->dev,
+ "LCDC Win%d H-Size: %d, V-Size: %d, lay_en: %d, Src: %d, AddrMode: %d\n",
+ win_num, hsize, vsize, lay_en, src_sel_v, addr_mode);
+}
+
+static void lcdc_win_cfgB(struct starfive_crtc *sf_crtc,
+ int win_num, int xpos, int ypos, int argb_ord)
+{
+ int win_format = sf_crtc->lcdcfmt;
+ u32 cfg;
+
+#ifdef CONFIG_DRM_STARFIVE_MIPI_DSI
+ argb_ord = 0;
+#else
+ argb_ord = 1;
+#endif
+
+ cfg = xpos |
+ ypos << LCDC_WIN_VPOS |
+ win_format << LCDC_WIN_FMT |
+ argb_ord << LCDC_WIN_ARGB_ORDER;
+
+ sf_fb_lcdcwrite32(sf_crtc, LCDC_WIN0_CFG_B + win_num * 0xC, cfg);
+ dev_dbg(sf_crtc->dev,
+ "LCDC Win%d Xpos: %d, Ypos: %d, win_format: 0x%x, ARGB Order: 0x%x\n",
+ win_num, xpos, ypos, win_format, argb_ord);
+}
+
+//? Color key
+static void lcdc_win_cfgC(struct starfive_crtc *sf_crtc, int win_num, int color_key)
+{
+ sf_fb_lcdcwrite32(sf_crtc, LCDC_WIN0_CFG_C + win_num * 0xC, color_key);
+ dev_dbg(sf_crtc->dev, "LCDC Win%d Color Key: 0x%6x\n", win_num, color_key);
+}
+
+//? hsize
+//lcdc_win_src_size(sf_dev, win_num, sf_dev->display_info.xres-1);
+static void lcdc_win_src_size(struct starfive_crtc *sf_crtc,
+ struct drm_crtc_state *state, int win_num)
+{
+ int addr, off, winsize, pre_cfg, cfg;
+ int hsize = state->adjusted_mode.crtc_hdisplay - 1;
+
+ switch (win_num) {
+ case 0:
+ addr = LCDC_WIN01_HSIZE;
+ off = 0xfffff000;
+ winsize = hsize;
+ break;
+ case 1:
+ addr = LCDC_WIN01_HSIZE;
+ off = 0xff000fff;
+ winsize = hsize << LCDC_IMG_HSIZE;
+ break;
+ case 2:
+ addr = LCDC_WIN23_HSIZE;
+ off = 0xfffff000;
+ winsize = hsize;
+ break;
+ case 3:
+ addr = LCDC_WIN23_HSIZE;
+ off = 0xff000fff;
+ winsize = hsize << LCDC_IMG_HSIZE;
+ break;
+ case 4:
+ addr = LCDC_WIN45_HSIZE;
+ off = 0xfffff000;
+ winsize = hsize;
+ break;
+ case 5:
+ addr = LCDC_WIN45_HSIZE;
+ off = 0xff000fff;
+ winsize = hsize << LCDC_IMG_HSIZE;
+ break;
+ case 6:
+ addr = LCDC_WIN67_HSIZE;
+ off = 0xfffff000;
+ winsize = hsize;
+ break;
+ case 7:
+ addr = LCDC_WIN67_HSIZE;
+ off = 0xff000fff;
+ winsize = hsize << LCDC_IMG_HSIZE;
+ break;
+ default:
+ addr = LCDC_WIN01_HSIZE;
+ off = 0xfffff000;
+ winsize = hsize;
+ break;
+ }
+ pre_cfg = sf_fb_lcdcread32(sf_crtc, addr) & off;
+ cfg = winsize | pre_cfg;
+ sf_fb_lcdcwrite32(sf_crtc, addr, cfg);
+ dev_dbg(sf_crtc->dev, "LCDC Win%d Src Hsize: %d\n", win_num, hsize);
+}
+
+static void lcdc_alpha_val_cfg(struct starfive_crtc *sf_crtc,
+ int val1, int val2, int val3, int val4, int sel)
+{
+ u32 val = val1 |
+ val2 << LCDC_ALPHA2 |
+ val3 << LCDC_ALPHA3 |
+ val4 << LCDC_ALPHA4 |
+ sel << LCDC_01_ALPHA_SEL;
+ u32 pre_val = sf_fb_lcdcread32(sf_crtc, LCDC_ALPHA_VALUE) & 0xfffb0000U;
+
+ sf_fb_lcdcwrite32(sf_crtc, LCDC_ALPHA_VALUE, pre_val | val);
+ dev_dbg(sf_crtc->dev, "LCDC Alpha 1: %x, 2: %x, 3: %x, 4: %x\n", val1, val2, val3, val4);
+}
+
+static void lcdc_panel_cfg(struct starfive_crtc *sf_crtc,
+ int buswid, int depth, int txcycle, int pixpcycle,
+ int rgb565sel, int rgb888sel)
+{
+ u32 cfg = buswid |
+ depth << LCDC_COLOR_DEP |
+ txcycle << LCDC_TCYCLES |
+ pixpcycle << LCDC_PIXELS |
+ rgb565sel << LCDC_565RGB_SEL |
+ rgb888sel << LCDC_888RGB_SEL;
+
+ sf_fb_lcdcwrite32(sf_crtc, LCDC_PANELDATAFMT, cfg);
+ dev_dbg(sf_crtc->dev, "LCDC bus bit: :%d, pixDep: 0x%x, txCyle: %d, %dpix/cycle, RGB565 2cycle_%d, RGB888 3cycle_%d\n",
+ buswid, depth, txcycle, pixpcycle, rgb565sel, rgb888sel);
+}
+
+//win_num: 0-2
+static void lcdc_win02_addr_cfg(struct starfive_crtc *sf_crtc, int addr0, int addr1)
+{
+ sf_fb_lcdcwrite32(sf_crtc, LCDC_WIN0STARTADDR0 + sf_crtc->win_num * 0x8, addr0);
+ sf_fb_lcdcwrite32(sf_crtc, LCDC_WIN0STARTADDR1 + sf_crtc->win_num * 0x8, addr1);
+ dev_dbg(sf_crtc->dev, "LCDC Win%d Start Addr0: 0x%8x, Addr1: 0x%8x\n",
+ sf_crtc->win_num, addr0, addr1);
+}
+
+void starfive_set_win_addr(struct starfive_crtc *sf_crtc, int addr)
+{
+ lcdc_win02_addr_cfg(sf_crtc, addr, 0x0);
+}
+
+void lcdc_enable_intr(struct starfive_crtc *sf_crtc)
+{
+ u32 cfg = ~(1U << LCDC_OUT_FRAME_END);
+
+ sf_fb_lcdcwrite32(sf_crtc, LCDC_INT_MSK, cfg);
+}
+
+void lcdc_disable_intr(struct starfive_crtc *sf_crtc)
+{
+ sf_fb_lcdcwrite32(sf_crtc, LCDC_INT_MSK, 0xff);
+ sf_fb_lcdcwrite32(sf_crtc, LCDC_INT_CLR, 0xff);
+}
+
+int lcdc_win_sel(struct starfive_crtc *sf_crtc, enum lcdc_in_mode sel)
+{
+ int win_num;
+
+ switch (sel) {
+ case LCDC_IN_LCD_AXI:
+ win_num = LCDC_WIN_0;
+ break;
+ case LCDC_IN_VPP2:
+ win_num = LCDC_WIN_0;
+ break;
+ case LCDC_IN_VPP1:
+ win_num = LCDC_WIN_2;
+ break;
+ case LCDC_IN_VPP0:
+ win_num = LCDC_WIN_1;
+ //mapconv_pp0_sel(sf_dev, 0x0);
+ break;
+ case LCDC_IN_MAPCONVERT:
+ win_num = LCDC_WIN_1;
+ //mapconv_pp0_sel(sf_dev, 0x1);
+ break;
+ default:
+ win_num = 2;
+ }
+
+ return win_num;
+}
+
+void lcdc_dsi_sel(struct starfive_crtc *sf_crtc)
+{
+ int temp;
+ u32 lcdc_en = 0x1;
+ u32 work_mode = 0x1;
+ u32 cfg = lcdc_en | work_mode << LCDC_WORK_MODE;
+
+ sf_fb_lcdcwrite32(sf_crtc, LCDC_GCTRL, cfg);
+ temp = starfive_lcdc_rstread32(sf_crtc, SRST_ASSERT0);
+ temp &= ~(0x1 << BIT_RST_DSI_DPI_PIX);
+ starfive_lcdc_rstwrite32(sf_crtc, SRST_ASSERT0, temp);
+}
+
+irqreturn_t lcdc_isr_handler(int this_irq, void *dev_id)
+{
+ struct starfive_crtc *sf_crtc = dev_id;
+ //u32 intr_status = sf_fb_lcdcread32(sf_crtc, LCDC_INT_STATUS);
+
+ sf_fb_lcdcwrite32(sf_crtc, LCDC_INT_CLR, 0xffffffff);
+
+ return IRQ_HANDLED;
+}
+
+void lcdc_int_cfg(struct starfive_crtc *sf_crtc, int mask)
+{
+ u32 cfg;
+
+ if (mask == 0x1)
+ cfg = 0xffffffff;
+ else
+ cfg = ~(1U << LCDC_OUT_FRAME_END); //only frame end interrupt mask
+
+ sf_fb_lcdcwrite32(sf_crtc, LCDC_INT_MSK, cfg);
+}
+
+void lcdc_config(struct starfive_crtc *sf_crtc, struct drm_crtc_state *state, int win_num)
+{
+ lcdc_mode_cfg(sf_crtc, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0);
+ lcdc_timing_cfg(sf_crtc, state, 0);
+ lcdc_desize_cfg(sf_crtc, state);
+ lcdc_rgb_dclk_cfg(sf_crtc, 0x1);
+
+ if (sf_crtc->pp_conn_lcdc < 0) //ddr->lcdc
+ lcdc_win02_addr_cfg(sf_crtc, sf_crtc->dma_addr, 0x0);
+
+ lcdc_win_cfgA(sf_crtc, state, win_num, 0x1, 0x0, 0x0, 0x0, 0x0);
+ lcdc_win_cfgB(sf_crtc, win_num, 0x0, 0x0, 0x0);
+ lcdc_win_cfgC(sf_crtc, win_num, 0xffffff);
+
+ lcdc_win_src_size(sf_crtc, state, win_num);
+ lcdc_alpha_val_cfg(sf_crtc, 0xf, 0xf, 0xf, 0xf, 0x0);
+ lcdc_panel_cfg(sf_crtc, 0x3, 0x4, 0x0, 0x0, 0x0, 0x1); //rgb888sel?
+}
+
+void lcdc_run(struct starfive_crtc *sf_crtc, u32 win_mode, u32 lcd_trig)
+{
+ u32 runcfg = win_mode << LCDC_EN_CFG_MODE | lcd_trig;
+
+ sf_fb_lcdcwrite32(sf_crtc, LCDC_SWITCH, runcfg);
+ dev_dbg(sf_crtc->dev, "Start run LCDC\n");
+}
+
+static int sf_fb_lcdc_clk_cfg(struct starfive_crtc *sf_crtc, struct drm_crtc_state *state)
+{
+ u32 reg_val = clk_get_rate(sf_crtc->clk_vout_src) / (state->mode.clock * HZ_PER_KHZ);
+ u32 tmp_val;
+
+ dev_dbg(sf_crtc->dev, "%s: reg_val = %u\n", __func__, reg_val);
+
+ switch (state->adjusted_mode.crtc_hdisplay) {
+ case 640:
+ tmp_val = sf_fb_clkread32(sf_crtc, CLK_LCDC_OCLK_CTRL);
+ tmp_val &= ~(0x3F);
+ tmp_val |= (59 & 0x3F);
+ sf_fb_clkwrite32(sf_crtc, CLK_LCDC_OCLK_CTRL, tmp_val);
+ break;
+ case 840:
+ tmp_val = sf_fb_clkread32(sf_crtc, CLK_LCDC_OCLK_CTRL);
+ tmp_val &= ~(0x3F);
+ tmp_val |= (54 & 0x3F);
+ sf_fb_clkwrite32(sf_crtc, CLK_LCDC_OCLK_CTRL, tmp_val);
+ break;
+ case 1024:
+ tmp_val = sf_fb_clkread32(sf_crtc, CLK_LCDC_OCLK_CTRL);
+ tmp_val &= ~(0x3F);
+ tmp_val |= (30 & 0x3F);
+ sf_fb_clkwrite32(sf_crtc, CLK_LCDC_OCLK_CTRL, tmp_val);
+ break;
+ case 1280:
+ tmp_val = sf_fb_clkread32(sf_crtc, CLK_LCDC_OCLK_CTRL);
+ tmp_val &= ~(0x3F);
+ tmp_val |= (30 & 0x3F);
+ sf_fb_clkwrite32(sf_crtc, CLK_LCDC_OCLK_CTRL, tmp_val);
+ break;
+ case 1440:
+ tmp_val = sf_fb_clkread32(sf_crtc, CLK_LCDC_OCLK_CTRL);
+ tmp_val &= ~(0x3F);
+ tmp_val |= (30 & 0x3F);
+ sf_fb_clkwrite32(sf_crtc, CLK_LCDC_OCLK_CTRL, tmp_val);
+ break;
+ case 1680:
+ tmp_val = sf_fb_clkread32(sf_crtc, CLK_LCDC_OCLK_CTRL);
+ tmp_val &= ~(0x3F);
+ tmp_val |= (24 & 0x3F); //24 30MHZ
+ sf_fb_clkwrite32(sf_crtc, CLK_LCDC_OCLK_CTRL, tmp_val);
+ break;
+ case 1920:
+ tmp_val = sf_fb_clkread32(sf_crtc, CLK_LCDC_OCLK_CTRL);
+ tmp_val &= ~(0x3F);
+ tmp_val |= (10 & 0x3F); //20 30MHz , 15 40Mhz, 10 60Mhz
+ sf_fb_clkwrite32(sf_crtc, CLK_LCDC_OCLK_CTRL, tmp_val);
+ break;
+ case 2048:
+ tmp_val = sf_fb_clkread32(sf_crtc, CLK_LCDC_OCLK_CTRL);
+ tmp_val &= ~(0x3F);
+ tmp_val |= (10 & 0x3F);
+ sf_fb_clkwrite32(sf_crtc, CLK_LCDC_OCLK_CTRL, tmp_val);
+ break;
+ default:
+ tmp_val = sf_fb_clkread32(sf_crtc, CLK_LCDC_OCLK_CTRL);
+ tmp_val &= ~(0x3F);
+ tmp_val |= (reg_val & 0x3F);
+ sf_fb_clkwrite32(sf_crtc, CLK_LCDC_OCLK_CTRL, tmp_val);
+ }
+
+ return 0;
+}
+
+static int sf_fb_lcdc_init(struct starfive_crtc *sf_crtc, struct drm_crtc_state *state)
+{
+ int pp_id;
+ int lcd_in_pp;
+ int win_num;
+
+ pp_id = sf_crtc->pp_conn_lcdc;
+ if (pp_id < 0) {
+ dev_dbg(sf_crtc->dev, "DDR to LCDC\n");
+ lcd_in_pp = LCDC_IN_LCD_AXI;
+ win_num = lcdc_win_sel(sf_crtc, lcd_in_pp);
+ sf_crtc->win_num = win_num;
+ lcdc_config(sf_crtc, state, win_num);
+ } else {
+ dev_dbg(sf_crtc->dev, "DDR to VPP to LCDC\n");
+ lcd_in_pp = (pp_id == 0) ? LCDC_IN_VPP0 :
+ ((pp_id == 1) ? LCDC_IN_VPP1 : LCDC_IN_VPP2);
+ win_num = lcdc_win_sel(sf_crtc, lcd_in_pp);
+ sf_crtc->win_num = win_num;
+ lcdc_config(sf_crtc, state, win_num);
+ }
+
+ return 0;
+}
+
+int starfive_lcdc_enable(struct starfive_crtc *sf_crtc)
+{
+ struct drm_crtc_state *state = sf_crtc->crtc.state;
+
+ lcdc_disable_intr(sf_crtc);
+
+ if (sf_fb_lcdc_clk_cfg(sf_crtc, state)) {
+ dev_err(sf_crtc->dev, "lcdc clock configure fail\n");
+ return -EINVAL;
+ }
+
+ if (sf_fb_lcdc_init(sf_crtc, state)) {
+ dev_err(sf_crtc->dev, "lcdc init fail\n");
+ return -EINVAL;
+ }
+
+ lcdc_run(sf_crtc, sf_crtc->win_num, LCDC_RUN);
+ lcdc_enable_intr(sf_crtc);
+
+ return 0;
+}
+
+MODULE_AUTHOR("StarFive Technology Co., Ltd.");
+MODULE_DESCRIPTION("loadable LCDC driver for StarFive");
+MODULE_LICENSE("GPL");