blob: d2403314addbd2b08ed970fe3b17b5990a4e8197 [file] [log] [blame]
/*
* 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 <linux/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_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 */
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);
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);
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);
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);
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");