diff options
Diffstat (limited to 'drivers/video/fbdev/omap/lcdc.c')
| -rw-r--r-- | drivers/video/fbdev/omap/lcdc.c | 793 | 
1 files changed, 793 insertions, 0 deletions
diff --git a/drivers/video/fbdev/omap/lcdc.c b/drivers/video/fbdev/omap/lcdc.c new file mode 100644 index 00000000000..6efa2591eaa --- /dev/null +++ b/drivers/video/fbdev/omap/lcdc.c @@ -0,0 +1,793 @@ +/* + * OMAP1 internal LCD controller + * + * Copyright (C) 2004 Nokia Corporation + * Author: Imre Deak <imre.deak@nokia.com> + * + * 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/device.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/err.h> +#include <linux/mm.h> +#include <linux/fb.h> +#include <linux/dma-mapping.h> +#include <linux/vmalloc.h> +#include <linux/clk.h> +#include <linux/gfp.h> + +#include <mach/lcdc.h> +#include <linux/omap-dma.h> + +#include <asm/mach-types.h> + +#include "omapfb.h" + +#include "lcdc.h" + +#define MODULE_NAME			"lcdc" + +#define MAX_PALETTE_SIZE		PAGE_SIZE + +enum lcdc_load_mode { +	OMAP_LCDC_LOAD_PALETTE, +	OMAP_LCDC_LOAD_FRAME, +	OMAP_LCDC_LOAD_PALETTE_AND_FRAME +}; + +static struct omap_lcd_controller { +	enum omapfb_update_mode	update_mode; +	int			ext_mode; + +	unsigned long		frame_offset; +	int			screen_width; +	int			xres; +	int			yres; + +	enum omapfb_color_format	color_mode; +	int			bpp; +	void			*palette_virt; +	dma_addr_t		palette_phys; +	int			palette_code; +	int			palette_size; + +	unsigned int		irq_mask; +	struct completion	last_frame_complete; +	struct completion	palette_load_complete; +	struct clk		*lcd_ck; +	struct omapfb_device	*fbdev; + +	void			(*dma_callback)(void *data); +	void			*dma_callback_data; + +	dma_addr_t		vram_phys; +	void			*vram_virt; +	unsigned long		vram_size; +} lcdc; + +static void inline enable_irqs(int mask) +{ +	lcdc.irq_mask |= mask; +} + +static void inline disable_irqs(int mask) +{ +	lcdc.irq_mask &= ~mask; +} + +static void set_load_mode(enum lcdc_load_mode mode) +{ +	u32 l; + +	l = omap_readl(OMAP_LCDC_CONTROL); +	l &= ~(3 << 20); +	switch (mode) { +	case OMAP_LCDC_LOAD_PALETTE: +		l |= 1 << 20; +		break; +	case OMAP_LCDC_LOAD_FRAME: +		l |= 2 << 20; +		break; +	case OMAP_LCDC_LOAD_PALETTE_AND_FRAME: +		break; +	default: +		BUG(); +	} +	omap_writel(l, OMAP_LCDC_CONTROL); +} + +static void enable_controller(void) +{ +	u32 l; + +	l = omap_readl(OMAP_LCDC_CONTROL); +	l |= OMAP_LCDC_CTRL_LCD_EN; +	l &= ~OMAP_LCDC_IRQ_MASK; +	l |= lcdc.irq_mask | OMAP_LCDC_IRQ_DONE;	/* enabled IRQs */ +	omap_writel(l, OMAP_LCDC_CONTROL); +} + +static void disable_controller_async(void) +{ +	u32 l; +	u32 mask; + +	l = omap_readl(OMAP_LCDC_CONTROL); +	mask = OMAP_LCDC_CTRL_LCD_EN | OMAP_LCDC_IRQ_MASK; +	/* +	 * Preserve the DONE mask, since we still want to get the +	 * final DONE irq. It will be disabled in the IRQ handler. +	 */ +	mask &= ~OMAP_LCDC_IRQ_DONE; +	l &= ~mask; +	omap_writel(l, OMAP_LCDC_CONTROL); +} + +static void disable_controller(void) +{ +	init_completion(&lcdc.last_frame_complete); +	disable_controller_async(); +	if (!wait_for_completion_timeout(&lcdc.last_frame_complete, +				msecs_to_jiffies(500))) +		dev_err(lcdc.fbdev->dev, "timeout waiting for FRAME DONE\n"); +} + +static void reset_controller(u32 status) +{ +	static unsigned long reset_count; +	static unsigned long last_jiffies; + +	disable_controller_async(); +	reset_count++; +	if (reset_count == 1 || time_after(jiffies, last_jiffies + HZ)) { +		dev_err(lcdc.fbdev->dev, +			  "resetting (status %#010x,reset count %lu)\n", +			  status, reset_count); +		last_jiffies = jiffies; +	} +	if (reset_count < 100) { +		enable_controller(); +	} else { +		reset_count = 0; +		dev_err(lcdc.fbdev->dev, +			"too many reset attempts, giving up.\n"); +	} +} + +/* + * Configure the LCD DMA according to the current mode specified by parameters + * in lcdc.fbdev and fbdev->var. + */ +static void setup_lcd_dma(void) +{ +	static const int dma_elem_type[] = { +		0, +		OMAP_DMA_DATA_TYPE_S8, +		OMAP_DMA_DATA_TYPE_S16, +		0, +		OMAP_DMA_DATA_TYPE_S32, +	}; +	struct omapfb_plane_struct *plane = lcdc.fbdev->fb_info[0]->par; +	struct fb_var_screeninfo *var = &lcdc.fbdev->fb_info[0]->var; +	unsigned long	src; +	int		esize, xelem, yelem; + +	src = lcdc.vram_phys + lcdc.frame_offset; + +	switch (var->rotate) { +	case 0: +		if (plane->info.mirror || (src & 3) || +		    lcdc.color_mode == OMAPFB_COLOR_YUV420 || +		    (lcdc.xres & 1)) +			esize = 2; +		else +			esize = 4; +		xelem = lcdc.xres * lcdc.bpp / 8 / esize; +		yelem = lcdc.yres; +		break; +	case 90: +	case 180: +	case 270: +		if (cpu_is_omap15xx()) { +			BUG(); +		} +		esize = 2; +		xelem = lcdc.yres * lcdc.bpp / 16; +		yelem = lcdc.xres; +		break; +	default: +		BUG(); +		return; +	} +#ifdef VERBOSE +	dev_dbg(lcdc.fbdev->dev, +		 "setup_dma: src %#010lx esize %d xelem %d yelem %d\n", +		 src, esize, xelem, yelem); +#endif +	omap_set_lcd_dma_b1(src, xelem, yelem, dma_elem_type[esize]); +	if (!cpu_is_omap15xx()) { +		int bpp = lcdc.bpp; + +		/* +		 * YUV support is only for external mode when we have the +		 * YUV window embedded in a 16bpp frame buffer. +		 */ +		if (lcdc.color_mode == OMAPFB_COLOR_YUV420) +			bpp = 16; +		/* Set virtual xres elem size */ +		omap_set_lcd_dma_b1_vxres( +			lcdc.screen_width * bpp / 8 / esize); +		/* Setup transformations */ +		omap_set_lcd_dma_b1_rotation(var->rotate); +		omap_set_lcd_dma_b1_mirror(plane->info.mirror); +	} +	omap_setup_lcd_dma(); +} + +static irqreturn_t lcdc_irq_handler(int irq, void *dev_id) +{ +	u32 status; + +	status = omap_readl(OMAP_LCDC_STATUS); + +	if (status & (OMAP_LCDC_STAT_FUF | OMAP_LCDC_STAT_SYNC_LOST)) +		reset_controller(status); +	else { +		if (status & OMAP_LCDC_STAT_DONE) { +			u32 l; + +			/* +			 * Disable IRQ_DONE. The status bit will be cleared +			 * only when the controller is reenabled and we don't +			 * want to get more interrupts. +			 */ +			l = omap_readl(OMAP_LCDC_CONTROL); +			l &= ~OMAP_LCDC_IRQ_DONE; +			omap_writel(l, OMAP_LCDC_CONTROL); +			complete(&lcdc.last_frame_complete); +		} +		if (status & OMAP_LCDC_STAT_LOADED_PALETTE) { +			disable_controller_async(); +			complete(&lcdc.palette_load_complete); +		} +	} + +	/* +	 * Clear these interrupt status bits. +	 * Sync_lost, FUF bits were cleared by disabling the LCD controller +	 * LOADED_PALETTE can be cleared this way only in palette only +	 * load mode. In other load modes it's cleared by disabling the +	 * controller. +	 */ +	status &= ~(OMAP_LCDC_STAT_VSYNC | +		    OMAP_LCDC_STAT_LOADED_PALETTE | +		    OMAP_LCDC_STAT_ABC | +		    OMAP_LCDC_STAT_LINE_INT); +	omap_writel(status, OMAP_LCDC_STATUS); +	return IRQ_HANDLED; +} + +/* + * Change to a new video mode. We defer this to a later time to avoid any + * flicker and not to mess up the current LCD DMA context. For this we disable + * the LCD controller, which will generate a DONE irq after the last frame has + * been transferred. Then it'll be safe to reconfigure both the LCD controller + * as well as the LCD DMA. + */ +static int omap_lcdc_setup_plane(int plane, int channel_out, +				 unsigned long offset, int screen_width, +				 int pos_x, int pos_y, int width, int height, +				 int color_mode) +{ +	struct fb_var_screeninfo *var = &lcdc.fbdev->fb_info[0]->var; +	struct lcd_panel *panel = lcdc.fbdev->panel; +	int rot_x, rot_y; + +	if (var->rotate == 0) { +		rot_x = panel->x_res; +		rot_y = panel->y_res; +	} else { +		rot_x = panel->y_res; +		rot_y = panel->x_res; +	} +	if (plane != 0 || channel_out != 0 || pos_x != 0 || pos_y != 0 || +	    width > rot_x || height > rot_y) { +#ifdef VERBOSE +		dev_dbg(lcdc.fbdev->dev, +			"invalid plane params plane %d pos_x %d pos_y %d " +			"w %d h %d\n", plane, pos_x, pos_y, width, height); +#endif +		return -EINVAL; +	} + +	lcdc.frame_offset = offset; +	lcdc.xres = width; +	lcdc.yres = height; +	lcdc.screen_width = screen_width; +	lcdc.color_mode = color_mode; + +	switch (color_mode) { +	case OMAPFB_COLOR_CLUT_8BPP: +		lcdc.bpp = 8; +		lcdc.palette_code = 0x3000; +		lcdc.palette_size = 512; +		break; +	case OMAPFB_COLOR_RGB565: +		lcdc.bpp = 16; +		lcdc.palette_code = 0x4000; +		lcdc.palette_size = 32; +		break; +	case OMAPFB_COLOR_RGB444: +		lcdc.bpp = 16; +		lcdc.palette_code = 0x4000; +		lcdc.palette_size = 32; +		break; +	case OMAPFB_COLOR_YUV420: +		if (lcdc.ext_mode) { +			lcdc.bpp = 12; +			break; +		} +		/* fallthrough */ +	case OMAPFB_COLOR_YUV422: +		if (lcdc.ext_mode) { +			lcdc.bpp = 16; +			break; +		} +		/* fallthrough */ +	default: +		/* FIXME: other BPPs. +		 * bpp1: code  0,     size 256 +		 * bpp2: code  0x1000 size 256 +		 * bpp4: code  0x2000 size 256 +		 * bpp12: code 0x4000 size 32 +		 */ +		dev_dbg(lcdc.fbdev->dev, "invalid color mode %d\n", color_mode); +		BUG(); +		return -1; +	} + +	if (lcdc.ext_mode) { +		setup_lcd_dma(); +		return 0; +	} + +	if (lcdc.update_mode == OMAPFB_AUTO_UPDATE) { +		disable_controller(); +		omap_stop_lcd_dma(); +		setup_lcd_dma(); +		enable_controller(); +	} + +	return 0; +} + +static int omap_lcdc_enable_plane(int plane, int enable) +{ +	dev_dbg(lcdc.fbdev->dev, +		"plane %d enable %d update_mode %d ext_mode %d\n", +		plane, enable, lcdc.update_mode, lcdc.ext_mode); +	if (plane != OMAPFB_PLANE_GFX) +		return -EINVAL; + +	return 0; +} + +/* + * Configure the LCD DMA for a palette load operation and do the palette + * downloading synchronously. We don't use the frame+palette load mode of + * the controller, since the palette can always be downloaded separately. + */ +static void load_palette(void) +{ +	u16	*palette; + +	palette = (u16 *)lcdc.palette_virt; + +	*(u16 *)palette &= 0x0fff; +	*(u16 *)palette |= lcdc.palette_code; + +	omap_set_lcd_dma_b1(lcdc.palette_phys, +		lcdc.palette_size / 4 + 1, 1, OMAP_DMA_DATA_TYPE_S32); + +	omap_set_lcd_dma_single_transfer(1); +	omap_setup_lcd_dma(); + +	init_completion(&lcdc.palette_load_complete); +	enable_irqs(OMAP_LCDC_IRQ_LOADED_PALETTE); +	set_load_mode(OMAP_LCDC_LOAD_PALETTE); +	enable_controller(); +	if (!wait_for_completion_timeout(&lcdc.palette_load_complete, +				msecs_to_jiffies(500))) +		dev_err(lcdc.fbdev->dev, "timeout waiting for FRAME DONE\n"); +	/* The controller gets disabled in the irq handler */ +	disable_irqs(OMAP_LCDC_IRQ_LOADED_PALETTE); +	omap_stop_lcd_dma(); + +	omap_set_lcd_dma_single_transfer(lcdc.ext_mode); +} + +/* Used only in internal controller mode */ +static int omap_lcdc_setcolreg(u_int regno, u16 red, u16 green, u16 blue, +			       u16 transp, int update_hw_pal) +{ +	u16 *palette; + +	if (lcdc.color_mode != OMAPFB_COLOR_CLUT_8BPP || regno > 255) +		return -EINVAL; + +	palette = (u16 *)lcdc.palette_virt; + +	palette[regno] &= ~0x0fff; +	palette[regno] |= ((red >> 12) << 8) | ((green >> 12) << 4 ) | +			   (blue >> 12); + +	if (update_hw_pal) { +		disable_controller(); +		omap_stop_lcd_dma(); +		load_palette(); +		setup_lcd_dma(); +		set_load_mode(OMAP_LCDC_LOAD_FRAME); +		enable_controller(); +	} + +	return 0; +} + +static void calc_ck_div(int is_tft, int pck, int *pck_div) +{ +	unsigned long lck; + +	pck = max(1, pck); +	lck = clk_get_rate(lcdc.lcd_ck); +	*pck_div = (lck + pck - 1) / pck; +	if (is_tft) +		*pck_div = max(2, *pck_div); +	else +		*pck_div = max(3, *pck_div); +	if (*pck_div > 255) { +		/* FIXME: try to adjust logic clock divider as well */ +		*pck_div = 255; +		dev_warn(lcdc.fbdev->dev, "pixclock %d kHz too low.\n", +			 pck / 1000); +	} +} + +static void inline setup_regs(void) +{ +	u32 l; +	struct lcd_panel *panel = lcdc.fbdev->panel; +	int is_tft = panel->config & OMAP_LCDC_PANEL_TFT; +	unsigned long lck; +	int pcd; + +	l = omap_readl(OMAP_LCDC_CONTROL); +	l &= ~OMAP_LCDC_CTRL_LCD_TFT; +	l |= is_tft ? OMAP_LCDC_CTRL_LCD_TFT : 0; +#ifdef CONFIG_MACH_OMAP_PALMTE +/* FIXME:if (machine_is_omap_palmte()) { */ +		/* PalmTE uses alternate TFT setting in 8BPP mode */ +		l |= (is_tft && panel->bpp == 8) ? 0x810000 : 0; +/*	} */ +#endif +	omap_writel(l, OMAP_LCDC_CONTROL); + +	l = omap_readl(OMAP_LCDC_TIMING2); +	l &= ~(((1 << 6) - 1) << 20); +	l |= (panel->config & OMAP_LCDC_SIGNAL_MASK) << 20; +	omap_writel(l, OMAP_LCDC_TIMING2); + +	l = panel->x_res - 1; +	l |= (panel->hsw - 1) << 10; +	l |= (panel->hfp - 1) << 16; +	l |= (panel->hbp - 1) << 24; +	omap_writel(l, OMAP_LCDC_TIMING0); + +	l = panel->y_res - 1; +	l |= (panel->vsw - 1) << 10; +	l |= panel->vfp << 16; +	l |= panel->vbp << 24; +	omap_writel(l, OMAP_LCDC_TIMING1); + +	l = omap_readl(OMAP_LCDC_TIMING2); +	l &= ~0xff; + +	lck = clk_get_rate(lcdc.lcd_ck); + +	if (!panel->pcd) +		calc_ck_div(is_tft, panel->pixel_clock * 1000, &pcd); +	else { +		dev_warn(lcdc.fbdev->dev, +		    "Pixel clock divider value is obsolete.\n" +		    "Try to set pixel_clock to %lu and pcd to 0 " +		    "in drivers/video/omap/lcd_%s.c and submit a patch.\n", +			lck / panel->pcd / 1000, panel->name); + +		pcd = panel->pcd; +	} +	l |= pcd & 0xff; +	l |= panel->acb << 8; +	omap_writel(l, OMAP_LCDC_TIMING2); + +	/* update panel info with the exact clock */ +	panel->pixel_clock = lck / pcd / 1000; +} + +/* + * Configure the LCD controller, download the color palette and start a looped + * DMA transfer of the frame image data. Called only in internal + * controller mode. + */ +static int omap_lcdc_set_update_mode(enum omapfb_update_mode mode) +{ +	int r = 0; + +	if (mode != lcdc.update_mode) { +		switch (mode) { +		case OMAPFB_AUTO_UPDATE: +			setup_regs(); +			load_palette(); + +			/* Setup and start LCD DMA */ +			setup_lcd_dma(); + +			set_load_mode(OMAP_LCDC_LOAD_FRAME); +			enable_irqs(OMAP_LCDC_IRQ_DONE); +			/* This will start the actual DMA transfer */ +			enable_controller(); +			lcdc.update_mode = mode; +			break; +		case OMAPFB_UPDATE_DISABLED: +			disable_controller(); +			omap_stop_lcd_dma(); +			lcdc.update_mode = mode; +			break; +		default: +			r = -EINVAL; +		} +	} + +	return r; +} + +static enum omapfb_update_mode omap_lcdc_get_update_mode(void) +{ +	return lcdc.update_mode; +} + +/* PM code called only in internal controller mode */ +static void omap_lcdc_suspend(void) +{ +	omap_lcdc_set_update_mode(OMAPFB_UPDATE_DISABLED); +} + +static void omap_lcdc_resume(void) +{ +	omap_lcdc_set_update_mode(OMAPFB_AUTO_UPDATE); +} + +static void omap_lcdc_get_caps(int plane, struct omapfb_caps *caps) +{ +	return; +} + +int omap_lcdc_set_dma_callback(void (*callback)(void *data), void *data) +{ +	BUG_ON(callback == NULL); + +	if (lcdc.dma_callback) +		return -EBUSY; +	else { +		lcdc.dma_callback = callback; +		lcdc.dma_callback_data = data; +	} +	return 0; +} +EXPORT_SYMBOL(omap_lcdc_set_dma_callback); + +void omap_lcdc_free_dma_callback(void) +{ +	lcdc.dma_callback = NULL; +} +EXPORT_SYMBOL(omap_lcdc_free_dma_callback); + +static void lcdc_dma_handler(u16 status, void *data) +{ +	if (lcdc.dma_callback) +		lcdc.dma_callback(lcdc.dma_callback_data); +} + +static int alloc_palette_ram(void) +{ +	lcdc.palette_virt = dma_alloc_writecombine(lcdc.fbdev->dev, +		MAX_PALETTE_SIZE, &lcdc.palette_phys, GFP_KERNEL); +	if (lcdc.palette_virt == NULL) { +		dev_err(lcdc.fbdev->dev, "failed to alloc palette memory\n"); +		return -ENOMEM; +	} +	memset(lcdc.palette_virt, 0, MAX_PALETTE_SIZE); + +	return 0; +} + +static void free_palette_ram(void) +{ +	dma_free_writecombine(lcdc.fbdev->dev, MAX_PALETTE_SIZE, +			lcdc.palette_virt, lcdc.palette_phys); +} + +static int alloc_fbmem(struct omapfb_mem_region *region) +{ +	int bpp; +	int frame_size; +	struct lcd_panel *panel = lcdc.fbdev->panel; + +	bpp = panel->bpp; +	if (bpp == 12) +		bpp = 16; +	frame_size = PAGE_ALIGN(panel->x_res * bpp / 8 * panel->y_res); +	if (region->size > frame_size) +		frame_size = region->size; +	lcdc.vram_size = frame_size; +	lcdc.vram_virt = dma_alloc_writecombine(lcdc.fbdev->dev, +			lcdc.vram_size, &lcdc.vram_phys, GFP_KERNEL); +	if (lcdc.vram_virt == NULL) { +		dev_err(lcdc.fbdev->dev, "unable to allocate FB DMA memory\n"); +		return -ENOMEM; +	} +	region->size = frame_size; +	region->paddr = lcdc.vram_phys; +	region->vaddr = lcdc.vram_virt; +	region->alloc = 1; + +	memset(lcdc.vram_virt, 0, lcdc.vram_size); + +	return 0; +} + +static void free_fbmem(void) +{ +	dma_free_writecombine(lcdc.fbdev->dev, lcdc.vram_size, +			      lcdc.vram_virt, lcdc.vram_phys); +} + +static int setup_fbmem(struct omapfb_mem_desc *req_md) +{ +	if (!req_md->region_cnt) { +		dev_err(lcdc.fbdev->dev, "no memory regions defined\n"); +		return -EINVAL; +	} + +	if (req_md->region_cnt > 1) { +		dev_err(lcdc.fbdev->dev, "only one plane is supported\n"); +		req_md->region_cnt = 1; +	} + +	return alloc_fbmem(&req_md->region[0]); +} + +static int omap_lcdc_init(struct omapfb_device *fbdev, int ext_mode, +			  struct omapfb_mem_desc *req_vram) +{ +	int r; +	u32 l; +	int rate; +	struct clk *tc_ck; + +	lcdc.irq_mask = 0; + +	lcdc.fbdev = fbdev; +	lcdc.ext_mode = ext_mode; + +	l = 0; +	omap_writel(l, OMAP_LCDC_CONTROL); + +	/* FIXME: +	 * According to errata some platforms have a clock rate limitiation +	 */ +	lcdc.lcd_ck = clk_get(fbdev->dev, "lcd_ck"); +	if (IS_ERR(lcdc.lcd_ck)) { +		dev_err(fbdev->dev, "unable to access LCD clock\n"); +		r = PTR_ERR(lcdc.lcd_ck); +		goto fail0; +	} + +	tc_ck = clk_get(fbdev->dev, "tc_ck"); +	if (IS_ERR(tc_ck)) { +		dev_err(fbdev->dev, "unable to access TC clock\n"); +		r = PTR_ERR(tc_ck); +		goto fail1; +	} + +	rate = clk_get_rate(tc_ck); +	clk_put(tc_ck); + +	if (machine_is_ams_delta()) +		rate /= 4; +	if (machine_is_omap_h3()) +		rate /= 3; +	r = clk_set_rate(lcdc.lcd_ck, rate); +	if (r) { +		dev_err(fbdev->dev, "failed to adjust LCD rate\n"); +		goto fail1; +	} +	clk_enable(lcdc.lcd_ck); + +	r = request_irq(OMAP_LCDC_IRQ, lcdc_irq_handler, 0, MODULE_NAME, fbdev); +	if (r) { +		dev_err(fbdev->dev, "unable to get IRQ\n"); +		goto fail2; +	} + +	r = omap_request_lcd_dma(lcdc_dma_handler, NULL); +	if (r) { +		dev_err(fbdev->dev, "unable to get LCD DMA\n"); +		goto fail3; +	} + +	omap_set_lcd_dma_single_transfer(ext_mode); +	omap_set_lcd_dma_ext_controller(ext_mode); + +	if (!ext_mode) +		if ((r = alloc_palette_ram()) < 0) +			goto fail4; + +	if ((r = setup_fbmem(req_vram)) < 0) +		goto fail5; + +	pr_info("omapfb: LCDC initialized\n"); + +	return 0; +fail5: +	if (!ext_mode) +		free_palette_ram(); +fail4: +	omap_free_lcd_dma(); +fail3: +	free_irq(OMAP_LCDC_IRQ, lcdc.fbdev); +fail2: +	clk_disable(lcdc.lcd_ck); +fail1: +	clk_put(lcdc.lcd_ck); +fail0: +	return r; +} + +static void omap_lcdc_cleanup(void) +{ +	if (!lcdc.ext_mode) +		free_palette_ram(); +	free_fbmem(); +	omap_free_lcd_dma(); +	free_irq(OMAP_LCDC_IRQ, lcdc.fbdev); +	clk_disable(lcdc.lcd_ck); +	clk_put(lcdc.lcd_ck); +} + +const struct lcd_ctrl omap1_int_ctrl = { +	.name			= "internal", +	.init			= omap_lcdc_init, +	.cleanup		= omap_lcdc_cleanup, +	.get_caps		= omap_lcdc_get_caps, +	.set_update_mode	= omap_lcdc_set_update_mode, +	.get_update_mode	= omap_lcdc_get_update_mode, +	.update_window		= NULL, +	.suspend		= omap_lcdc_suspend, +	.resume			= omap_lcdc_resume, +	.setup_plane		= omap_lcdc_setup_plane, +	.enable_plane		= omap_lcdc_enable_plane, +	.setcolreg		= omap_lcdc_setcolreg, +};  | 
