summaryrefslogtreecommitdiff
path: root/drivers/video/fbdev/mxc/imx_dcss.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/fbdev/mxc/imx_dcss.c')
-rw-r--r--drivers/video/fbdev/mxc/imx_dcss.c3585
1 files changed, 3585 insertions, 0 deletions
diff --git a/drivers/video/fbdev/mxc/imx_dcss.c b/drivers/video/fbdev/mxc/imx_dcss.c
new file mode 100644
index 000000000000..b065956335f3
--- /dev/null
+++ b/drivers/video/fbdev/mxc/imx_dcss.c
@@ -0,0 +1,3585 @@
+/*
+ * Copyright 2017 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/cache.h>
+#include <asm/cacheflush.h>
+#include <asm/uaccess.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/irqdesc.h>
+#include <linux/fb.h>
+#include <linux/freezer.h>
+#include <linux/kfifo.h>
+#include <linux/kthread.h>
+#include <linux/log2.h>
+#include <linux/regulator/consumer.h>
+#include <linux/scatterlist.h>
+#include <linux/videodev2.h>
+#include <video/mxc_edid.h>
+#include <linux/workqueue.h>
+
+#include "mxc_dispdrv.h"
+#include "imx_dcss_table.h"
+
+/* sub engines address start offset */
+#define HDR_CHAN1_START 0x00000
+#define HDR_CHAN2_START 0x04000
+#define HDR_CHAN3_START 0x08000
+#define HDR_OUT_START 0x0C000
+#define DOLBY_VISION_START 0x10000
+#define DOLBY_GRAPHIC_START 0x11000
+#define DOLBY_OUTPUT_START 0x12000
+#define DEC400D_CHAN1_START 0x15000
+#define DTRC_CHAN2_START 0x16000
+#define DTRC_CHAN3_START 0x17000
+#define DPR_CHAN1_START 0x18000
+#define DPR_CHAN2_START 0x19000
+#define DPR_CHAN3_START 0x1A000
+#define SUBSAM_START 0x1B000
+#define SCALER_CHAN1_START 0x1C000
+#define SCALER_CHAN2_START 0x1C400
+#define SCALER_CHAN3_START 0x1C800
+#define DTG_START 0x20000
+#define WR_SCL_START 0x21000
+#define RD_SRC_START 0x22000
+#define CTX_LD_START 0x23000
+#define LUT_LD_START 0x24000
+#define IRQ_STEER_START 0x2D000
+#define LPCG_START 0x2E000
+#define BLK_CTRL_START 0x2F000
+
+#define DCSS_MODE_SCALE_EN (1 << 0)
+#define DCSS_MODE_CSC_EN (1 << 1)
+#define DCSS_MODE_RESOLVE_EN (1 << 2)
+#define DCSS_MODE_DECOMPRESS_EN (1 << 3)
+#define DCSS_MODE_HDR10_EN (1 << 4)
+#define DCSS_MODE_DOLBY_EN (1 << 5)
+
+/* define several clocks rate */
+#define DISP_AXI_RATE 800000000
+#define DISP_APB_RATE 133000000
+#define DISP_RTRAM_RATE 400000000
+#define DISP_PIXEL_RATE 100000000
+
+#define TILE_TYPE_LINEAR 0x0
+#define TILE_TYPE_GPU_STANDARD 0x1
+#define TILE_TYPE_GPU_SUPER 0x2
+#define TILE_TYPE_VPU_2PYUV420 0x3
+#define TILE_TYPE_VPU_2PVP9 0x4
+
+#define PIXEL_STORE_NONCOMPRESS 0x1
+#define PIXEL_STORE_COMPRESS 0x2
+
+#define CSC_MODE_BYPASS 0x0
+#define CSC_MODE_YUV2RGB 0x1
+#define CSC_MODE_RGB2YUV 0x2
+
+#define HSYNC_ACTIVE_HIGH 0x1
+#define VSYNC_ACTIVE_HIGH 0x1
+#define DE_ACTIVE_HIGH 0x1
+
+/* pixel format macros */
+#define PIX_FMT_A8R8G8B8 0x1
+#define PIX_FMT_A2R10G10B10 0x2
+#define PIX_FMT_1PYUV422 0x3
+#define PIX_FMT_2PYUV420_8 0x4
+#define PIX_FMT_2PYUV420_10 0x5
+#define PIX_FMT_2PVP9_8 0x6
+#define PIX_FMT_2PVP9_10 0x7
+#define PIX_FMT_AYUV444 0x8
+
+/* more formats fourcc define */
+#define V4L2_PIX_FMT_A2R10G10B10 v4l2_fourcc('B', 'A', '3', '0') /* 32 ARGB-2-10-10-10 */
+
+#define MAX_WIDTH 4096
+#define MAX_HEIGHT 4096
+
+#define RED 0
+#define GREEN 1
+#define BLUE 2
+#define TRANSP 3
+
+#define CTXLD_TYPE_DB 0x1
+#define CTXLD_TYPE_SB 0x2
+
+#define DCSS_REGS_SIZE 0x30000
+#define DCSS_CFIFO_SIZE 0x100000 /* power of 2 */
+#define DCSS_IRQS_NUM 32
+
+/* define registers offset */
+#define CTXLD_CTRL_STATUS 0x0
+#define CTXLD_CTRL_STATUS_SET 0x4
+#define CTXLD_CTRL_STATUS_CLR 0x8
+#define CTXLD_CTRL_STATUS_TOG 0xC
+
+#define CTXLD_DB_BASE_ADDR 0x10
+#define CTXLD_DB_COUNT 0x14
+#define CTXLD_SB_BASE_ADDR 0x18
+#define CTXLD_SB_COUNT 0x1C
+
+#define TC_LINE1_INT 0x50
+#define TC_INTERRUPT_STATUS 0x5C
+#define TC_INTERRUPT_CONTROL 0x60
+#define TC_INTERRUPT_MASK 0x68
+
+/* define dcss state */
+#define DCSS_STATE_RESET 0x0
+#define DCSS_STATE_RUNNING 0x1
+#define DCSS_STATE_STOP 0x2
+
+/* io memory blocks number */
+#define IORESOURCE_MEM_NUM 0x2
+
+#define USE_CTXLD 0x1
+
+#define NAME_LEN 32
+
+/* TODO: DCSS IRQs indexes, more added later */
+#define IRQ_DPR_CH1 3
+#define IRQ_DPR_CH2 4
+#define IRQ_DPR_CH3 5
+#define IRQ_CTX_LD 6
+#define IRQ_TC_LINE1 8
+#define IRQ_DEC400D_CH1 15
+#define IRQ_DTRC_CH2 16
+#define IRQ_DTRC_CH3 17
+
+/* ctxld irqs status */
+#define RD_ERR (1 << 16)
+#define DB_COMP (1 << 17)
+#define SB_HP_COMP (1 << 18)
+#define SB_LP_COMP (1 << 19)
+#define AHB_ERR (1 << 22)
+
+/* ctxld irqs mask */
+#define RD_ERR_EN (1 << 2)
+#define DB_COMP_EN (1 << 3)
+#define SB_HP_COMP_EN (1 << 4)
+#define SB_LP_COMP_EN (1 << 5)
+#define AHB_ERR_EN (1 << 8)
+
+/* channels */
+#define DCSS_CHAN_MAIN 0
+#define DCSS_CHAN_SECONDARY 1
+#define DCSS_CHAN_THIRD 2
+/* all channels are disabled */
+#define DCSS_CHAN_NULL 3
+
+/**
+ * kfifo_to_end_len - returns the size from 'out' to buffer end
+ * this is a kfifo extend interface as required
+ */
+#define kfifo_to_end_len(fifo) ( \
+{ \
+ unsigned int ptr; \
+ \
+ if (!(fifo)->kfifo.in) \
+ ptr = 0; \
+ else \
+ ptr = ((((fifo)->kfifo.in - 1) & (fifo)->kfifo.mask) + 1); \
+ \
+ kfifo_size(fifo) - ptr; \
+} \
+)
+
+/* TODO: */
+struct coordinate {
+ uint32_t x;
+ uint32_t y;
+};
+
+struct rectangle {
+ uint32_t ulc_x;
+ uint32_t ulc_y;
+ uint32_t lrc_x;
+ uint32_t lrc_y;
+};
+
+struct pix_fmt_info {
+ uint32_t fourcc;
+ uint32_t bpp;
+ bool is_yuv;
+};
+
+struct dcss_pixmap {
+ uint32_t channel_id;
+ uint32_t width;
+ uint32_t height;
+ uint32_t bits_per_pixel;
+ uint32_t pitch;
+ struct rectangle crop; /* active area */
+ uint32_t format;
+ uint32_t tile_type; /* see TILE_TYPE_* macros */
+ uint32_t pixel_store; /* see PIXEL_STORE_* macros */
+ uint32_t flags;
+ dma_addr_t paddr;
+};
+
+/* Display state format in DRAM used by CTX_LD */
+struct ctxld_unit {
+ uint32_t reg_value;
+ uint32_t reg_offset;
+};
+
+/* ctxld buffer */
+struct cbuffer{
+ void *sb_addr;
+ void *db_addr;
+ uint32_t sb_len; /* buffer length in elements */
+ uint32_t db_len;
+ uint32_t sb_data_len; /* data length in elements */
+ uint32_t db_data_len;
+ uint32_t esize; /* size per element */
+};
+
+struct vsync_info {
+ wait_queue_head_t vwait;
+ unsigned long vcount;
+};
+
+struct ctxld_commit {
+ struct list_head list;
+ struct kref refcount;
+ struct work_struct work;
+ void *data;
+ uint32_t sb_data_len;
+ uint32_t sb_hp_data_len;
+ uint32_t db_data_len;
+ uint32_t sb_trig_pos;
+ uint32_t db_trig_pos;
+};
+
+struct ctxld_fifo {
+ uint32_t size;
+ void *vaddr;
+ dma_addr_t dma_handle;
+ struct list_head ctxld_list; /* manage context loader */
+ DECLARE_KFIFO_PTR(fifo, struct ctxld_unit);
+ struct scatterlist sgl[1];
+ uint32_t sgl_num;
+ struct workqueue_struct *ctxld_wq;
+ /* synchronization in two points:
+ * a. simutanous fifo commits
+ * b. queue waiting for cfifo flush
+ */
+ wait_queue_head_t cqueue;
+ struct completion complete;
+};
+
+/* channel info: 3 channels in DCSS */
+struct dcss_channel_info {
+ uint32_t channel_id;
+ uint32_t channel_en; /* channel 1 enable by default */
+ struct platform_device *pdev;
+ struct fb_info *fb_info;
+ struct dcss_pixmap input;
+ struct rectangle ch_pos; /* display position in dtg for one channel */
+ struct cbuffer cb;
+ uint32_t hdr10_in_addr;
+ uint32_t decomp_addr;
+ uint32_t dpr_addr;
+ uint32_t scaler_addr;
+ int blank; /* see FB_BLANK_* macros */
+ uint32_t csc_mode; /* see CSC_MODE_* macros */
+ bool dpr_scaler_en; /* record dpr and scaler enabled or not */
+ unsigned long update_stamp; /* default is ~0x0UL */
+
+ void *dev_data; /* pointer to dcss_info */
+};
+
+struct dcss_channels {
+ struct dcss_channel_info chan_info[3];
+ uint32_t hdr10_out_addr;
+ uint32_t subsam_addr;
+ uint32_t dtg_addr;
+ uint32_t wrscl_addr;
+ uint32_t rdsrc_addr;
+ uint32_t ctxld_addr;
+ uint32_t lutld_addr;
+ uint32_t hdmi_phy_addr;
+ uint32_t irq_steer_addr;
+ uint32_t lpcg_addr;
+ uint32_t blk_ctrl_addr;
+};
+
+/* display info: output to monitor */
+struct dcss_info {
+ struct platform_device *pdev;
+ void __iomem *base;
+ void __iomem *blkctl_base;
+ spinlock_t llock; /* list lock: for ctxld_list */
+ int irqs[DCSS_IRQS_NUM];
+ uint32_t irqs_num;
+ uint32_t dcss_state; /* see DCSS_STATE_* macros */
+ struct clk *clk_axi;
+ struct clk *clk_apb;
+ struct clk *clk_rtram;
+ struct clk *clk_dtrc;
+ struct clk *clk_pix;
+ struct regulator *power;
+ struct ctxld_fifo cfifo;
+ struct task_struct *handler;
+ struct dcss_pixmap *output;
+ struct dcss_channels chans; /* maximum 3 channels
+ * TODO: better change to layer
+ */
+ const struct fb_videomode *dft_disp_mode; /* Default display mode */
+ uint32_t tile_type; /* see TILE_TYPE_* macros */
+ uint32_t pixel_store; /* see PIXEL_STORE_* macros */
+ uint32_t csc_mode; /* see CSC_MODE_* macros */
+ uint32_t mode_flags; /* see DCSS_MODE_* macros */
+ uint32_t sync_flags; /* see FB_SYNC_* macros */
+ uint32_t hsync_pol;
+ uint32_t vsync_pol;
+ uint32_t de_pol;
+ char disp_dev[NAME_LEN];
+ struct mxc_dispdrv_handle *dispdrv;
+ struct vsync_info vinfo;
+
+ atomic_t flush;
+};
+
+const struct fb_videomode imx_cea_mode[100] = {
+ /* #1: 640x480p@59.94/60Hz 4:3 */
+ [1] = {
+ NULL, 60, 640, 480, 39722, 48, 16, 33, 10, 96, 2, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0,
+ },
+ /* #2: 720x480p@59.94/60Hz 4:3 */
+ [2] = {
+ NULL, 60, 720, 480, 37037, 60, 16, 30, 9, 62, 6, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0,
+ },
+ /* #3: 720x480p@59.94/60Hz 16:9 */
+ [3] = {
+ NULL, 60, 720, 480, 37037, 60, 16, 30, 9, 62, 6, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #4: 1280x720p@59.94/60Hz 16:9 */
+ [4] = {
+ NULL, 60, 1280, 720, 13468, 220, 110, 20, 5, 40, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0
+ },
+ /* #5: 1920x1080i@59.94/60Hz 16:9 */
+ [5] = {
+ NULL, 60, 1920, 1080, 13763, 148, 88, 15, 2, 44, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_INTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #6: 720(1440)x480iH@59.94/60Hz 4:3 */
+ [6] = {
+ NULL, 60, 1440, 480, 18554/*37108*/, 114, 38, 15, 4, 124, 3, 0,
+ FB_VMODE_INTERLACED | FB_VMODE_ASPECT_4_3, 0,
+ },
+ /* #7: 720(1440)x480iH@59.94/60Hz 16:9 */
+ [7] = {
+ NULL, 60, 1440, 480, 18554/*37108*/, 114, 38, 15, 4, 124, 3, 0,
+ FB_VMODE_INTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #8: 720(1440)x240pH@59.94/60Hz 4:3 */
+ [8] = {
+ NULL, 60, 1440, 240, 37108, 114, 38, 15, 4, 124, 3, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0,
+ },
+ /* #9: 720(1440)x240pH@59.94/60Hz 16:9 */
+ [9] = {
+ NULL, 60, 1440, 240, 37108, 114, 38, 15, 4, 124, 3, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #14: 1440x480p@59.94/60Hz 4:3 */
+ [14] = {
+ NULL, 60, 1440, 480, 18500, 120, 32, 30, 9, 124, 6, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0,
+ },
+ /* #15: 1440x480p@59.94/60Hz 16:9 */
+ [15] = {
+ NULL, 60, 1440, 480, 18500, 120, 32, 30, 9, 124, 6, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #16: 1920x1080p@60Hz 16:9 */
+ [16] = {
+ NULL, 60, 1920, 1080, 6734, 148, 88, 36, 4, 44, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #17: 720x576pH@50Hz 4:3 */
+ [17] = {
+ NULL, 50, 720, 576, 37037, 68, 12, 39, 5, 64, 5, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0,
+ },
+ /* #18: 720x576pH@50Hz 16:9 */
+ [18] = {
+ NULL, 50, 720, 576, 37037, 68, 12, 39, 5, 64, 5, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #19: 1280x720p@50Hz */
+ [19] = {
+ NULL, 50, 1280, 720, 13468, 220, 440, 20, 5, 40, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #20: 1920x1080i@50Hz */
+ [20] = {
+ NULL, 50, 1920, 1080, 13480, 148, 528, 15, 5, 528, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_INTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #23: 720(1440)x288pH@50Hz 4:3 */
+ [23] = {
+ NULL, 50, 1440, 288, 37037, 138, 24, 19, 2, 126, 3, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0,
+ },
+ /* #24: 720(1440)x288pH@50Hz 16:9 */
+ [24] = {
+ NULL, 50, 1440, 288, 37037, 138, 24, 19, 2, 126, 3, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #29: 720(1440)x576pH@50Hz 4:3 */
+ [29] = {
+ NULL, 50, 1440, 576, 18518, 136, 24, 39, 5, 128, 5, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0,
+ },
+ /* #30: 720(1440)x576pH@50Hz 16:9 */
+ [30] = {
+ NULL, 50, 1440, 576, 18518, 136, 24, 39, 5, 128, 5, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #31: 1920x1080p@50Hz */
+ [31] = {
+ NULL, 50, 1920, 1080, 6734, 148, 528, 36, 4, 44, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #32: 1920x1080p@23.98/24Hz */
+ [32] = {
+ NULL, 24, 1920, 1080, 13468, 148, 638, 36, 4, 44, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #33: 1920x1080p@25Hz */
+ [33] = {
+ NULL, 25, 1920, 1080, 13468, 148, 528, 36, 4, 44, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #34: 1920x1080p@30Hz */
+ [34] = {
+ NULL, 30, 1920, 1080, 13468, 148, 88, 36, 4, 44, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #41: 1280x720p@100Hz 16:9 */
+ [41] = {
+ NULL, 100, 1280, 720, 6734, 220, 440, 20, 5, 40, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0
+ },
+ /* #47: 1280x720p@119.88/120Hz 16:9 */
+ [47] = {
+ NULL, 120, 1280, 720, 6734, 220, 110, 20, 5, 40, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0
+ },
+ /* #95: 3840x2160p@30Hz 16:9 */
+ [95] = {
+ NULL, 30, 3840, 2160, 3367, 296, 176, 72, 8, 88, 10,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0
+ },
+ /* #97: 3840x2160p@60Hz 16:9 */
+ [97] = {
+ NULL, 30, 3840, 2160, 1684, 296, 176, 72, 8, 88, 10,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0
+ },
+};
+
+static const struct of_device_id dcss_dt_ids[] ={
+ { .compatible = "fsl,imx8mq-dcss", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dcss_dt_ids);
+
+static void dcss_free_fbmem(struct fb_info *fbi);
+static int dcss_open(struct fb_info *fbi, int user);
+static int dcss_check_var(struct fb_var_screeninfo *var,
+ struct fb_info *fbi);
+static int dcss_set_par(struct fb_info *fbi);
+static int dcss_setcolreg(unsigned regno, unsigned red, unsigned green,
+ unsigned blue, unsigned transp, struct fb_info *info);
+static int dcss_blank(int blank, struct fb_info *fbi);
+static int dcss_pan_display(struct fb_var_screeninfo *var,
+ struct fb_info *fbi);
+static int dcss_ioctl(struct fb_info *fbi, unsigned int cmd,
+ unsigned long arg);
+static int vcount_compare(unsigned long vcount,
+ struct vsync_info *vinfo);
+static int dcss_wait_for_vsync(unsigned long crtc,
+ struct dcss_info *info);
+static void flush_cfifo(struct ctxld_fifo *cfifo,
+ struct work_struct *work);
+
+static struct fb_ops dcss_ops = {
+ .owner = THIS_MODULE,
+ .fb_open = dcss_open,
+ .fb_check_var = dcss_check_var,
+ .fb_set_par = dcss_set_par,
+ .fb_setcolreg = dcss_setcolreg,
+ .fb_blank = dcss_blank,
+ .fb_pan_display = dcss_pan_display,
+ .fb_ioctl = dcss_ioctl,
+ .fb_fillrect = cfb_fillrect,
+ .fb_copyarea = cfb_copyarea,
+ .fb_imageblit = cfb_imageblit,
+};
+
+/* TODO: more added later */
+static const struct pix_fmt_info formats[] = {
+ {
+ .fourcc = V4L2_PIX_FMT_ARGB32,
+ .bpp = 32,
+ .is_yuv = false,
+ }, {
+ .fourcc = V4L2_PIX_FMT_A2R10G10B10,
+ .bpp = 32,
+ .is_yuv = false,
+ }, {
+ .fourcc = V4L2_PIX_FMT_YUYV,
+ .bpp = 16,
+ .is_yuv = true,
+ }, {
+ .fourcc = V4L2_PIX_FMT_YVYU,
+ .bpp = 16,
+ .is_yuv = true,
+ }, {
+ .fourcc = V4L2_PIX_FMT_UYVY,
+ .bpp = 16,
+ .is_yuv = true,
+ }, {
+ .fourcc = V4L2_PIX_FMT_VYUY,
+ .bpp = 16,
+ .is_yuv = true,
+ }, {
+ .fourcc = V4L2_PIX_FMT_YUV32,
+ .bpp = 32,
+ .is_yuv = true,
+ }, {
+ .fourcc = V4L2_PIX_FMT_NV12,
+ .bpp = 8,
+ .is_yuv = true,
+ },
+};
+
+static const struct fb_bitfield def_a8r8g8b8[] = {
+ [RED] = {
+ .offset = 16,
+ .length = 8,
+ .msb_right = 0,
+ },
+ [GREEN] = {
+ .offset = 8,
+ .length = 8,
+ .msb_right = 0,
+ },
+ [BLUE] = {
+ .offset = 0,
+ .length = 8,
+ .msb_right = 0,
+ },
+ [TRANSP] = {
+ .offset = 24,
+ .length = 8,
+ .msb_right = 0,
+ }
+};
+
+static const struct fb_bitfield def_a2r10g10b10[] = {
+ [RED] = {
+ .offset = 20,
+ .length = 10,
+ },
+ [GREEN] = {
+ .offset = 10,
+ .length = 10,
+ },
+ [BLUE] = {
+ .offset = 0,
+ .length = 10,
+ },
+ [TRANSP] = {
+ .offset = 30,
+ .length = 2,
+ }
+};
+
+static const struct pix_fmt_info *get_fmt_info(uint32_t fourcc)
+{
+ uint32_t i;
+
+ for (i = 0; i < ARRAY_SIZE(formats); i++) {
+ if (formats[i].fourcc == fourcc)
+ return &formats[i];
+ }
+
+ return NULL;
+}
+
+static int fmt_is_yuv(uint32_t fourcc)
+{
+ switch (fourcc) {
+ case V4L2_PIX_FMT_ARGB32:
+ case V4L2_PIX_FMT_A2R10G10B10:
+ return 0;
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ case V4L2_PIX_FMT_YUV32:
+ return 1;
+ case V4L2_PIX_FMT_NV12:
+ return 2;
+ default:
+ return -EINVAL;
+ }
+}
+
+/* TODO: writel ? */
+#define fill_unit(uint, offset, value) { \
+ unit->reg_value = value; \
+ unit->reg_offset = offset; \
+}
+
+static void fill_sb(struct cbuffer *cb,
+ uint32_t offset,
+ uint32_t value)
+{
+ struct ctxld_unit *unit = NULL;
+
+ BUG_ON(!cb);
+
+ if (unlikely(cb->sb_data_len == cb->sb_len)) {
+ /* sb is full */
+ BUG_ON(1);
+ }
+
+ unit = (struct ctxld_unit *)(cb->sb_addr + cb->sb_data_len * cb->esize);
+
+ fill_unit(unit, offset, value);
+ cb->sb_data_len++;
+}
+
+static void __maybe_unused fill_db(struct cbuffer *cb,
+ uint32_t offset,
+ uint32_t value)
+{
+ struct ctxld_unit *unit = NULL;
+
+ BUG_ON(!cb);
+
+ if (unlikely(cb->db_data_len == cb->db_len)) {
+ /* db is full */
+ BUG_ON(1);
+ }
+
+ unit = (struct ctxld_unit *)(cb->db_addr + cb->db_data_len * cb->esize);
+
+ fill_unit(unit, offset, value);
+ cb->db_data_len++;
+}
+
+static void ctxld_fifo_info_print(struct ctxld_fifo *cfifo)
+{
+ pr_debug("%s: print kfifo info: **********\n", __func__);
+ pr_debug("in = 0x%x, out = 0x%x, mask = 0x%x\n",
+ cfifo->fifo.kfifo.in,
+ cfifo->fifo.kfifo.out,
+ cfifo->fifo.kfifo.mask);
+}
+
+static int ctxld_fifo_alloc(struct device *dev,
+ struct ctxld_fifo *cfifo,
+ uint32_t fifo_size)
+{
+ if (!cfifo || fifo_size < 2)
+ return -EINVAL;
+
+ fifo_size = roundup_pow_of_two(fifo_size);
+ cfifo->vaddr = dma_alloc_coherent(dev, fifo_size,
+ &cfifo->dma_handle,
+ GFP_DMA | GFP_KERNEL);
+ if (!cfifo->vaddr) {
+ dev_err(dev, "allocate ctxld fifo failed\n");
+ return -ENOMEM;
+ }
+
+ cfifo->size = fifo_size;
+ kfifo_init(&cfifo->fifo, cfifo->vaddr, fifo_size);
+
+ /* TODO: sgl num can be changed if required */
+ cfifo->sgl_num = 1;
+
+ INIT_LIST_HEAD(&cfifo->ctxld_list);
+ init_waitqueue_head(&cfifo->cqueue);
+ init_completion(&cfifo->complete);
+
+ return 0;
+}
+
+static void ctxld_fifo_free(struct device *dev,
+ struct ctxld_fifo *cfifo)
+{
+ if (!cfifo)
+ return;
+
+ /* TODO: wait fifo flush empty */
+
+ kfifo_reset(&cfifo->fifo);
+
+ dma_free_coherent(dev, cfifo->size,
+ cfifo->vaddr,
+ cfifo->dma_handle);
+
+ cfifo->size = 0;
+ cfifo->vaddr = NULL;
+ cfifo->dma_handle = 0;
+
+ memset(cfifo->sgl, 0x0, sizeof(*cfifo->sgl));
+ cfifo->sgl_num = 0;
+}
+
+static int dcss_clks_get(struct dcss_info *info)
+{
+ int ret = 0;
+ struct platform_device *pdev = info->pdev;
+
+ info->clk_axi = devm_clk_get(&pdev->dev, "axi");
+ if (IS_ERR(info->clk_axi)) {
+ ret = PTR_ERR(info->clk_axi);
+ goto out;
+ }
+
+ info->clk_apb = devm_clk_get(&pdev->dev, "apb");
+ if (IS_ERR(info->clk_apb)) {
+ ret = PTR_ERR(info->clk_apb);
+ goto put_axi;
+ }
+
+ info->clk_rtram = devm_clk_get(&pdev->dev, "rtram");
+ if (IS_ERR(info->clk_rtram)) {
+ ret = PTR_ERR(info->clk_rtram);
+ goto put_apb;
+ }
+
+ info->clk_dtrc = devm_clk_get(&pdev->dev, "dtrc");
+ if (IS_ERR(info->clk_dtrc)) {
+ ret = PTR_ERR(info->clk_dtrc);
+ goto put_rtram;
+ }
+
+ info->clk_pix = devm_clk_get(&pdev->dev, "pix");
+ if (IS_ERR(info->clk_pix)) {
+ ret = PTR_ERR(info->clk_pix);
+ goto put_dtrc;
+ }
+
+ goto out;
+
+put_dtrc:
+ devm_clk_put(&pdev->dev, info->clk_dtrc);
+put_rtram:
+ devm_clk_put(&pdev->dev, info->clk_rtram);
+put_apb:
+ devm_clk_put(&pdev->dev, info->clk_apb);
+put_axi:
+ devm_clk_put(&pdev->dev, info->clk_axi);
+out:
+ return ret;
+}
+
+#if 0
+static void dcss_clks_put(struct dcss_info *info)
+{
+ struct platform_device *pdev = info->pdev;
+
+ devm_clk_put(&pdev->dev, info->clk_axi);
+ devm_clk_put(&pdev->dev, info->clk_apb);
+ devm_clk_put(&pdev->dev, info->clk_rtram);
+ devm_clk_put(&pdev->dev, info->clk_dtrc);
+ devm_clk_put(&pdev->dev, info->clk_pix);
+}
+#endif
+
+static void fb_var_to_pixmap(struct dcss_pixmap *pixmap,
+ struct fb_var_screeninfo *var)
+{
+ BUG_ON(!pixmap || !var);
+
+ pixmap->width = var->xres;
+ pixmap->height = var->yres;
+ pixmap->bits_per_pixel = var->bits_per_pixel;
+ pixmap->pitch = var->width * (var->bits_per_pixel >> 3);
+ pixmap->crop.ulc_x = 0;
+ pixmap->crop.ulc_y = 0;
+ pixmap->crop.lrc_x = var->xres;
+ pixmap->crop.lrc_y = var->yres;
+ pixmap->format = var->grayscale;
+
+ /* TODO possible passed through 'reserved' ? */
+ pixmap->tile_type = TILE_TYPE_LINEAR;
+ pixmap->pixel_store = PIXEL_STORE_NONCOMPRESS;
+}
+
+static int fill_one_chan_info(uint32_t chan_id,
+ struct dcss_channel_info *info)
+{
+ void *dsb; /* mem to store ctxld units */
+ struct cbuffer *cb;
+ struct platform_device *pdev;
+
+ BUG_ON(!info || IS_ERR(info));
+ pdev = info->pdev;
+
+ if (chan_id > 2) {
+ dev_err(&pdev->dev, "incorrect channel number: %d\n", chan_id);
+ return -EINVAL;
+ }
+
+ info->channel_id = chan_id;
+ info->channel_en = (chan_id == 0) ? 1 : 0;
+ info->blank = FB_BLANK_NORMAL;
+ info->update_stamp = ~0x0UL;
+
+ switch (chan_id) {
+ case 0:
+ info->hdr10_in_addr = HDR_CHAN1_START;
+ info->decomp_addr = DEC400D_CHAN1_START;
+ info->dpr_addr = DPR_CHAN1_START;
+ info->scaler_addr = SCALER_CHAN1_START;
+ break;
+ case 1:
+ info->hdr10_in_addr = HDR_CHAN2_START;
+ info->decomp_addr = DTRC_CHAN2_START;
+ info->dpr_addr = DPR_CHAN2_START;
+ info->scaler_addr = SCALER_CHAN2_START;
+ break;
+ case 2:
+ info->hdr10_in_addr = HDR_CHAN3_START;
+ info->decomp_addr = DTRC_CHAN3_START;
+ info->dpr_addr = DPR_CHAN3_START;
+ info->scaler_addr = SCALER_CHAN3_START;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* dsb is used to hold double & single buffer units */
+ dsb = devm_kzalloc(&pdev->dev, DCSS_REGS_SIZE << 1, GFP_KERNEL);
+ if (!dsb)
+ return -ENOMEM;
+
+ /* init cbuffer struct */
+ cb = &info->cb;
+ cb->esize = sizeof(struct ctxld_unit);
+
+ cb->sb_addr = dsb;
+ cb->sb_len = DCSS_REGS_SIZE / cb->esize;
+ cb->sb_data_len = 0x0;
+
+ cb->db_addr = dsb + DCSS_REGS_SIZE;
+ cb->db_len = DCSS_REGS_SIZE / cb->esize;
+ cb->db_data_len = 0x0;
+
+ return 0;
+}
+
+/* allocate fb info for one channel */
+static int alloc_one_fbinfo(struct dcss_channel_info *cinfo)
+{
+ struct platform_device *pdev;
+
+ BUG_ON(!cinfo || IS_ERR(cinfo));
+
+ pdev = cinfo->pdev;
+ if (!pdev)
+ return -ENODEV;
+
+ cinfo->fb_info = framebuffer_alloc(0, &pdev->dev);
+ if (!cinfo->fb_info) {
+ dev_err(&pdev->dev, "failed to alloc fb info for channel %d\n",
+ cinfo->channel_id);
+ return -ENOMEM;
+ }
+
+ cinfo->fb_info->par = cinfo;
+ INIT_LIST_HEAD(&cinfo->fb_info->modelist);
+
+ return 0;
+}
+
+static struct fb_info *get_one_fbinfo(uint32_t ch_id,
+ struct dcss_channels *chans)
+{
+ struct dcss_channel_info *cinfo;
+
+ if (ch_id > 2)
+ return NULL;
+
+ cinfo = &chans->chan_info[ch_id];
+
+ return cinfo->fb_info;
+}
+
+static int init_chan_pixmap(struct dcss_channel_info *cinfo)
+{
+ struct dcss_pixmap *pixmap;
+ struct fb_info *fbi;
+ struct fb_var_screeninfo *var;
+
+ BUG_ON(!cinfo || IS_ERR(cinfo));
+
+ pixmap = &cinfo->input;
+ fbi = cinfo->fb_info;
+ var = &fbi->var;
+
+ fb_var_to_pixmap(pixmap, var);
+
+ return 0;
+}
+
+static int init_ch_pos(struct dcss_channel_info *cinfo)
+{
+ struct rectangle *pos;
+ struct fb_info *fbi;
+ struct fb_var_screeninfo *var;
+
+ /* TODO: init ch_pos with var temporarily */
+ pos = &cinfo->ch_pos;
+ fbi = cinfo->fb_info;
+ var = &fbi->var;
+
+ pos->ulc_x = var->left_margin + var->hsync_len - 1;
+ pos->ulc_y = var->upper_margin + var->lower_margin +
+ var->vsync_len - 1;
+ pos->lrc_x = var->xres + var->left_margin +
+ var->hsync_len - 1;
+ pos->lrc_y = var->yres + var->upper_margin +
+ var->lower_margin + var->vsync_len - 1;
+
+ return 0;
+}
+
+static int dcss_init_chans(struct dcss_info *info)
+{
+ struct dcss_channels *dcss_chans = &info->chans;
+
+ /* init sharable info between chans */
+ dcss_chans->hdr10_out_addr = HDR_OUT_START;
+ dcss_chans->subsam_addr = SUBSAM_START;
+ dcss_chans->dtg_addr = DTG_START;
+ dcss_chans->wrscl_addr = WR_SCL_START;
+ dcss_chans->rdsrc_addr = RD_SRC_START;
+ dcss_chans->ctxld_addr = CTX_LD_START;
+ dcss_chans->lutld_addr = LUT_LD_START;
+ dcss_chans->hdmi_phy_addr = 0;
+ dcss_chans->irq_steer_addr = IRQ_STEER_START;
+ dcss_chans->lpcg_addr = LPCG_START;
+ dcss_chans->blk_ctrl_addr = BLK_CTRL_START;
+
+ return 0;
+}
+
+static int dcss_init_fbinfo(struct fb_info *fbi)
+{
+ int ret;
+ uint32_t luma_size;
+ struct dcss_channel_info *cinfo = fbi->par;
+ struct dcss_info *info = cinfo->dev_data;
+ struct fb_fix_screeninfo *fix = &fbi->fix;
+ struct fb_var_screeninfo *var = &fbi->var;
+ struct fb_modelist *modelist;
+
+ fbi->fbops = &dcss_ops;
+
+ /* init fix screeninfo */
+ fix->type = FB_TYPE_PACKED_PIXELS;
+ fix->ypanstep = 1;
+ fix->ywrapstep = 1;
+ fix->visual = FB_VISUAL_TRUECOLOR;
+ fix->accel = FB_ACCEL_NONE;
+
+ ret = fb_add_videomode(info->dft_disp_mode, &fbi->modelist);
+ if (ret)
+ return ret;
+
+ /* init var screeninfo */
+ modelist = list_first_entry(&fbi->modelist,
+ struct fb_modelist, list);
+ fb_videomode_to_var(var, &modelist->mode);
+ var->nonstd = 0;
+ var->activate = FB_ACTIVATE_NOW;
+ var->vmode = FB_VMODE_NONINTERLACED;
+ /* default format */
+ if (cinfo->channel_id == DCSS_CHAN_MAIN)
+ /* main channel is for graphic */
+ var->grayscale = V4L2_PIX_FMT_ARGB32;
+ else
+ /* other channels are for video */
+ var->grayscale = V4L2_PIX_FMT_NV12;
+
+ /* Allocate memory buffer: Maybe need alignment */
+ fix->smem_len = (fix->line_length * var->yres_virtual > SZ_32M) ?
+ fix->line_length * var->yres_virtual : SZ_32M;
+ fbi->screen_base = dma_alloc_writecombine(fbi->device, fix->smem_len,
+ (dma_addr_t *)&fix->smem_start,
+ GFP_DMA | GFP_KERNEL);
+ if (!fbi->screen_base) {
+ dev_err(fbi->device, "Unable to alloc fb memory\n");
+ fix->smem_len = 0;
+ fix->smem_start = 0;
+ return -ENOMEM;
+ }
+ dev_dbg(fbi->device, "%s: smem_start = 0x%lx, screen_base = 0x%p\n",
+ __func__, fix->smem_start, fbi->screen_base);
+
+ fbi->screen_size = fix->smem_len;
+
+ fbi->pseudo_palette = devm_kzalloc(fbi->device,
+ sizeof(u32) * 16, GFP_KERNEL);
+ if (!fbi->pseudo_palette) {
+ dev_err(fbi->device, "alloc pseudo_palette failed\n");
+ dcss_free_fbmem(fbi);
+ return -ENOMEM;
+ }
+
+ /* clear screen content to black */
+ switch (var->grayscale) {
+ case V4L2_PIX_FMT_ARGB32:
+ memset((void*)fbi->screen_base, 0x0, fix->smem_len);
+ break;
+ case V4L2_PIX_FMT_NV12:
+ /* set luma: 0x0 */
+ luma_size = var->xres * var->yres;
+ memset((void*)fbi->screen_base, 0x0, luma_size);
+ /* set chroma: 0x80 */
+ memset((void*)fbi->screen_base + luma_size, 0x80, luma_size);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (dcss_check_var(var, fbi)) {
+ devm_kfree(fbi->device, fbi->pseudo_palette);
+ dcss_free_fbmem(fbi);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void dcss_free_fbmem(struct fb_info *fbi)
+{
+ struct fb_fix_screeninfo *fix = &fbi->fix;
+
+ dma_free_writecombine(fbi->device, fix->smem_len,
+ fbi->screen_base,
+ (dma_addr_t)fix->smem_start);
+
+ fbi->screen_base = NULL;
+ fix->smem_start = 0;
+ fix->smem_len = 0;
+}
+
+static int dcss_clks_enable(struct dcss_info *info)
+{
+ int ret = 0;
+ struct platform_device *pdev = info->pdev;
+
+ /* TODO: Add return value check */
+ ret = clk_prepare_enable(info->clk_axi);
+ if (ret) {
+ dev_err(&pdev->dev, "enable axi clock failed\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(info->clk_apb);
+ if (ret) {
+ dev_err(&pdev->dev, "enable apb clock failed\n");
+ goto disable_axi;
+ }
+
+ ret = clk_prepare_enable(info->clk_rtram);
+ if (ret) {
+ dev_err(&pdev->dev, "enable rtram clock failed\n");
+ goto disable_apb;
+ }
+
+ ret = clk_prepare_enable(info->clk_dtrc);
+ if (ret) {
+ dev_err(&pdev->dev, "enable dtrc clock failed\n");
+ goto disable_rtram;
+ }
+
+ ret = clk_prepare_enable(info->clk_pix);
+ if (ret) {
+ dev_err(&pdev->dev, "enable pix clock failed\n");
+ goto disable_dtrc;
+ }
+
+ goto out;
+
+disable_dtrc:
+ clk_disable_unprepare(info->clk_dtrc);
+disable_rtram:
+ clk_disable_unprepare(info->clk_rtram);
+disable_apb:
+ clk_disable_unprepare(info->clk_apb);
+disable_axi:
+ clk_disable_unprepare(info->clk_axi);
+out:
+ return ret;
+}
+
+#if 0
+static void dcss_clks_disable(struct dcss_info *info)
+{
+ clk_disable_unprepare(info->clk_axi);
+ clk_disable_unprepare(info->clk_apb);
+ clk_disable_unprepare(info->clk_rtram);
+ clk_disable_unprepare(info->clk_dtrc);
+ clk_disable_unprepare(info->clk_pix);
+}
+#endif
+
+static int dcss_clks_rate_set(struct dcss_info *info)
+{
+ int ret;
+ uint32_t pix_clk_rate;
+ struct platform_device *pdev = info->pdev;
+
+ /* TODO: axi, abp, rtrm clock rate are set by uboot already */
+ ret = clk_set_rate(info->clk_axi, DISP_AXI_RATE);
+ if (ret) {
+ dev_err(&pdev->dev, "set axi clock rate failed\n");
+ return ret;
+ }
+
+ ret = clk_set_rate(info->clk_apb, DISP_APB_RATE);
+ if (ret) {
+ dev_err(&pdev->dev, "set apb clock rate failed\n");
+ return ret;
+ }
+
+ ret = clk_set_rate(info->clk_rtram, DISP_RTRAM_RATE);
+ if (ret) {
+ dev_err(&pdev->dev, "set rtram clock rate failed\n");
+ return ret;
+ }
+
+ pix_clk_rate = PICOS2KHZ(info->dft_disp_mode->pixclock) * 1000U;
+ dev_dbg(&pdev->dev, "%s: pix clock rate = %u\n", __func__, pix_clk_rate);
+
+ ret = clk_set_rate(info->clk_pix, pix_clk_rate);
+ if (ret) {
+ dev_err(&pdev->dev, "set pixel clock rate failed, ret = %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int dcss_dec400d_config(struct dcss_info *info,
+ bool decompress, bool resolve)
+{
+ struct dcss_channel_info *chan_info;
+ struct cbuffer *cb;
+
+ if (resolve == true)
+ return -EINVAL;
+
+ /* dec400d always in channel 1 */
+ chan_info = &info->chans.chan_info[0];
+ cb = &chan_info->cb;
+
+ if (decompress == true) {
+ /* TODO: configure decompress */
+ ;
+ } else {
+ /* TODO: configure bypass */
+ ;
+ }
+
+ return 0;
+}
+
+static int dcss_dtrc_config(uint32_t dtrc_ch,
+ struct dcss_info *info,
+ bool decompress,
+ bool resolve)
+{
+ struct platform_device *pdev = info->pdev;
+ struct dcss_channel_info *chan_info;
+ struct cbuffer *cb;
+
+ if (dtrc_ch != 1 && dtrc_ch != 2) {
+ dev_err(&pdev->dev, "invalid dtrc channel number\n");
+ return -EINVAL;
+ }
+
+ chan_info = &info->chans.chan_info[dtrc_ch];
+ cb = &chan_info->cb;
+
+ if (!decompress && !resolve) {
+#if USE_CTXLD
+ /* Bypass DTRC */
+ fill_sb(cb, chan_info->decomp_addr + 0xc8, 0x2);
+#else
+ writel(0x2, info->base + chan_info->decomp_addr + 0xc8);
+#endif
+ } else {
+ /* TODO: decompress & resolve config */
+ ;
+ }
+
+ return 0;
+}
+
+static int dcss_decomp_config(uint32_t decomp_ch, struct dcss_info *info)
+{
+ bool need_decomp, need_resolve;
+ struct platform_device *pdev = info->pdev;
+ struct dcss_channel_info *chan_info;
+ struct dcss_pixmap *input;
+
+ if (decomp_ch > 2) {
+ dev_err(&pdev->dev, "invalid decompression channel number\n");
+ return -EINVAL;
+ }
+
+ chan_info = &info->chans.chan_info[decomp_ch];
+ input = &chan_info->input;
+
+ switch (input->pixel_store) {
+ case PIXEL_STORE_NONCOMPRESS:
+ need_decomp = false;
+ break;
+ case PIXEL_STORE_COMPRESS:
+ need_decomp = true;
+ break;
+ default:
+ dev_err(&pdev->dev, "invalid pixel store type\n");
+ return -EINVAL;
+ }
+
+ switch (input->tile_type) {
+ case TILE_TYPE_LINEAR:
+ case TILE_TYPE_GPU_STANDARD:
+ case TILE_TYPE_GPU_SUPER:
+ need_resolve = false;
+ break;
+ case TILE_TYPE_VPU_2PYUV420:
+ case TILE_TYPE_VPU_2PVP9:
+ need_resolve = true;
+ break;
+ default:
+ dev_err(&pdev->dev, "invalid buffer tile type\n");
+ return -EINVAL;
+ }
+
+ switch (decomp_ch) {
+ case 0: /* DEC400D */
+ dcss_dec400d_config(info, need_decomp, need_resolve);
+ break;
+ case 1: /* DTRC1 */
+ case 2: /* DTRC2 */
+ dcss_dtrc_config(decomp_ch, info,
+ need_decomp, need_resolve);
+ break;
+ default:
+ dev_err(&pdev->dev, "invalid ch num = %d\n", decomp_ch);
+ break;
+ }
+
+ return 0;
+}
+
+/* for both luma and chroma
+ */
+static int dpr_pix_x_calc(u32 pix_size,
+ u32 width,
+ u32 tile_type)
+{
+ unsigned int num_pix_x_in_64byte;
+ unsigned int pix_x_div_64byte_mod;
+ unsigned int pix_x_offset;
+
+ if (pix_size > 2)
+ return -EINVAL;
+
+ /* 1st calculation step */
+ switch (tile_type) {
+ case TILE_TYPE_LINEAR:
+ /* Divisable by 64 bytes */
+ num_pix_x_in_64byte = 64 / (1 << pix_size);
+ break;
+ /* 4x4 tile or super tile */
+ case TILE_TYPE_GPU_STANDARD:
+ case TILE_TYPE_GPU_SUPER:
+ BUG_ON(!pix_size);
+ num_pix_x_in_64byte = 64 / (4 * (1 << pix_size));
+ break;
+ /* 8bpp YUV420 8x8 tile */
+ case TILE_TYPE_VPU_2PYUV420:
+ BUG_ON(pix_size);
+ num_pix_x_in_64byte = 64 / (8 * (1 << pix_size));
+ break;
+ /* 8bpp or 10bpp VP9 4x4 tile */
+ case TILE_TYPE_VPU_2PVP9:
+ BUG_ON(pix_size == 2);
+ num_pix_x_in_64byte = 64 / (4 * (1 << pix_size));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* 2nd calculation step */
+ pix_x_div_64byte_mod = width % num_pix_x_in_64byte;
+ pix_x_offset = !pix_x_div_64byte_mod ? 0 :
+ (num_pix_x_in_64byte - pix_x_div_64byte_mod);
+
+ return width + pix_x_offset;
+}
+
+/* Divisable by 4 or 8 */
+static int dpr_pix_y_calc(u32 rtr_lines,
+ u32 height,
+ u32 tile_type)
+{
+ unsigned int num_rows_buf;
+ unsigned int pix_y_mod = 0;
+ unsigned int pix_y_offset = 0;
+
+ if (rtr_lines != 0 && rtr_lines != 1)
+ return -EINVAL;
+
+ switch (tile_type) {
+ case TILE_TYPE_LINEAR:
+ num_rows_buf = rtr_lines ? 4 : 8;
+ break;
+ /* 4x4 tile or super tile */
+ case TILE_TYPE_GPU_STANDARD:
+ case TILE_TYPE_GPU_SUPER:
+ num_rows_buf = 4;
+ break;
+ /* 8bpp YUV420 8x8 tile */
+ case TILE_TYPE_VPU_2PYUV420:
+ num_rows_buf = 8;
+ break;
+ /* 8bpp or 10bpp VP9 4x4 tile */
+ case TILE_TYPE_VPU_2PVP9:
+ num_rows_buf = 4;
+ break;
+ default:
+ return -EINVAL;
+ }
+ pix_y_mod = height % num_rows_buf;
+ pix_y_offset = !pix_y_mod ? 0 : (num_rows_buf - pix_y_mod);
+
+ return height + pix_y_offset;
+}
+
+static int dcss_dpr_config(uint32_t dpr_ch, struct dcss_info *info)
+{
+ uint32_t pitch, pix_size;
+ uint32_t num_pix_x, num_pix_y;
+ bool need_resolve = false;
+ struct platform_device *pdev = info->pdev;
+ struct dcss_channels *chans = &info->chans;
+ struct dcss_channel_info *chan_info;
+ struct fb_info *fbi;
+ struct fb_fix_screeninfo *fix;
+ struct fb_var_screeninfo *var;
+ struct dcss_pixmap *input;
+ struct cbuffer *cb;
+
+ if (dpr_ch > 2) {
+ dev_err(&pdev->dev, "invalid dpr channel number\n");
+ return -EINVAL;
+ }
+
+ chan_info = &chans->chan_info[dpr_ch];
+ fbi = chan_info->fb_info;
+ fix = &fbi->fix;
+ var = &fbi->var;
+ input = &chan_info->input;
+
+ if (dpr_ch == 0) {
+ switch (input->tile_type) {
+ case TILE_TYPE_LINEAR:
+ need_resolve = false;
+ break;
+ case TILE_TYPE_GPU_STANDARD:
+ case TILE_TYPE_GPU_SUPER:
+ need_resolve = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+ /* For channel 2,3, 'Tile Resolve' will be done by DTRC */
+
+#if !USE_CTXLD
+ writel(fix->smem_start, info->base + chan_info->dpr_addr + 0xc0);
+ writel(0x2, info->base + chan_info->dpr_addr + 0x90);
+ writel(var->xres, info->base + chan_info->dpr_addr + 0xa0);
+ writel(var->yres, info->base + chan_info->dpr_addr + 0xb0);
+
+ /* TODO: second plane config for YUV2P formats */
+ writel(fix->smem_start + var->xres * var->yres,
+ info->base + chan_info->dpr_addr + 0x110);
+ writel(var->xres, info->base + chan_info->dpr_addr + 0xf0);
+ writel(var->yres, info->base + chan_info->dpr_addr + 0x100);
+
+ /* TODO: calculate pitch for different formats */
+ pitch = (var->xres * (var->bits_per_pixel >> 3)) << 16;
+ writel(pitch, info->base + chan_info->dpr_addr + 0x70);
+
+ if (!need_resolve) {
+ /* Bypass resolve */
+ writel(0xe4203, info->base + chan_info->dpr_addr + 0x50);
+ } else {
+ /* configure resolve */
+ ;
+ }
+
+ writel(0x38, info->base + chan_info->dpr_addr + 0x200);
+ writel(0x4, info->base + chan_info->dpr_addr + 0x0);
+
+ /* Trigger DPR on */
+ writel(0x4, info->base + chan_info->dpr_addr + 0x0);
+ writel(0x5, info->base + chan_info->dpr_addr + 0x0);
+#else
+ cb = &chan_info->cb;
+
+ fill_sb(cb, chan_info->dpr_addr + 0xc0, fix->smem_start);
+ fill_sb(cb, chan_info->dpr_addr + 0x90, 0x2);
+
+ pix_size = ilog2(input->bits_per_pixel >> 3);
+
+ num_pix_x = dpr_pix_x_calc(pix_size, input->width, input->tile_type);
+ BUG_ON(num_pix_x < 0);
+ fill_sb(cb, chan_info->dpr_addr + 0xa0, num_pix_x);
+
+ switch (fmt_is_yuv(input->format)) {
+ case 0: /* RGB */
+ num_pix_y = dpr_pix_y_calc(1, input->height, input->tile_type);
+ BUG_ON(num_pix_y < 0);
+ fill_sb(cb, chan_info->dpr_addr + 0xb0, num_pix_y);
+
+ if (!need_resolve)
+ /* Bypass resolve */
+ fill_sb(cb, chan_info->dpr_addr + 0x50, 0xe4203);
+ else {
+ /* TODO: configure resolve */
+ ;
+ }
+ pitch = var->xres * (var->bits_per_pixel >> 3);
+ break;
+ case 1: /* TODO: YUV 1P */
+ return -EINVAL;
+ case 2: /* YUV 2P */
+ /* Two planes YUV format */
+ num_pix_y = dpr_pix_y_calc(0, input->height, input->tile_type);
+ BUG_ON(num_pix_y < 0);
+ fill_sb(cb, chan_info->dpr_addr + 0xb0, num_pix_y);
+
+ fill_sb(cb, chan_info->dpr_addr + 0x50, 0xc1);
+ fill_sb(cb, chan_info->dpr_addr + 0xe0, 0x2);
+
+ /* TODO: VPU always has 16bytes alignment in width */
+ pitch = ALIGN(var->xres * (var->bits_per_pixel >> 3), 16);
+ fill_sb(cb, chan_info->dpr_addr + 0x110,
+ fix->smem_start + pitch * var->yres);
+ fill_sb(cb, chan_info->dpr_addr + 0xf0, num_pix_x);
+
+ /* TODO: Require alignment handling:
+ * value must be evenly divisible by
+ * the number of rows programmed in
+ * MODE_CTRL0: RTR_4LINE_BUF_EN.
+ * UV height is 1/2 height of Luma.
+ */
+ num_pix_y = dpr_pix_y_calc(0, input->height >> 1, input->tile_type);
+ BUG_ON(num_pix_y < 0);
+ fill_sb(cb, chan_info->dpr_addr + 0x100, num_pix_y);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* TODO: calculate pitch for different formats */
+ /* config pitch */
+ fill_sb(cb, chan_info->dpr_addr + 0x70, pitch << 16);
+
+ fill_sb(cb, chan_info->dpr_addr + 0x200, 0x38);
+
+ /* Trigger DPR on */
+ fill_sb(cb, chan_info->dpr_addr + 0x0, 0x5);
+#endif
+
+ return 0;
+}
+
+static int dcss_scaler_config(uint32_t scaler_ch, struct dcss_info *info)
+{
+ struct platform_device *pdev = info->pdev;
+ struct dcss_channels *chans = &info->chans;
+ struct dcss_channel_info *chan_info;
+ struct fb_info *fbi;
+ struct fb_var_screeninfo *var;
+ struct dcss_pixmap *input;
+ struct cbuffer *cb;
+ int scale_v_luma_inc, scale_h_luma_inc;
+ uint32_t align_width, align_height;
+ const struct fb_videomode *dmode = info->dft_disp_mode;
+
+ if (scaler_ch > 2) {
+ dev_err(&pdev->dev, "invalid scaler channel number\n");
+ return -EINVAL;
+ }
+
+ chan_info = &chans->chan_info[scaler_ch];
+ fbi = chan_info->fb_info;
+ var = &fbi->var;
+ input = &chan_info->input;
+#if !USE_CTXLD
+ writel(0x0, info->base + chan_info->scaler_addr + 0x8);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xc);
+ writel(0x2, info->base + chan_info->scaler_addr + 0x10); /* src format */
+ writel(0x2, info->base + chan_info->scaler_addr + 0x14); /* dst format */
+
+ writel((var->xres - 1) | (var->yres - 1) << 16,
+ info->base + chan_info->scaler_addr + 0x18); /* src resolution */
+ writel((var->xres - 1) | (var->yres - 1) << 16,
+ info->base + chan_info->scaler_addr + 0x1c);
+ writel((var->xres - 1) | (var->yres - 1) << 16,
+ info->base + chan_info->scaler_addr + 0x20); /* dst resolution */
+ writel((var->xres - 1) | (var->yres - 1) << 16,
+ info->base + chan_info->scaler_addr + 0x24);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x28);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x2c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x30);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x34);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x38);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x3c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x40);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x44);
+
+ /* scale ratio: ###.#_####_####_#### */
+ writel(0x0, info->base + chan_info->scaler_addr + 0x48);
+ writel(0x2000, info->base + chan_info->scaler_addr + 0x4c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x50);
+ writel(0x2000, info->base + chan_info->scaler_addr + 0x54);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x58);
+ writel(0x2000, info->base + chan_info->scaler_addr + 0x5c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x60);
+ writel(0x2000, info->base + chan_info->scaler_addr + 0x64);
+
+ writel(0x0, info->base + chan_info->scaler_addr + 0x80);
+
+ writel(0x40000, info->base + chan_info->scaler_addr + 0xc0);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x100);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x84);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xc4);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x104);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x88);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xc8);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x108);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x8c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xcc);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x10c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x90);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xd0);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x110);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x94);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xd4);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x114);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x98);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xd8);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x118);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x9c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xdc);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x11c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xa0);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xe0);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x120);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xa4);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xe4);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x124);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xa8);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xe8);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x128);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xac);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xec);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x12c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xb0);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xf0);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x130);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xb4);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xf4);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x134);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xb8);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xf8);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x138);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xbc);
+ writel(0x0, info->base + chan_info->scaler_addr + 0xfc);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x13c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x140);
+
+ writel(0x40000, info->base + chan_info->scaler_addr + 0x180);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1c0);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x144);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x184);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1c4);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x148);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x188);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1c8);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x14c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x18c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1cc);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x150);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x190);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1d0);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x154);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x194);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1d4);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x158);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x198);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1d8);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x15c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x19c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1dc);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x160);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1a0);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1e0);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x164);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1a4);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1e4);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x168);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1a8);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1e8);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x16c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1ac);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1ec);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x170);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1b0);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1f0);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x174);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1b4);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1f4);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x178);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1b8);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1f8);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x17c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1bc);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x1fc);
+
+ writel(0x0, info->base + chan_info->scaler_addr + 0x300);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x340);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x380);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x304);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x344);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x384);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x308);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x348);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x388);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x30c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x34c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x38c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x310);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x350);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x390);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x314);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x354);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x394);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x318);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x358);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x398);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x31c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x35c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x39c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x320);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x360);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x3a0);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x324);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x364);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x3a4);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x328);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x368);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x3a8);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x32c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x36c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x3ac);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x330);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x370);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x3b0);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x334);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x374);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x3b4);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x338);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x378);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x3b8);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x33c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x37c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x3bc);
+
+ writel(0x0, info->base + chan_info->scaler_addr + 0x200);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x240);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x280);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x204);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x244);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x284);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x208);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x248);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x288);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x20c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x24c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x28c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x210);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x250);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x290);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x214);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x254);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x294);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x218);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x258);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x298);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x21c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x25c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x29c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x220);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x260);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x2a0);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x224);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x264);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x2a4);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x228);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x268);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x2a8);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x22c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x26c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x2ac);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x230);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x270);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x2b0);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x234);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x274);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x2b4);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x238);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x278);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x2b8);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x23c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x27c);
+ writel(0x0, info->base + chan_info->scaler_addr + 0x2bc);
+
+ /* Trigger Scaler on */
+ writel(0x11, info->base + chan_info->scaler_addr + 0x0);
+#else
+ cb = &chan_info->cb;
+
+ switch (fmt_is_yuv(input->format)) {
+ case 0: /* ARGB8888 */
+ fill_sb(cb, chan_info->scaler_addr + 0x8, 0x0);
+ /* Scaler Input Format */
+ fill_sb(cb, chan_info->scaler_addr + 0x10, 0x2);
+ break;
+ case 1: /* TODO: YUV422 or YUV444 */
+ break;
+ case 2: /* YUV420 */
+ fill_sb(cb, chan_info->scaler_addr + 0x8, 0x3);
+ /* Scaler Input Format */
+ fill_sb(cb, chan_info->scaler_addr + 0x10, 0x0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Chroma and Luma bit depth */
+ fill_sb(cb, chan_info->scaler_addr + 0xc, 0x0);
+
+ /* Scaler Output Format
+ * TODO: set dst fmt always to RGB888/YUV444
+ */
+ fill_sb(cb, chan_info->scaler_addr + 0x14, 0x2);
+
+ /* Scaler Input Luma Resolution
+ * Alighment Workaround for YUV420:
+ * 'width' divisable by 16, 'height' divisable by 8.
+ */
+
+ if (fmt_is_yuv(input->format) == 2) {
+ align_width = round_down(input->width, 16);
+ align_height = round_down(input->height, 8);
+ } else {
+ align_width = input->width;
+ align_height = input->height;
+ }
+
+ fill_sb(cb, chan_info->scaler_addr + 0x18,
+ (align_height - 1) << 16 | (align_width - 1));
+
+ /* Scaler Input Chroma Resolution */
+ switch (fmt_is_yuv(input->format)) {
+ case 0: /* ARGB8888 */
+ fill_sb(cb, chan_info->scaler_addr + 0x1c,
+ (align_height - 1) << 16 | (align_width - 1));
+ break;
+ case 1: /* TODO: YUV422 or YUV444 */
+ break;
+ case 2: /* YUV420 */
+ fill_sb(cb, chan_info->scaler_addr + 0x1c,
+ ((align_height >> 1) - 1) << 16 |
+ ((align_width >> 1) - 1));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Scaler Output Luma Resolution
+ * TODO: It should be scaled result value.
+ */
+ fill_sb(cb, chan_info->scaler_addr + 0x20,
+ (dmode->yres - 1) << 16 | (dmode->xres - 1));
+
+ /* Scaler Output Chroma Resolution
+ * TODO: It should be scaled result value.
+ */
+ fill_sb(cb, chan_info->scaler_addr + 0x24,
+ (dmode->yres - 1) << 16 | (dmode->xres - 1));
+
+ fill_sb(cb, chan_info->scaler_addr + 0x28, 0x0);
+ fill_sb(cb, chan_info->scaler_addr + 0x2c, 0x0);
+ fill_sb(cb, chan_info->scaler_addr + 0x30, 0x0);
+ fill_sb(cb, chan_info->scaler_addr + 0x34, 0x0);
+ fill_sb(cb, chan_info->scaler_addr + 0x38, 0x0);
+ fill_sb(cb, chan_info->scaler_addr + 0x3c, 0x0);
+ fill_sb(cb, chan_info->scaler_addr + 0x40, 0x0);
+ fill_sb(cb, chan_info->scaler_addr + 0x44, 0x0);
+
+ /* scale ratio: ###.#_####_####_#### */
+ /* vertical ratio */
+ scale_v_luma_inc = ((align_height << 13) + (dmode->yres >> 1)) / dmode->yres;
+ /* horizontal ratio */
+ scale_h_luma_inc = ((align_width << 13) + (dmode->xres >> 1)) / dmode->xres;
+
+ fill_sb(cb, chan_info->scaler_addr + 0x48, 0x0);
+ fill_sb(cb, chan_info->scaler_addr + 0x4c, scale_v_luma_inc);
+ fill_sb(cb, chan_info->scaler_addr + 0x50, 0x0);
+ fill_sb(cb, chan_info->scaler_addr + 0x54, scale_h_luma_inc);
+
+ switch (fmt_is_yuv(input->format)) {
+ case 0: /* ARGB8888 */
+ /* Scale Vertical Chroma Start */
+ fill_sb(cb, chan_info->scaler_addr + 0x58, 0x0);
+
+ /* Scale Vertical Chroma Increment */
+ fill_sb(cb, chan_info->scaler_addr + 0x5c, scale_v_luma_inc);
+
+ /* Scale Horizontal Chroma Increment */
+ fill_sb(cb, chan_info->scaler_addr + 0x64, scale_h_luma_inc);
+ break;
+ case 1: /* TODO: YUV422 or YUV444 */
+ break;
+ case 2: /* YUV420 */
+ /* Scale Vertical Chroma Start */
+ fill_sb(cb, chan_info->scaler_addr + 0x58, 0x01fff800);
+
+ /* Scale Vertical Chroma Increment */
+ fill_sb(cb, chan_info->scaler_addr + 0x5c, scale_v_luma_inc >> 1);
+
+ /* Scale Horizontal Chroma Increment */
+ fill_sb(cb, chan_info->scaler_addr + 0x64, scale_h_luma_inc >> 1);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Scale Horizontal Chroma Start */
+ fill_sb(cb, chan_info->scaler_addr + 0x60, 0x0);
+
+ /* Trigger SCALER on */
+ fill_sb(cb, chan_info->scaler_addr + 0x0, 0x11);
+#endif
+ return 0;
+}
+
+static int dcss_dtg_start(struct dcss_info *info)
+{
+ uint32_t dtg_lrc_x, dtg_lrc_y;
+ uint32_t dis_ulc_x, dis_ulc_y;
+ uint32_t dis_lrc_x, dis_lrc_y;
+ struct dcss_channels *chans = &info->chans;
+ struct dcss_channel_info *chan_info;
+ const struct fb_videomode *dmode;
+ struct cbuffer *cb;
+
+ chan_info = &chans->chan_info[0];
+ dmode = info->dft_disp_mode;
+ cb = &chan_info->cb;
+
+ /* Display Timing Config */
+ dtg_lrc_x = dmode->xres + dmode->left_margin +
+ dmode->right_margin + dmode->hsync_len - 1;
+ dtg_lrc_y = dmode->yres + dmode->upper_margin +
+ dmode->lower_margin + dmode->vsync_len - 1;
+ writel(dtg_lrc_y << 16 | dtg_lrc_x, info->base + chans->dtg_addr + 0x4);
+
+ /* global output timing */
+ dis_ulc_x = dmode->left_margin + dmode->hsync_len - 1;
+ dis_ulc_y = dmode->upper_margin + dmode->lower_margin +
+ dmode->vsync_len - 1;
+ writel(dis_ulc_y << 16 | dis_ulc_x, info->base + chans->dtg_addr + 0x8);
+
+ dis_lrc_x = dmode->xres + dmode->left_margin +
+ dmode->hsync_len - 1;
+ dis_lrc_y = dmode->yres + dmode->upper_margin +
+ dmode->lower_margin + dmode->vsync_len - 1;
+ writel(dis_lrc_y << 16 | dis_lrc_x, info->base + chans->dtg_addr + 0xc);
+
+ /* config db and sb loading position of ctxld */
+ writel(0xb000a, info->base + chans->dtg_addr + 0x28);
+
+ /* config background color for graph layer: black */
+ writel(0x0, info->base + chans->dtg_addr + 0x2c);
+
+ /* config background color for video layer: black */
+ writel(0x00080200, info->base + chans->dtg_addr + 0x30);
+
+ /* Trigger DTG on */
+ writel(0xff00518e, info->base + chans->dtg_addr + 0x0);
+
+ info->dcss_state = DCSS_STATE_RUNNING;
+
+ return 0;
+}
+
+static void dtg_channel_timing_config(int blank,
+ struct dcss_channel_info *cinfo)
+{
+ struct cbuffer *cb;
+ uint32_t ch_ulc_reg, ch_lrc_reg;
+ struct fb_info *fbi = cinfo->fb_info;
+ struct rectangle *pos = &cinfo->ch_pos;
+ struct platform_device *pdev = cinfo->pdev;
+ struct dcss_info *info = cinfo->dev_data;
+ struct dcss_channels *chans = &info->chans;
+
+ switch (fbi->node) {
+ case 0:
+ ch_ulc_reg = 0x10;
+ ch_lrc_reg = 0x14;
+ break;
+ case 1:
+ ch_ulc_reg = 0x18;
+ ch_lrc_reg = 0x1c;
+ break;
+ case 2:
+ ch_ulc_reg = 0x20;
+ ch_lrc_reg = 0x24;
+ break;
+ default:
+ dev_err(&pdev->dev, "%s: invalid channel number %d\n",
+ __func__, fbi->node);
+ return;
+ }
+
+ cb = &cinfo->cb;
+
+ switch (blank) {
+ case FB_BLANK_UNBLANK:
+ /* set display window for one channel */
+ fill_sb(cb, chans->dtg_addr + ch_ulc_reg,
+ pos->ulc_y << 16 | pos->ulc_x);
+ fill_sb(cb, chans->dtg_addr + ch_lrc_reg,
+ pos->lrc_y << 16 | pos->lrc_x);
+ break;
+ case FB_BLANK_NORMAL:
+ case FB_BLANK_VSYNC_SUSPEND:
+ case FB_BLANK_HSYNC_SUSPEND:
+ case FB_BLANK_POWERDOWN:
+ fill_sb(cb, chans->dtg_addr + ch_ulc_reg, 0x0);
+ fill_sb(cb, chans->dtg_addr + ch_lrc_reg, 0x0);
+ break;
+ default:
+ return;
+ }
+}
+
+static void dtg_global_timing_config(struct dcss_info *info)
+{
+ struct cbuffer *cb;
+ uint32_t dtg_lrc_x, dtg_lrc_y;
+ uint32_t dis_ulc_x, dis_ulc_y;
+ uint32_t dis_lrc_x, dis_lrc_y;
+ struct dcss_channels *chans = &info->chans;
+ struct dcss_channel_info *cmain;
+ const struct fb_videomode *dmode = info->dft_disp_mode;
+
+ /* only main channel can change dtg timings */
+ cmain = &chans->chan_info[DCSS_CHAN_MAIN];
+ cb = &cmain->cb;
+
+ /* Display Timing config */
+ dtg_lrc_x = dmode->xres + dmode->left_margin +
+ dmode->right_margin + dmode->hsync_len - 1;
+ dtg_lrc_y = dmode->yres + dmode->upper_margin +
+ dmode->lower_margin + dmode->vsync_len - 1;
+ fill_sb(cb, chans->dtg_addr + 0x4, dtg_lrc_y << 16 | dtg_lrc_x);
+
+ /* Active Region Timing config*/
+ dis_ulc_x = dmode->left_margin + dmode->hsync_len - 1;
+ dis_ulc_y = dmode->upper_margin + dmode->lower_margin +
+ dmode->vsync_len - 1;
+ fill_sb(cb, chans->dtg_addr + 0x8, dis_ulc_y << 16 | dis_ulc_x);
+
+ dis_lrc_x = dmode->xres + dmode->left_margin +
+ dmode->hsync_len - 1;
+ dis_lrc_y = dmode->yres + dmode->upper_margin +
+ dmode->lower_margin + dmode->vsync_len - 1;
+ fill_sb(cb, chans->dtg_addr + 0xc, dis_lrc_y << 16 | dis_lrc_x);
+}
+
+static int dcss_dtg_config(uint32_t ch_id, struct dcss_info *info)
+{
+ struct platform_device *pdev = info->pdev;
+ struct dcss_channels *chans = &info->chans;
+ struct dcss_channel_info *cinfo;
+
+ if (ch_id > 2) {
+ dev_err(&pdev->dev, "invalid channel id\n");
+ return -EINVAL;
+ }
+
+ cinfo = &chans->chan_info[ch_id];
+
+ if (ch_id == DCSS_CHAN_MAIN)
+ dtg_global_timing_config(info);
+
+ /* TODO: Channel Timing Config */
+ dtg_channel_timing_config(FB_BLANK_UNBLANK, cinfo);
+
+ return 0;
+}
+
+static int dcss_subsam_config(struct dcss_info *info)
+{
+ uint32_t hsync_pol, vsync_pol, de_pol;
+ uint32_t disp_lrc_x, disp_lrc_y;
+ uint32_t hsync_start, hsync_end;
+ uint32_t vsync_start, vsync_end;
+ uint32_t de_ulc_x, de_ulc_y;
+ uint32_t de_lrc_x, de_lrc_y;
+ struct dcss_channels *chans = &info->chans;
+ struct dcss_channel_info *chan_info;
+ struct cbuffer *cb;
+ const struct fb_videomode *dmode;
+
+ /* using channel 0 by default */
+ chan_info = &chans->chan_info[0];
+ cb = &chan_info->cb;
+ dmode = info->dft_disp_mode;
+
+ /* TODO: for 1080p only */
+ hsync_pol = 1;
+ vsync_pol = 1;
+ de_pol = 1;
+
+#if USE_CTXLD
+ /* 3 tap fir filters coefficients */
+ fill_sb(cb, chans->subsam_addr + 0x70, 0x41614161);
+ fill_sb(cb, chans->subsam_addr + 0x80, 0x03ff0000);
+ fill_sb(cb, chans->subsam_addr + 0x90, 0x03ff0000);
+#else
+ writel(0x41614161, info->base + chans->subsam_addr + 0x70);
+ writel(0x03ff0000, info->base + chans->subsam_addr + 0x80);
+ writel(0x03ff0000, info->base + chans->subsam_addr + 0x90);
+#endif
+
+ /* Timing Config */
+ disp_lrc_x = dmode->xres + dmode->left_margin +
+ dmode->right_margin + dmode->hsync_len - 1;
+ disp_lrc_y = dmode->yres + dmode->upper_margin +
+ dmode->lower_margin + dmode->vsync_len - 1;
+#if USE_CTXLD
+ fill_sb(cb, chans->subsam_addr + 0x10,
+ disp_lrc_y << 16 | disp_lrc_x);
+#else
+ writel(disp_lrc_y << 16 | disp_lrc_x,
+ info->base + chans->subsam_addr + 0x10);
+#endif
+
+ /* horizontal sync will be asserted when
+ * horizontal count == START
+ */
+ hsync_start = dmode->xres + dmode->left_margin +
+ dmode->right_margin + dmode->hsync_len - 1;
+ hsync_end = dmode->hsync_len - 1;
+#if USE_CTXLD
+ fill_sb(cb, chans->subsam_addr + 0x20,
+ (hsync_pol << 31) | hsync_end << 16 | hsync_start);
+#else
+ writel((hsync_pol << 31) | hsync_end << 16 | hsync_start,
+ info->base + chans->subsam_addr + 0x20);
+#endif
+
+ vsync_start = dmode->lower_margin - 1;
+ vsync_end = dmode->lower_margin + dmode->vsync_len - 1;
+#if USE_CTXLD
+ fill_sb(cb, chans->subsam_addr + 0x30,
+ (vsync_pol << 31) | vsync_end << 16 | vsync_start);
+#else
+ writel((vsync_pol << 31) | vsync_end << 16 | vsync_start,
+ info->base + chans->subsam_addr + 0x30);
+#endif
+
+ de_ulc_x = dmode->left_margin + dmode->hsync_len - 1;
+ de_ulc_y = dmode->upper_margin + dmode->lower_margin +
+ dmode->vsync_len;
+#if USE_CTXLD
+ fill_sb(cb, chans->subsam_addr + 0x40,
+ (de_pol << 31) | de_ulc_y << 16 | de_ulc_x);
+#else
+ writel((de_pol << 31) | de_ulc_y << 16 | de_ulc_x,
+ info->base + chans->subsam_addr + 0x40);
+#endif
+
+ de_lrc_x = dmode->xres + dmode->left_margin +
+ dmode->hsync_len - 1;
+ de_lrc_y = dmode->yres + dmode->upper_margin +
+ dmode->lower_margin + dmode->vsync_len - 1;
+#if USE_CTXLD
+ fill_sb(cb, chans->subsam_addr + 0x50,
+ de_lrc_y << 16 | de_lrc_x);
+
+ /* Trigger Subsam on */
+ fill_sb(cb, chans->subsam_addr + 0x0, 0x1);
+#else
+ writel(de_lrc_y << 16 | de_lrc_x,
+ info->base + chans->subsam_addr + 0x50);
+ writel(0x1, info->base + chans->subsam_addr + 0x0);
+#endif
+
+ return 0;
+}
+
+static void ctxld_irq_unmask(uint32_t irq_en, struct dcss_info *info)
+{
+ struct dcss_channels *chans = &info->chans;
+
+ writel(irq_en, info->base + chans->ctxld_addr + CTXLD_CTRL_STATUS_SET);
+}
+
+static void __maybe_unused dtg_irq_mask(unsigned long hwirq,
+ struct dcss_info *info)
+{
+ unsigned long irq_mask = 0;
+ struct dcss_channels *chans = &info->chans;
+
+ irq_mask = readl(info->base + chans->dtg_addr + TC_INTERRUPT_MASK);
+ writel(~(1 << (hwirq - 8)) & irq_mask,
+ info->base + chans->dtg_addr + TC_INTERRUPT_MASK);
+}
+
+static void dtg_irq_unmask(unsigned long hwirq,
+ struct dcss_info *info)
+{
+ unsigned long irq_mask = 0;
+ struct dcss_channels *chans = &info->chans;
+
+ irq_mask = readl(info->base + chans->dtg_addr + TC_INTERRUPT_MASK);
+
+ writel(1 << (hwirq - 8) | irq_mask,
+ info->base + chans->dtg_addr + TC_INTERRUPT_MASK);
+}
+
+static void dtg_irq_clear(unsigned long hwirq,
+ struct dcss_info *info)
+{
+ unsigned long irq_status = 0;
+ struct dcss_channels *chans = &info->chans;
+
+ irq_status = readl(info->base + chans->dtg_addr + TC_INTERRUPT_STATUS);
+ BUG_ON(!(irq_status & 1 << (hwirq - 8)));
+
+ /* write 1 to clear irq */
+ writel(1 << (hwirq - 8),
+ info->base + chans->dtg_addr + TC_INTERRUPT_CONTROL);
+}
+
+static void dcss_ctxld_config(struct work_struct *work)
+{
+ int ret;
+ uint32_t dsb_len, nsgl, esize, offset;
+ struct dcss_info *info;
+ struct platform_device *pdev;
+ struct dcss_channels *chans;
+ struct ctxld_commit *cc;
+ struct ctxld_fifo *cfifo;
+
+ cc = container_of(work, struct ctxld_commit, work);
+ info = (struct dcss_info *)cc->data;
+ pdev = info->pdev;
+ chans = &info->chans;
+ cfifo = &info->cfifo;
+ dsb_len = cc->sb_data_len + cc->db_data_len;
+ esize = kfifo_esize(&cfifo->fifo);
+
+ /* NOOP cc */
+ if (!cc->sb_data_len && !cc->db_data_len)
+ goto free_cc;
+
+ sg_init_table(cfifo->sgl, cfifo->sgl_num);
+ nsgl = kfifo_dma_out_prepare(&cfifo->fifo, cfifo->sgl,
+ cfifo->sgl_num, dsb_len);
+ BUG_ON(!nsgl);
+
+ if (nsgl == 1) {
+ if (cfifo->sgl[0].length != dsb_len * esize)
+ BUG_ON(1);
+ }
+
+ offset = cfifo->fifo.kfifo.out & cfifo->fifo.kfifo.mask;
+
+ /* configure sb buffer */
+ if (cc->sb_data_len) {
+ /* cfifo first store sb and than store db */
+ writel(cfifo->dma_handle + offset * esize,
+ info->base + chans->ctxld_addr + CTXLD_SB_BASE_ADDR);
+ writel(cc->sb_hp_data_len |
+ ((cc->sb_data_len - cc->sb_hp_data_len) << 16),
+ info->base + chans->ctxld_addr + CTXLD_SB_COUNT);
+ }
+
+ /* configure db buffer */
+ if (cc->db_data_len) {
+ writel(cfifo->dma_handle + (offset + cc->sb_data_len) * esize,
+ info->base + chans->ctxld_addr + CTXLD_DB_BASE_ADDR);
+ writel(cc->db_data_len,
+ info->base + chans->ctxld_addr + CTXLD_DB_COUNT);
+ }
+
+ /* enable ctx_ld */
+ writel(0x1, info->base + chans->ctxld_addr + CTXLD_CTRL_STATUS_SET);
+
+ /* wait finish */
+ reinit_completion(&cfifo->complete);
+ ret = wait_for_completion_timeout(&cfifo->complete, HZ);
+ if (!ret) /* timeout */
+ dev_err(&pdev->dev, "wait ctxld finish timeout\n");
+
+ ctxld_fifo_info_print(cfifo);
+ kfifo_dma_out_finish(&cfifo->fifo,
+ (cc->sb_data_len + cc->db_data_len) * esize);
+ ctxld_fifo_info_print(cfifo);
+
+free_cc:
+ kfree(cc);
+
+ dev_dbg(&pdev->dev, "finish ctxld config\n");
+}
+
+static void copy_data_to_cfifo(struct ctxld_fifo *cfifo,
+ struct cbuffer *cb,
+ struct ctxld_commit *cc)
+{
+ struct ctxld_unit *unit;
+ uint32_t count;
+
+ unit = (struct ctxld_unit *)cb->sb_addr;
+
+ if (cb->sb_data_len) {
+ count = kfifo_in(&cfifo->fifo, cb->sb_addr, cb->sb_data_len);
+ if (count != cb->sb_data_len) {
+ /* TODO: this case should be completely ignored */
+ pr_err("write sb data mismatch\n");
+ count = kfifo_out(&cfifo->fifo, cb->sb_addr, count);
+ WARN_ON(1);
+ }
+ cc->sb_hp_data_len += count;
+ cc->sb_data_len += count;
+ }
+
+ if (cb->db_data_len) {
+ count = kfifo_in(&cfifo->fifo, cb->db_addr, cb->db_data_len);
+ if (count != cb->db_data_len) {
+ /* TODO: this case should be completely ignored */
+ pr_err("write db data mismatch\n");
+ count = kfifo_out(&cfifo->fifo, cb->db_addr, count);
+ WARN_ON(1);
+ }
+ cc->db_data_len += count;
+ }
+}
+
+static struct ctxld_commit *alloc_cc(struct dcss_info *info)
+{
+ struct ctxld_commit *cc;
+
+ cc = kzalloc(sizeof(*cc), GFP_KERNEL);
+ if (!cc)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&cc->list);
+ INIT_WORK(&cc->work, dcss_ctxld_config);
+ kref_init(&cc->refcount);
+ cc->data = info;
+
+ return cc;
+}
+
+static struct ctxld_commit *obtain_cc(int ch_id, struct dcss_info *info)
+{
+ int ret;
+ unsigned long irqflags;
+ struct dcss_channel_info *cinfo;
+ struct ctxld_commit *cc = NULL;
+ struct platform_device *pdev = info->pdev;
+ struct dcss_channels *chans = &info->chans;
+ struct ctxld_fifo *cfifo = &info->cfifo;
+ struct vsync_info *vinfo = &info->vinfo;
+
+ cinfo = &chans->chan_info[ch_id];
+
+ /* wait for next frame window */
+ ret = wait_event_interruptible_timeout(vinfo->vwait,
+ vcount_compare(cinfo->update_stamp, vinfo),
+ HZ);
+ if (!ret) {
+ dev_err(&pdev->dev, "wait next frame timeout\n");
+ return ERR_PTR(-EBUSY);
+ }
+
+ spin_lock_irqsave(&vinfo->vwait.lock, irqflags);
+
+ cinfo->update_stamp = vinfo->vcount;
+ if (!list_empty(&cfifo->ctxld_list)) {
+ cc = list_first_entry(&cfifo->ctxld_list,
+ struct ctxld_commit,
+ list);
+ kref_get(&cc->refcount);
+ }
+
+ spin_unlock_irqrestore(&vinfo->vwait.lock, irqflags);
+
+ if (!cc) {
+ cc = alloc_cc(info);
+ if (IS_ERR(cc))
+ return cc;
+
+ spin_lock_irqsave(&vinfo->vwait.lock, irqflags);
+
+ if (list_empty(&cfifo->ctxld_list))
+ list_add_tail(&cfifo->ctxld_list, &cc->list);
+ else {
+ kfree(cc);
+ cc = list_first_entry(&cfifo->ctxld_list,
+ struct ctxld_commit,
+ list);
+ }
+ kref_get(&cc->refcount);
+
+ spin_unlock_irqrestore(&vinfo->vwait.lock, irqflags);
+ }
+
+ return cc;
+}
+
+static void release_cc(struct kref *kref)
+{
+ unsigned long irqflags;
+ struct ctxld_commit *cc;
+ struct dcss_info *info;
+ struct vsync_info *vinfo;
+ struct ctxld_fifo *cfifo;
+
+ cc = container_of(kref, struct ctxld_commit, refcount);
+ info = (struct dcss_info *)cc->data;
+ vinfo = &info->vinfo;
+ cfifo = &info->cfifo;
+
+ spin_lock_irqsave(&vinfo->vwait.lock, irqflags);
+
+ list_del(&cc->list);
+ flush_cfifo(cfifo, &cc->work);
+
+ spin_unlock_irqrestore(&vinfo->vwait.lock, irqflags);
+}
+
+/**
+ * Only be called when 'vwait.lock' is hold
+ */
+static void release_cc_locked(struct kref *kref)
+{
+ struct ctxld_commit *cc;
+ struct dcss_info *info;
+ struct ctxld_fifo *cfifo;
+
+ cc = container_of(kref, struct ctxld_commit, refcount);
+ info = (struct dcss_info *)cc->data;
+ cfifo = &info->cfifo;
+
+ list_del(&cc->list);
+ flush_cfifo(cfifo, &cc->work);
+}
+
+static void flush_cfifo(struct ctxld_fifo *cfifo,
+ struct work_struct *work)
+{
+ int ret;
+
+ ret = queue_work(cfifo->ctxld_wq, work);
+
+ WARN(!ret, "work has already been queued\n");
+}
+
+static int defer_flush_cfifo(struct ctxld_fifo *cfifo)
+{
+ int i;
+ struct dcss_info *info;
+ struct dcss_channels *chans;
+ struct dcss_channel_info *cinfo;
+
+ info = container_of(cfifo, struct dcss_info, cfifo);
+ chans = &info->chans;
+
+ for (i = 0; i < 3; i++) {
+ cinfo = &chans->chan_info[i];
+ cinfo->update_stamp = info->vinfo.vcount;
+ }
+
+ return 0;
+}
+
+static int finish_cfifo(struct ctxld_fifo *cfifo)
+{
+ int ret;
+ struct dcss_info *info;
+
+ info = container_of(cfifo, struct dcss_info, cfifo);
+
+ ret = dcss_wait_for_vsync(0, info);
+ if (ret)
+ return ret;
+
+ flush_workqueue(cfifo->ctxld_wq);
+
+ return 0;
+}
+
+static int commit_cfifo(uint32_t channel,
+ struct dcss_info *info,
+ struct ctxld_commit *cc)
+{
+ uint32_t commit_size;
+ struct dcss_channels *chans;
+ struct dcss_channel_info *chan_info;
+ struct ctxld_fifo *cfifo;
+ struct cbuffer *cb;
+
+ cfifo = &info->cfifo;
+ chans = &info->chans;
+ chan_info = &chans->chan_info[channel];
+ cb = &chan_info->cb;
+ commit_size = cb->sb_data_len + cb->db_data_len;
+
+ spin_lock(&cfifo->cqueue.lock);
+
+ if (unlikely(atomic_read(&info->flush) == 1)) {
+ /* cancel this commit and restart it later */
+ kref_put(&cc->refcount, release_cc);
+
+ wait_event_interruptible_exclusive_locked(cfifo->cqueue,
+ atomic_read(&info->flush));
+
+ spin_unlock(&cfifo->cqueue.lock);
+ return -ERESTARTSYS;
+ } else {
+ if (unlikely(waitqueue_active(&cfifo->cqueue)))
+ wake_up_locked(&cfifo->cqueue);
+ }
+
+ if (unlikely(commit_size > kfifo_to_end_len(&cfifo->fifo))) {
+ atomic_set(&info->flush, 1);
+ spin_unlock(&cfifo->cqueue.lock);
+
+ /* cancel this commit and restart it later */
+ kref_put(&cc->refcount, release_cc);
+
+ /* Wait fifo flush empty to avoid fifo wrap */
+ finish_cfifo(cfifo);
+
+ spin_lock(&cfifo->cqueue.lock);
+
+ atomic_set(&info->flush, 0);
+ kfifo_reset(&cfifo->fifo);
+ if (waitqueue_active(&cfifo->cqueue))
+ wake_up_locked(&cfifo->cqueue);
+
+ spin_unlock(&cfifo->cqueue.lock);
+
+ return -ERESTART;
+ }
+
+ copy_data_to_cfifo(cfifo, cb, cc);
+
+ ctxld_fifo_info_print(cfifo);
+
+ /* empty sb and db buffer */
+ cb->db_data_len = 0;
+ cb->sb_data_len = 0;
+
+ spin_unlock(&cfifo->cqueue.lock);
+
+ return 0;
+}
+
+static int dcss_open(struct fb_info *fbi, int user)
+{
+ int fb_node = fbi->node;
+ struct dcss_channel_info *cinfo = fbi->par;
+ struct dcss_info *info = cinfo->dev_data;
+ struct dcss_channels *chans = &info->chans;
+ struct dcss_channel_info *chan_info;
+ struct cbuffer *cb;
+
+ if (fb_node < 0 || fb_node > 2)
+ BUG_ON(1);
+
+ chan_info = &chans->chan_info[fb_node];
+ cb = &chan_info->cb;
+
+ if (fb_node == 0)
+ return 0;
+
+ return 0;
+}
+
+static int dcss_check_var(struct fb_var_screeninfo *var,
+ struct fb_info *fbi)
+{
+ uint32_t fb_size;
+ uint32_t scale_ratio_mode_x, scale_ratio_mode_y;
+ uint32_t scale_ratio_x, scale_ratio_y;
+ struct dcss_channel_info *cinfo = fbi->par;
+ struct dcss_info *info = cinfo->dev_data;
+ struct platform_device *pdev = info->pdev;
+ const struct fb_bitfield *rgb = NULL;
+ const struct pix_fmt_info *format = NULL;
+ struct fb_fix_screeninfo *fix = &fbi->fix;
+ const struct fb_videomode *dmode = info->dft_disp_mode;
+
+ if (var->xres > MAX_WIDTH || var->yres > MAX_HEIGHT) {
+ dev_err(&pdev->dev, "unsupport display resolution\n");
+ return -EINVAL;
+ }
+
+ if (var->xres_virtual > var->xres) {
+ dev_err(&pdev->dev, "stride not supported\n");
+ return -EINVAL;
+ }
+
+ if (var->xres_virtual < var->xres)
+ var->xres_virtual = var->xres;
+ if (var->yres_virtual < var->yres)
+ var->yres_virtual = var->yres;
+
+ switch (var->grayscale) {
+ case 0: /* TODO: color */
+ case 1: /* grayscale */
+ return -EINVAL;
+ default: /* fourcc */
+ format = get_fmt_info(var->grayscale);
+ if (!format) {
+ dev_err(&pdev->dev, "unsupport pixel format\n");
+ return -EINVAL;
+ }
+ var->bits_per_pixel = format->bpp;
+ }
+
+ /* Add alignment check for scaler */
+ switch (fmt_is_yuv(var->grayscale)) {
+ case 0: /* ARGB8888 */
+ case 2: /* YUV420 */
+ if (ALIGN(var->xres, 4) != var->xres ||
+ ALIGN(var->yres, 4) != var->yres) {
+ dev_err(&pdev->dev, "width or height is not aligned\n");
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Add scale ratio check:
+ * Maximum scale down ratio is 1/7;
+ * Maximum scale up ratio is 8;
+ */
+ if (dmode->xres > var->xres) {
+ /* upscaling */
+ scale_ratio_mode_x = dmode->xres % var->xres;
+ scale_ratio_mode_y = dmode->yres % var->yres;
+ scale_ratio_x = (dmode->xres - scale_ratio_mode_x) / var->xres;
+ scale_ratio_y = (dmode->yres - scale_ratio_mode_y) / var->yres;
+ if (scale_ratio_x >= 8) {
+ if ((scale_ratio_x == 8 && scale_ratio_mode_x > 0) ||
+ (scale_ratio_x > 8)) {
+ dev_err(&pdev->dev, "unsupport scaling ration for width\n");
+ return -EINVAL;
+ }
+ }
+
+ if (scale_ratio_y >= 8) {
+ if ((scale_ratio_y == 8 && scale_ratio_mode_y > 0) ||
+ (scale_ratio_y > 8)) {
+ dev_err(&pdev->dev, "unsupport scaling ration for height\n");
+ return -EINVAL;
+ }
+ }
+ } else {
+ /* downscaling */
+ scale_ratio_mode_x = var->xres % dmode->xres;
+ scale_ratio_mode_y = var->yres % dmode->yres;
+ scale_ratio_x = (var->xres - scale_ratio_mode_x) / dmode->xres;
+ scale_ratio_y = (var->yres - scale_ratio_mode_y) / dmode->yres;
+ if (scale_ratio_x >= 7) {
+ if ((scale_ratio_x == 7 && scale_ratio_mode_x > 0) ||
+ (scale_ratio_x > 7)) {
+ dev_err(&pdev->dev, "unsupport scaling ration for width\n");
+ return -EINVAL;
+ }
+ }
+
+ if (scale_ratio_y >= 7) {
+ if ((scale_ratio_y == 7 && scale_ratio_mode_y > 0) ||
+ (scale_ratio_y > 7)) {
+ dev_err(&pdev->dev, "unsupport scaling ration for height\n");
+ return -EINVAL;
+ }
+ }
+ }
+
+ fix->line_length = var->xres * (var->bits_per_pixel >> 3);
+ fb_size = var->yres_virtual * fix->line_length;
+
+ if (fb_size > fix->smem_len) {
+ dev_err(&pdev->dev, "exceeds fb size limit!\n");
+ return -ENOMEM;
+ }
+
+ if (format && !format->is_yuv) {
+ switch (format->fourcc) {
+ case V4L2_PIX_FMT_ARGB32:
+ rgb = def_a8r8g8b8;
+ break;
+ case V4L2_PIX_FMT_A2R10G10B10:
+ rgb = def_a2r10g10b10;
+ break;
+ default:
+ dev_err(&pdev->dev, "unsupport pixel format\n");
+ return -EINVAL;
+ }
+
+ var->red = rgb[RED];
+ var->green = rgb[GREEN];
+ var->blue = rgb[BLUE];
+ var->transp = rgb[TRANSP];
+ } else {
+ /* TODO: YUV format */
+ ;
+ }
+
+ return 0;
+}
+
+static int config_channel_pipe(struct dcss_channel_info *cinfo)
+{
+ int ret = 0;
+ int fb_node;
+ struct fb_info *fbi = cinfo->fb_info;
+ struct dcss_info *info = cinfo->dev_data;
+ struct platform_device *pdev = info->pdev;
+
+ fb_node = fbi->node;
+
+ dev_dbg(&cinfo->pdev->dev, "begin config pipe %d\n", fb_node);
+
+ /* configure all the sub modules on one channel:
+ * 1. DEC400D/DTRC
+ * 2. DPR
+ * 3. SCALER
+ * 4. HDR10_INPUT
+ */
+ ret = dcss_decomp_config(fb_node, info);
+ if (ret) {
+ dev_err(&pdev->dev, "decomp config failed\n");
+ goto out;
+ }
+
+ ret = dcss_dpr_config(fb_node, info);
+ if (ret) {
+ dev_err(&pdev->dev, "dpr config failed\n");
+ goto out;
+ }
+
+ ret = dcss_scaler_config(fb_node, info);
+ if (ret) {
+ dev_err(&pdev->dev, "scaler config failed\n");
+ goto out;
+ }
+
+out:
+ return ret;
+}
+
+static int dcss_set_par(struct fb_info *fbi)
+{
+ int ret = 0;
+ int fb_node = fbi->node;
+ struct dcss_channel_info *cinfo = fbi->par;
+ struct dcss_info *info = cinfo->dev_data;
+ struct cbuffer *cb = &cinfo->cb;
+ struct ctxld_commit *cc;
+
+ if (fb_node < 0 || fb_node > 2)
+ BUG_ON(1);
+
+ /* TODO: add save/recovery when config failed */
+ fb_var_to_pixmap(&cinfo->input, &fbi->var);
+
+ ret = config_channel_pipe(cinfo);
+ if (ret)
+ goto fail;
+
+ ret = dcss_dtg_config(fb_node, info);
+ if (ret)
+ goto fail;
+
+#if USE_CTXLD
+restart:
+ cc = obtain_cc(fb_node, info);
+ if (IS_ERR(cc)) {
+ ret = PTR_ERR(cc);
+ goto fail;
+ }
+
+ ret = commit_cfifo(fb_node, info, cc);
+ if (ret == -ERESTART)
+ goto restart;
+
+ kref_put(&cc->refcount, release_cc);
+#endif
+
+ goto out;
+
+fail:
+ /* drop any ctxld_uint already
+ * been written to sb or db
+ */
+ cb->sb_data_len = 0;
+ cb->db_data_len = 0;
+out:
+ return ret;
+}
+
+static int dcss_setcolreg(unsigned regno, unsigned red, unsigned green,
+ unsigned blue, unsigned transp, struct fb_info *info)
+{
+ return 0;
+}
+
+static int dcss_channel_blank(int blank,
+ struct dcss_channel_info *cinfo)
+{
+ uint32_t dtg_ctrl;
+ struct dcss_info *info = cinfo->dev_data;
+ struct dcss_channels *chans = &info->chans;
+ struct cbuffer *cb = &cinfo->cb;
+
+ dtg_ctrl = readl(info->base + chans->dtg_addr + 0x0);
+
+ switch (blank) {
+ case FB_BLANK_UNBLANK:
+ /* set global alpha */
+ if (cinfo->channel_id == DCSS_CHAN_MAIN)
+ dtg_ctrl |= (0xff << 24);
+ else
+ dtg_ctrl &= ~(0xff << 24);
+ break;
+ case FB_BLANK_NORMAL:
+ case FB_BLANK_VSYNC_SUSPEND:
+ case FB_BLANK_HSYNC_SUSPEND:
+ case FB_BLANK_POWERDOWN:
+ /* clear global alpha */
+ if (cinfo->channel_id == DCSS_CHAN_MAIN)
+ dtg_ctrl &= ~(0xff << 24);
+ else
+ dtg_ctrl |= (0xff << 24);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ fill_sb(cb, chans->dtg_addr + 0x0, dtg_ctrl);
+
+ return 0;
+}
+
+static int dcss_blank(int blank, struct fb_info *fbi)
+{
+ int ret = 0;
+ int fb_node = fbi->node;
+ struct dcss_channel_info *cinfo = fbi->par;
+ struct dcss_info *info = cinfo->dev_data;
+ struct cbuffer *cb;
+ struct ctxld_commit *cc;
+
+ cb = &cinfo->cb;
+
+ dtg_channel_timing_config(blank, cinfo);
+ dcss_channel_blank(blank, cinfo);
+
+#if USE_CTXLD
+restart:
+ cc = obtain_cc(fb_node, info);
+ if (IS_ERR(cc)) {
+ ret = PTR_ERR(cc);
+ goto fail;
+ }
+
+ ret = commit_cfifo(fb_node, info, cc);
+ if (ret == -ERESTART)
+ goto restart;
+
+ kref_put(&cc->refcount, release_cc);
+#endif
+
+ cinfo->blank = blank;
+
+ goto out;
+
+fail:
+ /* drop any ctxld_uint already
+ * been written to sb or db
+ */
+ cb->sb_data_len = 0;
+ cb->db_data_len = 0;
+
+out:
+ return ret;
+}
+
+static int dcss_pan_display(struct fb_var_screeninfo *var,
+ struct fb_info *fbi)
+{
+ int ret = 0;
+ int fb_node = fbi->node;
+ uint32_t offset, pitch, luma_addr, chroma_addr = 0;
+ struct dcss_channel_info *cinfo = fbi->par;
+ struct dcss_info *info = cinfo->dev_data;
+ struct platform_device *pdev = info->pdev;
+ struct cbuffer *cb = &cinfo->cb;
+ struct dcss_pixmap *input = &cinfo->input;
+ struct ctxld_commit *cc;
+
+ /* TODO: change framebuffer memory start address */
+ luma_addr = var->reserved[0] ? var->reserved[0] :
+ fbi->fix.smem_start;
+
+ /* change display offset in framebuffer */
+ if (var->xoffset > 0) {
+ dev_dbg(&pdev->dev, "x panning not supported\n");
+ return -EINVAL;
+ }
+
+ if ((var->yoffset + var->yres > var->yres_virtual)) {
+ dev_err(&pdev->dev, "y panning exceeds\n");
+ return -EINVAL;
+ }
+
+ offset = fbi->fix.line_length * var->yoffset;
+
+ fill_sb(cb, cinfo->dpr_addr + 0xc0, luma_addr + offset);
+
+ /* Two planes YUV format */
+ if (fmt_is_yuv(input->format) == 2) {
+ pitch = ALIGN(var->xres * (var->bits_per_pixel >> 3), 16);
+ chroma_addr = luma_addr + pitch * var->yres;
+ fill_sb(cb,
+ cinfo->dpr_addr + 0x110,
+ chroma_addr + (offset >> 1));
+ }
+
+#if USE_CTXLD
+restart:
+ cc = obtain_cc(fb_node, info);
+ if (IS_ERR(cc)) {
+ ret = PTR_ERR(cc);
+ goto fail;
+ }
+
+ ret = commit_cfifo(fb_node, info, cc);
+ if (ret == -ERESTART)
+ goto restart;
+
+ kref_put(&cc->refcount, release_cc);
+#endif
+
+ goto out;
+
+fail:
+ /* drop any ctxld_uint already
+ * been written to sb or db
+ */
+ cb->sb_data_len = 0;
+ cb->db_data_len = 0;
+
+out:
+ return ret;
+}
+
+static int vcount_compare(unsigned long vcount,
+ struct vsync_info *vinfo)
+{
+ int ret = 0;
+ unsigned long irqflags;
+
+ spin_lock_irqsave(&vinfo->vwait.lock, irqflags);
+
+ ret = (vcount != vinfo->vcount) ? 1 : 0;
+
+ spin_unlock_irqrestore(&vinfo->vwait.lock, irqflags);
+
+ return ret;
+}
+
+static int dcss_wait_for_vsync(unsigned long crtc,
+ struct dcss_info *info)
+{
+ int ret = 0;
+ unsigned long irqflags, vcount;
+ struct platform_device *pdev = info->pdev;
+
+ spin_lock_irqsave(&info->vinfo.vwait.lock, irqflags);
+ vcount = info->vinfo.vcount;
+ spin_unlock_irqrestore(&info->vinfo.vwait.lock, irqflags);
+
+ ret = wait_event_interruptible_timeout(info->vinfo.vwait,
+ vcount_compare(vcount, &info->vinfo),
+ HZ);
+ if (!ret) {
+ dev_err(&pdev->dev, "wait vsync active timeout\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int dcss_ioctl(struct fb_info *fbi, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret = 0;
+ unsigned long crtc;
+ void __user *argp = (void __user *)arg;
+ struct dcss_channel_info *cinfo = fbi->par;
+ struct dcss_info *info = cinfo->dev_data;
+ struct platform_device *pdev = cinfo->pdev;
+
+ switch (cmd) {
+ case FBIO_WAITFORVSYNC:
+ if (copy_from_user(&crtc, argp, sizeof(unsigned long)))
+ return -EFAULT;
+
+ ret = dcss_wait_for_vsync(crtc, info);
+ break;
+ default:
+ dev_err(&pdev->dev, "invalid ioctl command: 0x%x\n", cmd);
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static void ctxld_irq_clear(struct dcss_info *info)
+{
+ uint32_t irq_status;
+ struct dcss_channels *chans = &info->chans;
+ struct platform_device *pdev = info->pdev;
+
+ irq_status = readl(info->base + chans->ctxld_addr + CTXLD_CTRL_STATUS);
+ dev_dbg(&pdev->dev, "ctxld irq_status before = 0x%x\n", irq_status);
+
+ if (irq_status & RD_ERR)
+ writel(RD_ERR, info->base + chans->ctxld_addr + CTXLD_CTRL_STATUS_CLR);
+
+ if (irq_status & DB_COMP)
+ writel(DB_COMP, info->base + chans->ctxld_addr + CTXLD_CTRL_STATUS_CLR);
+
+ if (irq_status & SB_HP_COMP)
+ writel(SB_HP_COMP,
+ info->base + chans->ctxld_addr + CTXLD_CTRL_STATUS_CLR);
+
+ if (irq_status & SB_LP_COMP)
+ writel(SB_LP_COMP,
+ info->base + chans->ctxld_addr + CTXLD_CTRL_STATUS_CLR);
+
+ if (irq_status & AHB_ERR)
+ writel(AHB_ERR,
+ info->base + chans->ctxld_addr + CTXLD_CTRL_STATUS_CLR);
+
+ irq_status = readl(info->base + chans->ctxld_addr + CTXLD_CTRL_STATUS);
+}
+
+static irqreturn_t dcss_irq_handler(int irq, void *dev_id)
+{
+ int ret;
+ struct irq_desc *desc;
+ uint32_t irq_status;
+ unsigned long irqflags;
+ struct dcss_info *info = (struct dcss_info *)dev_id;
+ struct dcss_channels *chans = &info->chans;
+ struct dcss_channel_info *chan;
+ struct ctxld_fifo *cfifo;
+ struct ctxld_commit *cc;
+
+ cfifo = &info->cfifo;
+ desc = irq_to_desc(irq);
+
+ switch (desc->irq_data.hwirq) {
+ case IRQ_DPR_CH1:
+ chan = &chans->chan_info[0];
+ irq_status = readl(info->base + chan->dpr_addr + 0x40);
+ writel(irq_status, info->base + chan->dpr_addr + 0x40);
+ break;
+ case IRQ_DPR_CH2:
+ break;
+ case IRQ_DPR_CH3:
+ break;
+ case IRQ_CTX_LD:
+ ctxld_irq_clear(info);
+ complete(&cfifo->complete);
+ break;
+ case IRQ_TC_LINE1:
+ dtg_irq_clear(IRQ_TC_LINE1, info);
+
+ spin_lock_irqsave(&info->vinfo.vwait.lock, irqflags);
+
+ /* unblock new commits */
+ info->vinfo.vcount++;
+
+ if (!list_empty(&cfifo->ctxld_list)) {
+ cc = list_first_entry(&cfifo->ctxld_list,
+ struct ctxld_commit,
+ list);
+
+ ret = kref_put(&cc->refcount, release_cc_locked);
+
+ /* 'cc' can not be released */
+ if (!ret)
+ defer_flush_cfifo(cfifo);
+ }
+
+ spin_unlock_irqrestore(&info->vinfo.vwait.lock, irqflags);
+
+ wake_up_all(&info->vinfo.vwait);
+ break;
+ case IRQ_DEC400D_CH1:
+ case IRQ_DTRC_CH2:
+ case IRQ_DTRC_CH3:
+ break;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int dcss_interrupts_init(struct dcss_info *info)
+{
+ int i, ret = 0;
+ struct irq_desc *desc;
+ struct dcss_channels *chans = &info->chans;
+ struct platform_device *pdev = info->pdev;
+
+ for (i = 0; i < DCSS_IRQS_NUM; i++) {
+ info->irqs[i] = platform_get_irq(pdev, i);
+ if (info->irqs[i] < 0)
+ break;
+
+ desc = irq_to_desc(info->irqs[i]);
+ switch (desc->irq_data.hwirq) {
+ case 6: /* CTX_LD */
+ ctxld_irq_unmask(SB_HP_COMP_EN, info);
+ break;
+ case 8: /* dtg_programmable_1: for vsync */
+ /* TODO: (0, 0) or (last, last)? */
+ writel(0x0, info->base + chans->dtg_addr + TC_LINE1_INT);
+ dtg_irq_unmask(IRQ_TC_LINE1, info);
+ break;
+ default: /* TODO: add support later */
+ continue;
+ }
+
+ ret = devm_request_irq(&pdev->dev, info->irqs[i],
+ dcss_irq_handler, 0,
+ dev_name(&pdev->dev), info);
+ if (ret) {
+ dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n",
+ info->irqs[i], ret);
+ return ret;
+ }
+ }
+
+ if (i == 0)
+ return -ENXIO;
+
+ info->irqs_num = i + 1;
+
+ return 0;
+}
+
+static void __iomem *dev_iomem_init(struct platform_device *pdev,
+ unsigned int res_idx)
+{
+ struct resource *res;
+
+ if (res_idx > IORESOURCE_MEM_NUM)
+ return NULL;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, res_idx);
+ if (!res)
+ return ERR_PTR(-ENODEV);
+
+ return devm_ioremap_resource(&pdev->dev, res);
+}
+
+static int dcss_dispdrv_init(struct platform_device *pdev,
+ struct fb_info *fbi)
+{
+ struct dcss_channel_info *cinfo = fbi->par;
+ struct dcss_info *info = cinfo->dev_data;
+ struct mxc_dispdrv_setting setting;
+ char disp_dev[NAME_LEN];
+
+ memset(&setting, 0x0, sizeof(setting));
+ setting.fbi = fbi;
+ memcpy(disp_dev, info->disp_dev, strlen(info->disp_dev));
+ disp_dev[strlen(info->disp_dev)] = '\0';
+
+ info->dispdrv = mxc_dispdrv_gethandle(disp_dev, &setting);
+ if (IS_ERR(info->dispdrv)) {
+ dev_info(&pdev->dev, "no encoder driver exists\n");
+ return -EPROBE_DEFER;
+ }
+
+ dev_info(&pdev->dev, "%s encoder registered success\n", disp_dev);
+
+ return 0;
+}
+
+static int dcss_register_one_ch(uint32_t ch_id,
+ struct dcss_info *info)
+{
+ int ret = 0;
+ struct dcss_channels *chans;
+ struct dcss_channel_info *cinfo;
+
+ BUG_ON(ch_id > 2);
+
+ chans = &info->chans;
+ cinfo = &chans->chan_info[ch_id];
+
+ cinfo->pdev = info->pdev;
+ cinfo->dev_data = (void *)info;
+
+ ret = fill_one_chan_info(ch_id, cinfo);
+ if (ret) {
+ dev_err(&info->pdev->dev, "register channel %d failed\n", ch_id);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int dcss_register_one_fb(struct dcss_channel_info *cinfo)
+{
+ int ret = 0;
+ struct fb_info *fbi;
+
+ ret = alloc_one_fbinfo(cinfo);
+ if (ret) {
+ dev_err(&cinfo->pdev->dev,
+ "register fb %d failed\n", cinfo->channel_id);
+ goto out;
+ }
+
+ fbi = cinfo->fb_info;
+ ret = dcss_init_fbinfo(fbi);
+ if (ret)
+ goto out;
+
+ init_chan_pixmap(cinfo);
+ init_ch_pos(cinfo);
+
+ if (cinfo->channel_id == 0) {
+ ret = dcss_dispdrv_init(cinfo->pdev, fbi);
+ if (ret == -EPROBE_DEFER) {
+ dev_info(&cinfo->pdev->dev,
+ "Defer fb probe for encoder unready\n");
+ goto out;
+ }
+ }
+
+ ret = register_framebuffer(fbi);
+ if (ret) {
+ dev_err(&cinfo->pdev->dev, "failed to register fb%d\n",
+ cinfo->channel_id);
+ goto out;
+ }
+
+out:
+ return ret;
+}
+
+static int read_dcss_properties(struct dcss_info *info)
+{
+ int ret = 0;
+ uint32_t disp_mode;
+ const char *disp_dev;
+ struct platform_device *pdev = info->pdev;
+ struct device_node *np = pdev->dev.of_node;
+
+ /* read disp-mode */
+ ret = of_property_read_u32(np, "disp-mode", &disp_mode);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "invalid disp-mode provided in dtb\n");
+ return -EINVAL;
+ }
+
+ info->dft_disp_mode = &imx_cea_mode[disp_mode];
+ if (!info->dft_disp_mode->xres)
+ return -EINVAL;
+
+ /* read disp-dev */
+ ret = of_property_read_string(np, "disp-dev", &disp_dev);
+ if (!ret) {
+ memcpy(info->disp_dev, disp_dev, strlen(disp_dev));
+ dev_info(&pdev->dev, "%s: disp_dev = %s\n", __func__,
+ info->disp_dev);
+ }
+
+ return 0;
+}
+
+static int dcss_info_init(struct dcss_info *info)
+{
+ int ret = 0;
+ struct platform_device *pdev = info->pdev;
+
+ spin_lock_init(&info->llock);
+
+ info->dcss_state = DCSS_STATE_RESET;
+
+ ret = read_dcss_properties(info);
+ if (ret)
+ return -ENODEV;
+
+ ret = dcss_init_chans(info);
+
+ info->base = dev_iomem_init(pdev, 0);
+ if (IS_ERR(info->base)) {
+ ret = PTR_ERR(info->base);
+ goto out;
+ }
+
+ info->blkctl_base = dev_iomem_init(pdev, 1);
+ if (IS_ERR(info->blkctl_base)) {
+ ret = PTR_ERR(info->blkctl_base);
+ goto out;
+ }
+
+ ret = dcss_clks_get(info);
+ if (ret)
+ goto out;
+
+ ret = dcss_clks_rate_set(info);
+ if (ret)
+ goto out;
+
+ ret = dcss_clks_enable(info);
+ if (ret)
+ goto out;
+
+ /* alloc ctxld fifo */
+ ret = ctxld_fifo_alloc(&pdev->dev, &info->cfifo, DCSS_CFIFO_SIZE);
+ if (ret) {
+ dev_err(&pdev->dev, "ctxld fifo alloc failed\n");
+ goto out;
+ }
+
+ info->cfifo.ctxld_wq = alloc_ordered_workqueue("ctxld-wq", WQ_FREEZABLE);
+ if (!info->cfifo.ctxld_wq) {
+ dev_err(&pdev->dev, "allocate ctxld wq failed\n");
+ ret = -EINVAL;
+ goto free_cfifo;
+ }
+
+ platform_set_drvdata(pdev, info);
+ init_waitqueue_head(&info->vinfo.vwait);
+ info->vinfo.vcount = 0;
+
+ goto out;
+
+free_cfifo:
+ ctxld_fifo_free(&pdev->dev, &info->cfifo);
+out:
+ return ret;
+}
+
+static int dcss_enable_encoder(struct dcss_info *info)
+{
+ int ret = 0;
+ struct fb_info *main_fbinfo;
+ struct platform_device *pdev;
+
+ if (!info->dispdrv)
+ goto out;
+
+ pdev = info->pdev;
+ main_fbinfo = get_one_fbinfo(0, &info->chans);
+
+ if (info->dispdrv->drv->setup) {
+ ret = info->dispdrv->drv->setup(info->dispdrv, main_fbinfo);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "setup encoder failed: %d\n", ret);
+ goto out;
+ }
+ }
+
+ if (info->dispdrv->drv->enable) {
+ ret = info->dispdrv->drv->enable(info->dispdrv, main_fbinfo);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "enable encoder failed: %d\n", ret);
+ goto out;
+ }
+ }
+
+out:
+ return ret;
+}
+
+static void dcss_fix_data_config(struct dcss_info *info)
+{
+ int i, esize;
+
+ esize = sizeof(struct data_unit);
+
+ /* SCALER COEFFS config */
+ for (i = 0; i < sizeof(scaler_coeffs_ch0) / esize; i++)
+ writel(scaler_coeffs_ch0[i].data,
+ info->base + scaler_coeffs_ch0[i].addr);
+
+ for (i = 0; i < sizeof(scaler_coeffs_ch1) / esize; i++)
+ writel(scaler_coeffs_ch1[i].data,
+ info->base + scaler_coeffs_ch1[i].addr);
+
+ /* HDR10 PIPE1 config */
+ for (i = 0; i < sizeof(hdr10_pipe1_lut_a0) / esize; i++)
+ writel(hdr10_pipe1_lut_a0[i].data,
+ info->base + hdr10_pipe1_lut_a0[i].addr);
+
+ for (i = 0; i < sizeof(hdr10_pipe1_lut_a1) / esize; i++)
+ writel(hdr10_pipe1_lut_a1[i].data,
+ info->base + hdr10_pipe1_lut_a1[i].addr);
+
+ for (i = 0; i < sizeof(hdr10_pipe1_lut_a2) / esize; i++)
+ writel(hdr10_pipe1_lut_a2[i].data,
+ info->base + hdr10_pipe1_lut_a2[i].addr);
+
+ for (i = 0; i < sizeof(hdr10_pipe1_csca) / esize; i++)
+ writel(hdr10_pipe1_csca[i].data,
+ info->base + hdr10_pipe1_csca[i].addr);
+
+ for (i = 0; i < sizeof(hdr10_pipe1_cscb) / esize; i++)
+ writel(hdr10_pipe1_cscb[i].data,
+ info->base + hdr10_pipe1_cscb[i].addr);
+
+ /* HDR10 PIPE2 config */
+ for (i = 0; i < sizeof(hdr10_pipe2_lut_a0) / esize; i++)
+ writel(hdr10_pipe2_lut_a0[i].data,
+ info->base + hdr10_pipe2_lut_a0[i].addr);
+
+ for (i = 0; i < sizeof(hdr10_pipe2_lut_a1) / esize; i++)
+ writel(hdr10_pipe2_lut_a1[i].data,
+ info->base + hdr10_pipe2_lut_a1[i].addr);
+
+ for (i = 0; i < sizeof(hdr10_pipe2_lut_a2) / esize; i++)
+ writel(hdr10_pipe2_lut_a2[i].data,
+ info->base + hdr10_pipe2_lut_a2[i].addr);
+
+ for (i = 0; i < sizeof(hdr10_pipe2_csca) / esize; i++)
+ writel(hdr10_pipe2_csca[i].data,
+ info->base + hdr10_pipe2_csca[i].addr);
+
+ for (i = 0; i < sizeof(hdr10_pipe2_cscb) / esize; i++)
+ writel(hdr10_pipe2_cscb[i].data,
+ info->base + hdr10_pipe2_cscb[i].addr);
+
+ /* HDR10 OPIPE config */
+ for (i = 0; i < sizeof(hdr10_opipe_a0) / esize; i++)
+ writel(hdr10_opipe_a0[i].data,
+ info->base + hdr10_opipe_a0[i].addr);
+
+ for (i = 0; i < sizeof(hdr10_opipe_a1) / esize; i++)
+ writel(hdr10_opipe_a1[i].data,
+ info->base + hdr10_opipe_a1[i].addr);
+
+ for (i = 0; i < sizeof(hdr10_opipe_a2) / esize; i++)
+ writel(hdr10_opipe_a2[i].data,
+ info->base + hdr10_opipe_a2[i].addr);
+
+ for (i = 0; i < sizeof(hdr10_opipe_csco) / esize; i++)
+ writel(hdr10_opipe_csco[i].data,
+ info->base + hdr10_opipe_csco[i].addr);
+}
+
+static int dcss_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct dcss_info *info;
+ struct fb_info *m_fbinfo;
+
+ info = devm_kzalloc(&pdev->dev, sizeof(struct dcss_info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+ info->pdev = pdev;
+
+ ret = dcss_info_init(info);
+ if (ret)
+ goto kfree_info;
+
+ /* TODO: reset DCSS to make it clean */
+
+ /* Clocks select: before dcss de-resets */
+ if (!strcmp(info->disp_dev, "hdmi_disp"))
+ /* HDMI */
+ writel(0x0, info->blkctl_base + 0x10);
+ else
+ /* MIPI DSI */
+ writel(0x101, info->blkctl_base + 0x10);
+
+ /* Pull DCSS out of resets */
+ writel(0xffffffff, info->blkctl_base + 0x0);
+
+ /* TODO: config fixed data for DCSS */
+ dcss_fix_data_config(info);
+
+ dcss_interrupts_init(info);
+
+ ret = dcss_dtg_start(info);
+ if (ret)
+ goto kfree_info;
+
+ /* register channel 0: graphic */
+ ret = dcss_register_one_ch(0, info);
+ if (ret)
+ goto kfree_info;
+
+ /* register fb 0 */
+ ret = dcss_register_one_fb(&info->chans.chan_info[0]);
+ if (ret)
+ goto unregister_ch0;
+
+ /* enable encoder if exists */
+ dcss_enable_encoder(info);
+
+ ret = dcss_subsam_config(info);
+ if (ret)
+ goto unregister_fb0;
+
+ /* register channel 1: video */
+ ret = dcss_register_one_ch(1, info);
+ if (ret)
+ goto unregister_fb0;
+
+ /* register fb 1 */
+ ret = dcss_register_one_fb(&info->chans.chan_info[1]);
+ if (ret)
+ goto unregister_ch1;
+
+ /* unblank fb0 */
+ m_fbinfo = get_one_fbinfo(0, &info->chans);
+ dcss_blank(FB_BLANK_UNBLANK, m_fbinfo);
+
+ /* init fb1 */
+ dcss_set_par(get_one_fbinfo(1, &info->chans));
+
+ goto out;
+
+unregister_ch1:
+ /* TODO: add later */
+ ;
+unregister_fb0:
+ framebuffer_release(get_one_fbinfo(0, &info->chans));
+unregister_ch0:
+ /* TODO: add later */
+ ;
+kfree_info:
+ devm_kfree(&pdev->dev, info);
+out:
+ return ret;
+}
+
+static int dcss_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static void dcss_shutdown(struct platform_device *pdev)
+{
+}
+
+static struct platform_driver dcss_driver = {
+ .probe = dcss_probe,
+ .remove = dcss_remove,
+ .shutdown = dcss_shutdown,
+ .driver = {
+ .name = "dcss_fb",
+ .of_match_table = dcss_dt_ids,
+ },
+};
+
+module_platform_driver(dcss_driver);
+
+MODULE_DESCRIPTION("NXP DCSS framebuffer driver");
+MODULE_LICENSE("GPL");