1704 lines · 43.2 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * Copyright (C) 2008 * Guennadi Liakhovetski, DENX Software Engineering, <lg@denx.de> * * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. */ #include <linux/module.h>#include <linux/kernel.h>#include <linux/platform_device.h>#include <linux/sched.h>#include <linux/errno.h>#include <linux/string.h>#include <linux/interrupt.h>#include <linux/slab.h>#include <linux/fb.h>#include <linux/delay.h>#include <linux/init.h>#include <linux/ioport.h>#include <linux/dma-mapping.h>#include <linux/dmaengine.h>#include <linux/console.h>#include <linux/clk.h>#include <linux/mutex.h>#include <linux/dma/ipu-dma.h>#include <linux/backlight.h> #include <linux/dma/imx-dma.h>#include <linux/platform_data/video-mx3fb.h> #include <asm/io.h>#include <linux/uaccess.h> #define MX3FB_NAME		"mx3_sdc_fb" #define MX3FB_REG_OFFSET	0xB4 /* SDC Registers */#define SDC_COM_CONF		(0xB4 - MX3FB_REG_OFFSET)#define SDC_GW_CTRL		(0xB8 - MX3FB_REG_OFFSET)#define SDC_FG_POS		(0xBC - MX3FB_REG_OFFSET)#define SDC_BG_POS		(0xC0 - MX3FB_REG_OFFSET)#define SDC_CUR_POS		(0xC4 - MX3FB_REG_OFFSET)#define SDC_PWM_CTRL		(0xC8 - MX3FB_REG_OFFSET)#define SDC_CUR_MAP		(0xCC - MX3FB_REG_OFFSET)#define SDC_HOR_CONF		(0xD0 - MX3FB_REG_OFFSET)#define SDC_VER_CONF		(0xD4 - MX3FB_REG_OFFSET)#define SDC_SHARP_CONF_1	(0xD8 - MX3FB_REG_OFFSET)#define SDC_SHARP_CONF_2	(0xDC - MX3FB_REG_OFFSET) /* Register bits */#define SDC_COM_TFT_COLOR	0x00000001UL#define SDC_COM_FG_EN		0x00000010UL#define SDC_COM_GWSEL		0x00000020UL#define SDC_COM_GLB_A		0x00000040UL#define SDC_COM_KEY_COLOR_G	0x00000080UL#define SDC_COM_BG_EN		0x00000200UL#define SDC_COM_SHARP		0x00001000UL #define SDC_V_SYNC_WIDTH_L	0x00000001UL /* Display Interface registers */#define DI_DISP_IF_CONF		(0x0124 - MX3FB_REG_OFFSET)#define DI_DISP_SIG_POL		(0x0128 - MX3FB_REG_OFFSET)#define DI_SER_DISP1_CONF	(0x012C - MX3FB_REG_OFFSET)#define DI_SER_DISP2_CONF	(0x0130 - MX3FB_REG_OFFSET)#define DI_HSP_CLK_PER		(0x0134 - MX3FB_REG_OFFSET)#define DI_DISP0_TIME_CONF_1	(0x0138 - MX3FB_REG_OFFSET)#define DI_DISP0_TIME_CONF_2	(0x013C - MX3FB_REG_OFFSET)#define DI_DISP0_TIME_CONF_3	(0x0140 - MX3FB_REG_OFFSET)#define DI_DISP1_TIME_CONF_1	(0x0144 - MX3FB_REG_OFFSET)#define DI_DISP1_TIME_CONF_2	(0x0148 - MX3FB_REG_OFFSET)#define DI_DISP1_TIME_CONF_3	(0x014C - MX3FB_REG_OFFSET)#define DI_DISP2_TIME_CONF_1	(0x0150 - MX3FB_REG_OFFSET)#define DI_DISP2_TIME_CONF_2	(0x0154 - MX3FB_REG_OFFSET)#define DI_DISP2_TIME_CONF_3	(0x0158 - MX3FB_REG_OFFSET)#define DI_DISP3_TIME_CONF	(0x015C - MX3FB_REG_OFFSET)#define DI_DISP0_DB0_MAP	(0x0160 - MX3FB_REG_OFFSET)#define DI_DISP0_DB1_MAP	(0x0164 - MX3FB_REG_OFFSET)#define DI_DISP0_DB2_MAP	(0x0168 - MX3FB_REG_OFFSET)#define DI_DISP0_CB0_MAP	(0x016C - MX3FB_REG_OFFSET)#define DI_DISP0_CB1_MAP	(0x0170 - MX3FB_REG_OFFSET)#define DI_DISP0_CB2_MAP	(0x0174 - MX3FB_REG_OFFSET)#define DI_DISP1_DB0_MAP	(0x0178 - MX3FB_REG_OFFSET)#define DI_DISP1_DB1_MAP	(0x017C - MX3FB_REG_OFFSET)#define DI_DISP1_DB2_MAP	(0x0180 - MX3FB_REG_OFFSET)#define DI_DISP1_CB0_MAP	(0x0184 - MX3FB_REG_OFFSET)#define DI_DISP1_CB1_MAP	(0x0188 - MX3FB_REG_OFFSET)#define DI_DISP1_CB2_MAP	(0x018C - MX3FB_REG_OFFSET)#define DI_DISP2_DB0_MAP	(0x0190 - MX3FB_REG_OFFSET)#define DI_DISP2_DB1_MAP	(0x0194 - MX3FB_REG_OFFSET)#define DI_DISP2_DB2_MAP	(0x0198 - MX3FB_REG_OFFSET)#define DI_DISP2_CB0_MAP	(0x019C - MX3FB_REG_OFFSET)#define DI_DISP2_CB1_MAP	(0x01A0 - MX3FB_REG_OFFSET)#define DI_DISP2_CB2_MAP	(0x01A4 - MX3FB_REG_OFFSET)#define DI_DISP3_B0_MAP		(0x01A8 - MX3FB_REG_OFFSET)#define DI_DISP3_B1_MAP		(0x01AC - MX3FB_REG_OFFSET)#define DI_DISP3_B2_MAP		(0x01B0 - MX3FB_REG_OFFSET)#define DI_DISP_ACC_CC		(0x01B4 - MX3FB_REG_OFFSET)#define DI_DISP_LLA_CONF	(0x01B8 - MX3FB_REG_OFFSET)#define DI_DISP_LLA_DATA	(0x01BC - MX3FB_REG_OFFSET) /* DI_DISP_SIG_POL bits */#define DI_D3_VSYNC_POL_SHIFT		28#define DI_D3_HSYNC_POL_SHIFT		27#define DI_D3_DRDY_SHARP_POL_SHIFT	26#define DI_D3_CLK_POL_SHIFT		25#define DI_D3_DATA_POL_SHIFT		24 /* DI_DISP_IF_CONF bits */#define DI_D3_CLK_IDLE_SHIFT		26#define DI_D3_CLK_SEL_SHIFT		25#define DI_D3_DATAMSK_SHIFT		24 enum ipu_panel {	IPU_PANEL_SHARP_TFT,	IPU_PANEL_TFT,}; struct ipu_di_signal_cfg {	unsigned datamask_en:1;	unsigned clksel_en:1;	unsigned clkidle_en:1;	unsigned data_pol:1;	/* true = inverted */	unsigned clk_pol:1;	/* true = rising edge */	unsigned enable_pol:1;	unsigned Hsync_pol:1;	/* true = active high */	unsigned Vsync_pol:1;}; static const struct fb_videomode mx3fb_modedb[] = {	{		/* 240x320 @ 60 Hz */		.name		= "Sharp-QVGA",		.refresh	= 60,		.xres		= 240,		.yres		= 320,		.pixclock	= 185925,		.left_margin	= 9,		.right_margin	= 16,		.upper_margin	= 7,		.lower_margin	= 9,		.hsync_len	= 1,		.vsync_len	= 1,		.sync		= FB_SYNC_HOR_HIGH_ACT | FB_SYNC_SHARP_MODE |				  FB_SYNC_CLK_INVERT | FB_SYNC_DATA_INVERT |				  FB_SYNC_CLK_IDLE_EN,		.vmode		= FB_VMODE_NONINTERLACED,		.flag		= 0,	}, {		/* 240x33 @ 60 Hz */		.name		= "Sharp-CLI",		.refresh	= 60,		.xres		= 240,		.yres		= 33,		.pixclock	= 185925,		.left_margin	= 9,		.right_margin	= 16,		.upper_margin	= 7,		.lower_margin	= 9 + 287,		.hsync_len	= 1,		.vsync_len	= 1,		.sync		= FB_SYNC_HOR_HIGH_ACT | FB_SYNC_SHARP_MODE |				  FB_SYNC_CLK_INVERT | FB_SYNC_DATA_INVERT |				  FB_SYNC_CLK_IDLE_EN,		.vmode		= FB_VMODE_NONINTERLACED,		.flag		= 0,	}, {		/* 640x480 @ 60 Hz */		.name		= "NEC-VGA",		.refresh	= 60,		.xres		= 640,		.yres		= 480,		.pixclock	= 38255,		.left_margin	= 144,		.right_margin	= 0,		.upper_margin	= 34,		.lower_margin	= 40,		.hsync_len	= 1,		.vsync_len	= 1,		.sync		= FB_SYNC_VERT_HIGH_ACT | FB_SYNC_OE_ACT_HIGH,		.vmode		= FB_VMODE_NONINTERLACED,		.flag		= 0,	}, {		/* NTSC TV output */		.name		= "TV-NTSC",		.refresh	= 60,		.xres		= 640,		.yres		= 480,		.pixclock	= 37538,		.left_margin	= 38,		.right_margin	= 858 - 640 - 38 - 3,		.upper_margin	= 36,		.lower_margin	= 518 - 480 - 36 - 1,		.hsync_len	= 3,		.vsync_len	= 1,		.sync		= 0,		.vmode		= FB_VMODE_NONINTERLACED,		.flag		= 0,	}, {		/* PAL TV output */		.name		= "TV-PAL",		.refresh	= 50,		.xres		= 640,		.yres		= 480,		.pixclock	= 37538,		.left_margin	= 38,		.right_margin	= 960 - 640 - 38 - 32,		.upper_margin	= 32,		.lower_margin	= 555 - 480 - 32 - 3,		.hsync_len	= 32,		.vsync_len	= 3,		.sync		= 0,		.vmode		= FB_VMODE_NONINTERLACED,		.flag		= 0,	}, {		/* TV output VGA mode, 640x480 @ 65 Hz */		.name		= "TV-VGA",		.refresh	= 60,		.xres		= 640,		.yres		= 480,		.pixclock	= 40574,		.left_margin	= 35,		.right_margin	= 45,		.upper_margin	= 9,		.lower_margin	= 1,		.hsync_len	= 46,		.vsync_len	= 5,		.sync		= 0,		.vmode		= FB_VMODE_NONINTERLACED,		.flag		= 0,	},}; struct mx3fb_data {	struct fb_info		*fbi;	int			backlight_level;	void __iomem		*reg_base;	spinlock_t		lock;	struct device		*dev;	struct backlight_device	*bl; 	uint32_t		h_start_width;	uint32_t		v_start_width;	enum disp_data_mapping	disp_data_fmt;}; struct dma_chan_request {	struct mx3fb_data	*mx3fb;	enum ipu_channel	id;}; /* MX3 specific framebuffer information. */struct mx3fb_info {	int				blank;	enum ipu_channel		ipu_ch;	uint32_t			cur_ipu_buf; 	u32				pseudo_palette[16]; 	struct completion		flip_cmpl;	struct mutex			mutex;	/* Protects fb-ops */	struct mx3fb_data		*mx3fb;	struct idmac_channel		*idmac_channel;	struct dma_async_tx_descriptor	*txd;	dma_cookie_t			cookie;	struct scatterlist		sg[2]; 	struct fb_var_screeninfo	cur_var; /* current var info */}; static void sdc_set_brightness(struct mx3fb_data *mx3fb, uint8_t value);static u32 sdc_get_brightness(struct mx3fb_data *mx3fb); static int mx3fb_bl_get_brightness(struct backlight_device *bl){	struct mx3fb_data *fbd = bl_get_data(bl); 	return sdc_get_brightness(fbd);} static int mx3fb_bl_update_status(struct backlight_device *bl){	struct mx3fb_data *fbd = bl_get_data(bl);	int brightness = bl->props.brightness; 	if (bl->props.power != FB_BLANK_UNBLANK)		brightness = 0;	if (bl->props.fb_blank != FB_BLANK_UNBLANK)		brightness = 0; 	fbd->backlight_level = (fbd->backlight_level & ~0xFF) | brightness; 	sdc_set_brightness(fbd, fbd->backlight_level); 	return 0;} static const struct backlight_ops mx3fb_lcdc_bl_ops = {	.update_status = mx3fb_bl_update_status,	.get_brightness = mx3fb_bl_get_brightness,}; static void mx3fb_init_backlight(struct mx3fb_data *fbd){	struct backlight_properties props;	struct backlight_device	*bl; 	if (fbd->bl)		return; 	memset(&props, 0, sizeof(struct backlight_properties));	props.max_brightness = 0xff;	props.type = BACKLIGHT_RAW;	sdc_set_brightness(fbd, fbd->backlight_level); 	bl = backlight_device_register("mx3fb-bl", fbd->dev, fbd,				       &mx3fb_lcdc_bl_ops, &props);	if (IS_ERR(bl)) {		dev_err(fbd->dev, "error %ld on backlight register\n",				PTR_ERR(bl));		return;	} 	fbd->bl = bl;	bl->props.power = FB_BLANK_UNBLANK;	bl->props.fb_blank = FB_BLANK_UNBLANK;	bl->props.brightness = mx3fb_bl_get_brightness(bl);} static void mx3fb_exit_backlight(struct mx3fb_data *fbd){	backlight_device_unregister(fbd->bl);} static void mx3fb_dma_done(void *); /* Used fb-mode and bpp. Can be set on kernel command line, therefore file-static. */static const char *fb_mode;static unsigned long default_bpp = 16; static u32 mx3fb_read_reg(struct mx3fb_data *mx3fb, unsigned long reg){	return __raw_readl(mx3fb->reg_base + reg);} static void mx3fb_write_reg(struct mx3fb_data *mx3fb, u32 value, unsigned long reg){	__raw_writel(value, mx3fb->reg_base + reg);} struct di_mapping {	uint32_t b0, b1, b2;}; static const struct di_mapping di_mappings[] = {	[IPU_DISP_DATA_MAPPING_RGB666] = { 0x0005000f, 0x000b000f, 0x0011000f },	[IPU_DISP_DATA_MAPPING_RGB565] = { 0x0004003f, 0x000a000f, 0x000f003f },	[IPU_DISP_DATA_MAPPING_RGB888] = { 0x00070000, 0x000f0000, 0x00170000 },}; static void sdc_fb_init(struct mx3fb_info *fbi){	struct mx3fb_data *mx3fb = fbi->mx3fb;	uint32_t reg; 	reg = mx3fb_read_reg(mx3fb, SDC_COM_CONF); 	mx3fb_write_reg(mx3fb, reg | SDC_COM_BG_EN, SDC_COM_CONF);} /* Returns enabled flag before uninit */static uint32_t sdc_fb_uninit(struct mx3fb_info *fbi){	struct mx3fb_data *mx3fb = fbi->mx3fb;	uint32_t reg; 	reg = mx3fb_read_reg(mx3fb, SDC_COM_CONF); 	mx3fb_write_reg(mx3fb, reg & ~SDC_COM_BG_EN, SDC_COM_CONF); 	return reg & SDC_COM_BG_EN;} static void sdc_enable_channel(struct mx3fb_info *mx3_fbi){	struct mx3fb_data *mx3fb = mx3_fbi->mx3fb;	struct idmac_channel *ichan = mx3_fbi->idmac_channel;	struct dma_chan *dma_chan = &ichan->dma_chan;	unsigned long flags;	dma_cookie_t cookie; 	if (mx3_fbi->txd)		dev_dbg(mx3fb->dev, "mx3fbi %p, desc %p, sg %p\n", mx3_fbi,			to_tx_desc(mx3_fbi->txd), to_tx_desc(mx3_fbi->txd)->sg);	else		dev_dbg(mx3fb->dev, "mx3fbi %p, txd = NULL\n", mx3_fbi); 	/* This enables the channel */	if (mx3_fbi->cookie < 0) {		mx3_fbi->txd = dmaengine_prep_slave_sg(dma_chan,		      &mx3_fbi->sg[0], 1, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT);		if (!mx3_fbi->txd) {			dev_err(mx3fb->dev, "Cannot allocate descriptor on %d\n",				dma_chan->chan_id);			return;		} 		mx3_fbi->txd->callback_param	= mx3_fbi->txd;		mx3_fbi->txd->callback		= mx3fb_dma_done; 		cookie = mx3_fbi->txd->tx_submit(mx3_fbi->txd);		dev_dbg(mx3fb->dev, "%d: Submit %p #%d [%c]\n", __LINE__,		       mx3_fbi->txd, cookie, list_empty(&ichan->queue) ? '-' : '+');	} else {		if (!mx3_fbi->txd || !mx3_fbi->txd->tx_submit) {			dev_err(mx3fb->dev, "Cannot enable channel %d\n",				dma_chan->chan_id);			return;		} 		/* Just re-activate the same buffer */		dma_async_issue_pending(dma_chan);		cookie = mx3_fbi->cookie;		dev_dbg(mx3fb->dev, "%d: Re-submit %p #%d [%c]\n", __LINE__,		       mx3_fbi->txd, cookie, list_empty(&ichan->queue) ? '-' : '+');	} 	if (cookie >= 0) {		spin_lock_irqsave(&mx3fb->lock, flags);		sdc_fb_init(mx3_fbi);		mx3_fbi->cookie = cookie;		spin_unlock_irqrestore(&mx3fb->lock, flags);	} 	/*	 * Attention! Without this msleep the channel keeps generating	 * interrupts. Next sdc_set_brightness() is going to be called	 * from mx3fb_blank().	 */	msleep(2);} static void sdc_disable_channel(struct mx3fb_info *mx3_fbi){	struct mx3fb_data *mx3fb = mx3_fbi->mx3fb;	unsigned long flags; 	if (mx3_fbi->txd == NULL)		return; 	spin_lock_irqsave(&mx3fb->lock, flags); 	sdc_fb_uninit(mx3_fbi); 	spin_unlock_irqrestore(&mx3fb->lock, flags); 	dmaengine_terminate_all(mx3_fbi->txd->chan);	mx3_fbi->txd = NULL;	mx3_fbi->cookie = -EINVAL;} /** * sdc_set_window_pos() - set window position of the respective plane. * @mx3fb:	mx3fb context. * @channel:	IPU DMAC channel ID. * @x_pos:	X coordinate relative to the top left corner to place window at. * @y_pos:	Y coordinate relative to the top left corner to place window at. * @return:	0 on success or negative error code on failure. */static int sdc_set_window_pos(struct mx3fb_data *mx3fb, enum ipu_channel channel,			      int16_t x_pos, int16_t y_pos){	if (channel != IDMAC_SDC_0)		return -EINVAL; 	x_pos += mx3fb->h_start_width;	y_pos += mx3fb->v_start_width; 	mx3fb_write_reg(mx3fb, (x_pos << 16) | y_pos, SDC_BG_POS);	return 0;} /** * sdc_init_panel() - initialize a synchronous LCD panel. * @mx3fb:		mx3fb context. * @panel:		panel type. * @pixel_clk:		desired pixel clock frequency in Hz. * @width:		width of panel in pixels. * @height:		height of panel in pixels. * @h_start_width:	number of pixel clocks between the HSYNC signal pulse *			and the start of valid data. * @h_sync_width:	width of the HSYNC signal in units of pixel clocks. * @h_end_width:	number of pixel clocks between the end of valid data *			and the HSYNC signal for next line. * @v_start_width:	number of lines between the VSYNC signal pulse and the *			start of valid data. * @v_sync_width:	width of the VSYNC signal in units of lines * @v_end_width:	number of lines between the end of valid data and the

Lines 1–500 of 1704.