author: Tom <support@vamrs.com> 2021-01-11 04:06:22 +0800
committer: Fu Wei <fu.wei@linaro.org> 2021-02-25 03:46:57 +0800
commit: 7f89889b8b96dbafc3f384cf9001439107f5a61a
parent: 066d2be4eb17b89269f1523cbd92dff5c879653b
Commit Summary:
Diffstat:
23 files changed, 4418 insertions, 0 deletions
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index 7e152bbb4fa6..7423be6cb98c 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -138,6 +138,13 @@ config VIDEO_STM32_DCMI
To compile this driver as a module, choose M here: the module
will be called stm32-dcmi.
+config VIDEO_STF_VIN
+ tristate "starfive VIC video in support"
+ depends on VIDEO_V4L2 && OF
+ help
+ To compile this driver as a module, choose M here: the module
+ will be called stf-vin.
+
config VIDEO_RENESAS_CEU
tristate "Renesas Capture Engine Unit (CEU) driver"
depends on VIDEO_DEV && VIDEO_V4L2
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 62b6cdc8c730..ecc171bc24ed 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -41,6 +41,8 @@ obj-$(CONFIG_VIDEO_STI_DELTA) += sti/delta/
obj-y += stm32/
+obj-y += starfive/
+
obj-y += davinci/
obj-$(CONFIG_VIDEO_SH_VOU) += sh_vou.o
diff --git a/drivers/media/platform/starfive/Makefile b/drivers/media/platform/starfive/Makefile
new file mode 100644
index 000000000000..e11facdcd148
--- /dev/null
+++ b/drivers/media/platform/starfive/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for RTC class/drivers.
+#
+
+obj-$(CONFIG_VIDEO_STF_VIN) += stf_vin.o stf_event.o stf_isp.o
diff --git a/drivers/media/platform/starfive/stf_event.c b/drivers/media/platform/starfive/stf_event.c
new file mode 100644
index 000000000000..088354fd700f
--- /dev/null
+++ b/drivers/media/platform/starfive/stf_event.c
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Event for VIC Video In
+ *
+ * Copyright (C) starfivetech.Inc
+ * Authors: Xing Tang <eric.tang@starfivetech.com>
+ *
+ */
+#include <linux/notifier.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+
+static ATOMIC_NOTIFIER_HEAD(vin_notifier_list);
+
+int vin_notifier_register(struct notifier_block *nb)
+{
+ return atomic_notifier_chain_register(&vin_notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(vin_notifier_register);
+
+void vin_notifier_unregister(struct notifier_block *nb)
+{
+ atomic_notifier_chain_unregister(&vin_notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(vin_notifier_unregister);
+
+int vin_notifier_call(unsigned long e, void *v)
+{
+ return atomic_notifier_call_chain(&vin_notifier_list, e, v);
+}
+EXPORT_SYMBOL_GPL(vin_notifier_call);
+
+
+MODULE_AUTHOR("StarFive Technology Co., Ltd.");
+MODULE_DESCRIPTION("Starfive VIC video in notifier");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("video");
diff --git a/drivers/media/platform/starfive/stf_isp.c b/drivers/media/platform/starfive/stf_isp.c
new file mode 100644
index 000000000000..77cf7995a495
--- /dev/null
+++ b/drivers/media/platform/starfive/stf_isp.c
@@ -0,0 +1,170 @@
+/* /drivers/media/platform/starfive/stf_isp.c
+**
+** This program is free software; you can redistribute it and/or modify
+** it under the terms of the GNU General Public License version 2 as
+** published by the Free Software Foundation.
+**
+** Copyright (C) 2020 StarFive, Inc.
+**
+** PURPOSE: This files contains the driver of VPP.
+**
+** CHANGE HISTORY:
+** Version Date Author Description
+** 0.1.0 2020-12-09 starfive created
+**
+*/
+#include <asm/io.h>
+#include <linux/fb.h>
+#include <linux/module.h>
+#include <video/stf-vin.h>
+
+static inline u32 reg_read(void __iomem * base, u32 reg)
+{
+ return ioread32(base + reg);
+}
+
+static inline void reg_write(void __iomem * base, u32 reg, u32 val)
+{
+ iowrite32(val, base + reg);
+}
+
+void isp_ddr_format_config(struct stf_vin_dev *vin)
+{
+ switch (vin->format.format) {
+ case SRC_COLORBAR_VIN_ISP:
+ reg_write(vin->base_isp, ISP_REG_DVP_POLARITY_CFG, 0xd);
+ break;
+
+ case SRC_DVP_SENSOR_VIN_ISP:
+ reg_write(vin->base_isp, ISP_REG_DVP_POLARITY_CFG, 0x08);
+ break;
+
+ default:
+ pr_err("unknown format\n");
+ return;
+ }
+
+ reg_write(vin->base_isp, ISP_REG_RAW_FORMAT_CFG, 0x000011BB); // sym_order = [0+:16]
+ reg_write(vin->base_isp, ISP_REG_CFA_MODE, 0x00000030);
+ reg_write(vin->base_isp, ISP_REG_PIC_CAPTURE_START_CFG, 0x00000000); // cro_hstart = [0+:16], cro_vstart = [16+:16]
+}
+
+void isp_ddr_resolution_config(struct stf_vin_dev *vin)
+{
+ u32 val = 0;
+ val = (vin->frame.width-1) + ((vin->frame.height-1)<<16);
+
+ reg_write(vin->base_isp, ISP_REG_PIC_CAPTURE_END_CFG, val); // cro_hend = [0+:16], cro_vend = [16+:16]
+ val = (vin->frame.width) + ((vin->frame.height)<<16);
+ reg_write(vin->base_isp, ISP_REG_PIPELINE_XY_SIZE, val); // ISP pipeline width[0+:16], height[16+:16]
+
+ reg_write(vin->base_isp, ISP_REG_Y_PLANE_START_ADDR, FB_FIRST_ADDR); // Unscaled Output Image Y Plane Start Address Register
+ reg_write(vin->base_isp, ISP_REG_UV_PLANE_START_ADDR, FB_FIRST_ADDR+(vin->frame.width*vin->frame.height)); // Unscaled Output Image UV Plane Start Address Register
+
+ reg_write(vin->base_isp, ISP_REG_STRIDE, vin->frame.width); // Unscaled Output Image Stride Register
+
+ reg_write(vin->base_isp, ISP_REG_PIXEL_COORDINATE_GEN, 0x00000010); // Unscaled Output Pixel Coordinate Generator Mode Register
+ reg_write(vin->base_isp, ISP_REG_PIXEL_AXI_CONTROL, 0x00000000);
+ reg_write(vin->base_isp, ISP_REG_SS_AXI_CONTROL, 0x00000000);
+
+ reg_write(vin->base_isp, ISP_REG_RGB_TO_YUV_COVERSION0, 0x0000004D); // ICCONV_0
+ reg_write(vin->base_isp, ISP_REG_RGB_TO_YUV_COVERSION1, 0x00000096); // ICCONV_1
+ reg_write(vin->base_isp, ISP_REG_RGB_TO_YUV_COVERSION2, 0x0000001D); // ICCONV_2
+ reg_write(vin->base_isp, ISP_REG_RGB_TO_YUV_COVERSION3, 0x000001DA); // ICCONV_3
+ reg_write(vin->base_isp, ISP_REG_RGB_TO_YUV_COVERSION4, 0x000001B6); // ICCONV_4
+ reg_write(vin->base_isp, ISP_REG_RGB_TO_YUV_COVERSION5, 0x00000070); // ICCONV_5
+ reg_write(vin->base_isp, ISP_REG_RGB_TO_YUV_COVERSION6, 0x0000009D); // ICCONV_6
+ reg_write(vin->base_isp, ISP_REG_RGB_TO_YUV_COVERSION7, 0x0000017C); // ICCONV_7
+ reg_write(vin->base_isp, ISP_REG_RGB_TO_YUV_COVERSION8, 0x000001E6); // ICCONV_8
+
+ reg_write(vin->base_isp, ISP_REG_CIS_MODULE_CFG, 0x00000000);
+ reg_write(vin->base_isp, ISP_REG_ISP_CTRL_1, 0x10000022); //0x30000022);//
+ reg_write(vin->base_isp, ISP_REG_DC_AXI_ID, 0x00000000);
+ reg_write(vin->base_isp, 0x00000008, 0x00010005);//this reg can not be found in document
+}
+
+void isp_reset(struct stf_vin_dev *vin)
+{
+ u32 isp_enable = ISP_NO_SCALE_ENABLE | ISP_MULTI_FRAME_ENABLE;
+ reg_write(vin->base_isp, ISP_REG_ISP_CTRL_0, (isp_enable | ISP_RESET) /*0x00120002 */ ); // isp_rst = [1]
+ reg_write(vin->base_isp, ISP_REG_ISP_CTRL_0, isp_enable /*0x00120000 */ ); // isp_rst = [1]
+}
+
+void isp_enable(struct stf_vin_dev *vin)
+{
+ u32 isp_enable = ISP_NO_SCALE_ENABLE | ISP_MULTI_FRAME_ENABLE;
+
+ reg_write(vin->base_isp, ISP_REG_ISP_CTRL_0, (isp_enable | ISP_ENBALE) /*0x00120001 */ ); // isp_en = [0]
+ reg_write(vin->base_isp, 0x00000008, 0x00010004); // CSI immed shadow update
+ reg_write(vin->base_isp, ISP_REG_CSI_INPUT_EN_AND_STATUS, 0x00000001); // csi_en = [0]
+}
+
+void isp_dvp_2ndframe_config(struct stf_vin_dev *vin)
+{
+ reg_write(vin->base_isp, ISP_REG_Y_PLANE_START_ADDR, FB_SECOND_ADDR); // Unscaled Output Image Y Plane Start Address Register
+ reg_write(vin->base_isp, ISP_REG_UV_PLANE_START_ADDR, FB_SECOND_ADDR+vin->frame.width*vin->frame.height); // Unscaled Output Image UV Plane Start Address Register
+ reg_write(vin->base_isp, ISP_REG_STRIDE, vin->frame.width); // Unscaled Output Image Stride Register
+}
+
+void isp_clk_set(struct stf_vin_dev *vin)
+{
+ void __iomem *clkgen_base = vin->base + VIN_CLKGEN_OFFSET;
+
+ if (vin->isp0) {
+ /* enable isp0 clk */
+ reg_write(clkgen_base, CLK_ISP0_CTRL, 0x80000002); //ISP0-CLK
+ reg_write(clkgen_base, CLK_ISP0_2X_CTRL, 0x80000000);
+ reg_write(clkgen_base, CLK_ISP0_MIPI_CTRL, 0x80000000);
+
+ reg_write(clkgen_base, CLK_MIPI_RX0_PXL_CTRL, 0x00000008); //0x00000010);colorbar
+
+ if (vin->format.format == SRC_COLORBAR_VIN_ISP)
+ reg_write(clkgen_base, CLK_C_ISP0_CTRL, 0x00000000);
+ else
+ reg_write(clkgen_base, CLK_C_ISP0_CTRL, 0x02000000);
+ }
+
+ if (vin->isp1) {
+ /* enable isp1 clk */
+ reg_write(clkgen_base, CLK_ISP1_CTRL, 0x80000002); //ISP1-CLK
+ reg_write(clkgen_base, CLK_ISP1_2X_CTRL, 0x80000000);
+ reg_write(clkgen_base, CLK_ISP1_MIPI_CTRL, 0x80000000);
+
+ reg_write(clkgen_base, CLK_MIPI_RX1_PXL_CTRL, 0x00000008); //0x00000010);colorbar
+
+ if (vin->format.format == SRC_COLORBAR_VIN_ISP)
+ reg_write(clkgen_base, CLK_C_ISP1_CTRL, 0x00000000);
+ else
+ reg_write(clkgen_base, CLK_C_ISP1_CTRL, 0x02000000);
+ }
+}
+EXPORT_SYMBOL(isp_clk_set);
+
+void isp_ddr_config(struct stf_vin_dev *vin)
+{
+ isp_ddr_format_config(vin);
+ isp_ddr_resolution_config(vin);
+ /* reset isp */
+ isp_reset(vin);
+ /* enable isp */
+ isp_enable(vin);
+ if(SRC_DVP_SENSOR_VIN_ISP == vin->format.format)
+ isp_dvp_2ndframe_config(vin);
+}
+EXPORT_SYMBOL(isp_ddr_config);
+
+void isp_base_addr_config(struct stf_vin_dev *vin)
+{
+ if(vin->isp0)
+ vin->base_isp = ioremap(VIN_ISP0_BASE_ADDR, 0x30000);
+ else if(vin->isp1)
+ vin->base_isp = ioremap(VIN_ISP1_BASE_ADDR, 0x30000);
+ else{
+ return;
+ }
+}
+EXPORT_SYMBOL(isp_base_addr_config);
+
+MODULE_AUTHOR("StarFive Technology Co., Ltd.");
+MODULE_DESCRIPTION("loadable ISP driver for StarFive");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/starfive/stf_isp.h b/drivers/media/platform/starfive/stf_isp.h
new file mode 100644
index 000000000000..91dba96f57bb
--- /dev/null
+++ b/drivers/media/platform/starfive/stf_isp.h
@@ -0,0 +1,17 @@
+/*
+ * StarFive isp driver
+ *
+ * Copyright 2020 StarFive Inc.
+ *
+ * Licensed under the GPL-2.
+ */
+
+#ifndef __SF_ISP_H__
+#define __SF_ISP_H__
+
+extern void isp_clk_set(struct stf_vin_dev *vin);
+extern void isp_ddr_config(struct stf_vin_dev *vin);
+extern void isp_base_addr_config(struct stf_vin_dev *vin);
+
+#endif
+
diff --git a/drivers/media/platform/starfive/stf_vin.c b/drivers/media/platform/starfive/stf_vin.c
new file mode 100644
index 000000000000..cb161c349514
--- /dev/null
+++ b/drivers/media/platform/starfive/stf_vin.c
@@ -0,0 +1,764 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for VIC Video In
+ *
+ * Copyright (C) starfivetech.Inc
+ * Authors: Xing Tang <eric.tang@starfivetech.com>
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/dmaengine.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/of_graph.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <linux/io.h>
+#include <linux/dma-mapping.h>
+#include <linux/uaccess.h>
+#include <video/stf-vin.h>
+#include "stf_isp.h"
+
+static DEFINE_MUTEX(vin_mutex);
+static void __iomem *isp0_base;
+
+static inline u32 reg_read(void __iomem * base, u32 reg)
+{
+ return ioread32(base + reg);
+}
+
+static inline void reg_write(void __iomem * base, u32 reg, u32 val)
+{
+ iowrite32(val, base + reg);
+}
+
+static inline void reg_set(void __iomem * base, u32 reg, u32 mask)
+{
+ reg_write(base, reg, reg_read(base, reg) | mask);
+}
+
+static inline void reg_clear(void __iomem * base, u32 reg, u32 mask)
+{
+ reg_write(base, reg, reg_read(base, reg) & ~mask);
+}
+
+static void reg_set_highest_bit(void __iomem * base, u32 reg)
+{
+ u32 val;
+ val = ioread32(base + reg);
+ val &= ~(0x1 << 31);
+ val |= (0x1 & 0x1) << 31;
+ iowrite32(val, base + reg);
+}
+
+static void reg_clear_highest_bit(void __iomem * base, u32 reg)
+{
+ u32 val;
+ val = ioread32(base + reg);
+ val &= ~(0x1 << 31);
+ val |= (0x0 & 0x1) << 31;
+ iowrite32(val, base + reg);
+}
+
+static int vin_open(struct inode *inode, struct file *file)
+{
+ int ret = 0;
+ mutex_lock(&vin_mutex);
+
+ struct stf_vin_dev *dev;
+ dev=container_of(inode->i_cdev, struct stf_vin_dev, vin_cdev);
+
+ file->private_data = dev;
+out:
+ mutex_unlock(&vin_mutex);
+ return ret;
+}
+
+static ssize_t vin_read(struct file *file, char __user * buf,
+ size_t count, loff_t * ppos)
+{
+ int ret;
+ int data[2];
+ struct stf_vin_dev *vin = file->private_data;
+ if (vin->condition == false) {
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ if (wait_event_interruptible(vin->wq, vin->condition != false))
+ return -ERESTARTSYS;
+ }
+ data[0] = vin->odd;
+ data[1] = vin->buf.size;
+
+ mutex_lock(&vin_mutex);
+ ret = copy_to_user(buf, data, count);
+ if (ret != 0) {
+ pr_err("Failed to copy data\n");
+ return -EINVAL;
+ }
+ mutex_unlock(&vin_mutex);
+
+ vin->condition = false;
+
+ return count;
+}
+
+static int vin_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static const struct vm_operations_struct mmap_mem_ops = {
+#ifdef CONFIG_HAVE_IOREMAP_PROT
+ .access = generic_access_phys
+#endif
+};
+
+static int vin_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct stf_vin_dev *vin = file->private_data;
+ size_t size = vma->vm_end - vma->vm_start;
+
+ vma->vm_ops = &mmap_mem_ops;
+
+ /* Remap-pfn-range will mark the range VM_IO */
+ if (remap_pfn_range(vma,
+ vma->vm_start,
+ vin->buf.paddr >> PAGE_SHIFT,
+ size, vma->vm_page_prot)) {
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+
+static long int vin_ioctl(struct file *file, unsigned int cmd,
+ long unsigned int arg)
+{
+ return 0;
+}
+
+static const struct file_operations vin_fops = {
+ .owner = THIS_MODULE,
+ .open = vin_open,
+ .read = vin_read,
+ .release = vin_release,
+ .unlocked_ioctl = vin_ioctl,
+ .mmap = vin_mmap,
+};
+
+/*-------------------------------------------------------*/
+/*
+ * NOTE:
+ * vic clk driver hasn't complete, using follow functions
+ * to reset
+ * TODO: vic clk driver
+ *
+ */
+static int vin_clk_reset(void)
+{
+ void __iomem *vin_clk_gen_base
+ = ioremap(VIN_CLKGEN_BASE_ADDR, 0x1000);
+ //disable clk
+ reg_clear_highest_bit(vin_clk_gen_base,CLK_VIN_SRC_CTRL);
+ reg_clear_highest_bit(vin_clk_gen_base,CLK_ISP0_AXI_CTRL);
+ reg_clear_highest_bit(vin_clk_gen_base,CLK_ISP0NOC_AXI_CTRL);
+ reg_clear_highest_bit(vin_clk_gen_base,CLK_ISPSLV_AXI_CTRL);
+ reg_clear_highest_bit(vin_clk_gen_base,CLK_ISP1_AXI_CTRL);
+ reg_clear_highest_bit(vin_clk_gen_base,CLK_ISP1NOC_AXI_CTRL);
+ reg_clear_highest_bit(vin_clk_gen_base,CLK_VIN_AXI);
+ reg_clear_highest_bit(vin_clk_gen_base,CLK_VINNOC_AXI);
+
+ //enable clk
+ reg_set_highest_bit(vin_clk_gen_base,CLK_VIN_SRC_CTRL);
+ reg_set_highest_bit(vin_clk_gen_base,CLK_ISP0_AXI_CTRL);
+ reg_set_highest_bit(vin_clk_gen_base,CLK_ISP0NOC_AXI_CTRL);
+ reg_set_highest_bit(vin_clk_gen_base,CLK_ISPSLV_AXI_CTRL);
+ reg_set_highest_bit(vin_clk_gen_base,CLK_ISP1_AXI_CTRL);
+ reg_set_highest_bit(vin_clk_gen_base,CLK_ISP1NOC_AXI_CTRL);
+ reg_set_highest_bit(vin_clk_gen_base,CLK_VIN_AXI);
+ reg_set_highest_bit(vin_clk_gen_base,CLK_VINNOC_AXI);
+
+ return 0;
+}
+
+static int vin_rstgen_assert_reset(void)
+{
+ u32 val;
+ void __iomem *vin_rst_gen_base
+ = ioremap(VIN_RSTGEN_BASE_ADDR, 0x1000);
+ /*
+ * Software_RESET_assert1 (0x11840004)
+ * ------------------------------------
+ * bit[15] rstn_vin_src
+ * bit[16] rstn_ispslv_axi
+ * bit[17] rstn_vin_axi
+ * bit[18] rstn_vinnoc_axi
+ * bit[19] rstn_isp0_axi
+ * bit[20] rstn_isp0noc_axi
+ * bit[21] rstn_isp1_axi
+ * bit[22] rstn_isp1noc_axi
+ *
+ */
+ u32 val_reg_reset_config = 0x7f8000;
+
+ val = ioread32(vin_rst_gen_base + SOFTWARE_RESET_ASSERT1);
+ val |= val_reg_reset_config;
+ iowrite32(val, vin_rst_gen_base + SOFTWARE_RESET_ASSERT1);
+
+ val = ioread32(vin_rst_gen_base + SOFTWARE_RESET_ASSERT1);
+ val &= ~(val_reg_reset_config);
+
+ iowrite32(val, vin_rst_gen_base + SOFTWARE_RESET_ASSERT1);
+
+ return 0;
+}
+
+/*
+ * TODO: should replace with DTS pinctrl
+ *
+ */
+
+static void dvp_io_pad_func_shared_config(void)
+{
+ void __iomem *vin_rst_gen_base
+ = ioremap(VIN_IOPAD_BASE_ADDR, 0x1000);
+ /*
+ * pin: 49 ~ 57
+ * offset: 0x144 ~ 0x164
+ * SCFG_funcshare_pad_ctrl
+ */
+ u32 val_scfg_funcshare_config = 0x800080;
+ iowrite32(val_scfg_funcshare_config, vin_rst_gen_base + IOPAD_REG81);
+ iowrite32(val_scfg_funcshare_config, vin_rst_gen_base + IOPAD_REG82);
+ iowrite32(val_scfg_funcshare_config, vin_rst_gen_base + IOPAD_REG83);
+ iowrite32(val_scfg_funcshare_config, vin_rst_gen_base + IOPAD_REG84);
+ iowrite32(val_scfg_funcshare_config, vin_rst_gen_base + IOPAD_REG85);
+ iowrite32(val_scfg_funcshare_config, vin_rst_gen_base + IOPAD_REG86);
+ iowrite32(val_scfg_funcshare_config, vin_rst_gen_base + IOPAD_REG87);
+ iowrite32(val_scfg_funcshare_config, vin_rst_gen_base + IOPAD_REG88);
+ iowrite32(val_scfg_funcshare_config, vin_rst_gen_base + IOPAD_REG89);
+}
+
+static int vin_rstgen_clkgen(struct stf_vin_dev *vin)
+{
+ void __iomem *rstgen_base = vin->base + VIN_RSTGEN_OFFSET;
+ void __iomem *clkgen_base = vin->base + VIN_CLKGEN_OFFSET;
+ u32 val_vin_axi_wr_ctrl_reg = 0x02000000;
+
+ /* rst disable */
+ reg_write(rstgen_base, SOFTWARE_RESET_ASSERT0, 0xFFFFFFFF);
+
+ /* rst enable */
+ reg_write(rstgen_base, SOFTWARE_RESET_ASSERT0, 0x0);
+
+ switch (vin->format.format) {
+ case SRC_DVP_SENSOR_VIN_OV5640:
+ case SRC_DVP_SENSOR_VIN:
+ reg_write(clkgen_base, CLK_VIN_AXI_WR_CTRL, val_vin_axi_wr_ctrl_reg);
+ break;
+
+ case SRC_COLORBAR_VIN_ISP:
+ case SRC_DVP_SENSOR_VIN_ISP:
+ isp_clk_set(vin);
+ break;
+
+ case SRC_CSI2RX_VIN_ISP:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int get_vin_axiwr_pix_ct(struct stf_vin_dev *vin)
+{
+ void __iomem *sysctrl_base = vin->base + VIN_SYSCONTROLLER_OFFSET;
+ u32 value;
+ int cnfg_axiwr_pix_ct = 0;
+
+ value = reg_read(sysctrl_base, SYSCTRL_REG14) & 0x3;
+ if (value == 0)
+ cnfg_axiwr_pix_ct = 2;
+ else if (value == 1)
+ cnfg_axiwr_pix_ct = 4;
+ else if (value == 2)
+ cnfg_axiwr_pix_ct = 8;
+ else
+ return 0;
+
+ return cnfg_axiwr_pix_ct;
+}
+
+static void set_vin_wr_pix_total(struct stf_vin_dev *vin)
+{
+ void __iomem *sysctrl_base = vin->base + VIN_SYSCONTROLLER_OFFSET;
+ int val, pix_ct;
+ pix_ct = get_vin_axiwr_pix_ct(vin);
+ val = (vin->frame.width / pix_ct) - 1;
+ reg_write(sysctrl_base, SYSCTRL_REG12, val);
+}
+
+static int stf_vin_clk_init(struct stf_vin_dev *vin)
+{
+ int ret=0;
+ ret = vin_clk_reset();
+ ret |= vin_rstgen_assert_reset();
+ ret |= vin_rstgen_clkgen(vin);
+ return ret;
+}
+
+static void dvp_vin_ddr_addr_config(struct stf_vin_dev *vin)
+{
+ void __iomem *sysctrl_base = vin->base + VIN_SYSCONTROLLER_OFFSET;
+ u32 val_vin_axi_ctrl_reg = 0x00000003;
+ dev_dbg(vin->dev, "%d: addr: 0x%lx, size: 0x%lx\n", __LINE__,
+ (long)vin->buf.paddr, (long)(vin->buf.paddr + vin->buf.size));
+ /* set the start address of vin and enable */
+ reg_write(sysctrl_base, SYSCTRL_REG10, (long)vin->buf.paddr);
+ reg_write(sysctrl_base, SYSCTRL_REG11, (long)(vin->buf.paddr + vin->buf.size));
+ reg_write(sysctrl_base, SYSCTRL_REG6, val_vin_axi_ctrl_reg);
+}
+
+static int stf_vin_config_set(struct stf_vin_dev *vin)
+{
+ void __iomem *sysctrl_base = vin->base + VIN_SYSCONTROLLER_OFFSET;
+ u32 val_vin_rd_pix_total_reg ;
+ u32 val_vin_rd_vblank_reg ;
+ u32 val_vin_rd_vend_reg ;
+ u32 val_vin_rd_hblank_reg ;
+ u32 val_vin_rd_hend_reg ;
+ u32 val_vin_rw_ctrl_reg ;
+ u32 val_vin_src_channel_reg ;
+ u32 val_vin_axi_ctrl_reg ;
+ u32 val_vin_rw_start_addr_reg ;
+ u32 val_vin_rd_end_addr_reg ;
+ u32 val_vin_wr_pix_reg ;
+
+ switch (vin->format.format) {
+ case SRC_COLORBAR_VIN_ISP:
+ /*vin */
+ val_vin_rd_pix_total_reg = 0x000003BF;
+ val_vin_rd_vblank_reg = 0x0000002D;
+ val_vin_rd_vend_reg = 0x00000464;
+ val_vin_rd_hblank_reg = 0x00000117;
+ val_vin_rd_hend_reg = 0x00000897;
+ val_vin_rw_ctrl_reg = 0x00010300;
+ val_vin_src_channel_reg = 0x00000088;
+ val_vin_axi_ctrl_reg = 0x00000004;
+
+ reg_write(sysctrl_base, SYSCTRL_REG13, val_vin_rd_pix_total_reg);
+ reg_write(sysctrl_base, SYSCTRL_REG17, val_vin_rd_vblank_reg);
+ reg_write(sysctrl_base, SYSCTRL_REG18, val_vin_rd_vend_reg);
+ reg_write(sysctrl_base, SYSCTRL_REG19, val_vin_rd_hblank_reg);
+ reg_write(sysctrl_base, SYSCTRL_REG20, val_vin_rd_hend_reg);
+ reg_write(sysctrl_base, SYSCTRL_REG14, val_vin_rw_ctrl_reg);
+ reg_write(sysctrl_base, SYSCTRL_REG15, val_vin_src_channel_reg);
+ reg_write(sysctrl_base, SYSCTRL_REG6, val_vin_axi_ctrl_reg);
+ isp_base_addr_config(vin);
+ if(vin->isp0||vin->isp1)
+ isp_ddr_config(vin);
+ break;
+
+ case SRC_DVP_SENSOR_VIN:
+ dvp_io_pad_func_shared_config();
+ /*
+ * NOTE:
+ * 0x38: [13:12]
+ * 00: pix_data[7:0]
+ * 01: pix_data[9:2]
+ */
+ val_vin_rw_ctrl_reg = 0x00010301;
+ reg_write(sysctrl_base, SYSCTRL_REG14, val_vin_rw_ctrl_reg);
+
+ set_vin_wr_pix_total(vin);
+ break;
+
+ case SRC_DVP_SENSOR_VIN_OV5640:
+ dvp_io_pad_func_shared_config();
+ val_vin_rw_start_addr_reg = FB_FIRST_ADDR;
+ val_vin_rd_end_addr_reg = FB_FIRST_ADDR+0x3f4800;
+ val_vin_wr_pix_reg = 0x000001df;
+ val_vin_rw_ctrl_reg = 0x00001202;
+ val_vin_axi_ctrl_reg = 0x00000003;
+ reg_write(sysctrl_base, SYSCTRL_REG10, val_vin_rw_start_addr_reg); // First frame address
+ reg_write(sysctrl_base, SYSCTRL_REG11, val_vin_rd_end_addr_reg); //4bytes 0x887e9000 2bytes 0x883f4800, 1byte 0x881FA400
+ reg_write(sysctrl_base, SYSCTRL_REG12, val_vin_wr_pix_reg); // 0x000003bf 0x000001df 0x000000ef
+ reg_write(sysctrl_base, SYSCTRL_REG14, val_vin_rw_ctrl_reg);
+ reg_write(sysctrl_base, SYSCTRL_REG6, val_vin_axi_ctrl_reg);
+ break;
+
+ case SRC_DVP_SENSOR_VIN_ISP:
+ dvp_io_pad_func_shared_config();
+ val_vin_rw_ctrl_reg = 0x00011300;
+ val_vin_src_channel_reg = 0x00001100;
+ reg_write(sysctrl_base, SYSCTRL_REG14, val_vin_rw_ctrl_reg);
+ reg_write(sysctrl_base, SYSCTRL_REG15, val_vin_src_channel_reg);
+ isp_base_addr_config(vin);
+ if(vin->isp0||vin->isp1)
+ isp_ddr_config(vin);
+ break;
+
+ case SRC_CSI2RX_VIN_ISP:
+ break;
+
+ default:
+ pr_err("unknown format\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * vin_get_pixel_size:
+ *
+ * size = (width * height * 4)/(cnfg_axiwr_pix_ct / 2)
+ */
+static int vin_get_axiwr_pixel_size(struct stf_vin_dev *vin)
+{
+ void __iomem *sysctrl_base = vin->base + VIN_SYSCONTROLLER_OFFSET;
+ u32 value;
+ int cnfg_axiwr_pix_ct;
+
+ value = reg_read(sysctrl_base, SYSCTRL_REG14) & 0x3;
+
+ if (value == 0)
+ cnfg_axiwr_pix_ct = 2;
+ else if (value == 1)
+ cnfg_axiwr_pix_ct = 4;
+ else if (value == 2)
+ cnfg_axiwr_pix_ct = 8;
+ else
+ return 0;
+
+ return (vin->frame.height * vin->frame.width * 4) / (cnfg_axiwr_pix_ct /
+ 2);
+}
+
+static int vin_alloc_buf(struct stf_vin_dev *vin)
+{
+ struct vin_buf *buf = &vin->buf;
+ u32 size;
+ if (vin->format.format == SRC_DVP_SENSOR_VIN_OV5640)
+ size = vin->frame.height*vin->frame.width*2;
+ else
+ size = vin_get_axiwr_pixel_size(vin);
+
+ if (size == 0) {
+ dev_err(vin->dev, "size is 0");
+ return -EINVAL;
+ }
+
+ buf->vaddr = dma_alloc_coherent(vin->dev, 2 * size, &buf->paddr,
+ GFP_KERNEL);
+ if (!buf->vaddr) {
+ dev_err(vin->dev,
+ "Failed to allocate buffer of size 0x%x\n", size);
+ return -ENOMEM;
+ }
+
+ buf->size = size;
+
+ return 0;
+}
+
+static void vin_free_buf(struct stf_vin_dev *vin)
+{
+ struct vin_buf *buf = &vin->buf;
+
+ if (buf->vaddr) {
+ dma_free_coherent(vin->dev, buf->size, buf->vaddr, buf->paddr);
+ buf->vaddr = NULL;
+ buf->size = 0;
+ }
+}
+
+static void vin_intr_clear(void __iomem * sysctrl_base)
+{
+ reg_write(sysctrl_base, SYSCTRL_REG21, 0x1);
+ reg_write(sysctrl_base, SYSCTRL_REG21, 0x0);
+}
+
+static irqreturn_t vin_wr_irq_handler(int irq, void *priv)
+{
+ static struct vin_params params;
+ static int wtimes = 0;
+ struct stf_vin_dev *vin = priv;
+
+ void __iomem *sysctrl_base = vin->base + VIN_SYSCONTROLLER_OFFSET;
+
+ /*clear interrupt */
+ vin_intr_clear(sysctrl_base);
+
+ wtimes = wtimes % 2;
+ if (wtimes == 0)
+ params.paddr = (void *)vin->buf.paddr;
+ else
+ params.paddr = (void *)(vin->buf.paddr + vin->buf.size);
+
+ params.size = vin->buf.size;
+
+ vin_notifier_call(1, ¶ms);
+
+ vin->condition = true;
+ vin->odd = wtimes;
+ wake_up_interruptible(&vin->wq);
+ wtimes++;
+
+ return IRQ_HANDLED;
+}
+
+void vin_isp0_intr_clear(void)
+{
+ u32 value;
+ value = reg_read(isp0_base, 0xA00);
+ reg_write(isp0_base, 0xA00, value);
+}
+
+static irqreturn_t vin_isp0_irq_handler(int irq, void *priv)
+{
+ static struct vin_params params;
+ static int wtimes = 0;
+ struct stf_vin_dev *vin = priv;
+
+ /*clear interrupt */
+ vin_isp0_intr_clear();
+
+ wtimes = wtimes % 2;
+ if (wtimes == 0) {
+ params.paddr = (void *)FB_FIRST_ADDR;
+ reg_write(isp0_base, ISP_REG_Y_PLANE_START_ADDR, FB_FIRST_ADDR); // Unscaled Output Image Y Plane Start Address Register
+ reg_write(isp0_base, ISP_REG_UV_PLANE_START_ADDR, FB_FIRST_ADDR+(vin->frame.width*vin->frame.height)); // Unscaled Output Image UV Plane Start Address Register
+ reg_write(isp0_base, ISP_REG_STRIDE, vin->frame.width); // Unscaled Output Image Stride Register
+ } else {
+ params.paddr = (void *)FB_SECOND_ADDR;
+ reg_write(isp0_base, ISP_REG_Y_PLANE_START_ADDR, FB_SECOND_ADDR); // Unscaled Output Image Y Plane Start Address Register
+ reg_write(isp0_base, ISP_REG_UV_PLANE_START_ADDR, FB_SECOND_ADDR+(vin->frame.width*vin->frame.height)); // Unscaled Output Image UV Plane Start Address Register
+ reg_write(isp0_base, ISP_REG_STRIDE, vin->frame.width); // Unscaled Output Image Stride Register
+ }
+
+ params.size = vin->buf.size;
+
+ vin->condition = true;
+ vin->odd = wtimes;
+ wake_up_interruptible(&vin->wq);
+
+ vin_notifier_call(1, ¶ms);
+
+ wtimes++;
+ return IRQ_HANDLED;
+}
+
+static void vin_irq_disable(struct stf_vin_dev *vin)
+{
+ unsigned int mask_value = 0, value = 0;
+
+ void __iomem *sysctrl_base = vin->base + VIN_SYSCONTROLLER_OFFSET;
+
+ /* mask and clear vin interrupt */
+ mask_value = (0x1 << 4) | (0x1 << 20);
+
+ value = 0x1 | (0x1 << 0x16) | mask_value;
+ reg_write(sysctrl_base, SYSCTRL_REG21, value);
+
+ value = mask_value;
+ reg_write(sysctrl_base, SYSCTRL_REG21, value);
+}
+
+static void vin_irq_enable(struct stf_vin_dev *vin)
+{
+ unsigned int value = 0;
+
+ void __iomem *sysctrl_base = vin->base + VIN_SYSCONTROLLER_OFFSET;
+
+ value = ~((0x1 << 4) | (0x1 << 20));
+
+ reg_write(sysctrl_base, SYSCTRL_REG21, value);
+}
+
+static int stf_vin_probe(struct platform_device *pdev)
+{
+ struct stf_vin_dev *vin;
+ struct resource *mem;
+ int irq;
+ dev_t devid;
+ struct class *vin_cls;
+ int major = 0;
+ int ret = 0;
+ dev_info(&pdev->dev, "vin probe enter!\n");
+
+ vin = devm_kzalloc(&pdev->dev, sizeof(struct stf_vin_dev), GFP_KERNEL);
+ if (!vin)
+ return -ENOMEM;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq <= 0) {
+ dev_err(&pdev->dev, "Could not get irq\n");
+ return -ENODEV;
+ }
+
+ vin->isp0_irq = platform_get_irq(pdev, 1);
+ if (vin->isp0_irq <= 0) {
+ dev_err(&pdev->dev, "Could not get irq\n");
+ return -ENODEV;
+ }
+
+ isp0_base = ioremap(VIN_ISP0_BASE_ADDR, 0x30000);
+ if (IS_ERR(isp0_base)) {
+ dev_err(&pdev->dev, "Could not map registers\n");
+ return PTR_ERR(isp0_base);
+ }
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(&pdev->dev, "Could not get resource\n");
+ return -ENODEV;
+ }
+
+ vin->base = devm_ioremap(&pdev->dev, mem->start,
+ resource_size(mem));
+ if (IS_ERR(vin->base)) {
+ dev_err(&pdev->dev, "Could not map registers\n");
+ return PTR_ERR(vin->base);
+ }
+
+ spin_lock_init(&vin->irqlock);
+
+ vin->dev = &pdev->dev;
+ vin->irq = irq;
+
+ vin_irq_disable(vin);
+
+ ret = devm_request_irq(&pdev->dev, vin->irq, vin_wr_irq_handler, 0,
+ "vin_axiwr_irq", vin);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request irq\n");
+ goto out;
+ }
+
+ ret = devm_request_irq(&pdev->dev, vin->isp0_irq, vin_isp0_irq_handler, 0,
+ "vin_isp0_irq", vin);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request irq\n");
+ goto out;
+ }
+
+ ret = of_reserved_mem_device_init(&pdev->dev);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not get reserved memory\n");
+ goto out;
+ }
+
+ /* default config */
+ vin->format.format = SRC_DVP_SENSOR_VIN_OV5640;
+ vin->frame.height = VD_HEIGHT_1080P;
+ vin->frame.width = VD_WIDTH_1080P;
+ vin->isp0 = false;
+ vin->isp1 = false;
+
+ vin->condition = false;
+ init_waitqueue_head(&vin->wq);
+
+ spin_lock_init(&vin->irqlock);
+ platform_set_drvdata(pdev, vin);
+
+ /* Reset device */
+ ret = stf_vin_clk_init(vin);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to reset device \n");
+ goto out;
+ }
+
+ /* set the sysctl config */
+ ret = stf_vin_config_set(vin);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to config device\n");
+ goto out;
+ }
+
+ ret = vin_alloc_buf(vin);
+ if (ret)
+ goto out;
+
+ vin_irq_enable(vin);
+ if (vin->format.format == SRC_DVP_SENSOR_VIN)
+ dvp_vin_ddr_addr_config(vin);
+
+ usleep_range(3000, 5000);
+
+ ret = alloc_chrdev_region(&devid, 0, 1, "vin");
+
+ major = MAJOR(devid);
+ if (major < 0) {
+ dev_err(&pdev->dev, "Failed register chrdev\n");
+ ret = major;
+ }
+ vin->major = major;
+
+ cdev_init(&vin->vin_cdev, &vin_fops);
+ cdev_add(&vin->vin_cdev, devid, 1);
+
+ vin_cls = class_create(THIS_MODULE, "vin");
+ device_create(vin_cls, NULL, MKDEV(major, 0), NULL, "vin");
+
+ return 0;
+out:
+ return ret;
+}
+
+static int stf_vin_remove(struct platform_device *pdev)
+{
+ struct stf_vin_dev *vin = platform_get_drvdata(pdev);
+ vin_free_buf(vin);
+
+ cdev_del(&vin->vin_cdev);
+ unregister_chrdev_region(MKDEV(vin->major, 0), 1);
+ dev_info(&pdev->dev, "remove done\n");
+
+ return 0;
+}
+
+static const struct of_device_id stf_vin_of_match[] = {
+ {.compatible = "starfive,stf-vin"},
+ { /* end node */ },
+};
+
+MODULE_DEVICE_TABLE(of, stf_vin_of_match);
+
+static struct platform_driver stf_vin_driver = {
+ .probe = stf_vin_probe,
+ .remove = stf_vin_remove,
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = of_match_ptr(stf_vin_of_match),
+ },
+};
+
+static int __init stf_vin_init(void)
+{
+ return platform_driver_register(&stf_vin_driver);
+}
+
+static void __exit stf_vin_cleanup(void)
+{
+ platform_driver_unregister(&stf_vin_driver);
+}
+
+fs_initcall(stf_vin_init);
+module_exit(stf_vin_cleanup);
+
+MODULE_AUTHOR("StarFive Technology Co., Ltd.");
+MODULE_DESCRIPTION("Starfive VIC video in driver");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("video");
diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig
old mode 100644
new mode 100755
index cfb7f5612ef0..e8700a23dd5f
--- a/drivers/video/fbdev/Kconfig
+++ b/drivers/video/fbdev/Kconfig
@@ -1795,6 +1795,15 @@ config PXA3XX_GCU
If you compile this as a module, it will be called pxa3xx_gcu.
+config FB_STARFIVE
+ tristate "Starfive framebuffer support"
+ depends on FB
+ select FB_CFB_FILLRECT
+ select FB_CFB_COPYAREA
+ select FB_CFB_IMAGEBLIT
+ help
+ Framebuffer driver for the Starfive SOC
+
config FB_FSL_DIU
tristate "Freescale DIU framebuffer support"
depends on FB && FSL_SOC
@@ -2239,3 +2248,4 @@ config FB_SM712
source "drivers/video/fbdev/omap/Kconfig"
source "drivers/video/fbdev/omap2/Kconfig"
source "drivers/video/fbdev/mmp/Kconfig"
+source "drivers/video/fbdev/starfive/Kconfig"
diff --git a/drivers/video/fbdev/Makefile b/drivers/video/fbdev/Makefile
old mode 100644
new mode 100755
index 477b9624b703..bb7b6d0455d8
--- a/drivers/video/fbdev/Makefile
+++ b/drivers/video/fbdev/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_FB_VIA) += via/
obj-$(CONFIG_FB_KYRO) += kyro/
obj-$(CONFIG_FB_SAVAGE) += savage/
obj-$(CONFIG_FB_GEODE) += geode/
+obj-$(CONFIG_FB_STARFIVE) += starfive/
obj-$(CONFIG_FB_NEOMAGIC) += neofb.o
obj-$(CONFIG_FB_3DFX) += tdfxfb.o
obj-$(CONFIG_FB_CONTROL) += controlfb.o
diff --git a/drivers/video/fbdev/starfive/Kconfig b/drivers/video/fbdev/starfive/Kconfig
new file mode 100644
index 000000000000..cb617708752a
--- /dev/null
+++ b/drivers/video/fbdev/starfive/Kconfig
@@ -0,0 +1,12 @@
+#
+# Video TX driver configuration
+#
+menu "Video TX Configuration"
+
+config FB_STARFIVE_HDMI_ADV7513
+ bool "HDMI ADV7513 displayer support"
+ depends on FB_STARFIVE
+ help
+ Say Y here if you want to have support for ADV7513 displayer
+
+endmenu
diff --git a/drivers/video/fbdev/starfive/Makefile b/drivers/video/fbdev/starfive/Makefile
new file mode 100644
index 000000000000..80daf1a41b4e
--- /dev/null
+++ b/drivers/video/fbdev/starfive/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for RTC class/drivers.
+#
+
+obj-$(CONFIG_FB_STARFIVE) += starfive_fb.o starfive_lcdc.o starfive_vpp.o \
+ starfive_display_dev.o starfive_displayer.o
+
+obj-$(CONFIG_FB_STARFIVE_HDMI_ADV7513) += adv7513.o
diff --git a/drivers/video/fbdev/starfive/adv7513.c b/drivers/video/fbdev/starfive/adv7513.c
new file mode 100644
index 000000000000..bb4dd0c375fa
--- /dev/null
+++ b/drivers/video/fbdev/starfive/adv7513.c
@@ -0,0 +1,265 @@
+/*
+ ******************************************************************************
+ * @file adv7513.c
+ * @author StarFive Technology
+ * @version V1.0
+ * @date 09/21/2020
+ * @brief
+ ******************************************************************************
+ * @copy
+ *
+ * THE PRESENT SOFTWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
+ * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE
+ * TIME. AS A RESULT, STARFIVE SHALL NOT BE HELD LIABLE FOR ANY
+ * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING
+ * FROM THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE
+ * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
+ *
+ * <h2><center>© COPYRIGHT 2020 Shanghai StarFive Technology Co., Ltd. </center></h2>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/gpio-starfive-vic.h>
+#include <linux/gpio/consumer.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/media.h>
+#include <linux/module.h>
+#include <linux/ratelimit.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "adv7513.h"
+
+static int adv7513_write(struct i2c_client *client, u16 reg, u8 val)
+{
+ struct i2c_msg msg;
+ u8 buf[2];
+ int ret;
+
+ buf[0] = reg;
+ buf[1] = val;
+
+ msg.addr = client->addr;
+ msg.flags = 0;
+ msg.buf = buf;
+ msg.len = 2;
+
+ ret = i2c_transfer(client->adapter, &msg, 1);
+ if (ret >= 0)
+ return 0;
+
+ dev_err(&client->dev,
+ "adv7513 write reg(0x%x val:0x%x) failed !\n", reg, val);
+
+ return ret;
+}
+
+static int adv7513_read(struct i2c_client *client, u8 reg, u8 *val)
+{
+ struct i2c_msg msg[2];
+ u8 buf[2];
+ int ret;
+
+ buf[0] = reg;
+
+ msg[0].addr = client->addr;
+ msg[0].flags = 0;
+ msg[0].buf = buf;
+ msg[0].len = 1;
+
+ msg[1].addr = client->addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].buf = val;
+ msg[1].len = 1;
+
+ ret = i2c_transfer(client->adapter, msg, 2);
+ if (ret >= 0) {
+ return 0;
+ }
+
+ dev_err(&client->dev,
+ "adv7513 read reg(0x%x val:0x%x) failed,ret = %d !\n", reg, *val, ret);
+
+ return ret;
+}
+
+/*============================================================================
+ * Read up to 8-bit field from a single 8-bit register
+ * ________
+ * Example |___***__| Mask = 0x1C BitPos = 2
+ *
+ *
+ * Entry: DevAddr = Device Address
+ * RegAddr = 8-bit register address
+ * Mask = Field mask
+ * BitPos = Field LSBit position in the register (0-7)
+ *
+ * Return: Field value in the LSBits of the return value
+ *
+ *===========================================================================*/
+static u8 adv7513_I2CReadField8 (struct i2c_client *client, u8 RegAddr, u8 Mask,
+ u8 BitPos)
+{
+ u8 data;
+
+ adv7513_read(client, RegAddr, &data);
+ return (data&Mask)>>BitPos;
+}
+
+/*============================================================================
+ * Write up to 8-bit field to a single 8-bit register
+ * ________
+ * Example |___****_| Mask = 0x1E BitPos = 1
+ *
+ * Entry: DevAddr = Device Address
+ * RegAddr = 8-bit register address
+ * Mask = Field mask
+ * BitPos = Field LSBit position in the register (0-7)
+ * Set to 0 if FieldVal is in correct position of the reg
+ * FieldVal= Value (in the LSBits) of the field to be written
+ * If FieldVal is already in the correct position (i.e.,
+ * does not need to be shifted,) set BitPos to 0
+ *
+ * Return: None
+ *
+ *===========================================================================*/
+static void adv7513_I2CWriteField8 (struct i2c_client *client,u8 RegAddr, u8 Mask,
+ u8 BitPos, u8 FieldVal)
+{
+ u8 rdata, wdata;
+
+ adv7513_read(client, RegAddr, &rdata);
+ rdata &= (~Mask);
+ wdata = rdata | ((FieldVal<<BitPos)&Mask);
+ adv7513_write(client, RegAddr, wdata);
+}
+
+static int adv7513_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ struct adv7513_data *adv7513;
+ struct device *dev = &client->dev;
+ u8 value;
+ int ret = 0;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_warn(&client->dev,
+ "I2C adapter doesn't support I2C_FUNC_SMBUS_BYTE\n");
+ return -EIO;
+ }
+
+ adv7513 = devm_kzalloc(&client->dev, sizeof(*adv7513), GFP_KERNEL);
+ if (!adv7513)
+ return -ENOMEM;
+
+ if (of_property_read_u32(dev->of_node, "def-width", &adv7513->def_width)) {
+ dev_err(dev,"Missing def_width property in the DT \n");
+ ret = -EINVAL;
+ }
+
+ adv7513->client = client;
+ i2c_set_clientdata(client, adv7513);
+
+ adv7513_read(client, 0x00, &value);
+ dev_info(&client->dev, "%s[%d],version = 0x%x\n",__func__,__LINE__,value);
+
+ adv7513_I2CWriteField8(client, 0x41, 0x40, 0x6, 0x00);
+ adv7513_I2CWriteField8(client, 0x98, 0xFF, 0x0, 0x03);
+ adv7513_I2CWriteField8(client, 0x9A, 0xE0, 0x5, 0x07);
+ adv7513_I2CWriteField8(client, 0x9C, 0xFF, 0x0, 0x30);
+ adv7513_I2CWriteField8(client, 0x9D, 0x03, 0x0, 0x01);
+ adv7513_I2CWriteField8(client, 0xA2, 0xFF, 0x0, 0xA4);
+ adv7513_I2CWriteField8(client, 0xA3, 0xFF, 0x0, 0xA4);
+ adv7513_I2CWriteField8(client, 0xE0, 0xFF, 0x0, 0xD0);
+ adv7513_I2CWriteField8(client, 0xF9, 0xFF, 0x0, 0x00);
+
+ adv7513_I2CWriteField8(client, 0x15, 0x0F, 0x0, 0x00);
+ adv7513_I2CWriteField8(client, 0x16, 0x30, 0x4, 0x03);
+ adv7513_I2CWriteField8(client, 0x16, 0x0C, 0x2, 0x00);
+ adv7513_I2CWriteField8(client, 0x17, 0x02, 0x1, 0x00);
+
+ adv7513_I2CWriteField8(client, 0x16, 0xC0, 0x6, 0x00);
+ adv7513_I2CWriteField8(client, 0x18, 0x80, 0x7, 0x00);
+ adv7513_I2CWriteField8(client, 0x18, 0x60, 0x5, 0x00);
+ adv7513_I2CWriteField8(client, 0xAF, 0x02, 0x1, 0x01);
+ adv7513_I2CWriteField8(client, 0x3C, 0x3F, 0x0, 0x01);
+
+ switch(adv7513->def_width) {
+ case 288:
+ adv7513_I2CWriteField8(client, 0x3C, 0x3F, 0x0, 0x18);//288P
+ adv7513_I2CWriteField8(client, 0x3B, 0xFF, 0x0, 0x00);
+ break;
+ case 640:
+ adv7513_I2CWriteField8(client, 0x3C, 0x3F, 0x0, 0x01);
+ adv7513_I2CWriteField8(client, 0x3B, 0xFF, 0x0, 0x4A); //b01001010
+ break;
+ case 1280:
+ adv7513_I2CWriteField8(client, 0x3C, 0x3F, 0x0, 0x04);//720P
+ adv7513_I2CWriteField8(client, 0x3B, 0xFF, 0x0, 0x00);//b01011000
+ break;
+ case 1920:
+ adv7513_I2CWriteField8(client, 0x3C, 0x3F, 0x0, 0x10);//1080P
+ adv7513_I2CWriteField8(client, 0x3B, 0xFF, 0x0, 0x00);//b01011000
+ break;
+ default:
+ dev_err(dev,"not support width %d \n",adv7513->def_width);
+ }
+
+ return ret;
+}
+
+static int adv7513_remove(struct i2c_client *client)
+{
+ struct adv7513 *adv7513 = i2c_get_clientdata(client);
+
+ return 0;
+}
+
+static const struct i2c_device_id adv7513_id[] = {
+ { "adv7513", 0 },
+ { }
+};
+
+static const struct of_device_id dvp_adv7513_dt_ids[] = {
+ { .compatible = "adv7513", },
+ { /* sentinel */ }
+};
+
+static struct i2c_driver adv7513_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "adv7513",
+ .of_match_table = dvp_adv7513_dt_ids,
+ },
+ .probe = adv7513_probe,
+ .remove = adv7513_remove,
+ .id_table = adv7513_id,
+};
+
+static __init int init_adv7513(void)
+{
+ int err;
+
+ err = i2c_add_driver(&adv7513_driver);
+ if (err != 0)
+ printk("i2c driver registration failed, error=%d\n", err);
+
+ return err;
+}
+
+static __exit void exit_adv7513(void)
+{
+ i2c_del_driver(&adv7513_driver);
+}
+
+//late_initcall(init_adv7513);
+fs_initcall(init_adv7513);
+module_exit(exit_adv7513);
+
+MODULE_DESCRIPTION("A driver for adv7513");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/fbdev/starfive/adv7513.h b/drivers/video/fbdev/starfive/adv7513.h
new file mode 100644
index 000000000000..fe98b00ff2b9
--- /dev/null
+++ b/drivers/video/fbdev/starfive/adv7513.h
@@ -0,0 +1,22 @@
+/*
+ * Analog Devices ADV7513 HDMI transmitter driver
+ *
+ * Copyright 2020 StarFive Inc.
+ *
+ * Licensed under the GPL-2.
+ */
+
+#ifndef __AD_I2C_ADV7513_H__
+#define __AD_I2C_ADV7513_H__
+
+#define H_SIZE 1920//352//1920//1280
+#define V_SIZE 1080//288//1080//720
+
+struct adv7513_data {
+ struct i2c_client *client;
+ struct device *dev;
+ int irq;
+ int def_width;
+};
+
+#endif
diff --git a/drivers/video/fbdev/starfive/starfive_display_dev.c b/drivers/video/fbdev/starfive/starfive_display_dev.c
new file mode 100644
index 000000000000..2997a0721621
--- /dev/null
+++ b/drivers/video/fbdev/starfive/starfive_display_dev.c
@@ -0,0 +1,106 @@
+/* driver/video/starfive/starfive_display_dev.c
+**
+** This program is free software; you can redistribute it and/or modify
+** it under the terms of the GNU General Public License version 2 as
+** published by the Free Software Foundation.
+**
+** Copyright (C) 2020 StarFive, Inc.
+**
+** PURPOSE: This files contains the driver of LCD controller and VPP.
+**
+** CHANGE HISTORY:
+** Version Date Author Description
+** 0.1.0 2020-11-10 starfive created
+**
+*/
+#include <linux/of_device.h>
+#include <linux/of.h>
+#include <linux/mutex.h>
+#include "starfive_display_dev.h"
+
+struct sf_fb_display_dev_data {
+ struct sf_fb_display_dev* dev;
+ struct list_head list;
+};
+
+static DEFINE_MUTEX(sf_fb_display_dev_lock);
+static LIST_HEAD(sf_fb_display_dev_list);
+
+int sf_fb_display_dev_register(struct sf_fb_display_dev* dev)
+{
+ struct sf_fb_display_dev_data *display_dev;
+
+ display_dev = kzalloc(sizeof(struct sf_fb_display_dev_data), GFP_KERNEL);
+ if (!display_dev)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&display_dev->list);
+ display_dev->dev = dev;
+
+ mutex_lock(&sf_fb_display_dev_lock);
+ list_add_tail(&display_dev->list, &sf_fb_display_dev_list);
+ mutex_unlock(&sf_fb_display_dev_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(sf_fb_display_dev_register);
+
+int sf_fb_display_dev_unregister(struct sf_fb_display_dev* dev)
+{
+ struct sf_fb_display_dev_data *display_dev;
+
+ mutex_lock(&sf_fb_display_dev_lock);
+ list_for_each_entry(display_dev, &sf_fb_display_dev_list, list) {
+ if (display_dev->dev == dev) {
+ list_del_init(&display_dev->list);
+ kfree(display_dev);
+ }
+ }
+ mutex_unlock(&sf_fb_display_dev_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(sf_fb_display_dev_unregister);
+
+static int build_dev_list(struct sf_fb_data *fb_data)
+{
+ int rc = 0;
+
+ rc = of_platform_populate(fb_data->dev->of_node,
+ NULL, NULL, fb_data->dev);
+ if (rc) {
+ dev_err(fb_data->dev,
+ "%s: failed to add child nodes, rc=%d\n",
+ __func__, rc);
+ }
+
+ return rc;
+}
+
+struct sf_fb_display_dev* sf_fb_display_dev_get(struct sf_fb_data *fb_data)
+{
+ struct sf_fb_display_dev_data *display_dev;
+ struct sf_fb_display_dev *dev = NULL;
+ char *connect_panel_name;
+
+ build_dev_list(fb_data);
+
+ connect_panel_name = fb_data->dis_dev_name;
+ mutex_lock(&sf_fb_display_dev_lock);
+ list_for_each_entry(display_dev, &sf_fb_display_dev_list, list) {
+ if(!strcmp(connect_panel_name, display_dev->dev->name)) {
+ dev = display_dev->dev;
+ dev_info(fb_data->dev, "select displayer: %s\n", dev->name);
+ break;
+ }
+ }
+
+ if (!dev) {
+ display_dev = list_first_entry(&sf_fb_display_dev_list, typeof(*display_dev), list);
+ dev = display_dev->dev;
+ dev_info(fb_data->dev,"default get first displayer(%s)! \n", display_dev->dev->name);
+ }
+
+ return dev;
+}
+EXPORT_SYMBOL(sf_fb_display_dev_get);
diff --git a/drivers/video/fbdev/starfive/starfive_display_dev.h b/drivers/video/fbdev/starfive/starfive_display_dev.h
new file mode 100644
index 000000000000..13348226e421
--- /dev/null
+++ b/drivers/video/fbdev/starfive/starfive_display_dev.h
@@ -0,0 +1,257 @@
+/*
+ * StarFive Vout driver
+ *
+ * Copyright 2020 StarFive Inc.
+ *
+ * Licensed under the GPL-2.
+ */
+
+#ifndef __SF_FB_DISPLAY_DEV_H_
+#define __SF_FB_DISPLAY_DEV_H_
+
+#include <linux/fb.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+
+#include "starfive_fb.h"
+
+#define RST_SEQ_LEN 16
+
+#define COMIPFB_HSYNC_HIGH_ACT (0x03)
+#define COMIPFB_VSYNC_HIGH_ACT (0x04)
+
+#define MIPI_VIDEO_MODE (0x05)
+#define MIPI_COMMAND_MODE (0x06)
+
+#define ARRAY_AND_SIZE(x) (u8 *)(x), ARRAY_SIZE(x)
+
+/*display prefer and ce*/
+#define COLOR_TYPE_MAX (3)
+#define PREFER_WARM (1)
+#define PREFER_NATURE (2)
+#define PREFER_COOL (3)
+
+#define CE_BRIGHT (12)
+#define CE_VELVIA (11)
+#define CE_STANDARD (10)
+
+
+enum {
+ DCS_CMD = 2,
+ GEN_CMD,
+ SW_PACK0,
+ SW_PACK1,
+ SW_PACK2,
+ LW_PACK,
+ SHUTDOWN_SW_PACK,
+};
+
+/*Device flags*/
+#define PREFER_CMD_SEND_MONOLITHIC (0x00000001)
+#define CE_CMD_SEND_MONOLITHIC (0x00000002)
+
+#define RESUME_WITH_PREFER (0x00000010)
+#define RESUME_WITH_CE (0x00000020)
+
+
+/**
+ * Video stream type
+ */
+typedef enum {
+ VIDEO_NON_BURST_WITH_SYNC_PULSES = 0,
+ VIDEO_NON_BURST_WITH_SYNC_EVENTS, // hs_freq and pck should be multipe
+ VIDEO_BURST_WITH_SYNC_PULSES,
+}dsih_video_mode_t;
+
+#ifdef CONFIG_FBCON_DRAW_PANIC_TEXT
+extern int kpanic_in_progress;
+#endif
+
+/*struct sf_fb_dev_cmds.cmds must follow this format*/
+struct wr_cmd_hdr {
+ u8 wait;
+ u8 cmd_type0;
+ u8 cmd_type1;
+ u8 dlen;
+} __packed;
+
+struct sf_fb_dev_cmds {
+ unsigned char *cmds;
+ unsigned short n_pack;
+ unsigned short n_cmds;
+};
+
+struct bl_cmds {
+ struct sf_fb_dev_cmds bl_cmd;
+ unsigned int brightness_bit;
+};
+
+struct mipi_color_bits {
+ unsigned int color_bits; // must be set !!
+ unsigned int is_18bit_loosely; // optional
+};
+
+struct rw_timeout {
+ unsigned int hs_rd_to_cnt;
+ unsigned int lp_rd_to_cnt;
+ unsigned int hs_wr_to_cnt;
+ unsigned int lp_wr_to_cnt;
+ unsigned int bta_to_cnt;
+};
+
+struct video_mode_info {
+ unsigned int hsync; /* Horizontal Synchronization, unit : pclk. */
+ unsigned int hbp; /* Horizontal Back Porch, unit : pclk. */
+ unsigned int hfp; /* Horizontal Front Porch, unit : pclk. */
+ unsigned int vsync; /* Vertical Synchronization, unit : line. */
+ unsigned int vbp; /* Vertical Back Porch, unit : line. */
+ unsigned int vfp; /* Vertical Front Porch, unit : line. */
+ unsigned int sync_pol;
+ unsigned int lp_cmd_en:1;
+ unsigned int frame_bta_ack:1;
+ unsigned int lp_hfp_en:1; // default should be 1
+ unsigned int lp_hbp_en:1;
+ unsigned int lp_vact_en:1;
+ unsigned int lp_vfp_en:1;
+ unsigned int lp_vbp_en:1;
+ unsigned int lp_vsa_en:1;
+ dsih_video_mode_t mipi_trans_type; /* burst or no burst*/
+};
+
+struct command_mode_info {
+ unsigned int tear_fx_en:1;
+ unsigned int ack_rqst_en:1;
+ unsigned int gen_sw_0p_tx:1; // default should be 1
+ unsigned int gen_sw_1p_tx:1; // default should be 1
+ unsigned int gen_sw_2p_tx:1; // default should be 1
+ unsigned int gen_sr_0p_tx:1; // default should be 1
+ unsigned int gen_sr_1p_tx:1; // default should be 1
+ unsigned int gen_sr_2p_tx:1; // default should be 1
+ unsigned int gen_lw_tx:1; // default should be 1
+ unsigned int dcs_sw_0p_tx:1; // default should be 1
+ unsigned int dcs_sw_1p_tx:1; // default should be 1
+ unsigned int dcs_sr_0p_tx:1; // default should be 1
+ unsigned int dcs_lw_tx:1; // default should be 1
+ unsigned int max_rd_pkt_size:1; // default should be 1
+ struct rw_timeout timeout;
+};
+
+struct external_info {
+ unsigned char crc_rx_en:1;
+ unsigned char ecc_rx_en:1;
+ unsigned char eotp_rx_en:1;
+ unsigned char eotp_tx_en:1;
+ unsigned int dev_read_time; //HSBYTECLK is danwe
+};
+
+struct phy_time_info {
+ unsigned char lpx;
+ //unsigned char clk_lpx;
+ unsigned char clk_tprepare;
+ unsigned char clk_hs_zero;
+ unsigned char clk_hs_trail;
+ unsigned char clk_hs_exit;
+ unsigned char clk_hs_post;
+
+ //unsigned char data_lpx;
+ unsigned char data_tprepare;
+ unsigned char data_hs_zero;
+ unsigned char data_hs_trail;
+ unsigned char data_hs_exit;
+ unsigned char data_hs_post;
+};
+
+struct te_info {
+ unsigned int te_source;
+ unsigned int te_trigger_mode;
+ unsigned int te_en;
+ unsigned int te_sync_en; // In command mode should set 1, video should set 0
+ unsigned int te_cps; // te count per second
+};
+
+struct sf_fb_timing_mipi {
+ unsigned int hs_freq; /*PHY output freq, bytes KHZ*/
+ unsigned int lp_freq; /*default is 10MHZ*/
+ unsigned int no_lanes; /*lane numbers*/
+ unsigned int display_mode; //video mode or command mode.
+ unsigned int auto_stop_clklane_en;
+ unsigned int im_pin_val; /*IM PIN val, if use gpio_im, default config is 1 ?? */
+ struct mipi_color_bits color_mode; /*color bits*/
+ struct video_mode_info videomode_info;
+ struct command_mode_info commandmode_info;
+ struct phy_time_info phytime_info;
+ struct te_info teinfo;
+ struct external_info ext_info;
+};
+
+struct rd_cmd_hdr {
+ unsigned char pack_type;
+ unsigned char cmd;
+ unsigned char id_count;
+} __packed;
+struct common_id_info {
+ struct rd_cmd_hdr hdr;
+ unsigned char id[6];
+} __packed;
+
+struct sf_fb_id_info {
+ unsigned char num_id_info;
+ struct common_id_info *id_info;
+ struct sf_fb_dev_cmds prepare_cmd;
+};
+
+struct prefer_ce_info {
+ int type;
+ struct sf_fb_dev_cmds cmds;
+};
+
+struct sf_fb_prefer_ce {
+ int types;
+ struct prefer_ce_info *info;
+};
+
+struct sf_fb_display_dev {
+ const char* name; /* Device name. */
+ unsigned int interface_info;//interface infomation MIPI or RGB
+ unsigned int lcd_id;
+ unsigned int refresh_en; /* Refresh enable. */
+ unsigned int pclk; /* Pixel clock in HZ. */
+ unsigned int bpp; /* Bits per pixel. */
+ unsigned int xres; /* Device resolution. */
+ unsigned int yres;
+ unsigned int width; /* Width of device in mm. */
+ unsigned int height; /* Height of device in mm. */
+ unsigned int flags; /* Device flags. */
+ unsigned int auto_fps; /* auto adjust frame rate flag*/
+ unsigned int send_suspend_cmd_in_hs_mode; /* send suspend cmd is hs mode */
+ union {
+ //struct comipfb_dev_timing_rgb rgb;
+ struct sf_fb_timing_mipi mipi;
+ } timing;
+
+ struct sf_fb_id_info panel_id_info;
+ struct sf_fb_id_info esd_id_info;
+ struct sf_fb_dev_cmds cmds_init;
+ struct sf_fb_dev_cmds cmds_suspend;
+ struct sf_fb_dev_cmds cmds_resume;
+ struct sf_fb_dev_cmds cmds_pre_suspend;
+ struct bl_cmds backlight_info;
+
+ struct sf_fb_prefer_ce resume_prefer_info;
+ struct sf_fb_prefer_ce display_prefer_info;
+ struct sf_fb_prefer_ce display_ce_info;
+ int init_last; /*when resume, send gamma/ce cmd before init cmd*/
+ u32 rst_seq[RST_SEQ_LEN];
+ u32 rst_seq_len;
+
+ int (*power)(struct sf_fb_data *fb_data, int onoff);
+ int (*reset)(struct sf_fb_data *fb_data);
+ int (*suspend)(struct sf_fb_data *fb_data);
+ int (*resume)(struct sf_fb_data *fb_data);
+};
+
+extern int sf_fb_display_dev_register(struct sf_fb_display_dev* dev);
+extern int sf_fb_display_dev_unregister(struct sf_fb_display_dev* dev);
+extern struct sf_fb_display_dev* sf_fb_display_dev_get(struct sf_fb_data *fb_data);
+
+#endif /*__COMIPFB_DEV_H_*/
diff --git a/drivers/video/fbdev/starfive/starfive_displayer.c b/drivers/video/fbdev/starfive/starfive_displayer.c
new file mode 100644
index 000000000000..e23e1ff6ce9d
--- /dev/null
+++ b/drivers/video/fbdev/starfive/starfive_displayer.c
@@ -0,0 +1,811 @@
+/* driver/video/starfive/starfive_displayer.c
+**
+** This program is free software; you can redistribute it and/or modify
+** it under the terms of the GNU General Public License version 2 as
+** published by the Free Software Foundation.
+**
+** Copyright (C) 2020 StarFive, Inc.
+**
+** PURPOSE: This files contains the driver of LCD controller and VPP.
+**
+** CHANGE HISTORY:
+** Version Date Author Description
+** 0.1.0 2020-11-10 starfive created
+**
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/compiler.h>
+#include <linux/init.h>
+#include <linux/of_device.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+
+#include "starfive_fb.h"
+#include "starfive_display_dev.h"
+
+#define _ALIGN_UP(addr, size) (((addr)+((size)-1))&(~((typeof(addr))(size)-1)))
+#define DSI_CMD_LEN(hdr) (sizeof(*hdr) + (hdr)->dlen)
+
+static int sf_displayer_reset(struct sf_fb_data *fbi)
+{
+ return 0;
+}
+
+static int sf_displayer_power_on(struct sf_fb_data *fbi, int onoff)
+{
+ return 0;
+}
+
+static int sf_displayer_suspend(struct sf_fb_data *fbi)
+{
+ return 0;
+}
+
+static int sf_displayer_resume(struct sf_fb_data *fbi)
+{
+ return 0;
+}
+
+static void __maybe_unused dump_panel_info(struct device *dev,
+ struct sf_fb_display_dev *dev_data)
+{
+
+ dev_dbg(dev, "id info: pack_type = 0x%x, cmd = 0x%x, id_count = %d, id = 0x%x, 0x%x\n",
+ dev_data->panel_id_info.id_info->hdr.pack_type,
+ dev_data->panel_id_info.id_info->hdr.cmd,
+ dev_data->panel_id_info.id_info->hdr.id_count,
+ dev_data->panel_id_info.id_info->id[0],
+ dev_data->panel_id_info.id_info->id[1]);
+
+}
+
+static int of_parse_video_mode(struct device_node *np,
+ struct video_mode_info *videomode_info)
+{
+ int rc;
+ u32 temp_val;
+ const char *data;
+
+ rc = of_property_read_u32(np, "h-pulse-width", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, h-pulse-width not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ videomode_info->hsync= temp_val;
+ rc = of_property_read_u32(np, "h-back-porch", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, h-back-porch not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ videomode_info->hbp = temp_val;
+ rc = of_property_read_u32(np, "h-front-porch", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, h-front-porch not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ videomode_info->hfp = temp_val;
+
+ rc = of_property_read_u32(np, "v-pulse-width", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, v-pulse-width not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ videomode_info->vsync = temp_val;
+ rc = of_property_read_u32(np, "v-back-porch", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, v-back-porch not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ videomode_info->vbp = temp_val;
+ rc = of_property_read_u32(np, "v-front-porch", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, v-front-porch not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ videomode_info->vfp = temp_val;
+
+ videomode_info->sync_pol = COMIPFB_VSYNC_HIGH_ACT;
+ data = of_get_property(np, "sync_pol", NULL);
+ if (data) {
+ if (!strcmp(data, "vsync_high_act"))
+ videomode_info->sync_pol = COMIPFB_VSYNC_HIGH_ACT;
+ else if (!strcmp(data, "hsync_high_act"))
+ videomode_info->sync_pol = COMIPFB_HSYNC_HIGH_ACT;
+ }
+
+ videomode_info->lp_cmd_en = of_property_read_bool(np,
+ "lp_cmd_en");
+ videomode_info->lp_hfp_en = of_property_read_bool(np,
+ "lp_hfp_en");
+ videomode_info->lp_hbp_en = of_property_read_bool(np,
+ "lp_hbp_en");
+ videomode_info->lp_vact_en = of_property_read_bool(np,
+ "lp_vact_en");
+ videomode_info->lp_vfp_en = of_property_read_bool(np,
+ "lp_vfp_en");
+ videomode_info->lp_vbp_en = of_property_read_bool(np,
+ "lp_vbp_en");
+ videomode_info->lp_vsa_en = of_property_read_bool(np,
+ "lp_vsa_en");
+
+ videomode_info->mipi_trans_type = VIDEO_BURST_WITH_SYNC_PULSES;
+ data = of_get_property(np, "traffic-mode", NULL);
+ if (data) {
+ if (!strcmp(data, "burst_with_sync_pulses"))
+ videomode_info->sync_pol = VIDEO_BURST_WITH_SYNC_PULSES;
+ else if (!strcmp(data, "non_burst_with_sync_pulses"))
+ videomode_info->sync_pol = VIDEO_NON_BURST_WITH_SYNC_PULSES;
+ else if (!strcmp(data, "non_burst_with_sync_events"))
+ videomode_info->sync_pol = VIDEO_NON_BURST_WITH_SYNC_EVENTS;
+ }
+
+ return 0;
+}
+
+static int of_parse_command_mode(struct device_node *np,
+ struct command_mode_info *cmdmode_info)
+{
+ int rc;
+ u32 temp_val;
+
+ cmdmode_info->tear_fx_en = of_property_read_bool(np,
+ "tear_fx_en");
+ cmdmode_info->ack_rqst_en = of_property_read_bool(np,
+ "ack_rqst_en");
+ cmdmode_info->gen_sw_0p_tx = of_property_read_bool(np,
+ "gen_sw_0p_tx");
+ cmdmode_info->gen_sw_1p_tx = of_property_read_bool(np,
+ "gen_sw_1p_tx");
+ cmdmode_info->gen_sw_2p_tx = of_property_read_bool(np,
+ "gen_sw_2p_tx");
+ cmdmode_info->gen_sr_0p_tx = of_property_read_bool(np,
+ "gen_sr_0p_tx");
+ cmdmode_info->gen_sr_1p_tx = of_property_read_bool(np,
+ "gen_sr_1p_tx");
+ cmdmode_info->gen_sr_2p_tx = of_property_read_bool(np,
+ "gen_sr_2p_tx");
+ cmdmode_info->gen_lw_tx = of_property_read_bool(np,
+ "gen_lw_tx");
+ cmdmode_info->dcs_sw_0p_tx = of_property_read_bool(np,
+ "dcs_sw_0p_tx");
+ cmdmode_info->dcs_sw_1p_tx = of_property_read_bool(np,
+ "dcs_sw_1p_tx");
+ cmdmode_info->dcs_sr_0p_tx = of_property_read_bool(np,
+ "dcs_sr_0p_tx");
+ cmdmode_info->dcs_lw_tx = of_property_read_bool(np,
+ "dcs_lw_tx");
+ cmdmode_info->max_rd_pkt_size = of_property_read_bool(np,
+ "max_rd_pkt_size");
+
+
+ rc = of_property_read_u32(np, "hs_rd_to_cnt", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, hs_rd_to_cnt not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ cmdmode_info->timeout.hs_rd_to_cnt = temp_val;
+
+ rc = of_property_read_u32(np, "lp_rd_to_cnt", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, lp_rd_to_cnt not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ cmdmode_info->timeout.lp_rd_to_cnt = temp_val;
+
+ rc = of_property_read_u32(np, "hs_wr_to_cnt", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, hs_wr_to_cnt not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ cmdmode_info->timeout.hs_wr_to_cnt = temp_val;
+
+ rc = of_property_read_u32(np, "lp_wr_to_cnt", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, lp_wr_to_cnt not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ cmdmode_info->timeout.lp_wr_to_cnt = temp_val;
+
+ rc = of_property_read_u32(np, "bta_to_cnt", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, bta_to_cnt not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ cmdmode_info->timeout.bta_to_cnt = temp_val;
+
+ return 0;
+
+}
+
+static int of_parse_phy_timing(struct device_node *np,
+ struct phy_time_info *phy_timing)
+{
+ int rc;
+ u8 temp_val;
+
+ rc = of_property_read_u8(np, "data_tprepare", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, data_tprepare not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ phy_timing->data_tprepare = (!rc ? temp_val : 0);
+
+ rc = of_property_read_u8(np, "data_hs_zero", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, data_hs_zero not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ phy_timing->data_hs_zero = temp_val;
+
+ rc = of_property_read_u8(np, "data_hs_exit", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, data_hs_exit not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ phy_timing->data_hs_exit = temp_val;
+
+ rc = of_property_read_u8(np, "data_hs_trail", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, data_hs_trail not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ phy_timing->data_hs_trail = temp_val;
+
+ return 0;
+}
+
+static int of_parse_te_info(struct device_node *np,
+ struct te_info *teinfo)
+{
+ int rc;
+ u32 temp_val;
+ const char *data;
+
+ teinfo->te_source = 1;
+ data = of_get_property(np, "te_source", NULL);
+ if (data) {
+ if (!strcmp(data, "external_pin"))
+ teinfo->te_source = 1;
+ else if (!strcmp(data, "dsi_te_trigger"))
+ teinfo->te_source = 0;
+ }
+
+ teinfo->te_trigger_mode = 1;
+ data = of_get_property(np, "te_trigger_mode", NULL);
+ if (data) {
+ if (!strcmp(data, "rising_edge"))
+ teinfo->te_source = 0;
+ else if (!strcmp(data, "high_1000us"))
+ teinfo->te_source = 1;
+ }
+
+ rc = of_property_read_u32(np, "te_enable", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, te_enable not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ teinfo->te_en = temp_val;
+
+ rc = of_property_read_u32(np, "cm_te_effect_sync_enable", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, cm_te_effect_sync_enable not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ teinfo->te_sync_en = temp_val;
+
+ rc = of_property_read_u32(np, "te_count_per_sec", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, te_count_per_sec not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ teinfo->te_cps = temp_val;
+
+ return 0;
+}
+
+static int of_parse_ext_info(struct device_node *np,
+ struct external_info *ext_info)
+{
+ int rc;
+ u32 temp_val;
+
+ ext_info->crc_rx_en = of_property_read_bool(np, "crc_rx_en");
+ ext_info->ecc_rx_en = of_property_read_bool(np, "ecc_rx_en");
+ ext_info->eotp_rx_en = of_property_read_bool(np, "eotp_rx_en");
+ ext_info->eotp_tx_en = of_property_read_bool(np, "eotp_tx_en");
+
+ rc = of_property_read_u32(np, "dev_read_time", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, dev_read_time not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ ext_info->dev_read_time = temp_val;
+
+ return 0;
+}
+
+
+static int of_parse_mipi_timing(struct device_node *np,
+ struct sf_fb_timing_mipi *mipi_timing)
+{
+ int rc;
+ u32 temp_val;
+ const char *data;
+
+ rc = of_property_read_u32(np, "mipi-byte-clock", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, mipi-byte-clock not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ mipi_timing->hs_freq = temp_val;
+
+ rc = of_property_read_u32(np, "mipi-escape-clock", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, mipi-escape-clock not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ mipi_timing->lp_freq= temp_val;
+
+ rc = of_property_read_u32(np, "lane-no", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, lane-no not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ mipi_timing->no_lanes= temp_val;
+
+ /*default use video mode*/
+ mipi_timing->display_mode = MIPI_VIDEO_MODE;
+ data = of_get_property(np, "display_mode", NULL);
+ if (data) {
+ if (!strcmp(data, "video_mode"))
+ mipi_timing->display_mode = MIPI_VIDEO_MODE;
+ else if (!strcmp(data, "command_mode"))
+ mipi_timing->display_mode = MIPI_COMMAND_MODE;
+ }
+
+ mipi_timing->auto_stop_clklane_en = of_property_read_bool(np,
+ "auto_stop_clklane_en");
+ mipi_timing->auto_stop_clklane_en = of_property_read_bool(np,
+ "im_pin_val");
+
+ rc = of_property_read_u32(np, "color_bits", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, color_bits not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ mipi_timing->color_mode.color_bits= temp_val;
+
+ mipi_timing->color_mode.is_18bit_loosely = of_property_read_bool(np,
+ "is_18bit_loosely");
+
+ /*video mode info*/
+ if (mipi_timing->display_mode == MIPI_VIDEO_MODE) {
+ of_parse_video_mode(np, &mipi_timing->videomode_info);
+ } else if (mipi_timing->display_mode == MIPI_COMMAND_MODE) {
+ of_parse_command_mode(np, &mipi_timing->commandmode_info);
+ }
+
+ of_parse_phy_timing(np, &mipi_timing->phytime_info);
+
+ of_parse_te_info(np, &mipi_timing->teinfo);
+
+ of_parse_ext_info(np, &mipi_timing->ext_info);
+
+ return 0;
+}
+
+static int of_parse_rd_cmd_info(struct device_node *np,
+ struct sf_fb_id_info *rd_id_info, const char *key)
+{
+ int blen = 0, len;
+ int i, cnt;
+ const char *data, *bp;
+ struct rd_cmd_hdr *hdr;
+
+
+ data = of_get_property(np, key, &blen);
+ if (!data) {
+ pr_err("%s: failed, key=%s\n", __func__, key);
+ return -ENOMEM;
+ }
+
+ bp = data;
+ len = blen;
+ cnt = 0;
+ while (len >= sizeof(*hdr)) {
+ hdr = (struct rd_cmd_hdr *)bp;
+ if (hdr->id_count > len) {
+ pr_err("%s: rd cmd parse error", __func__);
+ return -EINVAL;
+ }
+ bp += sizeof(*hdr);
+ len -= sizeof(*hdr);
+ bp += hdr->id_count;
+ len -= hdr->id_count;
+ cnt++;
+ }
+
+ if (len != 0) {
+ pr_err("%s: rd cmd parse error!", __func__);
+ return -EINVAL;
+ }
+
+ rd_id_info->num_id_info = cnt;
+ rd_id_info->id_info = kzalloc(cnt * sizeof(struct common_id_info),
+ GFP_KERNEL);
+ if (!rd_id_info->id_info)
+ return -ENOMEM;
+
+ bp = data;
+ for (i = 0; i < cnt; i++) {
+ hdr = (struct rd_cmd_hdr *)bp;
+ bp += sizeof(*hdr);
+ rd_id_info->id_info[i].hdr = *hdr;
+ memcpy(rd_id_info->id_info[i].id, bp, hdr->id_count);
+ bp += hdr->id_count;
+ }
+
+ return 0;
+}
+
+static int of_parse_wr_cmd(struct device_node *np,
+ struct sf_fb_dev_cmds *dev_cmds, const char *key)
+{
+ int blen = 0, len;
+ int i, cnt;
+ unsigned int alloc_bytes = 0;
+ const char *data, *bp;
+ char *buf;
+ struct wr_cmd_hdr *hdr;
+
+ data = of_get_property(np, key, &blen);
+ if (!data) {
+ pr_err("%s: failed, key=%s\n", __func__, key);
+ return -ENOMEM;
+ }
+
+ bp = data;
+ len = blen;
+ cnt = 0;
+ while (len >= sizeof(*hdr)) {
+ hdr = (struct wr_cmd_hdr *)bp;
+ if (hdr->dlen > len) {
+ pr_err("%s: wr parse error",
+ __func__);
+ return -EINVAL;
+ }
+ bp += sizeof(*hdr);
+ len -= sizeof(*hdr);
+ bp += hdr->dlen;
+ len -= hdr->dlen;
+ cnt++;
+ alloc_bytes += DSI_CMD_LEN(hdr);
+ }
+
+ if (len != 0) {
+ pr_err("%s: wr parse error!", __func__);
+ return -EINVAL;
+ }
+ dev_cmds->n_pack = cnt;
+ dev_cmds->cmds = kzalloc(_ALIGN_UP(alloc_bytes, 4), GFP_KERNEL);
+
+ if (IS_ERR_OR_NULL(dev_cmds->cmds))
+ return -ENOMEM;
+
+ bp = data;
+ buf = dev_cmds->cmds;
+ for (i = 0; i < cnt; i++) {
+ len = 0;
+ hdr = (struct wr_cmd_hdr *)bp;
+ len += sizeof(*hdr);
+ len += hdr->dlen;
+ memcpy(buf, bp, len);
+ bp += len;
+ buf += DSI_CMD_LEN(hdr);
+ }
+
+ return 0;
+}
+
+static int of_parse_gamma_ce_cmd(struct device_node *np,
+ struct sf_fb_prefer_ce *color_info, const char *key)
+{
+ int types = 0;
+
+ /*FIX ME, we only support up to 3 types, do not overflow.
+ * when add new gamma/ce types, please increase COLOR_TYPE_MAX also
+ */
+ color_info->info = kzalloc(COLOR_TYPE_MAX * sizeof(struct prefer_ce_info), GFP_KERNEL);
+ if (!color_info->info) {
+ pr_err("%s no memory!!\n", __func__);
+ return -ENOMEM;
+ }
+
+ if (!strcmp(key, "gamma")) {
+ if (of_find_property(np, "panel-gamma-warm-command", NULL)) {
+ of_parse_wr_cmd(np, &color_info->info[types].cmds, "panel-gamma-warm-command");
+ color_info->info[types].type = PREFER_WARM;
+ types++;
+ }
+ if (of_find_property(np, "panel-gamma-nature-command", NULL)) {
+ of_parse_wr_cmd(np, &color_info->info[types].cmds, "panel-gamma-nature-command");
+ color_info->info[types].type = PREFER_NATURE;
+ types++;
+ }
+ if (of_find_property(np, "panel-gamma-cool-command", NULL)) {
+ of_parse_wr_cmd(np, &color_info->info[types].cmds, "panel-gamma-cool-command");
+ color_info->info[types].type = PREFER_COOL;
+ types++;
+ }
+ } else if (!strcmp(key, "ce")) {
+ if (of_find_property(np, "panel-ce-bright-command", NULL)) {
+ of_parse_wr_cmd(np, &color_info->info[types].cmds, "panel-ce-bright-command");
+ color_info->info[types].type = CE_BRIGHT;
+ types++;
+ }
+ if (of_find_property(np, "panel-ce-std-command", NULL)) {
+ of_parse_wr_cmd(np, &color_info->info[types].cmds, "panel-ce-std-command");
+ color_info->info[types].type = CE_STANDARD;
+ types++;
+ }
+ if (of_find_property(np, "panel-ce-vivid-command", NULL)) {
+ of_parse_wr_cmd(np, &color_info->info[types].cmds, "panel-ce-vivid-command");
+ color_info->info[types].type = CE_VELVIA;
+ types++;
+ }
+ }
+ if (types > COLOR_TYPE_MAX) {
+ pr_err("%s types overflow %d\n", key, types);
+ types = COLOR_TYPE_MAX;
+ }
+ color_info->types = types;
+
+ pr_debug("%s support %d types\n", key, types);
+
+ return 0;
+}
+
+static int of_parse_reset_seq(struct device_node *np,
+ u32 rst_seq[RST_SEQ_LEN], u32 *rst_len,
+ const char *name)
+{
+ int num = 0, i;
+ int rc;
+ struct property *data;
+ u32 tmp[RST_SEQ_LEN];
+
+ *rst_len = 0;
+ data = of_find_property(np, name, &num);
+ num /= sizeof(u32);
+ if (!data || !num || num > RST_SEQ_LEN || num % 2) {
+ pr_err("%s:%d, error reading %s, length found = %d\n",
+ __func__, __LINE__, name, num);
+ } else {
+ rc = of_property_read_u32_array(np, name, tmp, num);
+ if (rc)
+ pr_err("%s:%d, error reading %s, rc = %d\n",
+ __func__, __LINE__, name, rc);
+ else {
+ for (i = 0; i < num; ++i)
+ rst_seq[i] = tmp[i];
+ *rst_len = num;
+ }
+ }
+ return 0;
+}
+
+static int sf_displayer_parse_dt(struct device *dev,
+ struct sf_fb_display_dev *pandev)
+{
+ int rc;
+ struct device_node *np = dev->of_node;
+ const char *data;
+ u32 temp_val;
+
+ dev_dbg(dev, "dsi panel parse dt\n");
+
+ pandev->name = of_get_property(np, "panel_name", NULL);
+ pr_info("panel_name: %s\n", pandev->name);
+
+ pandev->interface_info = STARFIVEFB_MIPI_IF;
+ data = of_get_property(np, "interface_info", NULL);
+ if (data) {
+ if (!strcmp(data, "mipi_interface"))
+ pandev->interface_info = STARFIVEFB_MIPI_IF;
+ else if (!strcmp(data, "rgb_interface"))
+ pandev->interface_info = STARFIVEFB_RGB_IF;
+ }
+ pandev->send_suspend_cmd_in_hs_mode = of_property_read_bool(np,
+ "send_suspend_cmd_in_hs");
+ /*must define within video mode*/
+ rc = of_property_read_u32(np, "refresh_en", &temp_val);
+ if (rc && (rc != -EINVAL)) {
+ pr_err("%s:%d, Unable to read refresh_en\n",
+ __func__, __LINE__);
+ return rc;
+ } else if (rc != -EINVAL)
+ pandev->refresh_en= temp_val;
+
+ pandev->auto_fps = of_property_read_bool(np, "dyn_fps");
+
+ rc = of_property_read_u32(np, "pixel-clock", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, pixel-clock not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ pandev->pclk= temp_val;
+
+ rc = of_property_read_u32(np, "panel-width", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, panel width not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ pandev->xres = temp_val;
+
+ rc = of_property_read_u32(np, "panel-height", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, panel width not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ pandev->yres = temp_val;
+
+ rc = of_property_read_u32(np, "physical-width", &temp_val);
+ if (rc && (rc != -EINVAL)) {
+ pr_err("%s:%d, Unable to read physical-width\n",
+ __func__, __LINE__);
+ return rc;
+ } else if (rc != -EINVAL)
+ pandev->width = temp_val;
+
+ rc = of_property_read_u32(np, "physical-height", &temp_val);
+ if (rc && (rc != -EINVAL)) {
+ pr_err("%s:%d, Unable to read physical-height\n",
+ __func__, __LINE__);
+ return rc;
+ } else if (rc != -EINVAL)
+ pandev->height = temp_val;
+
+ rc = of_property_read_u32(np, "bits-per-pixel", &temp_val);
+ if (rc) {
+ pr_err("%s:%d, bpp not specified\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+ pandev->bpp = temp_val;
+
+ pandev->flags = 0;
+ if (of_property_read_bool(np, "gamma-command-monolithic"))
+ pandev->flags |= PREFER_CMD_SEND_MONOLITHIC;
+ if (of_property_read_bool(np, "ce-command-monolithic"))
+ pandev->flags |= CE_CMD_SEND_MONOLITHIC;
+ if (of_property_read_bool(np, "resume-with-gamma"))
+ pandev->flags |= RESUME_WITH_PREFER;
+ if (of_property_read_bool(np, "resume-with-ce"))
+ pandev->flags |= RESUME_WITH_CE;
+
+ pandev->init_last = of_property_read_bool(np, "init_last");
+ /*mipi info parse*/
+ of_parse_mipi_timing(np, &pandev->timing.mipi);
+
+ of_parse_rd_cmd_info(np, &pandev->panel_id_info, "id_read_cmd_info");
+ if (of_find_property(np, "pre_id_cmd", NULL))
+ of_parse_wr_cmd(np, &pandev->panel_id_info.prepare_cmd, "pre_id_cmd");
+ of_parse_rd_cmd_info(np, &pandev->esd_id_info, "esd_read_cmd_info");
+
+ if (of_find_property(np, "pre_esd_cmd", NULL))
+ of_parse_wr_cmd(np, &pandev->esd_id_info.prepare_cmd, "pre_esd_cmd");
+
+ of_parse_wr_cmd(np, &pandev->cmds_init, "panel-on-command");
+ of_parse_wr_cmd(np, &pandev->cmds_suspend, "panel-off-command");
+
+ of_parse_reset_seq(np, pandev->rst_seq, &pandev->rst_seq_len, "reset-sequence");
+
+ of_parse_gamma_ce_cmd(np, &pandev->display_prefer_info, "gamma");
+ of_parse_gamma_ce_cmd(np, &pandev->display_ce_info, "ce");
+
+ dump_panel_info(dev, pandev);
+
+ return 0;
+}
+
+static int sf_displayer_probe(struct platform_device *pdev)
+{
+ struct sf_fb_display_dev *display_dev;
+ int ret;
+
+ if (pdev->dev.of_node) {
+ display_dev = devm_kzalloc(&pdev->dev,
+ sizeof(struct sf_fb_display_dev), GFP_KERNEL);
+ if (!display_dev) {
+ dev_err(&pdev->dev, "Failed to allocate memory\n");
+ return -ENOMEM;
+ }
+ ret = sf_displayer_parse_dt(&pdev->dev, display_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "DT parsing failed\n");
+ return -ENODEV;
+ }
+ } else {
+ dev_err(&pdev->dev, "error: null panel device-tree node");
+ return -ENODEV;
+ }
+
+ display_dev->power = sf_displayer_power_on;
+ display_dev->reset = sf_displayer_reset;
+ display_dev->suspend = sf_displayer_suspend;
+ display_dev->resume = sf_displayer_resume;
+
+ sf_fb_display_dev_register(display_dev);
+
+ return 0;
+
+}
+
+static int __exit sf_displayer_remove(struct platform_device *dev)
+{
+ return 0;
+}
+
+static struct of_device_id sf_displayer_dt_match[] = {
+ {
+ .compatible = "starfive,display-dev",
+ },
+ {}
+};
+
+static struct platform_driver sf_displayer_driver = {
+ .probe = sf_displayer_probe,
+ .remove = __exit_p(sf_displayer_remove),
+ .driver = {
+ .name = "starfive,display-dev",
+ .owner = THIS_MODULE,
+ .of_match_table = sf_displayer_dt_match,
+ },
+};
+
+static int __init sf_displayer_init(void)
+{
+ return platform_driver_register(&sf_displayer_driver);
+}
+
+static void __exit sf_displayer_exit(void)
+{
+ platform_driver_unregister(&sf_displayer_driver);
+}
+
+subsys_initcall(sf_displayer_init);
+module_exit(sf_displayer_exit);
+MODULE_AUTHOR("StarFive Technology Co., Ltd.");
+MODULE_DESCRIPTION("DISPLAYER DRIVER");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/fbdev/starfive/starfive_fb.c b/drivers/video/fbdev/starfive/starfive_fb.c
new file mode 100644
index 000000000000..4a8287aed0fe
--- /dev/null
+++ b/drivers/video/fbdev/starfive/starfive_fb.c
@@ -0,0 +1,1125 @@
+/* driver/video/starfive/starfivefb.c
+**
+** This program is free software; you can redistribute it and/or modify
+** it under the terms of the GNU General Public License version 2 as
+** published by the Free Software Foundation.
+**
+** Copyright (C) 2020 StarFive, Inc.
+**
+** PURPOSE: This files contains the driver of LCD controller and VPP.
+**
+** CHANGE HISTORY:
+** Version Date Author Description
+** 0.1.0 2020-10-07 starfive created
+**
+*/
+
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/interrupt.h>
+#include <linux/compiler.h>
+#include <linux/slab.h>
+#include <linux/fb.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/cpufreq.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/uaccess.h>
+#include <linux/notifier.h>
+#include <linux/mtd/mtd.h>
+#include <linux/workqueue.h>
+#include <linux/module.h>
+#include <linux/leds.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/div64.h>
+#include <asm/cacheflush.h>
+#include <linux/of_gpio.h>
+#include <linux/of_reserved_mem.h>
+#include <video/stf-vin.h>
+
+#include "starfive_fb.h"
+#include "starfive_lcdc.h"
+#include "starfive_vpp.h"
+#include "starfive_display_dev.h"
+
+static struct sf_fb_data *stf_dev = NULL;
+
+static DEFINE_MUTEX(stf_mutex);
+
+//#define SF_FB_DEBUG 1
+#ifdef SF_FB_DEBUG
+ #define FB_PRT(format, args...) printk(KERN_DEBUG "[FB]: " format, ## args)
+ #define FB_INFO(format, args...) printk(KERN_INFO "[FB]: " format, ## args)
+ #define FB_ERR(format, args...) printk(KERN_ERR "[FB]: " format, ## args)
+#else
+ #define FB_PRT(x...) do{} while(0)
+ #define FB_INFO(x...) do{} while(0)
+ #define FB_ERR(x...) do{} while(0)
+#endif
+
+static const struct res_name mem_res_name[] = {
+ {"lcdc"},
+ {"vpp0"},
+ {"vpp1"},
+ {"vpp2"},
+ {"clk"},
+ {"rst"},
+ {"sys"}
+};
+
+static u32 sf_fb_clkread32(struct sf_fb_data *sf_dev, u32 reg)
+{
+ return ioread32(sf_dev->base_clk + reg);
+}
+
+static void sf_fb_clkwrite32(struct sf_fb_data *sf_dev, u32 reg, u32 val)
+{
+ iowrite32(val, sf_dev->base_clk + reg);
+}
+
+static int sf_fb_lcdc_clk_cfg(struct sf_fb_data *sf_dev)
+{
+ u32 tmp_val = 0;
+ int ret = 0;
+
+ switch(sf_dev->display_info.xres) {
+ case 640:
+ dev_warn(sf_dev->dev, "640 do nothing! need to set clk\n");
+ break;
+ case 1280:
+ dev_warn(sf_dev->dev, "1280 do nothing! need to set clk\n");
+ break;
+ case 1920:
+ tmp_val = sf_fb_clkread32(sf_dev, CLK_LCDC_OCLK_CTRL);
+ tmp_val &= ~(0x3F);
+ tmp_val |= (24 & 0x3F);
+ sf_fb_clkwrite32(sf_dev, CLK_LCDC_OCLK_CTRL, tmp_val);
+ break;
+ default:
+ dev_err(sf_dev->dev, "Fail to allocate video RAM\n");
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int vin_frame_complete_notify(struct notifier_block *nb,
+ unsigned long val, void *v)
+{
+ struct vin_params *psy = v;
+ struct sf_fb_data *sf_dev = stf_dev;
+ unsigned int address;
+ unsigned int u_addr, v_addr, size;
+ unsigned int y_rgb_offset, u_offset, v_offset;
+ int i = 0;
+
+ address = psy->paddr;
+
+ if(NULL == sf_dev) {
+ return NOTIFY_OK;
+ }
+
+ if(sf_dev->pp_conn_lcdc < 0) {
+ dev_warn(sf_dev->dev, "%s NO use PPx\n",__func__);
+ } else {
+ if(sf_dev->pp[sf_dev->pp_conn_lcdc].src.format >= COLOR_RGB888_ARGB) {
+ u_addr = 0;
+ v_addr = 0;
+ y_rgb_offset = 0;
+ u_offset = 0;
+ v_offset = 0;
+ } else if (COLOR_YUV420_NV21 == sf_dev->pp[sf_dev->pp_conn_lcdc].src.format) {
+ u_addr = address + size + 1;
+ v_addr = address + size;
+ y_rgb_offset = 0;
+ u_offset = 0;
+ v_offset = size;
+ } else {
+ dev_err(sf_dev->dev, "format %d not SET\n", sf_dev->pp[sf_dev->pp_conn_lcdc].src.format);
+ return -EINVAL;
+ }
+ pp_srcAddr_next(sf_dev, sf_dev->pp_conn_lcdc, address, u_addr, v_addr);
+ pp_srcOffset_cfg(sf_dev, sf_dev->pp_conn_lcdc, y_rgb_offset, u_offset, v_offset);
+ //pp_run(sf_dev, sf_dev->pp_conn_lcdc, PP_RUN);
+ }
+
+ return NOTIFY_OK;
+}
+
+static int sf_get_mem_res(struct platform_device *pdev, struct sf_fb_data *sf_dev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ void __iomem *regs;
+ char *name;
+ int i;
+
+ for (i = 0; i < sizeof(mem_res_name)/sizeof(struct res_name); i++) {
+ name = (char *)(& mem_res_name[i]);
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
+ regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ if(!strcmp(name, "lcdc")) {
+ sf_dev->base_lcdc = regs;
+ } else if (!strcmp(name, "vpp0")) {
+ sf_dev->base_vpp0 = regs;
+ } else if (!strcmp(name, "vpp1")) {
+ sf_dev->base_vpp1 = regs;
+ } else if (!strcmp(name, "vpp2")) {
+ sf_dev->base_vpp2 = regs;
+ } else if (!strcmp(name, "clk")) {
+ sf_dev->base_clk = regs;
+ } else if (!strcmp(name, "rst")) {
+ sf_dev->base_rst = regs;
+ } else if (!strcmp(name, "sys")) {
+ sf_dev->base_sys = regs;
+ } else {
+ dev_err(&pdev->dev, "Could not match resource name\n");
+ }
+ }
+
+ return 0;
+}
+
+static void sf_fb_get_var(struct fb_var_screeninfo *var, struct sf_fb_data *sf_dev)
+{
+ var->xres = sf_dev->display_info.xres;
+ var->yres = sf_dev->display_info.yres;
+ var->bits_per_pixel = sf_dev->display_dev->bpp;
+ var->pixclock = 1000000 / (sf_dev->pixclock / 1000000);
+ var->hsync_len = sf_dev->display_info.hsync_len;
+ var->vsync_len = sf_dev->display_info.vsync_len;
+ var->left_margin = sf_dev->display_info.left_margin;
+ var->right_margin = sf_dev->display_info.right_margin;
+ var->upper_margin = sf_dev->display_info.upper_margin;
+ var->lower_margin = sf_dev->display_info.lower_margin;
+ var->sync = sf_dev->display_info.sync;
+ var->vmode = FB_VMODE_NONINTERLACED;
+}
+
+/*
+ * sf_fb_set_par():
+ * Set the user defined part of the display for the specified console
+ */
+static int sf_fb_set_par(struct fb_info *info)
+{
+ struct sf_fb_data *sf_dev = container_of(info, struct sf_fb_data, fb);
+ struct fb_var_screeninfo *var = &info->var;
+
+ FB_PRT("%s,%d\n",__func__, __LINE__);
+
+ if (var->bits_per_pixel == 16 ||
+ var->bits_per_pixel == 18 ||
+ var->bits_per_pixel == 24 ||
+ var->bits_per_pixel == 32)
+ sf_dev->fb.fix.visual = FB_VISUAL_TRUECOLOR;
+ else if (!sf_dev->cmap_static)
+ sf_dev->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR;
+ else {
+ /*
+ * Some people have weird ideas about wanting static
+ * pseudocolor maps. I suspect their user space
+ * applications are broken.
+ */
+ sf_dev->fb.fix.visual = FB_VISUAL_STATIC_PSEUDOCOLOR;
+ }
+
+ sf_dev->fb.fix.line_length = var->xres_virtual * var->bits_per_pixel / 8;
+
+ if (sf_dev->fb.var.bits_per_pixel == 16 ||
+ sf_dev->fb.var.bits_per_pixel == 18 ||
+ sf_dev->fb.var.bits_per_pixel == 24 ||
+ sf_dev->fb.var.bits_per_pixel == 32)
+ fb_dealloc_cmap(&sf_dev->fb.cmap);
+ else
+ fb_alloc_cmap(&sf_dev->fb.cmap,
+ 1 << sf_dev->fb.var.bits_per_pixel, 0);
+
+ /*for fbcon, it need cmap*/
+ switch(var->bits_per_pixel) {
+ case 16:
+ var->red.offset = 11; var->red.length = 5;
+ var->green.offset = 5; var->green.length = 6;
+ var->blue.offset = 0; var->blue.length = 5;
+ var->transp.offset = var->transp.length = 0;
+ break;
+ case 18:
+ var->red.offset = 12; var->red.length = 6;
+ var->green.offset = 6; var->green.length = 6;
+ var->blue.offset = 0; var->blue.length = 6;
+ var->transp.offset = var->transp.length = 0;
+ break;
+ case 24:
+ var->red.offset = 16; var->red.length = 8;
+ var->green.offset = 8; var->green.length = 8;
+ var->blue.offset = 0; var->blue.length = 8;
+ var->transp.offset = var->transp.length = 0;
+ break;
+ case 32:
+ var->red.offset = 16; var->red.length = 8;
+ var->green.offset = 8; var->green.length = 8;
+ var->blue.offset = 0; var->blue.length = 8;
+ var->transp.offset = var->transp.length = 0;
+ break;
+ default:
+ var->red.offset = var->green.offset = \
+ var->blue.offset = var->transp.offset = 0;
+ var->red.length = 8;
+ var->green.length = 8;
+ var->blue.length = 8;
+ var->transp.length = 0;
+ }
+
+ return 0;
+}
+
+static int sf_fb_open(struct fb_info *info, int user)
+{
+ struct sf_fb_data *sf_dev = container_of(info, struct sf_fb_data, fb);
+
+ FB_PRT("%s,%d\n",__func__, __LINE__);
+
+ sf_fb_set_par(info);
+ lcdc_run(sf_dev, sf_dev->winNum, LCDC_RUN);
+
+ //sf_fb_init_layer(layer, &info->var);
+ //if (layer->no == sf_fb_ids[0])
+ //sf_fb_enable_layer(layer);
+ return 0;
+}
+
+static int sf_fb_release(struct fb_info *info, int user)
+{
+ struct sf_fb_data *sf_dev = container_of(info, struct sf_fb_data, fb);
+
+ FB_PRT("%s,%d\n",__func__, __LINE__);
+
+ lcdc_run(sf_dev, sf_dev->winNum, LCDC_STOP);
+
+ return 0;
+}
+
+static int sf_fb_ioctl(struct fb_info *info, unsigned cmd, unsigned long arg)
+{
+ struct sf_fb_data *sf_dev = container_of(info, struct sf_fb_data, fb);
+
+ FB_PRT("%s,%d\n",__func__, __LINE__);
+ return 0;
+}
+
+/*
+ * sf_fb_check_var():
+ * Get the video params out of 'var'. If a value doesn't fit, round it up,
+ * if it's too big, return -EINVAL.
+ *
+ * Round up in the following order: bits_per_pixel, xres,
+ * yres, xres_virtual, yres_virtual, xoffset, yoffset, grayscale,
+ * bitfields, horizontal timing, vertical timing.
+ */
+static int sf_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+ struct sf_fb_data *sf_dev = container_of(info, struct sf_fb_data, fb);
+
+ FB_PRT("%s,%d\n",__func__, __LINE__);
+
+ if (var->xres < MIN_XRES)
+ var->xres = MIN_XRES;
+ if (var->yres < MIN_YRES)
+ var->yres = MIN_YRES;
+
+ sf_fb_get_var(var, sf_dev);
+
+ var->xres_virtual = var->xres;
+ var->yres_virtual = var->yres * sf_dev->buf_num;
+ /*
+ * Setup the RGB parameters for this display.
+ */
+ switch(var->bits_per_pixel) {
+ case 16:
+ var->red.offset = 11; var->red.length = 5;
+ var->green.offset = 5; var->green.length = 6;
+ var->blue.offset = 0; var->blue.length = 5;
+ var->transp.offset = var->transp.length = 0;
+ break;
+ case 18:
+ var->red.offset = 12; var->red.length = 6;
+ var->green.offset = 6; var->green.length = 6;
+ var->blue.offset = 0; var->blue.length = 6;
+ var->transp.offset = var->transp.length = 0;
+ break;
+ case 24:
+ var->red.offset = 16; var->red.length = 8;
+ var->green.offset = 8; var->green.length = 8;
+ var->blue.offset = 0; var->blue.length = 8;
+ var->transp.offset = var->transp.length = 0;
+ break;
+ case 32:
+ var->red.offset = 16; var->red.length = 8;
+ var->green.offset = 8; var->green.length = 8;
+ var->blue.offset = 0; var->blue.length = 8;
+ var->transp.offset = var->transp.length = 0;
+ break;
+ default:
+ var->red.offset = var->green.offset = \
+ var->blue.offset = var->transp.offset = 0;
+ var->red.length = 8;
+ var->green.length = 8;
+ var->blue.length = 8;
+ var->transp.length = 0;
+ }
+
+ return 0;
+}
+
+static inline u_int sf_chan_to_field(u_int chan, struct fb_bitfield *bf)
+{
+ chan &= 0xffff;
+ chan >>= 16 - bf->length;
+ return chan << bf->offset;
+}
+
+static int sf_fb_setcolreg(u_int regno, u_int red, u_int green,
+ u_int blue, u_int trans, struct fb_info *info)
+{
+ struct sf_fb_data *sf_dev = container_of(info, struct sf_fb_data, fb);
+ unsigned int val;
+ int ret = 1;
+
+ FB_PRT("%s,%d\n",__func__, __LINE__);
+ /*
+ * If inverse mode was selected, invert all the colours
+ * rather than the register number. The register number
+ * is what you poke into the framebuffer to produce the
+ * colour you requested.
+ */
+ if (sf_dev->cmap_inverse) {
+ red = 0xffff - red;
+ green = 0xffff - green;
+ blue = 0xffff - blue;
+ }
+
+ /*
+ * If greyscale is true, then we convert the RGB value
+ * to greyscale no matter what visual we are using.
+ */
+ if (sf_dev->fb.var.grayscale)
+ red = green = blue = (19595 * red + 38470 * green +
+ 7471 * blue) >> 16;
+
+ switch (sf_dev->fb.fix.visual) {
+ case FB_VISUAL_TRUECOLOR:
+ /*
+ * 16-bit True Colour. We encode the RGB value
+ * according to the RGB bitfield information.
+ */
+ if (regno < 16) {
+ u32 *pal = sf_dev->fb.pseudo_palette;
+
+ val = sf_chan_to_field(red, &sf_dev->fb.var.red);
+ val |= sf_chan_to_field(green, &sf_dev->fb.var.green);
+ val |= sf_chan_to_field(blue, &sf_dev->fb.var.blue);
+
+ pal[regno] = val;
+ ret = 0;
+ }
+ break;
+
+ case FB_VISUAL_STATIC_PSEUDOCOLOR:
+ case FB_VISUAL_PSEUDOCOLOR:
+ /* haven't support this function. */
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * sf_fb_set_addr():
+ * Configures LCD Controller based on entries in var parameter. Settings are
+ * only written to the controller if changes were made.
+ */
+static int sf_fb_set_addr(struct sf_fb_data *sf_dev, struct fb_var_screeninfo *var)
+{
+ unsigned int address;
+ unsigned int offset;
+ unsigned int u_addr, v_addr, size;
+ unsigned int y_rgb_offset, u_offset, v_offset;
+ int i = 0;
+
+ offset = var->yoffset * sf_dev->fb.fix.line_length +
+ var->xoffset * var->bits_per_pixel / 8;
+ address = sf_dev->fb.fix.smem_start + offset;
+ size = var->xres * var->yres;
+
+ FB_PRT("%s,%d\n",__func__, __LINE__);
+
+ if(sf_dev->pp_conn_lcdc < 0) {
+ dev_warn(sf_dev->dev, "%s NO use PPx\n",__func__);
+ } else {
+ if(sf_dev->pp[sf_dev->pp_conn_lcdc].src.format >= COLOR_RGB888_ARGB) {
+ u_addr = 0;
+ v_addr = 0;
+ y_rgb_offset = 0;
+ u_offset = 0;
+ v_offset = 0;
+ } else if (COLOR_YUV420_NV21 == sf_dev->pp[sf_dev->pp_conn_lcdc].src.format) {
+ u_addr = address + size + 1;
+ v_addr = address + size;
+ y_rgb_offset = 0;
+ u_offset = 0;
+ v_offset = size;
+ } else {
+ dev_err(sf_dev->dev, "format %d not SET\n", sf_dev->pp[sf_dev->pp_conn_lcdc].src.format);
+ return -EINVAL;
+ }
+ pp_srcAddr_next(sf_dev, sf_dev->pp_conn_lcdc, address, u_addr, v_addr);
+ pp_srcOffset_cfg(sf_dev, sf_dev->pp_conn_lcdc, y_rgb_offset, u_offset, v_offset);
+ pp_nxtAddr_load(sf_dev, sf_dev->pp_conn_lcdc, 0x1, (i & 0x1));
+ pp_run(sf_dev, sf_dev->pp_conn_lcdc, PP_RUN);
+ }
+
+ return 0;
+}
+
+
+static int sf_fb_pan_display(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ struct sf_fb_data *sf_dev = container_of(info, struct sf_fb_data, fb);
+
+ FB_PRT("%s,%d\n",__func__, __LINE__);
+
+ sf_fb_set_addr(sf_dev, var);
+
+ switch(sf_dev->display_dev->interface_info) {
+ case STARFIVEFB_MIPI_IF:
+ case STARFIVEFB_HDMI_IF:
+ //lcdc_run(sf_dev,0x2, 0x1);
+ break;
+ case STARFIVEFB_RGB_IF:
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int sf_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
+{
+ unsigned long off = vma->vm_pgoff << PAGE_SHIFT;
+ unsigned long start;
+ u32 len;
+
+ FB_PRT("%s,%d\n",__func__, __LINE__);
+ /* frame buffer memory */
+ start = info->fix.smem_start;
+ len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);
+
+ if (off >= len) {
+ /* memory mapped io */
+ off -= len;
+ if (info->var.accel_flags) {
+ mutex_unlock(&info->mm_lock);
+ return -EINVAL;
+ }
+ start = info->fix.mmio_start;
+ len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len);
+ }
+
+ start &= PAGE_MASK;
+ if ((vma->vm_end - vma->vm_start + off) > len) {
+ return -EINVAL;
+ }
+
+ off += start;
+ vma->vm_pgoff = off >> PAGE_SHIFT;
+ /* This is an IO map - tell maydump to skip this VMA */
+// vma->vm_flags |= VM_IO | VM_RESERVED;
+ vma->vm_flags |= VM_IO;
+
+// if (!(layer->parent->pdata->flags & COMIPFB_CACHED_BUFFER))
+// vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+
+ if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
+ vma->vm_end - vma->vm_start, vma->vm_page_prot)) {
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+
+static int sf_fb_blank(int blank, struct fb_info *info)
+{
+ struct sf_fb_data *sf_dev = container_of(info, struct sf_fb_data, fb);
+ int ret;
+
+ FB_PRT("%s,%d\n",__func__, __LINE__);
+
+ switch (blank) {
+ case FB_BLANK_UNBLANK:
+ lcdc_run(sf_dev,0x2, 0x1);
+ break;
+ case FB_BLANK_NORMAL:
+ case FB_BLANK_VSYNC_SUSPEND:
+ case FB_BLANK_HSYNC_SUSPEND:
+ case FB_BLANK_POWERDOWN:
+ lcdc_run(sf_dev,0x2, 0x0);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static struct fb_ops sf_fb_ops = {
+ .owner = THIS_MODULE,
+ .fb_open = sf_fb_open,
+ .fb_release = sf_fb_release,
+ .fb_ioctl = sf_fb_ioctl,
+ .fb_check_var = sf_fb_check_var,
+ .fb_set_par = sf_fb_set_par,
+ .fb_setcolreg = sf_fb_setcolreg,
+ .fb_fillrect = cfb_fillrect,
+ .fb_copyarea = cfb_copyarea,
+ .fb_imageblit = cfb_imageblit,
+ .fb_pan_display = sf_fb_pan_display,
+ .fb_mmap = sf_fb_mmap,
+ .fb_blank = sf_fb_blank,
+};
+
+static int sf_fb_map_video_memory(struct sf_fb_data *sf_dev)
+{
+ int ret;
+ dma_addr_t fb_phys_addr = 0;
+
+ ret = of_reserved_mem_device_init(sf_dev->dev);
+ if(ret) {
+ dev_err(sf_dev->dev, "Could not get reserved memory\n");
+ return ret;
+ }
+ sf_dev->fb.screen_base = dma_alloc_coherent(sf_dev->dev, 0x1000000, &fb_phys_addr, GFP_KERNEL);
+ sf_dev->fb.screen_size = 0x1000000;
+
+ sf_dev->fb.fix.smem_start = fb_phys_addr;
+
+ return 0;
+}
+
+static int sf_fb_init(struct sf_fb_data *sf_dev)
+{
+
+ INIT_LIST_HEAD(&sf_dev->fb.modelist);
+ sf_dev->fb.device = sf_dev->dev;
+ sf_dev->fb.fbops = &sf_fb_ops;
+ sf_dev->fb.flags = FBINFO_DEFAULT;
+ sf_dev->fb.node = -1;
+ sf_dev->fb.pseudo_palette = sf_dev->pseudo_pal;
+ sf_dev->fb.mode = &sf_dev->display_info;
+
+ strcpy(sf_dev->fb.fix.id, STARFIVE_NAME);
+ sf_dev->fb.fix.type = FB_TYPE_PACKED_PIXELS;
+ sf_dev->fb.fix.type_aux = 0;
+ sf_dev->fb.fix.xpanstep = 0;
+ sf_dev->fb.fix.ypanstep = 1;
+ sf_dev->fb.fix.ywrapstep = 0;
+ sf_dev->fb.fix.accel = FB_ACCEL_NONE;
+
+/* sf_dev->win_x_size = sf_dev->display_info.xres;
+ sf_dev->win_y_size = sf_dev->display_info.yres;
+ sf_dev->src_width = sf_dev->display_info.xres;
+ sf_dev->alpha = 255; // 255 = solid 0 = transparent
+ sf_dev->alpha_en = 0;
+ sf_dev->input_format = LCDC_LAYER_INPUT_FORMAT_ARGB8888;*/
+ sf_dev->buf_num = BUFFER_NUMS;
+
+ sf_fb_get_var(&sf_dev->fb.var, sf_dev);
+ sf_dev->fb.var.xres_virtual = sf_dev->display_info.xres;
+ sf_dev->fb.var.yres_virtual = sf_dev->display_info.yres * sf_dev->buf_num;
+ sf_dev->fb.var.xoffset = 0;
+ sf_dev->fb.var.yoffset = 0;
+ sf_dev->fb.var.nonstd = 0;
+ sf_dev->fb.var.activate = FB_ACTIVATE_NOW;
+ sf_dev->fb.var.width = sf_dev->display_dev->width;
+ sf_dev->fb.var.height = sf_dev->display_dev->height;
+ sf_dev->fb.var.accel_flags = 0;
+
+/* layer->flags = 0;
+ layer->cmap_inverse = 0;
+ layer->cmap_static = 0;
+*/
+ if (sf_dev->display_dev->bpp <= 16 ) {
+ /* 8, 16 bpp */
+ sf_dev->buf_size = sf_dev->display_info.xres *
+ sf_dev->display_info.yres * sf_dev->display_dev->bpp / 8;
+ } else {
+ /* 18, 32 bpp*/
+ sf_dev->buf_size = sf_dev->display_info.xres * sf_dev->display_info.yres * 4;
+ }
+
+ sf_dev->fb.fix.smem_len = sf_dev->buf_size * sf_dev->buf_num;
+
+ if (sf_fb_map_video_memory(sf_dev)) {
+ dev_err(sf_dev->dev, "Fail to allocate video RAM\n");
+ return -ENOMEM;
+ }
+
+ //layer->buf_addr = layer->map_dma;
+
+ return 0;
+}
+
+
+static int sf_fbinfo_init(struct device *dev, struct sf_fb_data *sf_dev)
+{
+ struct sf_fb_display_dev *display_dev = NULL;
+
+ display_dev = sf_fb_display_dev_get(sf_dev);
+ if (NULL == display_dev) {
+ dev_err(sf_dev->dev, "Could not get display dev\n");
+ }
+ sf_dev->display_dev = display_dev;
+
+ sf_dev->pixclock = display_dev->pclk;
+
+/* clk_set_rate(sf_dev->mclk, sf_dev->pixclock);
+ sf_dev->pixclock = clk_get_rate(sf_dev->mclk);
+ dev_info(sf_dev->dev,"sf_dev->pixclock = %d\n", sf_dev->pixclock);
+*/
+ switch(display_dev->interface_info) {
+ case STARFIVEFB_MIPI_IF:
+ if (display_dev->timing.mipi.display_mode == MIPI_VIDEO_MODE){
+ sf_dev->refresh_en = 1;
+ display_dev->refresh_en = 1;
+ sf_dev->display_info.name = display_dev->name;
+ sf_dev->display_info.xres = display_dev->xres;
+ sf_dev->display_info.yres = display_dev->yres;
+ sf_dev->display_info.pixclock = 1000000 / (sf_dev->pixclock / 1000000);
+ sf_dev->display_info.sync = 0;
+ sf_dev->display_info.left_margin = display_dev->timing.mipi.videomode_info.hbp;
+ sf_dev->display_info.right_margin = display_dev->timing.mipi.videomode_info.hfp;
+ sf_dev->display_info.upper_margin = display_dev->timing.mipi.videomode_info.vbp;
+ sf_dev->display_info.lower_margin = display_dev->timing.mipi.videomode_info.vfp;
+ sf_dev->display_info.hsync_len = display_dev->timing.mipi.videomode_info.hsync;
+ sf_dev->display_info.vsync_len = display_dev->timing.mipi.videomode_info.vsync;
+ if (display_dev->timing.mipi.videomode_info.sync_pol == COMIPFB_HSYNC_HIGH_ACT)
+ sf_dev->display_info.sync = FB_SYNC_HOR_HIGH_ACT;
+ if (display_dev->timing.mipi.videomode_info.sync_pol == COMIPFB_VSYNC_HIGH_ACT)
+ sf_dev->display_info.sync = FB_SYNC_VERT_HIGH_ACT;
+ }else if (display_dev->timing.mipi.display_mode == MIPI_COMMAND_MODE){
+ sf_dev->display_info.name = display_dev->name;
+ sf_dev->display_info.xres = display_dev->xres;
+ sf_dev->display_info.yres = display_dev->yres;
+ sf_dev->display_info.pixclock = 1000000 / (sf_dev->pixclock / 1000000);
+ sf_dev->display_info.left_margin = 0;
+ sf_dev->display_info.right_margin = 0;
+ sf_dev->display_info.upper_margin = 0;
+ sf_dev->display_info.lower_margin = 0;
+ sf_dev->display_info.hsync_len = 0;
+ sf_dev->display_info.vsync_len = 0;
+ sf_dev->display_info.sync = 0;
+ }
+ break;
+ case STARFIVEFB_RGB_IF:
+ break;
+ default:
+ break;
+ }
+
+ if (sf_fb_init(sf_dev)) {
+ dev_err(sf_dev->dev, "starfive fb init fail\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int stfb_open(struct inode *inode, struct file *file)
+{
+ int ret = 0;
+
+ mutex_lock(&stf_mutex);
+ if (stf_dev != NULL)
+ file->private_data = stf_dev;
+ else {
+ ret = -ENODEV;
+ pr_err("stf_dev is NULL !\n");
+ }
+ mutex_unlock(&stf_mutex);
+
+ return ret;
+}
+
+static ssize_t stfb_read(struct file *file, char __user * buf,
+ size_t count, loff_t * ppos)
+{
+ int ret = 1;
+
+ return ret;
+}
+
+static int stfb_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static const struct vm_operations_struct mmap_mem_ops = {
+#ifdef CONFIG_HAVE_IOREMAP_PROT
+ .access = generic_access_phys
+#endif
+};
+
+static int stfb_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct sf_fb_data *sf_dev = file->private_data;
+ size_t size = vma->vm_end - vma->vm_start;
+ //unsigned long pfn = sf_dev->fb.fix.smem_start + 0x1000000;
+ unsigned long pfn = 0xfc000000;
+
+ vma->vm_ops = &mmap_mem_ops;
+ /* Remap-pfn-range will mark the range VM_IO */
+ if (remap_pfn_range(vma,
+ vma->vm_start,
+ pfn >> PAGE_SHIFT,
+ size, vma->vm_page_prot)) {
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+
+static long int stfb_ioctl(struct file *file, unsigned int cmd, long unsigned int arg)
+{
+ return 0;
+}
+
+
+static const struct file_operations stfb_fops = {
+ .owner = THIS_MODULE,
+ .open = stfb_open,
+ .read = stfb_read,
+ .release = stfb_release,
+ .unlocked_ioctl = stfb_ioctl,
+ .mmap = stfb_mmap,
+};
+
+static void sf_fb_pp_enable_intr(struct sf_fb_data *sf_dev, int enable) {
+ int pp_id;
+
+ for (pp_id = 0; pp_id < PP_NUM; pp_id++) {
+ if(1 == sf_dev->pp[pp_id].inited) {
+ if (enable) {
+ pp_enable_intr(sf_dev, pp_id);
+ } else {
+ pp_disable_intr(sf_dev, pp_id);
+ }
+ }
+ }
+}
+
+static int sf_fb_pp_get_2lcdc_id(struct sf_fb_data *sf_dev) {
+ int pp_id;
+
+ for (pp_id = 0; pp_id < PP_NUM; pp_id++) {
+ if(1 == sf_dev->pp[pp_id].inited) {
+ if ((1 == sf_dev->pp[pp_id].fifo_out)
+ && (0 == sf_dev->pp[pp_id].bus_out)) {
+ return pp_id;
+ }
+ }
+ }
+
+ if (pp_id == PP_NUM - 1)
+ dev_warn(sf_dev->dev, "NO pp connect to LCDC\n");
+
+ return -ENODEV;
+}
+
+static int sf_fb_lcdc_init(struct sf_fb_data *sf_dev) {
+ int pp_id;
+ int lcd_in_pp;
+ int winNum;
+
+ pp_id = sf_dev->pp_conn_lcdc;
+ if (pp_id < 0) {
+ dev_warn(sf_dev->dev, "NO pp connect to LCDC\n");
+ } else {
+ lcd_in_pp = (pp_id == 0) ? LCDC_IN_VPP0 : ((pp_id == 1) ? LCDC_IN_VPP1 : LCDC_IN_VPP2);
+ winNum = lcdc_win_sel(sf_dev, lcd_in_pp);
+ sf_dev->winNum = winNum;
+ lcdc_config(sf_dev, winNum);
+ }
+
+ return 0;
+}
+
+static int sf_fb_pp_video_mode_init(struct sf_fb_data *sf_dev, struct pp_video_mode *src,
+ struct pp_video_mode *dst, int pp_id) {
+
+ if ((NULL == src) || (NULL == dst)) {
+ dev_err(sf_dev->dev, "Invalid argument!\n");
+ return -EINVAL;
+ }
+
+ if ((pp_id < PP_NUM) && (pp_id >= 0 )) {
+ src->format = sf_dev->pp[pp_id].src.format;
+ src->width = sf_dev->pp[pp_id].src.width;
+ src->height = sf_dev->pp[pp_id].src.height;
+ src->addr = 0xf9000000;
+
+ dst->format = sf_dev->pp[pp_id].dst.format;
+ dst->width = sf_dev->pp[pp_id].dst.width;
+ dst->height = sf_dev->pp[pp_id].dst.height;
+ if(true == sf_dev->pp[pp_id].bus_out) /*out to ddr*/
+ dst->addr = 0xfc000000;
+ else if (true == sf_dev->pp[pp_id].fifo_out) /*out to lcdc*/
+ dst->addr = 0;
+ } else {
+ dev_err(sf_dev->dev, "pp_id %d is not support\n", pp_id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sf_fb_pp_init(struct sf_fb_data *sf_dev) {
+ int pp_id;
+ int ret = 0;
+ struct pp_video_mode src, dst;
+
+ for (pp_id = 0; pp_id < PP_NUM; pp_id++) {
+ if(1 == sf_dev->pp[pp_id].inited) {
+ ret = sf_fb_pp_video_mode_init(sf_dev, &src, &dst, pp_id);
+ if (!ret)
+ pp_config(sf_dev, pp_id, &src, &dst);
+ }
+ }
+
+ return ret;
+}
+
+static int sf_fb_parse_dt(struct device *dev, struct sf_fb_data *sf_dev) {
+ int ret;
+ struct device_node *np = dev->of_node;
+ struct device_node *child;
+ int pp_num = 0;
+
+ if(!np)
+ return -EINVAL;
+
+ sf_dev->pp = devm_kzalloc(dev, sizeof(struct pp_mode) * PP_NUM, GFP_KERNEL);
+ if (!sf_dev->pp) {
+ dev_err(dev,"allocate memory for platform data failed\n");
+ return -ENOMEM;
+ }
+
+ for_each_child_of_node(np, child) {
+ if (of_property_read_u32(child, "pp-id", &pp_num)) {
+ dev_err(dev,"Missing pp-id property in the DT.\n");
+ ret = -EINVAL;
+ continue;
+ }
+ if (pp_num >= PP_NUM)
+ dev_err(dev," pp-id number %d is not support!\n", pp_num);
+
+ sf_dev->pp[pp_num].pp_id = pp_num;
+ sf_dev->pp[pp_num].bus_out = of_property_read_bool(child, "sys-bus-out");
+ sf_dev->pp[pp_num].fifo_out = of_property_read_bool(child, "fifo-out");
+ if (of_property_read_u32(child, "src-format", &sf_dev->pp[pp_num].src.format)) {
+ dev_err(dev,"Missing src-format property in the DT.\n");
+ ret = -EINVAL;
+ }
+ if (of_property_read_u32(child, "src-width", &sf_dev->pp[pp_num].src.width)) {
+ dev_err(dev,"Missing src-width property in the DT. w %d \n", sf_dev->pp[pp_num].src.width);
+ ret = -EINVAL;
+ }
+ if (of_property_read_u32(child, "src-height", &sf_dev->pp[pp_num].src.height)) {
+ dev_err(dev,"Missing src-height property in the DT.\n");
+ ret = -EINVAL;
+ }
+ if (of_property_read_u32(child, "dst-format", &sf_dev->pp[pp_num].dst.format)) {
+ dev_err(dev,"Missing dst-format property in the DT.\n");
+ ret = -EINVAL;
+ }
+ if (of_property_read_u32(child, "dst-width", &sf_dev->pp[pp_num].dst.width)) {
+ dev_err(dev,"Missing dst-width property in the DT.\n");
+ ret = -EINVAL;
+ }
+ if (of_property_read_u32(child, "dst-height", &sf_dev->pp[pp_num].dst.height)) {
+ dev_err(dev,"Missing dst-height property in the DT.\n");
+ ret = -EINVAL;
+ }
+ sf_dev->pp[pp_num].inited = 1;
+ }
+
+ return ret;
+}
+
+//#define FB_BUFF_VIN
+
+static int starfive_fb_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sf_fb_data *sf_dev;
+ int ret;
+
+ dev_info(dev, "%s\n", __func__);
+
+ sf_dev = devm_kzalloc(&pdev->dev, sizeof(struct sf_fb_data), GFP_KERNEL);
+ if (!sf_dev)
+ return -ENOMEM;
+
+ if (sf_get_mem_res(pdev, sf_dev)) {
+ dev_err(dev, "get memory resource FAIL\n");
+ return -ENOMEM;
+ }
+
+ ret = sf_fb_parse_dt(dev, sf_dev);
+
+ sf_dev->vin.notifier_call = vin_frame_complete_notify;
+ sf_dev->vin.priority = 0;
+ ret = vin_notifier_register(&sf_dev->vin);
+ if (ret) {
+ return ret;
+ }
+
+ sf_dev->dis_dev_name = "adv_7513_1080p";
+ sf_dev->cmap_inverse = 0;
+ sf_dev->cmap_static = 0;
+ sf_dev->dev = &pdev->dev;
+ if (sf_fbinfo_init(&pdev->dev, sf_dev)) {
+ dev_err(dev, "fb info init FAIL\n");
+ return -ENODEV;
+ }
+
+#ifdef FB_BUFF_VIN
+ /*the address 0xf9000000 is required in CMA modem by VIN,
+ *the case used to check VIN image data path only
+ *is not normal application.
+ */
+ sf_dev->fb.fix.smem_start = 0xf9000000;
+#endif
+
+ sf_dev->lcdc_irq = platform_get_irq_byname(pdev, "lcdc_irq");
+ if (sf_dev->lcdc_irq == -EPROBE_DEFER)
+ return sf_dev->lcdc_irq;
+ if (sf_dev->lcdc_irq < 0) {
+ dev_err(dev, "couldn't get lcdc irq\n");
+ return sf_dev->lcdc_irq;
+ }
+
+ sf_dev->vpp1_irq = platform_get_irq_byname(pdev, "vpp1_irq");
+ if (sf_dev->vpp1_irq == -EPROBE_DEFER)
+ return sf_dev->vpp1_irq;
+ if (sf_dev->vpp1_irq < 0) {
+ dev_err(dev, "couldn't get vpp1 irq\n");
+ return sf_dev->vpp1_irq;
+ }
+
+ lcdc_disable_intr(sf_dev);
+ sf_fb_pp_enable_intr(sf_dev, PP_INTR_DISABLE);
+
+ ret = devm_request_irq(&pdev->dev, sf_dev->lcdc_irq, lcdc_isr_handler, 0,
+ "sf_lcdc", sf_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "failure requesting irq %i: %d\n",
+ sf_dev->lcdc_irq, ret);
+ return ret;
+ }
+
+ ret = devm_request_irq(&pdev->dev, sf_dev->vpp1_irq, vpp1_isr_handler, 0,
+ "sf_vpp1", sf_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "failure requesting irq %i: %d\n",
+ sf_dev->vpp1_irq, ret);
+ return ret;
+ }
+
+ if (sf_fb_pp_init(sf_dev)) {
+ dev_err(dev, "pp init fail\n");
+ return -ENODEV;
+ }
+ pp_run(sf_dev, PP_ID_1, PP_RUN);
+ //pp_run(sf_dev, PP_ID_0, PP_RUN);
+
+ if (sf_fb_lcdc_clk_cfg(sf_dev)) {
+ dev_err(dev, "lcdc clock configure fail\n");
+ return -EINVAL;
+ }
+ sf_dev->pp_conn_lcdc = sf_fb_pp_get_2lcdc_id(sf_dev);
+ if (sf_fb_lcdc_init(sf_dev)) {
+ dev_err(dev, "lcdc init fail\n");
+ return -EINVAL;
+ }
+ lcdc_run(sf_dev, sf_dev->winNum, LCDC_RUN);
+
+ stf_dev = sf_dev;
+
+ platform_set_drvdata(pdev, sf_dev);
+ ret = register_framebuffer(&sf_dev->fb);
+ if (ret < 0) {
+ dev_err(&pdev->dev,"register framebuffer FAIL\n");
+ return ret;
+ }
+
+ sf_dev->stfbcdev.minor = MISC_DYNAMIC_MINOR;
+ sf_dev->stfbcdev.parent = &pdev->dev;
+ sf_dev->stfbcdev.name = "stfbcdev";
+ sf_dev->stfbcdev.fops = &stfb_fops;
+
+ ret = misc_register(&sf_dev->stfbcdev);
+ if (ret) {
+ dev_err(dev, "creare stfbcdev FAIL!\n");
+ }
+
+ lcdc_enable_intr(sf_dev);
+ sf_fb_pp_enable_intr(sf_dev, PP_INTR_ENABLE);
+
+ return 0;
+}
+
+static void starfive_fb_shutdown(struct platform_device *dev)
+{
+ return ;
+}
+
+static struct of_device_id starfive_fb_dt_match[] = {
+ {
+ .compatible = "starfive,vpp-lcdc",
+ },
+ {}
+};
+
+static struct platform_driver starfive_fb_driver = {
+ .probe = starfive_fb_probe,
+ //.remove = comipfb_remove,
+ .shutdown = starfive_fb_shutdown,
+ .driver = {
+ .name = "starfive,vpp-lcdc",
+ .of_match_table = starfive_fb_dt_match,
+ },
+};
+
+static int __init starfive_fb_init(void)
+{
+ return platform_driver_register(&starfive_fb_driver);
+}
+
+static void __exit starfive_fb_cleanup(void)
+{
+ platform_driver_unregister(&starfive_fb_driver);
+}
+
+module_init(starfive_fb_init);
+module_exit(starfive_fb_cleanup);
+
+MODULE_AUTHOR("StarFive Technology Co., Ltd.");
+MODULE_DESCRIPTION("loadable LCDC&VPP driver for StarFive");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/fbdev/starfive/starfive_fb.h b/drivers/video/fbdev/starfive/starfive_fb.h
new file mode 100644
index 000000000000..ed20616657ab
--- /dev/null
+++ b/drivers/video/fbdev/starfive/starfive_fb.h
@@ -0,0 +1,98 @@
+/*
+ * StarFive Vout driver
+ *
+ * Copyright 2020 StarFive Inc.
+ *
+ * Licensed under the GPL-2.
+ */
+
+#ifndef __SF_FRAMEBUFFER_H__
+#define __SF_FRAMEBUFFER_H__
+
+#include <linux/fb.h>
+#include <linux/miscdevice.h>
+
+#define H_SIZE 1920//352//1920//1280
+#define V_SIZE 1080//288//1080//720
+#define H_SIZE_DST H_SIZE
+#define V_SIZE_DST V_SIZE
+
+#define H_WID 40
+#define H_BP 220
+#define H_FP 110
+
+#define V_WID 5
+#define V_BP 20
+#define V_FP 5
+
+#define VD_1080P 1080
+#define VD_720P 720
+#define VD_PAL 480
+
+#define VD_HEIGHT_1080P VD_1080P
+#define VD_WIDTH_1080P 1920
+
+#define RGB_OFFSET_ADDR 0//H_SIZE*(PIX_BPP+1)
+
+#define MIN_XRES 64
+#define MIN_YRES 64
+
+#define STARFIVEFB_RGB_IF (0x00000002)
+#define STARFIVEFB_MIPI_IF (0x00000003)
+#define STARFIVEFB_HDMI_IF (0x00000004)
+#define STARFIVE_NAME "starfive"
+#define BUFFER_NUMS 2
+
+//sys registers
+#define SYS_CONF_LCDC 0x00
+#define SYS_CONF_PP 0x04
+#define SYS_MAP_CONV 0x08
+
+//vout clk registers
+#define CLK_LCDC_OCLK_CTRL 0x14
+
+struct res_name {
+ char name[10];
+};
+
+struct sf_fb_data {
+ struct device *dev;
+ char *dis_dev_name;
+ struct miscdevice stfbcdev;
+ int lcdc_irq;
+ int vpp0_irq;
+ int vpp1_irq;
+ int vpp2_irq;
+ void __iomem *base_clk;
+ void __iomem *base_rst;
+ void __iomem *base_sys;
+ void __iomem *base_vpp0;
+ void __iomem *base_vpp1;
+ void __iomem *base_vpp2;
+ void __iomem *base_lcdc;
+ struct notifier_block vin;
+ struct clk *mclk;
+
+ /*
+ * Hardware control information
+ */
+ struct fb_info fb;
+ struct fb_videomode display_info; /* reparent video mode source*/
+ unsigned int refresh_en;
+ unsigned int pixclock; /*lcdc_mclk*/
+ unsigned int buf_num; /* frame buffer number. */
+ unsigned int buf_size; /* frame buffer size. */
+ int cmap_inverse;
+ int cmap_static;
+ /* keep these registers in case we need to re-write palette */
+ u32 palette_buffer[256];
+ u32 pseudo_pal[16];
+
+ struct sf_fb_display_dev *display_dev;
+ //struct pp_mode pp[3];
+ struct pp_mode *pp;
+ int winNum;
+ int pp_conn_lcdc;
+};
+
+#endif
diff --git a/drivers/video/fbdev/starfive/starfive_lcdc.c b/drivers/video/fbdev/starfive/starfive_lcdc.c
new file mode 100644
index 000000000000..1626c3cf8fbb
--- /dev/null
+++ b/drivers/video/fbdev/starfive/starfive_lcdc.c
@@ -0,0 +1,294 @@
+/* driver/video/starfive/starfive_lcdc.c
+**
+** This program is free software; you can redistribute it and/or modify
+** it under the terms of the GNU General Public License version 2 as
+** published by the Free Software Foundation.
+**
+** Copyright (C) 2020 StarFive, Inc.
+**
+** PURPOSE: This files contains the driver of LCD controller.
+**
+** CHANGE HISTORY:
+** Version Date Author Description
+** 0.1.0 2020-11-03 starfive created
+**
+*/
+
+#include <linux/module.h>
+
+#include "starfive_fb.h"
+#include "starfive_lcdc.h"
+#include "starfive_vpp.h"
+
+//#define SF_LCDC_DEBUG 1
+#ifdef SF_LCDC_DEBUG
+ #define LCDC_PRT(format, args...) printk(KERN_DEBUG "[LCDC]: " format, ## args)
+ #define LCDC_INFO(format, args...) printk(KERN_INFO "[LCDC]: " format, ## args)
+ #define LCDC_ERR(format, args...) printk(KERN_ERR "[LCDC]: " format, ## args)
+#else
+ #define LCDC_PRT(x...) do{} while(0)
+ #define LCDC_INFO(x...) do{} while(0)
+ #define LCDC_ERR(x...) do{} while(0)
+#endif
+
+static u32 sf_fb_lcdcread32(struct sf_fb_data *sf_dev, u32 reg)
+{
+ return ioread32(sf_dev->base_lcdc + reg);
+}
+
+static void sf_fb_lcdcwrite32(struct sf_fb_data *sf_dev, u32 reg, u32 val)
+{
+ iowrite32(val, sf_dev->base_lcdc + reg);
+}
+
+void lcdc_mode_cfg(struct sf_fb_data *sf_dev, uint32_t workMode, int dotEdge, int syncEdge, int r2yBypass,
+ int srcSel, int intSrc, int intFreq)
+{
+ u32 lcdcEn = 0x1;
+ u32 cfg = lcdcEn | workMode << LCDC_WORK_MODE
+ | dotEdge << LCDC_DOTCLK_P
+ | syncEdge << LCDC_HSYNC_P
+ | syncEdge << LCDC_VSYNC_P
+ | 0x0 << LCDC_DITHER_EN
+ | r2yBypass << LCDC_R2Y_BPS
+ | srcSel << LCDC_TV_LCD_PATHSEL
+ | intSrc << LCDC_INT_SEL
+ | intFreq << LCDC_INT_FREQ;
+
+ sf_fb_lcdcwrite32(sf_dev, LCDC_GCTRL, cfg);
+ LCDC_PRT("LCDC WorkMode: 0x%x, LCDC Path: %d\n", workMode, srcSel);
+}
+
+//hbk, vbk=sa+bp, hpw?
+void lcdc_timing_cfg(struct sf_fb_data *sf_dev, int vunit)
+{
+ int hpw = sf_dev->display_info.hsync_len - 1;
+ int hbk = sf_dev->display_info.hsync_len + sf_dev->display_info.left_margin;
+ int hfp = sf_dev->display_info.right_margin;
+ int vpw = sf_dev->display_info.vsync_len - 1;
+ int vbk = sf_dev->display_info.vsync_len + sf_dev->display_info.upper_margin;
+ int vfp = sf_dev->display_info.lower_margin;
+
+ int htiming = hbk | hfp << LCDC_RGB_HFP;
+ int vtiming = vbk | vfp << LCDC_RGB_VFP;
+ int hvwid = hpw | vpw << LCDC_RGB_VPW | vunit << LCDC_RGB_UNIT;
+
+ sf_fb_lcdcwrite32(sf_dev, LCDC_RGB_H_TMG, htiming);
+ sf_fb_lcdcwrite32(sf_dev, LCDC_RGB_V_TMG, vtiming);
+ sf_fb_lcdcwrite32(sf_dev, LCDC_RGB_W_TMG, hvwid);
+ LCDC_PRT("LCDC HPW: %d, HBK: %d, HFP: %d\n", hpw, hbk, hfp);
+ LCDC_PRT("LCDC VPW: %d, VBK: %d, VFP: %d\n", vpw, vbk, vfp);
+ LCDC_PRT("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);
+void lcdc_desize_cfg(struct sf_fb_data *sf_dev)
+{
+ int hsize = sf_dev->display_info.xres - 1;
+ int vsize = sf_dev->display_info.yres - 1;
+
+ int sizecfg = hsize | vsize << LCDC_BG_VSIZE;
+ sf_fb_lcdcwrite32(sf_dev, LCDC_BACKGROUD, sizecfg);
+ LCDC_PRT("LCDC Dest H-Size: %d, V-Size: %d\n", hsize, vsize);
+}
+
+void lcdc_rgb_dclk_cfg(struct sf_fb_data *sf_dev, int dot_clk_sel)
+{
+ int cfg = dot_clk_sel << 16;
+
+ sf_fb_lcdcwrite32(sf_dev, LCDC_RGB_DCLK, cfg);
+
+ LCDC_PRT("LCDC Dot_clock_output_sel: 0x%x\n", cfg);
+}
+
+
+// color table
+//win0, no lock transfer
+//win3, no srcSel and addrMode, 0 assigned to them
+//lcdc_win_cfgA(sf_dev, winNum, sf_dev->display_info.xres-1, sf_dev->display_info.yres-1, 0x1, 0x0, 0x0, 0x1, 0x0, 0x0);
+void lcdc_win_cfgA(struct sf_fb_data *sf_dev, int winNum, int layEn, int clorTab,
+ int colorEn, int srcSel, int addrMode, int lock)
+{
+ int hsize = sf_dev->display_info.xres - 1;
+ int vsize = sf_dev->display_info.yres - 1;
+ int cfg = hsize | vsize << LCDC_WIN_VSIZE
+ | layEn << LCDC_WIN_EN
+ | clorTab << LCDC_CC_EN
+ | colorEn << LCDC_CK_EN
+ | srcSel << LCDC_WIN_ISSEL
+ | addrMode << LCDC_WIN_PM
+ | lock << LCDC_WIN_CLK;
+
+ sf_fb_lcdcwrite32(sf_dev, LCDC_WIN0_CFG_A + winNum * 0xC, cfg);
+ LCDC_PRT("LCDC Win%d H-Size: %d, V-Size: %d, layEn: %d, Src: %d, AddrMode: %d\n",
+ winNum, hsize, vsize, layEn, srcSel, addrMode);
+}
+
+void lcdc_win_cfgB(struct sf_fb_data *sf_dev, int winNum, int xpos, int ypos, int pixbpp, int argbOrd)
+{
+ int cfg = xpos | ypos << LCDC_WIN_VPOS
+ | pixbpp << LCDC_WIN_FMT
+ | argbOrd << LCDC_WIN_ARGB_ORDER;
+
+ sf_fb_lcdcwrite32(sf_dev, LCDC_WIN0_CFG_B + winNum * 0xC, cfg);
+ LCDC_PRT("LCDC Win%d Xpos: %d, Ypos: %d, pixBpp: 0x%x, ARGB Order: 0x%x\n",winNum, xpos, ypos, pixbpp, argbOrd);
+}
+
+//? Color key
+void lcdc_win_cfgC(struct sf_fb_data *sf_dev, int winNum, int colorKey)
+{
+ sf_fb_lcdcwrite32(sf_dev, LCDC_WIN0_CFG_C + winNum * 0xC, colorKey);
+ LCDC_PRT("LCDC Win%d Color Key: 0x%6x\n", winNum, colorKey);
+}
+
+//? hsize
+//lcdc_win_srcSize(sf_dev, winNum, sf_dev->display_info.xres-1);
+void lcdc_win_srcSize(struct sf_fb_data *sf_dev, int winNum)
+{
+ int addr, off, winsize, preCfg, cfg;
+ int hsize = sf_dev->display_info.xres - 1;
+ switch(winNum) {
+ 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;}
+ }
+ preCfg = sf_fb_lcdcread32(sf_dev, addr) & off;
+ cfg = winsize | preCfg;
+ sf_fb_lcdcwrite32(sf_dev, addr, cfg);
+ LCDC_PRT("LCDC Win%d Src Hsize: %d\n", winNum, hsize);
+}
+
+void lcdc_alphaVal_cfg(struct sf_fb_data *sf_dev, int val1, int val2, int val3, int val4, int sel)
+{
+ int val = val1 | val2 << LCDC_ALPHA2
+ | val3 << LCDC_ALPHA3
+ | val4 << LCDC_ALPHA4
+ | sel << LCDC_01_ALPHA_SEL;
+
+ int preVal = 0xfffb0000 & sf_fb_lcdcread32(sf_dev, LCDC_ALPHA_VALUE);
+ sf_fb_lcdcwrite32(sf_dev, LCDC_ALPHA_VALUE, preVal | val);
+ LCDC_PRT("LCDC Alpha 1: %x, 2: %x, 3: %x, 4: %x\n", val1, val2, val3, val4);
+}
+
+void lcdc_panel_cfg(struct sf_fb_data *sf_dev, int buswid, int depth, int txcycle, int pixpcycle,
+ int rgb565sel, int rgb888sel)
+{
+ int cfg = buswid | depth << LCDC_COLOR_DEP
+ | txcycle << LCDC_TCYCLES
+ | pixpcycle << LCDC_PIXELS
+ | rgb565sel << LCDC_565RGB_SEL
+ | rgb888sel << LCDC_888RGB_SEL;
+
+ sf_fb_lcdcwrite32(sf_dev, LCDC_PANELDATAFMT, cfg);
+ LCDC_PRT("LCDC bus bit: :%d, pixDep: 0x%x, txCyle: %d, %dpix/cycle, RGB565 2cycle_%d, RGB888 3cycle_%d\n",
+ buswid, depth, txcycle, pixpcycle, rgb565sel, rgb888sel);
+}
+
+void lcdc_enable_intr(struct sf_fb_data *sf_dev)
+{
+ int cfg;
+ cfg = ~(0x1 << LCDC_OUT_FRAME_END);
+
+ sf_fb_lcdcwrite32(sf_dev, LCDC_INT_MSK, cfg);
+}
+EXPORT_SYMBOL(lcdc_enable_intr);
+
+void lcdc_disable_intr(struct sf_fb_data *sf_dev)
+{
+ sf_fb_lcdcwrite32(sf_dev, LCDC_INT_MSK, 0xff);
+ sf_fb_lcdcwrite32(sf_dev, LCDC_INT_CLR, 0xff);
+}
+EXPORT_SYMBOL(lcdc_disable_intr);
+
+int lcdc_win_sel(struct sf_fb_data *sf_dev, enum lcdc_in_mode sel)
+{
+ int winNum = 2;
+
+ switch(sel)
+ {
+ case LCDC_IN_LCD_AXI:
+ break;
+ case LCDC_IN_VPP2:
+ winNum = LCDC_WIN_0;
+ break;
+ case LCDC_IN_VPP1:
+ winNum = LCDC_WIN_2;
+ break;
+ case LCDC_IN_VPP0:
+ winNum = LCDC_WIN_1;
+ mapconv_pp0_sel(sf_dev, 0x0);
+ break;
+ case LCDC_IN_MAPCONVERT:
+ winNum = LCDC_WIN_1;
+ mapconv_pp0_sel(sf_dev, 0x1);
+ break;
+ }
+
+ return winNum;
+}
+EXPORT_SYMBOL(lcdc_win_sel);
+
+irqreturn_t lcdc_isr_handler(int this_irq, void *dev_id)
+{
+ struct sf_fb_data *sf_dev = (struct sf_fb_data *)dev_id;
+ static int count = 0;
+ u32 intr_status = 0;
+
+ intr_status = sf_fb_lcdcread32(sf_dev, LCDC_INT_STATUS);
+ sf_fb_lcdcwrite32(sf_dev, LCDC_INT_CLR, 0xffffffff);
+
+ count ++;
+ //if(0 == count % 100)
+ //LCDC_PRT("++++\n");
+ //printk("+ count = %d, intr_status = 0x%x\n", count, intr_status);
+ return IRQ_HANDLED;
+}
+EXPORT_SYMBOL(lcdc_isr_handler);
+
+void lcdc_int_cfg(struct sf_fb_data *sf_dev, int mask)
+{
+ int cfg;
+
+ if(mask==0x1)
+ cfg = 0xffffffff;
+ else
+ cfg = ~(0x1 << LCDC_OUT_FRAME_END); //only frame end interrupt mask
+ sf_fb_lcdcwrite32(sf_dev, LCDC_INT_MSK, cfg);
+}
+EXPORT_SYMBOL(lcdc_int_cfg);
+
+void lcdc_config(struct sf_fb_data *sf_dev, int winNum)
+{
+ lcdc_mode_cfg(sf_dev, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0);
+ lcdc_timing_cfg(sf_dev, 0);
+ lcdc_desize_cfg(sf_dev);
+ lcdc_rgb_dclk_cfg(sf_dev, 0x1);
+
+ lcdc_win_cfgA(sf_dev, winNum, 0x1, 0x0, 0x0, 0x1, 0x0, 0x0);
+ lcdc_win_cfgB(sf_dev, winNum, 0x0, 0x0, 0x7, 0x0);
+ lcdc_win_cfgC(sf_dev, winNum, 0xffffff);
+
+ lcdc_win_srcSize(sf_dev, winNum);
+ lcdc_alphaVal_cfg(sf_dev, 0xf, 0xf, 0xf, 0xf, 0x0);
+ lcdc_panel_cfg(sf_dev, 0x3, 0x4, 0x0, 0x0, 0x0, 0x1); //rgb888sel?
+}
+EXPORT_SYMBOL(lcdc_config);
+
+void lcdc_run(struct sf_fb_data *sf_dev, uint32_t winMode, uint32_t lcdTrig)
+{
+ uint32_t runcfg = winMode << LCDC_EN_CFG_MODE | lcdTrig;
+ sf_fb_lcdcwrite32(sf_dev, LCDC_SWITCH, runcfg);
+ LCDC_PRT("Start run LCDC\n");
+}
+EXPORT_SYMBOL(lcdc_run);
+
+MODULE_AUTHOR("StarFive Technology Co., Ltd.");
+MODULE_DESCRIPTION("loadable LCDC driver for StarFive");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/fbdev/starfive/starfive_lcdc.h b/drivers/video/fbdev/starfive/starfive_lcdc.h
new file mode 100644
index 000000000000..2dcb44f6ec0d
--- /dev/null
+++ b/drivers/video/fbdev/starfive/starfive_lcdc.h
@@ -0,0 +1,141 @@
+/*
+ * StarFive Vout driver
+ *
+ * Copyright 2020 StarFive Inc.
+ *
+ * Licensed under the GPL-2.
+ */
+
+#ifndef __SF_FB_LCDC_H__
+#define __SF_FB_LCDC_H__
+
+enum lcdc_in_mode{
+ LCDC_IN_LCD_AXI = 0,
+ LCDC_IN_VPP2,
+ LCDC_IN_VPP1,
+ LCDC_IN_VPP0,
+ LCDC_IN_MAPCONVERT,
+};
+
+enum lcdc_win_num{
+ LCDC_WIN_0 = 0,
+ LCDC_WIN_1,
+ LCDC_WIN_2,
+ LCDC_WIN_3,
+ LCDC_WIN_4,
+ LCDC_WIN_5,
+};
+
+#define LCDC_STOP 0
+#define LCDC_RUN 1
+
+//lcdc registers
+#define LCDC_SWITCH 0x0000
+#define LCDC_GCTRL 0x0004
+#define LCDC_INT_STATUS 0x0008
+#define LCDC_INT_MSK 0x000C
+#define LCDC_INT_CLR 0x0010
+#define LCDC_RGB_H_TMG 0x0014
+#define LCDC_RGB_V_TMG 0x0018
+#define LCDC_RGB_W_TMG 0x001C
+#define LCDC_RGB_DCLK 0x0020
+#define LCDC_M_CS_CTRL 0x0024
+#define LCDC_DeltaRGB_CFG 0x0028
+#define LCDC_BACKGROUD 0x002C
+#define LCDC_WIN0_CFG_A 0x0030
+#define LCDC_WIN0_CFG_B 0x0034
+#define LCDC_WIN0_CFG_C 0x0038
+#define LCDC_WIN1_CFG_A 0x003C
+#define LCDC_WIN1_CFG_B 0x0040
+#define LCDC_WIN1_CFG_C 0x0044
+#define LCDC_WIN2_CFG_A 0x0048
+#define LCDC_WIN2_CFG_B 0x004C
+#define LCDC_WIN2_CFG_C 0x0050
+#define LCDC_WIN3_CFG_A 0x0054
+#define LCDC_WIN3_CFG_B 0x0058
+#define LCDC_WIN3_CFG_C 0x005C
+#define LCDC_WIN01_HSIZE 0x0090
+#define LCDC_WIN23_HSIZE 0x0094
+#define LCDC_WIN45_HSIZE 0x0098
+#define LCDC_WIN67_HSIZE 0x009C
+#define LCDC_ALPHA_VALUE 0x00A0
+#define LCDC_PANELDATAFMT 0x00A4
+
+/* Definition controller bit for LCDC registers */
+//for LCDC_SWITCH
+#define LCDC_DTRANS_SWITCH 0
+#define LCDC_MPU_START 1
+#define LCDC_EN_CFG_MODE 2
+//for LCDC_GCTRL
+#define LCDC_EN 0
+#define LCDC_WORK_MODE 1
+#define LCDC_A0_P 4
+#define LCDC_ENABLE_P 5
+#define LCDC_DOTCLK_P 6
+#define LCDC_HSYNC_P 7
+#define LCDC_VSYNC_P 8
+#define LCDC_DITHER_EN 9
+#define LCDC_R2Y_BPS 10
+#define LCDC_MS_SEL 11
+#define LCDC_TV_LCD_PATHSEL 12
+#define LCDC_INTERLACE 13
+#define LCDC_CBCR_ORDER 14
+#define LCDC_INT_SEL 15
+#define LCDC_INT_FREQ 24
+//for LCDC_INT_MSK
+#define LCDC_OUT_FRAME_END 5
+//for RGB_H_TMG,RGB_V_TMG,RGB_W_TMG
+#define LCDC_RGB_HBK 0
+#define LCDC_RGB_HFP 16
+#define LCDC_RGB_VBK 0
+#define LCDC_RGB_VFP 16
+#define LCDC_RGB_HPW 0
+#define LCDC_RGB_VPW 8
+#define LCDC_RGB_UNIT 16
+//for BACKGROUD
+#define LCDC_BG_HSIZE 0
+#define LCDC_BG_VSIZE 12
+//for WINx_CFG_A/B/C
+#define LCDC_WIN_HSIZE 0
+#define LCDC_WIN_VSIZE 12
+#define LCDC_WIN_EN 24
+#define LCDC_CC_EN 25
+#define LCDC_CK_EN 26
+#define LCDC_WIN_ISSEL 27
+#define LCDC_WIN_PM 28
+#define LCDC_WIN_CLK 30
+#define LCDC_WIN_HPOS 0
+#define LCDC_WIN_VPOS 12
+#define LCDC_WIN_FMT 24
+#define LCDC_WIN_ARGB_ORDER 27
+#define LCDC_WIN_CC 0
+//for WINxx_HSIZE
+#define LCDC_IMG_HSIZE 12
+//for LCDC_ALPHA_VALUE
+#define LCDC_ALPHA1 0
+#define LCDC_ALPHA2 4
+#define LCDC_ALPHA3 8
+#define LCDC_ALPHA4 12
+#define LCDC_A_GLBL_ALPHA 16
+#define LCDC_B_GLBL_ALPHA 17
+#define LCDC_01_ALPHA_SEL 18
+//for LCDC_PANELDATAFMT
+#define LCDC_BUS_W 0
+#define LCDC_TCYCLES 2
+#define LCDC_COLOR_DEP 4
+#define LCDC_PIXELS 7
+#define LCDC_332RGB_SEL 8
+#define LCDC_444RGB_SEL 9
+#define LCDC_666RGB_SEL 12
+#define LCDC_565RGB_SEL 16
+#define LCDC_888RGB_SEL 18
+
+extern void lcdc_enable_intr(struct sf_fb_data *sf_dev);
+extern void lcdc_disable_intr(struct sf_fb_data *sf_dev);
+extern irqreturn_t lcdc_isr_handler(int this_irq, void *dev_id);
+extern void lcdc_int_cfg(struct sf_fb_data *sf_dev, int mask);
+extern void lcdc_config(struct sf_fb_data *sf_dev, int winNum);
+extern int lcdc_win_sel(struct sf_fb_data *sf_dev, enum lcdc_in_mode sel);
+extern void lcdc_run(struct sf_fb_data *sf_dev, uint32_t winMode, uint32_t lcdTrig);
+
+#endif
diff --git a/drivers/video/fbdev/starfive/starfive_vpp.c b/drivers/video/fbdev/starfive/starfive_vpp.c
new file mode 100644
index 000000000000..2097782a9fdc
--- /dev/null
+++ b/drivers/video/fbdev/starfive/starfive_vpp.c
@@ -0,0 +1,593 @@
+/* driver/video/starfive/starfive_vpp.c
+**
+** This program is free software; you can redistribute it and/or modify
+** it under the terms of the GNU General Public License version 2 as
+** published by the Free Software Foundation.
+**
+** Copyright (C) 2020 StarFive, Inc.
+**
+** PURPOSE: This files contains the driver of VPP.
+**
+** CHANGE HISTORY:
+** Version Date Author Description
+** 0.1.0 2020-10-09 starfive created
+**
+*/
+
+#include <linux/module.h>
+
+#include "starfive_fb.h"
+#include "starfive_vpp.h"
+
+//#define SF_PP_DEBUG 1
+#ifdef SF_PP_DEBUG
+ #define PP_PRT(format, args...) printk(KERN_DEBUG "[pp]: " format, ## args)
+ #define PP_INFO(format, args...) printk(KERN_INFO "[pp]: " format, ## args)
+ #define PP_ERR(format, args...) printk(KERN_ERR "[pp]: " format, ## args)
+#else
+ #define PP_PRT(x...) do{} while(0)
+ #define PP_INFO(x...) do{} while(0)
+ #define PP_ERR(x...) do{} while(0)
+#endif
+
+static u32 sf_fb_sysread32(struct sf_fb_data *sf_dev, u32 reg)
+{
+ return ioread32(sf_dev->base_sys + reg);
+}
+
+static void sf_fb_syswrite32(struct sf_fb_data *sf_dev, u32 reg, u32 val)
+{
+ iowrite32(val, sf_dev->base_sys + reg);
+}
+
+static u32 sf_fb_vppread32(struct sf_fb_data *sf_dev, int ppNum, u32 reg)
+{
+ void __iomem *base_vpp = 0;
+ switch(ppNum) {
+ case 0 : {base_vpp = sf_dev->base_vpp0; break;}
+ case 1 : {base_vpp = sf_dev->base_vpp1; break;}
+ case 2 : {base_vpp = sf_dev->base_vpp2; break;}
+ default: {PP_ERR("Err:invalid vpp Number!\n"); break;}
+ }
+ return ioread32(base_vpp + reg);
+}
+
+static void sf_fb_vppwrite32(struct sf_fb_data *sf_dev, int ppNum, u32 reg, u32 val)
+{
+ void __iomem *base_vpp = 0;
+ switch(ppNum) {
+ case 0 : {base_vpp = sf_dev->base_vpp0; break;}
+ case 1 : {base_vpp = sf_dev->base_vpp1; break;}
+ case 2 : {base_vpp = sf_dev->base_vpp2; break;}
+ default: {PP_ERR("Err:invalid vpp Number!\n"); break;}
+ }
+ iowrite32(val, base_vpp + reg);
+}
+
+void mapconv_pp0_sel(struct sf_fb_data *sf_dev, int sel)
+{
+ u32 temp;
+ temp = sf_fb_sysread32(sf_dev, SYS_MAP_CONV);
+ temp &= ~(0x1);
+ temp |= (sel & 0x1);
+ sf_fb_syswrite32(sf_dev, SYS_MAP_CONV, temp);
+}
+EXPORT_SYMBOL(mapconv_pp0_sel);
+
+void pp_output_cfg(struct sf_fb_data *sf_dev, int ppNum, int outSel, int progInter, int desformat, int ptMode)
+{
+ int cfg = outSel | progInter << PP_INTERLACE
+ | desformat << PP_DES_FORMAT
+ | ptMode << PP_POINTER_MODE;
+
+ int preCfg = 0xffff8f0 & sf_fb_vppread32(sf_dev, ppNum, PP_CTRL1);
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_CTRL1, cfg | preCfg);
+ PP_PRT("PP%d outSel: %d, outFormat: 0x%x, Out Interlace: %d, ptMode: %d\n",
+ ppNum, outSel, desformat, progInter, ptMode);
+}
+
+void pp_srcfmt_cfg(struct sf_fb_data *sf_dev, int ppNum, int srcformat, int yuv420Inter, int yuv422_mode,
+ int yuv420_mode, int argbOrd)
+{
+ int cfg = srcformat << PP_SRC_FORMAT_N | yuv420Inter << PP_420_ITLC
+ | yuv422_mode << PP_SRC_422_YUV_POS
+ | yuv420_mode << PP_SRC_420_YUV_POS
+ | argbOrd << PP_SRC_ARGB_ORDER;
+
+ int preCfg = 0x83ffff0f & sf_fb_vppread32(sf_dev, ppNum, PP_CTRL1);
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_CTRL1, cfg | preCfg);
+ PP_PRT("PP%d Src Format: 0x%x, YUV420 Interlace: %d, YUV422: %d, YUV420: %d, ARGB Order: %d\n",
+ ppNum, srcformat,yuv420Inter,yuv422_mode,yuv420_mode, argbOrd);
+}
+
+void pp_r2yscal_bypass(struct sf_fb_data *sf_dev, int ppNum, int r2yByp, int scalByp, int y2rByp)
+{
+ int bypass = (r2yByp | scalByp<<1 | y2rByp<<2) << PP_R2Y_BPS;
+ int preCfg = 0xffff8fff & sf_fb_vppread32(sf_dev, ppNum, PP_CTRL1);
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_CTRL1, bypass | preCfg);
+ PP_PRT("PP%d Bypass R2Y: %d, Y2R: %d, MainSacle: %d\n", ppNum, r2yByp, y2rByp, scalByp);
+}
+
+void pp_argb_alpha(struct sf_fb_data *sf_dev, int ppNum, int alpha)
+{
+ int preCfg = 0xff00ffff & sf_fb_vppread32(sf_dev, ppNum, PP_CTRL1);
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_CTRL1, alpha << PP_ARGB_ALPHA | preCfg);
+ PP_PRT("PP%d Alpha: 0x%4x\n", ppNum, alpha);
+}
+
+//rgbNum: 1-3
+void pp_r2y_coeff(struct sf_fb_data *sf_dev, int ppNum, int coefNum, int rcoef, int gcoef, int bcoef, int off)
+{
+ int rgcoeff = rcoef | gcoef << PP_COEF_G1;
+ int bcoefoff = bcoef| off << PP_OFFSET_1;
+ u32 addr1 = (coefNum - 1) * 0x8 + PP_R2Y_COEF1;
+ u32 addr2 = (coefNum - 1) * 0x8 + PP_R2Y_COEF2;
+ sf_fb_vppwrite32(sf_dev, ppNum, addr1, rgcoeff);
+ sf_fb_vppwrite32(sf_dev, ppNum, addr2, bcoefoff);
+ PP_PRT("PP%d coefNum: %d, rCoef: 0x%4x, gCoef: 0x%4x, bCoef: 0x%4x, off: 0x%4x\n",
+ ppNum, coefNum, rcoef, gcoef, bcoef, off);
+}
+
+void pp_output_fmt_cfg(struct sf_fb_data *sf_dev, int ppNum, int yuv420Inter, int yuv422_mode)
+{
+ int preCfg = 0xfffffffe & sf_fb_vppread32(sf_dev, ppNum, PP_CTRL2);
+ preCfg = preCfg | yuv420Inter << PP_DES_420_ORDER
+ | yuv422_mode << PP_DES_422_ORDER;
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_CTRL2, preCfg);
+ PP_PRT("PP%d Lock Transfer: %d\n", ppNum, yuv422_mode);
+}
+
+void pp_lockTrans_cfg(struct sf_fb_data *sf_dev, int ppNum, int lockTrans)
+{
+ int preCfg = 0xfffffffe & sf_fb_vppread32(sf_dev, ppNum, PP_CTRL2);
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_CTRL2, lockTrans | preCfg);
+ PP_PRT("PP%d Lock Transfer: %d\n", ppNum, lockTrans);
+}
+
+void pp_int_interval_cfg(struct sf_fb_data *sf_dev, int ppNum, int interval)
+{
+ int preCfg = 0xffff00ff & sf_fb_vppread32(sf_dev, ppNum, PP_CTRL2);
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_CTRL2, interval << PP_INT_INTERVAL | preCfg);
+ PP_PRT("PP%d Frame Interrupt interval: %d Frames\n", ppNum, interval);
+}
+
+void pp_srcSize_cfg(struct sf_fb_data *sf_dev, int ppNum, int hsize, int vsize)
+{
+ int size = hsize | vsize << PP_SRC_VSIZE;
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_SRC_SIZE, size);
+ PP_PRT("PP%d HSize: %d, VSize: %d\n", ppNum, hsize, vsize);
+}
+
+//0-no drop, 1-1/2, 2-1/4, down to 1/32
+void pp_drop_cfg(struct sf_fb_data *sf_dev, int ppNum, int hdrop, int vdrop)
+{
+ int drop = hdrop | vdrop << PP_DROP_VRATION;
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_DROP_CTRL, drop);
+ PP_PRT("PP%d HDrop: %d, VDrop: %d\n", ppNum, hdrop, vdrop);
+}
+
+
+void pp_desSize_cfg(struct sf_fb_data *sf_dev, int ppNum, int hsize, int vsize)
+{
+ int size = hsize | vsize << PP_DES_VSIZE;
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_DES_SIZE, size);
+ PP_PRT("PP%d HSize: %d, VSize: %d\n", ppNum, hsize, vsize);
+}
+
+void pp_desAddr_cfg(struct sf_fb_data *sf_dev, int ppNum, int yaddr, int uaddr, int vaddr)
+{
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_DES_Y_SA, yaddr);
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_DES_U_SA, uaddr);
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_DES_V_SA, vaddr);
+ PP_PRT("PP%d des-Addr Y: 0x%8x, U: 0x%8x, V: 0x%8x\n", ppNum, yaddr, uaddr, vaddr);
+}
+
+void pp_desOffset_cfg(struct sf_fb_data *sf_dev, int ppNum, int yoff, int uoff, int voff)
+{
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_DES_Y_OFS, yoff);
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_DES_U_OFS, uoff);
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_DES_V_OFS, voff);
+ PP_PRT("PP%d des-Offset Y: 0x%4x, U: 0x%4x, V: 0x%4x\n", ppNum, yoff, uoff, voff);
+}
+
+
+void pp_intcfg(struct sf_fb_data *sf_dev, int ppNum, int intMask)
+{
+ int intcfg = ~(0x1<<0);
+
+ if(intMask)
+ intcfg = 0xf;
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_INT_MASK, intcfg);
+}
+EXPORT_SYMBOL(pp_intcfg);
+
+//next source frame Y/RGB start address, ?
+void pp_srcAddr_next(struct sf_fb_data *sf_dev, int ppNum, int ysa, int usa, int vsa)
+{
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_SRC_Y_SA_NXT, ysa);
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_SRC_U_SA_NXT, usa);
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_SRC_V_SA_NXT, vsa);
+ PP_PRT("PP%d next Y startAddr: 0x%8x, U startAddr: 0x%8x, V startAddr: 0x%8x\n", ppNum, ysa, usa, vsa);
+}
+EXPORT_SYMBOL(pp_srcAddr_next);
+
+void pp_srcOffset_cfg(struct sf_fb_data *sf_dev, int ppNum, int yoff, int uoff, int voff)
+{
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_SRC_Y_OFS, yoff);
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_SRC_U_OFS, uoff);
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_SRC_V_OFS, voff);
+ PP_PRT("PP%d src-Offset Y: 0x%4x, U: 0x%4x, V: 0x%4x\n", ppNum, yoff, uoff, voff);
+}
+EXPORT_SYMBOL(pp_srcOffset_cfg);
+
+void pp_nxtAddr_load(struct sf_fb_data *sf_dev, int ppNum, int nxtPar, int nxtPos)
+{
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_LOAD_NXT_PAR, nxtPar | nxtPos);
+ PP_PRT("PP%d next addrPointer: %d, %d set Regs\n", ppNum, nxtPar, nxtPos);
+}
+EXPORT_SYMBOL(pp_nxtAddr_load);
+
+void pp_run(struct sf_fb_data *sf_dev, int ppNum, int start)
+{
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_SWITCH, start);
+ //if(start)
+ // PP_PRT("Now start the PP%d\n\n", ppNum);
+}
+EXPORT_SYMBOL(pp_run);
+
+void pp1_enable_intr(struct sf_fb_data *sf_dev)
+{
+ sf_fb_vppwrite32(sf_dev, 1, PP_INT_MASK, 0x0);
+}
+EXPORT_SYMBOL(pp1_enable_intr);
+
+void pp_enable_intr(struct sf_fb_data *sf_dev, int ppNum)
+{
+ u32 cfg = 0xfffe;
+
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_INT_MASK, cfg);
+}
+EXPORT_SYMBOL(pp_enable_intr);
+
+void pp_disable_intr(struct sf_fb_data *sf_dev, int ppNum)
+{
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_INT_MASK, 0xf);
+ sf_fb_vppwrite32(sf_dev, ppNum, PP_INT_CLR, 0xf);
+}
+EXPORT_SYMBOL(pp_disable_intr);
+
+static void pp_srcfmt_set(struct sf_fb_data *sf_dev, int ppNum, struct pp_video_mode *src)
+{
+ switch(src->format)
+ {
+ case COLOR_YUV422_YVYU:
+ pp_srcfmt_cfg(sf_dev, ppNum, PP_SRC_YUV422, 0x0, COLOR_YUV422_YVYU, 0x0, 0x0);
+ break;
+ case COLOR_YUV422_VYUY:
+ pp_srcfmt_cfg(sf_dev, ppNum, PP_SRC_YUV422, 0x0, COLOR_YUV422_VYUY, 0x0, 0x0);
+ break;
+ case COLOR_YUV422_YUYV:
+ pp_srcfmt_cfg(sf_dev, ppNum, PP_SRC_YUV422, 0x0, COLOR_YUV422_YUYV, 0x0, 0x0);
+ break;
+ case COLOR_YUV422_UYVY:
+ pp_srcfmt_cfg(sf_dev, ppNum, PP_SRC_YUV422, 0x0, COLOR_YUV422_UYVY, 0x0, 0x0);
+ break;
+ case COLOR_YUV420P:
+ pp_srcfmt_cfg(sf_dev, ppNum, PP_SRC_YUV420P, 0x0, 0, 0x0, 0x0);
+ break;
+ case COLOR_YUV420_NV21:
+ pp_srcfmt_cfg(sf_dev, ppNum, PP_SRC_YUV420I, 0x1, 0, COLOR_YUV420_NV21-COLOR_YUV420_NV21, 0x0);
+ break;
+ case COLOR_YUV420_NV12:
+ pp_srcfmt_cfg(sf_dev, ppNum, PP_SRC_YUV420I, 0x1, 0, COLOR_YUV420_NV12-COLOR_YUV420_NV21, 0x0);
+ break;
+ case COLOR_RGB888_ARGB:
+ pp_srcfmt_cfg(sf_dev, ppNum, PP_SRC_GRB888, 0x0, 0x0, 0x0, COLOR_RGB888_ARGB-COLOR_RGB888_ARGB);//0x0);
+ break;
+ case COLOR_RGB888_ABGR:
+ pp_srcfmt_cfg(sf_dev, ppNum, PP_SRC_GRB888, 0x0, 0x0, 0x0, COLOR_RGB888_ARGB-COLOR_RGB888_ABGR);//0x1);
+ break;
+ case COLOR_RGB888_RGBA:
+ pp_srcfmt_cfg(sf_dev, ppNum, PP_SRC_GRB888, 0x0, 0x0, 0x0, COLOR_RGB888_ARGB-COLOR_RGB888_RGBA);//0x2);
+ break;
+ case COLOR_RGB888_BGRA:
+ pp_srcfmt_cfg(sf_dev, ppNum, PP_SRC_GRB888, 0x0, 0x0, 0x0, COLOR_RGB888_ARGB-COLOR_RGB888_BGRA);//0x3);
+ break;
+ case COLOR_RGB565:
+ pp_srcfmt_cfg(sf_dev, ppNum, PP_SRC_RGB565, 0x0, 0x0, 0x0, 0x0);
+ break;
+ }
+}
+
+static void pp_dstfmt_set(struct sf_fb_data *sf_dev, int ppNum, struct pp_video_mode *dst)
+{
+ unsigned int outsel = 1;
+ if(dst->addr)
+ {
+ outsel = 0;
+ }
+
+ switch(dst->format) {
+ case COLOR_YUV422_YVYU:
+ pp_output_cfg(sf_dev, ppNum, outsel, 0x0, PP_DST_YUV422, 0x0);
+ pp_output_fmt_cfg(sf_dev, ppNum, 0, COLOR_YUV422_UYVY - COLOR_YUV422_YVYU);
+ break;
+ case COLOR_YUV422_VYUY:
+ pp_output_cfg(sf_dev, ppNum, outsel, 0x0, PP_DST_YUV422, 0x0);
+ pp_output_fmt_cfg(sf_dev, ppNum, 0, COLOR_YUV422_UYVY - COLOR_YUV422_VYUY);
+ break;
+ case COLOR_YUV422_YUYV:
+ pp_output_cfg(sf_dev, ppNum, outsel, 0x0, PP_DST_YUV422, 0x0);
+ pp_output_fmt_cfg(sf_dev, ppNum, 0, COLOR_YUV422_UYVY - COLOR_YUV422_YUYV);
+ break;
+ case COLOR_YUV422_UYVY:
+ pp_output_cfg(sf_dev, ppNum, outsel, 0x0, PP_DST_YUV422, 0x0);
+ pp_output_fmt_cfg(sf_dev, ppNum, 0, COLOR_YUV422_UYVY - COLOR_YUV422_YVYU);
+ break;
+ case COLOR_YUV420P:
+ pp_output_cfg(sf_dev, ppNum, outsel, 0x0, PP_DST_YUV420P, 0x0);
+ pp_output_fmt_cfg(sf_dev, ppNum, 0, 0);
+ break;
+ case COLOR_YUV420_NV21:
+ pp_output_cfg(sf_dev, ppNum, outsel, 0x0, PP_DST_YUV420I, 0x0);
+ pp_output_fmt_cfg(sf_dev, ppNum, COLOR_YUV420_NV21 - COLOR_YUV420_NV21, 0);
+ break;
+ case COLOR_YUV420_NV12:
+ pp_output_cfg(sf_dev, ppNum, outsel, 0x0, PP_DST_YUV420I, 0x0);///0x2, 0x0);
+ //pp_output_fmt_cfg(ppNum, COLOR_YUV420_NV12 - COLOR_YUV420_NV21, 0);
+ break;
+ case COLOR_RGB888_ARGB:
+ pp_output_cfg(sf_dev, ppNum, outsel, 0x0, PP_DST_ARGB888, 0x0);
+ //pp_output_fmt_cfg(ppNum, 0, 0);
+ break;
+ case COLOR_RGB888_ABGR:
+ pp_output_cfg(sf_dev, ppNum, outsel, 0x0, PP_DST_ABGR888, 0x0);
+ pp_output_fmt_cfg(sf_dev, ppNum, 0, 0);
+ break;
+ case COLOR_RGB888_RGBA:
+ pp_output_cfg(sf_dev, ppNum, outsel, 0x0, PP_DST_RGBA888, 0x0);
+ pp_output_fmt_cfg(sf_dev, ppNum, 0, 0);
+ break;
+ case COLOR_RGB888_BGRA:
+ pp_output_cfg(sf_dev, ppNum, outsel, 0x0, PP_DST_BGRA888, 0x0);
+ pp_output_fmt_cfg(sf_dev, ppNum, 0, 0);
+ break;
+ case COLOR_RGB565:
+ pp_output_cfg(sf_dev, ppNum, outsel, 0x0, PP_DST_RGB565, 0x0);
+ pp_output_fmt_cfg(sf_dev, ppNum, 0, 0);
+ break;
+ }
+}
+
+
+void pp_format_set(struct sf_fb_data *sf_dev, int ppNum, struct pp_video_mode *src, struct pp_video_mode *dst)
+{
+
+ /* 1:bypass, 0:not bypass */
+ unsigned int scale_byp = 1;
+
+ pp_srcfmt_set(sf_dev, ppNum, src);
+ pp_dstfmt_set(sf_dev, ppNum, dst);
+
+ if((src->height != dst->height) || (src->width != dst->width)) {
+ scale_byp = 0;
+ }
+
+ if((src->format >= COLOR_RGB888_ARGB) && (dst->format <= COLOR_YUV420_NV12)) {
+ /* rgb -> yuv-420 */
+ pp_r2yscal_bypass(sf_dev, ppNum, NOT_BYPASS, scale_byp, BYPASS);
+ pp_r2y_coeff(sf_dev, ppNum, 1, R2Y_COEF_R1, R2Y_COEF_G1, R2Y_COEF_B1, R2Y_OFFSET1);
+ pp_r2y_coeff(sf_dev, ppNum, 2, R2Y_COEF_R2, R2Y_COEF_G2, R2Y_COEF_B2, R2Y_OFFSET2);
+ pp_r2y_coeff(sf_dev, ppNum, 3, R2Y_COEF_R3, R2Y_COEF_G3, R2Y_COEF_B3, R2Y_OFFSET3);
+ } else if ((src->format <= COLOR_YUV420_NV12) && (dst->format >= COLOR_RGB888_ARGB)) {
+ /* yuv-420 -> rgb */
+ pp_r2yscal_bypass(sf_dev, ppNum, BYPASS, scale_byp, NOT_BYPASS);
+ } else if ((src->format <= COLOR_YUV422_YVYU) && (dst->format <= COLOR_YUV420_NV12)) {
+ /* yuv422 -> yuv420 */
+ pp_r2yscal_bypass(sf_dev, ppNum, BYPASS, scale_byp, BYPASS);
+ } else {
+ /* rgb565->argb888 */
+ pp_r2yscal_bypass(sf_dev, ppNum, BYPASS, scale_byp, BYPASS);
+ } //else if((src->format >= COLOR_RGB888_ARGB) && (dst->format >= COLOR_RGB888_ARGB))
+ {
+ /* rgb -> rgb */
+ // pp_r2yscal_bypass(ppNum, BYPASS, scale_byp, BYPASS);
+ }
+ pp_argb_alpha(sf_dev, ppNum, 0xff);
+
+ if(dst->addr) {
+ pp_lockTrans_cfg(sf_dev, ppNum, SYS_BUS_OUTPUT);
+ } else {
+ pp_lockTrans_cfg(sf_dev, ppNum, FIFO_OUTPUT);
+ }
+
+ pp_int_interval_cfg(sf_dev, ppNum, 0x1);
+
+}
+
+void pp_size_set(struct sf_fb_data *sf_dev, int ppNum, struct pp_video_mode *src, struct pp_video_mode *dst)
+{
+ uint32_t srcAddr, dstaddr;
+ unsigned int size, y_rgb_ofst, uofst;
+ unsigned int v_uvofst = 0, next_y_rgb_addr = 0, next_u_addr = 0, next_v_addr = 0;
+ unsigned int i = 0;
+
+ pp_srcSize_cfg(sf_dev, ppNum, src->width - 1, src->height - 1);
+ pp_drop_cfg(sf_dev, ppNum, 0x0, 0x0);///0:no drop
+ pp_desSize_cfg(sf_dev, ppNum, dst->width - 1, dst->height - 1);
+
+ srcAddr = src->addr + (i<<30);///PP_SRC_BASE_ADDR + (i<<30);
+ size = src->width * src->height;
+
+ if(src->format >= COLOR_RGB888_ARGB) {
+ next_y_rgb_addr = srcAddr;
+ next_u_addr = 0;
+ next_v_addr = 0;
+
+ y_rgb_ofst = 0;
+ uofst = 0;
+ v_uvofst = 0;
+
+ //pp_srcAddr_next(ppNum, srcAddr, 0, 0);
+ //pp_srcOffset_cfg(ppNum, 0x0, 0x0, 0x0);
+
+ } else {
+ //if((src->format == COLOR_YUV420_NV21) || (src->format == COLOR_YUV420_NV12)){
+ if(src->format == COLOR_YUV420_NV21) { //ok
+ next_y_rgb_addr = srcAddr;
+ next_u_addr = srcAddr+size+1;
+ next_v_addr = srcAddr+size;//0;
+
+ y_rgb_ofst = 0;
+ uofst = 0;
+ v_uvofst = size;
+ //pp_srcAddr_next(ppNum, next_y_rgb_addr, next_u_addr, next_v_addr);
+ //pp_srcOffset_cfg(ppNum, y_rgb_ofst, uofst, v_uvofst);
+ }
+
+ if(src->format == COLOR_YUV420_NV12) { //ok
+ next_y_rgb_addr = srcAddr;
+ next_u_addr = srcAddr+size;
+ next_v_addr = srcAddr+size+1;
+ y_rgb_ofst = 0;
+ uofst = 0;
+ v_uvofst = size;
+ } else if (src->format == COLOR_YUV420P) {
+ next_y_rgb_addr = srcAddr;
+ next_u_addr = srcAddr+size;
+ next_v_addr = srcAddr+size*5/4;
+ y_rgb_ofst = 0;
+ uofst = 0;
+ v_uvofst = 0;
+ } else if (src->format == COLOR_YUV422_YVYU) { //ok
+ next_y_rgb_addr = srcAddr;
+ next_u_addr = srcAddr+1;
+ next_v_addr = srcAddr+3;
+ y_rgb_ofst = 0;
+ uofst = 0;
+ v_uvofst = 0;
+ } else if (src->format == COLOR_YUV422_VYUY) { //ok
+ next_y_rgb_addr = srcAddr+1;
+ next_u_addr = srcAddr+2;
+ next_v_addr = srcAddr;
+ y_rgb_ofst = 0;
+ uofst = 0;
+ v_uvofst = 0;
+ } else if(src->format == COLOR_YUV422_YUYV) { //ok
+ next_y_rgb_addr = srcAddr;
+ next_u_addr = srcAddr+1;
+ next_v_addr = srcAddr+2;
+ y_rgb_ofst = 0;
+ uofst = 0;
+ v_uvofst = 0;
+ } else if(src->format == COLOR_YUV422_UYVY) { //ok
+ next_y_rgb_addr = srcAddr+1;
+ next_u_addr = srcAddr;
+ next_v_addr = srcAddr+2;
+ y_rgb_ofst = 0;
+ uofst = 0;
+ v_uvofst = 0;
+ }
+ }
+ pp_srcAddr_next(sf_dev, ppNum, next_y_rgb_addr, next_u_addr, next_v_addr);
+ pp_srcOffset_cfg(sf_dev, ppNum, y_rgb_ofst, uofst, v_uvofst);
+ /* source addr not change */
+ pp_nxtAddr_load(sf_dev, ppNum, 0x1, (i & 0x1));
+
+ if(dst->addr) {
+ dstaddr = dst->addr;
+ size = dst->height*dst->width;
+ if(dst->format >= COLOR_RGB888_ARGB) {
+ next_y_rgb_addr = dstaddr;
+ next_u_addr = 0;
+ next_v_addr = 0;
+ y_rgb_ofst = 0;
+ uofst = 0;
+ v_uvofst = 0;
+ } else {
+ if(dst->format == COLOR_YUV420_NV21) {
+ /* yyyyvuvuvu */
+ next_y_rgb_addr = dstaddr;
+ next_u_addr = dstaddr+size;
+ next_v_addr = 0;//dstaddr+size;
+ y_rgb_ofst = 0;
+ uofst = 0;
+ v_uvofst = 0;
+ } else if (dst->format == COLOR_YUV420_NV12){
+ /* yyyyuvuvuv */
+ next_y_rgb_addr = dstaddr;
+ next_u_addr = dstaddr+size;
+ next_v_addr = dstaddr+size+1;
+ y_rgb_ofst = 0;
+ uofst = size;
+ v_uvofst = 0;
+ } else if(dst->format == COLOR_YUV420P) {
+ next_y_rgb_addr = dstaddr;
+ next_u_addr = dstaddr+size;
+ next_v_addr = dstaddr+size*5/4;
+ y_rgb_ofst = 0;
+ uofst = 0;
+ v_uvofst = 0;
+ } else if (dst->format == COLOR_YUV422_YVYU) {
+ next_y_rgb_addr = dstaddr;
+ next_u_addr = dstaddr+1;
+ next_v_addr = dstaddr+3;
+ y_rgb_ofst = 0;
+ uofst = 0;
+ v_uvofst = 0;
+ } else if(dst->format == COLOR_YUV422_VYUY) {
+ next_y_rgb_addr = dstaddr+1;
+ next_u_addr = dstaddr+2;
+ next_v_addr = dstaddr;
+ y_rgb_ofst = 0;
+ uofst = 0;
+ v_uvofst = 0;
+ } else if(dst->format == COLOR_YUV422_YUYV) {
+ next_y_rgb_addr = dstaddr;
+ next_u_addr = dstaddr+1;
+ next_v_addr = dstaddr+2;
+ y_rgb_ofst = 0;
+ uofst = 0;
+ v_uvofst = 0;
+ } else if(dst->format == COLOR_YUV422_UYVY) {
+ next_y_rgb_addr = dstaddr+1;
+ next_u_addr = dstaddr;
+ next_v_addr = dstaddr+2;
+ y_rgb_ofst = 0;
+ uofst = 0;
+ v_uvofst = 0;
+ }
+ }
+ pp_desAddr_cfg(sf_dev, ppNum, next_y_rgb_addr, next_u_addr, next_v_addr);
+ pp_desOffset_cfg(sf_dev, ppNum, y_rgb_ofst, uofst, v_uvofst);
+ }
+
+}
+
+
+void pp_config(struct sf_fb_data *sf_dev, int ppNum, struct pp_video_mode *src, struct pp_video_mode *dst)
+{
+ //pp_disable_intr(sf_dev, ppNum);
+ pp_format_set(sf_dev, ppNum, src, dst);
+ pp_size_set(sf_dev, ppNum, src, dst);
+}
+EXPORT_SYMBOL(pp_config);
+
+irqreturn_t vpp1_isr_handler(int this_irq, void *dev_id)
+{
+ struct sf_fb_data *sf_dev = (struct sf_fb_data *)dev_id;
+ static int count = 0;
+ sf_fb_vppwrite32(sf_dev, 1, PP_INT_CLR, 0xf);
+
+ count ++;
+ if(0 == count % 60)
+ PP_PRT("=");
+ //printk("=");
+
+ return IRQ_HANDLED;
+}
+EXPORT_SYMBOL(vpp1_isr_handler);
+
+MODULE_AUTHOR("StarFive Technology Co., Ltd.");
+MODULE_DESCRIPTION("loadable VPP driver for StarFive");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/fbdev/starfive/starfive_vpp.h b/drivers/video/fbdev/starfive/starfive_vpp.h
new file mode 100644
index 000000000000..a8c8802f41d6
--- /dev/null
+++ b/drivers/video/fbdev/starfive/starfive_vpp.h
@@ -0,0 +1,194 @@
+/*
+ * StarFive Vout driver
+ *
+ * Copyright 2020 StarFive Inc.
+ *
+ * Licensed under the GPL-2.
+ */
+
+#ifndef __SF_FB_VPP_H__
+#define __SF_FB_VPP_H__
+
+#define PP_ID_0 0
+#define PP_ID_1 1
+#define PP_ID_2 2
+
+#define PP_NUM 3
+
+#define PP_STOP 0
+#define PP_RUN 1
+
+#define PP_INTR_ENABLE 1
+#define PP_INTR_DISABLE 0
+//PP coefficients
+///*
+#define R2Y_COEF_R1 77
+#define R2Y_COEF_G1 150
+#define R2Y_COEF_B1 29
+#define R2Y_OFFSET1 0
+
+#define R2Y_COEF_R2 (0x400|43)
+#define R2Y_COEF_G2 (0x400|85)
+#define R2Y_COEF_B2 128
+#define R2Y_OFFSET2 128
+
+#define R2Y_COEF_R3 128
+#define R2Y_COEF_G3 (0x400|107)
+#define R2Y_COEF_B3 (0x400|21)
+#define R2Y_OFFSET3 128
+//*/
+enum PP_LCD_PATH
+{
+ SYS_BUS_OUTPUT = 0,
+ FIFO_OUTPUT = 1,
+};
+
+enum PP_COLOR_CONVERT_SCALE
+{
+ NOT_BYPASS = 0,
+ BYPASS,
+};
+
+enum PP_SRC_FORMAT
+{
+ PP_SRC_YUV420P = 0,
+ PP_SRC_YUV422,
+ PP_SRC_YUV420I,
+ PP_RESERVED,
+ PP_SRC_GRB888,
+ PP_SRC_RGB565,
+};
+
+enum PP_DST_FORMAT
+{
+ PP_DST_YUV420P = 0,
+ PP_DST_YUV422,
+ PP_DST_YUV420I,
+ PP_DST_RGBA888,
+ PP_DST_ARGB888,
+ PP_DST_RGB565,
+ PP_DST_ABGR888,
+ PP_DST_BGRA888,
+};
+
+enum COLOR_FORMAT{
+ COLOR_YUV422_UYVY = 0, //00={Y1,V0,Y0,U0}
+ COLOR_YUV422_VYUY = 1, //01={Y1,U0,Y0,V0}
+ COLOR_YUV422_YUYV = 2, //10={V0,Y1,U0,Y0}
+ COLOR_YUV422_YVYU = 3, //11={U0,Y1,V0,Y0}
+
+ COLOR_YUV420P,
+ COLOR_YUV420_NV21,
+ COLOR_YUV420_NV12,
+
+ COLOR_RGB888_ARGB,
+ COLOR_RGB888_ABGR,
+ COLOR_RGB888_RGBA,
+ COLOR_RGB888_BGRA,
+ COLOR_RGB565,
+};
+
+
+struct pp_video_mode {
+ enum COLOR_FORMAT format;
+ unsigned int height;
+ unsigned int width;
+ unsigned int addr;
+};
+
+struct pp_mode {
+ char pp_id;
+ bool bus_out; /*out to ddr*/
+ bool fifo_out; /*out to lcdc*/
+ bool inited;
+ struct pp_video_mode src;
+ struct pp_video_mode dst;
+};
+
+//vpp registers
+#define PP_SWITCH 0x0000
+#define PP_CTRL1 0x0004
+#define PP_CTRL2 0x0008
+#define PP_SRC_SIZE 0x000C
+#define PP_DROP_CTRL 0x0010
+#define PP_DES_SIZE 0x0014
+#define PP_Scale_Hratio 0x0018
+#define PP_Scale_Vratio 0x001C
+#define PP_Scale_limit 0x0020
+#define PP_SRC_Y_SA_NXT 0x0024
+#define PP_SRC_U_SA_NXT 0x0028
+#define PP_SRC_V_SA_NXT 0x002c
+#define PP_LOAD_NXT_PAR 0x0030
+#define PP_SRC_Y_SA0 0x0034
+#define PP_SRC_U_SA0 0x0038
+#define PP_SRC_V_SA0 0x003c
+#define PP_SRC_Y_OFS 0x0040
+#define PP_SRC_U_OFS 0x0044
+#define PP_SRC_V_OFS 0x0048
+#define PP_SRC_Y_SA1 0x004C
+#define PP_SRC_U_SA1 0x0050
+#define PP_SRC_V_SA1 0x0054
+#define PP_DES_Y_SA 0x0058
+#define PP_DES_U_SA 0x005C
+#define PP_DES_V_SA 0x0060
+#define PP_DES_Y_OFS 0x0064
+#define PP_DES_U_OFS 0x0068
+#define PP_DES_V_OFS 0x006C
+#define PP_INT_MASK 0x0074
+#define PP_INT_CLR 0x0078
+#define PP_R2Y_COEF1 0x007C
+#define PP_R2Y_COEF2 0x0080
+
+/* Definition controller bit for LCDC registers */
+//for PP_SWITCH
+#define PP_TRIG 0
+//for PP_CTRL1
+#define PP_LCDPATH_EN 0
+#define PP_INTERLACE 1
+#define PP_POINTER_MODE 2
+#define PP_SRC_FORMAT_N 4
+#define PP_420_ITLC 7
+#define PP_DES_FORMAT 8
+#define PP_R2Y_BPS 12
+#define PP_MSCALE_BPS 13
+#define PP_Y2R_BPS 14
+#define PP_ARGB_ALPHA 16
+#define PP_UV_IN_ADD_128 24
+#define PP_UV_OUT_ADD_128 25
+#define PP_SRC_422_YUV_POS 26
+#define PP_SRC_420_YUV_POS 28
+#define PP_SRC_ARGB_ORDER 29
+//for PP_CTRL2
+#define PP_LOCK_EN 0
+#define PP_INT_INTERVAL 8
+#define PP_DES_422_ORDER 16
+#define PP_DES_420_ORDER 18
+//for PP_SRC_SIZE
+#define PP_SRC_HSIZE 0
+#define PP_SRC_VSIZE 16
+//for PP_DROP_CTRL
+#define PP_DROP_HRATION 0
+#define PP_DROP_VRATION 4
+//for PP_DES_SIZE
+#define PP_DES_HSIZE 0
+#define PP_DES_VSIZE 16
+//for PP_R2Y_COEF1
+#define PP_COEF_R1 0
+#define PP_COEF_G1 16
+//for PP_R2Y_COEF2
+#define PP_COEF_B1 0
+#define PP_OFFSET_1 16
+
+extern void mapconv_pp0_sel(struct sf_fb_data *sf_dev, int sel);
+extern void pp_srcAddr_next(struct sf_fb_data *sf_dev, int ppNum, int ysa, int usa, int vsa);
+extern void pp_srcOffset_cfg(struct sf_fb_data *sf_dev, int ppNum, int yoff, int uoff, int voff);
+extern void pp_nxtAddr_load(struct sf_fb_data *sf_dev, int ppNum, int nxtPar, int nxtPos);
+extern void pp_intcfg(struct sf_fb_data *sf_dev, int ppNum, int intMask);
+extern irqreturn_t vpp1_isr_handler(int this_irq, void *dev_id);
+extern void pp1_enable_intr(struct sf_fb_data *sf_dev);
+extern void pp_enable_intr(struct sf_fb_data *sf_dev, int ppNum);
+extern void pp_disable_intr(struct sf_fb_data *sf_dev, int ppNum);
+extern void pp_config(struct sf_fb_data *sf_dev, int ppNum, struct pp_video_mode *src, struct pp_video_mode *dst);
+extern void pp_run(struct sf_fb_data *sf_dev, int ppNum, int start);
+
+#endif
diff --git a/include/video/stf-vin.h b/include/video/stf-vin.h
new file mode 100755
index 000000000000..292e0dd9e995
--- /dev/null
+++ b/include/video/stf-vin.h
@@ -0,0 +1,187 @@
+/* include/video/stf-vin.h
+ *
+ * Copyright 2020 starfive tech.
+ * Eric Tang <eric.tang@starfivetech.com>
+ *
+ * Generic vin notifier interface
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+*/
+#ifndef _VIDEO_VIN_H
+#define _VIDEO_VIN_H
+
+#include <linux/cdev.h>
+
+#define DRV_NAME "stf-vin"
+#define FB_FIRST_ADDR 0xf9000000
+#define FB_SECOND_ADDR 0xf97e9000
+
+#define VIN_MIPI_CONTROLLER0_OFFSET 0x00000
+#define VIN_CLKGEN_OFFSET 0x10000
+#define VIN_RSTGEN_OFFSET 0x20000
+#define VIN_MIPI_CONTROLLER1_OFFSET 0x30000
+#define VIN_SYSCONTROLLER_OFFSET 0x40000
+
+#define VD_1080P 1080
+#define VD_720P 720
+#define VD_PAL 480
+
+#define VD_HEIGHT_1080P VD_1080P
+#define VD_WIDTH_1080P 1920
+
+#define VD_HEIGHT_720P VD_720P
+#define VD_WIDTH_720P 1080
+
+#define VD_HEIGHT_480 480
+#define VD_WIDTH_640 640
+
+#define VIN_CLKGEN_BASE_ADDR 0x11800000
+#define VIN_RSTGEN_BASE_ADDR 0x11840000
+#define VIN_IOPAD_BASE_ADDR 0x11858000
+
+//vin clk registers
+#define CLK_VIN_SRC_CTRL 0x188
+#define CLK_ISP0_AXI_CTRL 0x190
+#define CLK_ISP0NOC_AXI_CTRL 0x194
+#define CLK_ISPSLV_AXI_CTRL 0x198
+#define CLK_ISP1_AXI_CTRL 0x1A0
+#define CLK_ISP1NOC_AXI_CTRL 0x1A4
+#define CLK_VIN_AXI 0x1AC
+#define CLK_VINNOC_AXI 0x1B0
+
+//isp clk registers
+#define CLK_MIPI_RX0_PXL_CTRL 0x0c
+#define CLK_ISP0_CTRL 0x3c
+#define CLK_ISP0_2X_CTRL 0x40
+#define CLK_ISP0_MIPI_CTRL 0x44
+#define CLK_C_ISP0_CTRL 0x64
+#define CLK_ISP1_CTRL 0x48
+#define CLK_ISP1_2X_CTRL 0x4C
+#define CLK_ISP1_MIPI_CTRL 0x50
+#define CLK_MIPI_RX1_PXL_CTRL 0x10
+#define CLK_C_ISP1_CTRL 0x68
+#define CLK_VIN_AXI_WR_CTRL 0x5C
+
+#define SOFTWARE_RESET_ASSERT0 0x0
+#define SOFTWARE_RESET_ASSERT1 0x4
+
+#define IOPAD_REG81 0x144
+#define IOPAD_REG82 0x148
+#define IOPAD_REG83 0x14C
+#define IOPAD_REG84 0x150
+#define IOPAD_REG85 0x154
+#define IOPAD_REG86 0x158
+#define IOPAD_REG87 0x15C
+#define IOPAD_REG88 0x160
+#define IOPAD_REG89 0x164
+
+//sys control REG DEFINE
+#define SYSCTRL_REG6 0x18
+#define SYSCTRL_REG10 0x28
+#define SYSCTRL_REG11 0x2C
+#define SYSCTRL_REG12 0x30
+#define SYSCTRL_REG13 0x34
+#define SYSCTRL_REG14 0x38
+#define SYSCTRL_REG15 0x3C
+#define SYSCTRL_REG17 0x44
+#define SYSCTRL_REG18 0x48
+#define SYSCTRL_REG19 0x4C
+#define SYSCTRL_REG20 0x50
+#define SYSCTRL_REG21 0x54
+
+#define ISP_NO_SCALE_ENABLE (0x1<<20)
+#define ISP_MULTI_FRAME_ENABLE (0x1<<17)
+#define ISP_SS0_ENABLE (0x1<<11)
+#define ISP_SS1_ENABLE (0x1<<12)
+#define ISP_RESET (0x1<<1)
+#define ISP_ENBALE (0x1)
+
+#define VIN_ISP0_BASE_ADDR 0x19870000
+#define VIN_ISP1_BASE_ADDR 0x198a0000
+
+ //ISP REG DEFINE
+#define ISP_REG_DVP_POLARITY_CFG 0x00000014
+#define ISP_REG_RAW_FORMAT_CFG 0x00000018
+#define ISP_REG_CFA_MODE 0x00000A1C
+#define ISP_REG_PIC_CAPTURE_START_CFG 0x0000001C
+#define ISP_REG_PIC_CAPTURE_END_CFG 0x00000020
+#define ISP_REG_PIPELINE_XY_SIZE 0x00000A0C
+#define ISP_REG_Y_PLANE_START_ADDR 0x00000A80
+#define ISP_REG_UV_PLANE_START_ADDR 0x00000A84
+#define ISP_REG_STRIDE 0x00000A88
+#define ISP_REG_PIXEL_COORDINATE_GEN 0x00000A8C
+#define ISP_REG_PIXEL_AXI_CONTROL 0x00000A90
+#define ISP_REG_SS_AXI_CONTROL 0x00000AC4
+#define ISP_REG_RGB_TO_YUV_COVERSION0 0x00000E40
+#define ISP_REG_RGB_TO_YUV_COVERSION1 0x00000E44
+#define ISP_REG_RGB_TO_YUV_COVERSION2 0x00000E48
+#define ISP_REG_RGB_TO_YUV_COVERSION3 0x00000E4C
+#define ISP_REG_RGB_TO_YUV_COVERSION4 0x00000E50
+#define ISP_REG_RGB_TO_YUV_COVERSION5 0x00000E54
+#define ISP_REG_RGB_TO_YUV_COVERSION6 0x00000E58
+#define ISP_REG_RGB_TO_YUV_COVERSION7 0x00000E5C
+#define ISP_REG_RGB_TO_YUV_COVERSION8 0x00000E60
+#define ISP_REG_CIS_MODULE_CFG 0x00000010
+#define ISP_REG_ISP_CTRL_1 0x00000A08
+#define ISP_REG_ISP_CTRL_0 0x00000A00
+#define ISP_REG_DC_AXI_ID 0x00000044
+#define ISP_REG_CSI_INPUT_EN_AND_STATUS 0x00000000
+
+enum VIN_SOURCE_FORMAT {
+ SRC_COLORBAR_VIN_ISP = 0,
+ SRC_DVP_SENSOR_VIN,
+ SRC_DVP_SENSOR_VIN_ISP,//need replace sensor
+ SRC_CSI2RX_VIN_ISP,
+ SRC_DVP_SENSOR_VIN_OV5640,
+};
+
+struct vin_params {
+ void *paddr;
+ unsigned long size;
+};
+
+struct vin_buf {
+ void *vaddr;
+ dma_addr_t paddr;
+ u32 size;
+};
+
+struct vin_framesize {
+ u32 width;
+ u32 height;
+};
+
+struct vin_format {
+ enum VIN_SOURCE_FORMAT format;
+ u8 fps;
+};
+
+struct stf_vin_dev {
+ /* Protects the access of variables shared within the interrupt */
+ spinlock_t irqlock;
+ int irq;
+ struct device *dev;
+ struct cdev vin_cdev;
+ void __iomem *base;
+ void __iomem *base_isp;
+ struct vin_framesize frame;
+ struct vin_format format;
+ bool isp0;
+ bool isp1;
+ int isp0_irq;
+ int isp1_irq;
+ u32 major;
+ struct vin_buf buf;
+
+ wait_queue_head_t wq;
+ bool condition;
+ int odd;
+};
+
+extern int vin_notifier_register(struct notifier_block *nb);
+extern void vin_notifier_unregister(struct notifier_block *nb);
+extern int vin_notifier_call(unsigned long e, void *v);
+#endif