[PATCH 4/5] fb: Add DCU framebuffer driver for Vybrid VF610 platform

Lucas Stach l.stach at pengutronix.de
Tue Aug 6 04:47:39 EDT 2013


Am Freitag, den 12.07.2013, 14:07 +0800 schrieb Alison Wang:
> The Display Controller Unit (DCU) module is a system master that
> fetches graphics stored in internal or external memory and displays
> them on a TFT LCD panel. A wide range of panel sizes is supported
> and the timing of the interface signals is highly configurable.
> Graphics are read directly from memory and then blended in real-time,
> which allows for dynamic content creation with minimal CPU intervention.
> 
> The features:
> 
> (1) Full RGB888 output to TFT LCD panel.
> (2) For the current LCD panel, WQVGA "480x272" is tested.
> (3) Blending of each pixel using up to 4 source layers dependent on size of panel.
> (4) Each graphic layer can be placed with one pixel resolution in either axis.
> (5) Each graphic layer support RGB565 and RGB888 direct colors without alpha channel
> and BGRA8888 direct colors with an alpha channel.
> (6) Each graphic layer support alpha blending with 8-bit resolution.
> 
> This driver has been tested on Vybrid VF610 TOWER board.
> 
> Signed-off-by: Alison Wang <b18965 at freescale.com>
> ---
> Changes in v2: None
> 
>  drivers/video/Kconfig      |    9 +
>  drivers/video/Makefile     |    1 +
>  drivers/video/fsl-dcu-fb.c | 1091 ++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 1101 insertions(+)
>  create mode 100644 drivers/video/fsl-dcu-fb.c
> 
[...]
> +
> +static struct fb_videomode dcu_mode_db[] = {
> +	{
> +		.name		= "480x272",
> +		.refresh	= 75,
> +		.xres		= 480,
> +		.yres		= 272,
> +		.pixclock	= 91996,
> +		.left_margin	= 2,
> +		.right_margin	= 2,
> +		.upper_margin	= 1,
> +		.lower_margin	= 1,
> +		.hsync_len	= 41,
> +		.vsync_len	= 2,
> +		.sync		= FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
> +		.vmode		= FB_VMODE_NONINTERLACED,
> +	},
> +};
Don't hardcode panel data in the driver. Use the videomode helpers to
get the relevant data from devicetree.

> +
> +/* DCU framebuffer data structure */
> +struct dcu_fb_data {
> +	struct fb_info *fsl_dcu_info[DCU_LAYER_NUM];
> +	void __iomem *reg_base;
> +	unsigned int irq;
> +	struct clk *clk;
> +};
> +
> +struct layer_display_offset {
> +	int x_layer_d;
> +	int y_layer_d;
> +};
> +
> +struct mfb_info {
> +	int index;
> +	char *id;
> +	unsigned long pseudo_palette[16];
> +	unsigned char alpha;
> +	unsigned char blend;
> +	unsigned int count;
> +	int x_layer_d;	/* layer display x offset to physical screen */
> +	int y_layer_d;	/* layer display y offset to physical screen */
> +	struct dcu_fb_data *parent;
> +};
> +
> +enum mfb_index {
> +	LAYER0 = 0,
> +	LAYER1,
> +	LAYER2,
> +	LAYER3,
> +};
Why are there only 4 layers here? I thought the controller supports at
least 6 simultaneous layers?

> +
> +static struct mfb_info mfb_template[] = {
> +	{
> +	.index = LAYER0,
> +	.id = "Layer0",
> +	.alpha = 0xff,
> +	.blend = 0,
> +	.count = 0,
> +	.x_layer_d = 0,
> +	.y_layer_d = 0,
> +	},
> +	{
> +	.index = LAYER1,
> +	.id = "Layer1",
> +	.alpha = 0xff,
> +	.blend = 0,
> +	.count = 0,
> +	.x_layer_d = 50,
> +	.y_layer_d = 50,
> +	},
> +	{
> +	.index = LAYER2,
> +	.id = "Layer2",
> +	.alpha = 0xff,
> +	.blend = 0,
> +	.count = 0,
> +	.x_layer_d = 100,
> +	.y_layer_d = 100,
> +	},
> +	{
> +	.index = LAYER3,
> +	.id = "Layer3",
> +	.alpha = 0xff,
> +	.blend = 0,
> +	.count = 0,
> +	.x_layer_d = 150,
> +	.y_layer_d = 150,
> +	},
> +};
[...]

> +
> +static int calc_div_ratio(struct fb_info *info)
> +{
> +	struct mfb_info *mfbi = info->par;
> +	struct dcu_fb_data *dcufb = mfbi->parent;
> +	unsigned long dcu_clk;
> +	unsigned long long tmp;
> +
> +	dcu_clk = clk_get_rate(dcufb->clk);
> +	tmp = info->var.pixclock * (unsigned long long)dcu_clk;
> +
> +	do_div(tmp, 1000000);
> +
> +	if (do_div(tmp, 1000000) > 500000)
> +		tmp++;
Urgh, you are changing the value of tmp inside the if clause. This isn't
nice. This function as a a whole looks really odd.

> +
> +	tmp = tmp - 1;
> +	return tmp;
> +}
> +
> +static void update_controller(struct fb_info *info)
> +{
> +	struct fb_var_screeninfo *var = &info->var;
> +	struct mfb_info *mfbi = info->par;
> +	struct dcu_fb_data *dcufb = mfbi->parent;
> +	unsigned int ratio;
> +
> +	ratio = calc_div_ratio(info);
> +	writel(ratio, dcufb->reg_base + DCU_DIV_RATIO);
> +
> +	writel(DCU_DISP_SIZE_DELTA_Y(var->yres) |
> +		DCU_DISP_SIZE_DELTA_X(var->xres / 16),
> +		dcufb->reg_base + DCU_DISP_SIZE);
> +
> +	/* Horizontal and vertical sync parameter */
> +	writel(DCU_HSYN_PARA_BP(var->left_margin) |
> +		DCU_HSYN_PARA_PW(var->hsync_len) |
> +		DCU_HSYN_PARA_FP(var->right_margin),
> +		dcufb->reg_base + DCU_HSYN_PARA);
> +
> +	writel(DCU_VSYN_PARA_BP(var->upper_margin) |
> +		DCU_VSYN_PARA_PW(var->vsync_len) |
> +		DCU_VSYN_PARA_FP(var->lower_margin),
> +		dcufb->reg_base + DCU_VSYN_PARA);
> +
> +	writel(DCU_SYN_POL_INV_PXCK_FALL | DCU_SYN_POL_NEG_REMAIN |
> +		DCU_SYN_POL_INV_VS_LOW | DCU_SYN_POL_INV_HS_LOW,
> +		dcufb->reg_base + DCU_SYN_POL);
> +
> +	writel(DCU_BGND_R(0) | DCU_BGND_G(0) | DCU_BGND_B(0),
> +		dcufb->reg_base + DCU_BGND);
> +
> +	writel(DCU_MODE_BLEND_ITER(DCU_LAYER_NUM_MAX) | DCU_MODE_RASTER_EN,
> +			dcufb->reg_base + DCU_DCU_MODE);
> +
> +	writel(DCU_THRESHOLD_LS_BF_VS(0x3) | DCU_THRESHOLD_OUT_BUF_HIGH(0x78) |
> +		DCU_THRESHOLD_OUT_BUF_LOW(0), dcufb->reg_base + DCU_THRESHOLD);
> +
> +	enable_controller(info);
> +}
> +
> +static int map_video_memory(struct fb_info *info)
> +{
> +	u32 smem_len = info->fix.line_length * info->var.yres_virtual;
> +
> +	info->fix.smem_len = smem_len;
> +
> +	info->screen_base = dma_alloc_coherent(info->device, info->fix.smem_len,
You are setting up an uncached mapping here. Use a writecombined mapping
to help performance. It's still coherent, but allows at least write to
be fast.

> +		(dma_addr_t *)&info->fix.smem_start, GFP_KERNEL);
> +	if (!info->screen_base) {
> +		printk(KERN_ERR "unable to allocate fb memory\n");
> +		return -ENOMEM;
> +	}
> +
> +	memset(info->screen_base, 0, info->fix.smem_len);
> +
> +	return 0;
> +}
> +
> +static void unmap_video_memory(struct fb_info *info)
> +{
> +	if (!info->screen_base)
> +		return;
> +
> +	dma_free_coherent(info->device, info->fix.smem_len,
> +		info->screen_base, info->fix.smem_start);
> +
> +	info->screen_base = NULL;
> +	info->fix.smem_start = 0;
> +	info->fix.smem_len = 0;
> +}
[...]

> +
> +static int fsl_dcu_open(struct fb_info *info, int user)
> +{
> +	struct mfb_info *mfbi = info->par;
> +	int ret = 0;
> +
> +	mfbi->index = info->node;
> +
> +	mfbi->count++;
> +	if (mfbi->count == 1) {
> +		fsl_dcu_check_var(&info->var, info);
> +		ret = fsl_dcu_set_par(info);
> +		if (ret < 0)
> +			mfbi->count--;
> +		else
> +			enable_interrupts(mfbi->parent);
> +	}
> +
> +	return ret;
> +}
> +
> +static int fsl_dcu_release(struct fb_info *info, int user)
> +{
> +	struct mfb_info *mfbi = info->par;
> +	int ret = 0;
> +
> +	mfbi->count--;
> +	if (mfbi->count == 0) {
> +		ret = disable_panel(info);
> +		if (ret < 0)
> +			mfbi->count++;
> +	}
> +
> +	return ret;
> +}
Could this be replaced by runtime pm?

> +
> +static struct fb_ops fsl_dcu_ops = {
> +	.owner = THIS_MODULE,
> +	.fb_check_var = fsl_dcu_check_var,
> +	.fb_set_par = fsl_dcu_set_par,
> +	.fb_setcolreg = fsl_dcu_setcolreg,
> +	.fb_blank = fsl_dcu_blank,
> +	.fb_pan_display = fsl_dcu_pan_display,
> +	.fb_fillrect = cfb_fillrect,
> +	.fb_copyarea = cfb_copyarea,
> +	.fb_imageblit = cfb_imageblit,
> +	.fb_ioctl = fsl_dcu_ioctl,
> +	.fb_open = fsl_dcu_open,
> +	.fb_release = fsl_dcu_release,
> +};
> +
> +static int install_framebuffer(struct fb_info *info)
> +{
> +	struct mfb_info *mfbi = info->par;
> +	struct fb_videomode *db = dcu_mode_db;
> +	unsigned int dbsize = ARRAY_SIZE(dcu_mode_db);
> +	int ret;
> +
> +	info->var.activate = FB_ACTIVATE_NOW;
> +	info->fbops = &fsl_dcu_ops;
> +	info->flags = FBINFO_FLAG_DEFAULT;
> +	info->pseudo_palette = &mfbi->pseudo_palette;
> +
> +	fb_alloc_cmap(&info->cmap, 16, 0);
> +
> +	ret = fb_find_mode(&info->var, info, fb_mode, db, dbsize,
> +			NULL, default_bpp);
> +
> +	if (fsl_dcu_check_var(&info->var, info)) {
> +		ret = -EINVAL;
> +		goto failed_checkvar;
> +	}
> +
> +	if (register_framebuffer(info) < 0) {
> +		ret = -EINVAL;
> +		goto failed_register_framebuffer;
> +	}
> +
> +	printk(KERN_INFO "fb%d: %s fb device registered successfully.\n",
> +		info->node, info->fix.id);
> +	return 0;
> +
> +failed_checkvar:
> +	fb_dealloc_cmap(&info->cmap);
> +failed_register_framebuffer:
> +	unmap_video_memory(info);
> +	fb_dealloc_cmap(&info->cmap);
> +	return ret;
> +}
> +
> +static void uninstall_framebuffer(struct fb_info *info)
> +{
> +	unregister_framebuffer(info);
> +	unmap_video_memory(info);
> +
> +	if (&info->cmap)
> +		fb_dealloc_cmap(&info->cmap);
> +}
> +
> +static irqreturn_t fsl_dcu_irq(int irq, void *dev_id)
> +{
> +	struct dcu_fb_data *dcufb = dev_id;
> +	unsigned int status = readl(dcufb->reg_base + DCU_INT_STATUS);
> +	u32 dcu_mode;
> +
> +	if (status) {
> +		if (status & DCU_INT_STATUS_UNDRUN) {
> +			dcu_mode = readl(dcufb->reg_base + DCU_DCU_MODE);
> +			dcu_mode &= ~DCU_MODE_DCU_MODE_MASK;
> +			writel(dcu_mode | DCU_MODE_DCU_MODE(DCU_MODE_OFF),
> +				dcufb->reg_base + DCU_DCU_MODE);
> +			udelay(1);
> +			writel(dcu_mode | DCU_MODE_DCU_MODE(DCU_MODE_NORMAL),
> +				dcufb->reg_base + DCU_DCU_MODE);
> +		}
> +		writel(status, dcufb->reg_base + DCU_INT_STATUS);
> +		return IRQ_HANDLED;
> +	}
> +	return IRQ_NONE;
> +}
> +
> +#ifdef CONFIG_PM
> +static int fsl_dcu_suspend(struct platform_device *pdev,
> +		pm_message_t state)
> +{
> +	struct dcu_fb_data *dcufb = dev_get_drvdata(&pdev->dev);
> +
> +	clk_disable_unprepare(dcufb->clk);
> +	return 0;
> +}
> +
> +static int fsl_dcu_resume(struct platform_device *pdev)
> +{
> +	struct dcu_fb_data *dcufb = dev_get_drvdata(&pdev->dev);
> +
> +	clk_prepare_enable(dcufb->clk);
> +	return 0;
> +}
> +#else
> +#define fsl_dcu_suspend	NULL
> +#define fsl_dcu_resume	NULL
> +#endif
> +
Could this be replaced by runtime pm?

> +static int bypass_tcon(struct device_node *np)
> +{
> +	struct device_node *tcon_np;
> +	struct platform_device *tcon_pdev;
> +	struct clk *tcon_clk;
> +	void __iomem *tcon_reg;
> +	int ret = 0;
> +
> +	tcon_np = of_parse_phandle(np, "tcon-controller", 0);
> +	if (!tcon_np)
> +		return -EINVAL;
> +
> +	tcon_pdev = of_find_device_by_node(tcon_np);
> +	if (!tcon_pdev)
> +		return -EINVAL;
> +
> +	tcon_clk = devm_clk_get(&tcon_pdev->dev, "tcon");
> +	if (IS_ERR(tcon_clk)) {
> +		ret = PTR_ERR(tcon_clk);
> +		goto failed_getclock;
> +	}
> +	clk_prepare_enable(tcon_clk);
> +
> +	tcon_reg = of_iomap(tcon_np, 0);
> +	if (!tcon_reg) {
> +		ret = -ENOMEM;
> +		goto failed_ioremap;
> +	}
> +	writel(TCON_BYPASS_ENABLE, tcon_reg + TCON_CTRL1);
> +
> +	return 0;
> +
> +failed_ioremap:
> +	clk_disable_unprepare(tcon_clk);
> +failed_getclock:
> +	of_node_put(tcon_np);
> +	return ret;
> +}
> +
Is the framebuffer driver the only user of tcon? If not you should not
map the memory here, but rather use something like regmap syscon.

[...]

Regards,
Lucas

-- 
Pengutronix e.K.                           | Lucas Stach                 |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-5076 |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |




More information about the linux-arm-kernel mailing list