diff options
Diffstat (limited to 'drivers/video/s3c2410fb_tft.c')
-rw-r--r-- | drivers/video/s3c2410fb_tft.c | 1008 |
1 files changed, 1008 insertions, 0 deletions
diff --git a/drivers/video/s3c2410fb_tft.c b/drivers/video/s3c2410fb_tft.c new file mode 100644 index 000000000000..8632beb68a7e --- /dev/null +++ b/drivers/video/s3c2410fb_tft.c @@ -0,0 +1,1008 @@ +/* -*- linux-c -*- + * + * linux/drivers/video/s3cfb.c + * + * $Id: s3cfb.c,v 1.63 2007/07/12 05:26:04 yreom Exp $ + * + * Revision 1.16 2006/09/14 04:45:15 ihlee215 + * OSD support added + * + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + * + * S3C LCD Controller Frame Buffer Driver + * based on skeletonfb.c, sa1100fb.c + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/tty.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/wait.h> +#include <linux/platform_device.h> +#include <linux/clk.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/div64.h> +#include <linux/math64.h> + +#include <asm/mach/map.h> +/* #include <asm/arch/registers.h> */ +#include <mach/idle.h> +#include <mach/fb.h> +#include <mach/regs-gpio.h> + +#include "s3c2410fb.h" + + +#ifdef CONFIG_PM +#include <linux/pm.h> +#endif + + + + +#define printk_err(fmt, args...) printk(KERN_ERR "[ ERROR ] s3cfb: " fmt, ## args) +#define printk_info(fmt, args...) printk(KERN_INFO "[ INFO ] s3cfb: " fmt, ## args) + + +#if 1 +#define S3CFB_DEBUG +#endif + +#ifdef S3CFB_DEBUG +# define printk_debug(fmt, args...) printk(KERN_DEBUG "s3cfb: " fmt, ## args) +#else +# define printk_debug(fmt, args...) +#endif + + + +#define S3CFB_DRIVER_NAME "s3c2410fb-tft" + + +static int s3c2410fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct s3c2410fb_info *fbi = info->par; + struct s3c2410fb_mach_info *mach_info = fbi->dev->platform_data; + struct s3c2410fb_display *display = NULL; + struct s3c2410fb_display *default_display = mach_info->displays + + mach_info->default_display; + int type = default_display->type; + unsigned i; + + printk_debug("Calling %s: var=%p, info=%p\n", __func__, var, info); + + /* validate x/y resolution */ + /* choose default mode if possible */ + if (var->yres == default_display->yres && + var->xres == default_display->xres && + var->bits_per_pixel == default_display->bpp) + display = default_display; + else + for (i = 0; i < mach_info->num_displays; i++) + if (type == mach_info->displays[i].type && + var->yres == mach_info->displays[i].yres && + var->xres == mach_info->displays[i].xres && + var->bits_per_pixel == mach_info->displays[i].bpp) { + display = mach_info->displays + i; + break; + } + + if (!display) { + printk_err("wrong resolution or depth %dx%d at %d bpp\n", + var->xres, var->yres, var->bits_per_pixel); + return -EINVAL; + } + + /* it is always the size as the display */ + var->xres_virtual = display->xres; + var->yres_virtual = display->yres; + var->height = display->height; + var->width = display->width; + + /* copy lcd settings */ + var->pixclock = display->pixclock; + var->left_margin = display->left_margin; + var->right_margin = display->right_margin; + var->upper_margin = display->upper_margin; + var->lower_margin = display->lower_margin; + var->vsync_len = display->vsync_len; + var->hsync_len = display->hsync_len; + + /* + * If using the Power Signal then request the GPIO + * (@XXX: Use the correct request and configuration function for the GPIO) + * Luis Galdos + */ + if (display->lcdcon5 & S3C2410_LCDCON5_PWREN) { + printk_info("Configuring the power LED GPIO\n"); + s3c2410_gpio_cfgpin(S3C2410_GPG4, S3C2410_GPG4_LCDPWREN); + } + + var->transp.offset = 0; + var->transp.length = 0; + /* set r/g/b positions */ + switch (var->bits_per_pixel) { + case 1: + case 2: + case 4: + var->red.offset = 0; + var->red.length = var->bits_per_pixel; + var->green = var->red; + var->blue = var->red; + break; + case 8: + /* 8 bpp 332 */ + var->red.length = 3; + var->red.offset = 5; + var->green.length = 3; + var->green.offset = 2; + var->blue.length = 2; + var->blue.offset = 0; + break; + case 12: + /* 12 bpp 444 */ + var->red.length = 4; + var->red.offset = 8; + var->green.length = 4; + var->green.offset = 4; + var->blue.length = 4; + var->blue.offset = 0; + break; + case 16: + /* 16 bpp, 565 format */ + if (S3C24XX_LCD_WINCON_BPP(display->wincon0) == + S3C24XX_LCD_WINCON_16BPP_565) { + /* 16 bpp, 565 format */ + var->red.offset = 11; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 6; + var->blue.length = 5; + } else { + /* 16 bpp, 1555 format */ + var->red.offset = 10; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 5; + var->blue.length = 5; + } + break; + case 18: + /* 18bpp only supports 666 */ + var->red.offset = 12; + var->green.offset = 6; + var->blue.offset = 0; + var->red.length = 6; + var->green.length = 6; + var->blue.length = 6; + break; + case 32: + /* 24 bpp 888 and 8 dummy */ + var->red.length = 8; + var->red.offset = 16; + var->green.length = 8; + var->green.offset = 8; + var->blue.length = 8; + var->blue.offset = 0; + break; + } + return 0; +} + + +#if 0 +static void s3cfb_dummy_values(struct s3c2410fb_info *fbi) +{ + + writel(0x10415, fbi->io + S3C24XX_LCD_WINCON0); + writel(0x00, fbi->io + S3C24XX_LCD_WINCON1); + + writel(0x173, fbi->io + S3C24XX_LCD_VIDCON0); + writel(0x195D060, fbi->io + S3C24XX_LCD_VIDCON1); + + writel(0x1F0203, fbi->io + S3C24XX_LCD_VIDTCON0); + writel(0x40618, fbi->io + S3C24XX_LCD_VIDTCON1); + writel(0xEFA7F, fbi->io + S3C24XX_LCD_VIDTCON2); + + writel(0x00, fbi->io + S3C24XX_LCD_VIDOSD0A); + writel(0x13F9DF, fbi->io + S3C24XX_LCD_VIDOSD0B); +} +#endif /* 0: Only for testing */ + + +/* Write into the register the passed display configuration */ +static void s3c2410fb_activate_var(struct fb_info *info) +{ + struct s3c2410fb_info *fbi; + unsigned short hsync_cnt, vclk_cnt; + unsigned char clkval; + struct s3c2410fb_display *display; + struct s3c2410fb_mach_info *mach_info; + unsigned long pixel_clk, lcd_clk; + unsigned long vidcon0, vidcon1, vidtcon0, vidtcon1, vidtcon2; + unsigned long wincon0, wincon1; + unsigned long vidosd0a, vidosd0b; + + printk_debug("Calling %s\n", __func__); + + fbi = info->par; + mach_info = fbi->dev->platform_data; + display = mach_info->displays + mach_info->default_display; + + /* Write the configuration into the register VIDCON0 */ + hsync_cnt = display->lower_margin + /* VBPD */ + display->upper_margin + /* VFPD */ + display->vsync_len + /* VSPW */ + display->height; /* LINEVAL */ + + vclk_cnt = display->right_margin + /* HBPD */ + display->left_margin + /* HFPD */ + display->hsync_len + /* HSPW */ + display->width; /* HOZVAL */ + + pixel_clk = (display->frame_rate * vclk_cnt * hsync_cnt); + lcd_clk = clk_get_rate(fbi->clk); + + /* + * @FIXME: The U-Boot has another clock calculation. See under: + * cpu/s3c24xx/s3c2443/fb.c + */ + clkval = (lcd_clk / pixel_clk) - 1; + printk_debug("Calculated CLKVAL is 0x%x (pixel clk: %lu | lcd clk %lu)\n", + clkval, pixel_clk, lcd_clk); + + vidcon1 = readl(fbi->io + S3C24XX_LCD_VIDCON1); + vidcon0 = readl(fbi->io + S3C24XX_LCD_VIDCON0); + wincon1 = readl(fbi->io + S3C24XX_LCD_WINCON1); + + /* Configure the clock */ + vidcon0 |= (display->vidcon0 | S3C24XX_LCD_VIDCON0_CLKVAL(clkval)); + + vidcon1 |= display->vidcon1; + + vidtcon0 = S3C24XX_LCD_VIDTCON0_VSPW(display->vsync_len - 1) | + S3C24XX_LCD_VIDTCON0_VFPD(display->upper_margin - 1) | + S3C24XX_LCD_VIDTCON0_VBPD(display->lower_margin - 1); + + vidtcon1 = S3C24XX_LCD_VIDTCON1_HSPW(display->hsync_len - 1) | + S3C24XX_LCD_VIDTCON1_HFPD(display->left_margin - 1) | + S3C24XX_LCD_VIDTCON1_HBPD(display->right_margin - 1); + + + vidtcon2 = S3C24XX_LCD_VIDTCON2_HOZVAL(display->width - 1) | + S3C24XX_LCD_VIDTCON2_LINEVAL(display->height - 1); + + /* Write the user configuration too */ + wincon0 = (display->wincon0 | + S3C24XX_LCD_WINCON0_ENWIN_F | + S3C24XX_LCD_WINCON0_HAWSWP | + S3C24XX_LCD_WINCON0_4WBURST); + + + vidosd0a = 0x00; + vidosd0b = S3C24XX_LCD_VIDOSD0B_RIGHT_X(display->width - 1) | + S3C24XX_LCD_VIDOSD0B_RIGHT_Y(display->height - 1); + + + /* And now write the configuration into the corresponding registers */ + writel(vidcon0, fbi->io + S3C24XX_LCD_VIDCON0); + writel(vidcon1, fbi->io + S3C24XX_LCD_VIDCON1); + writel(vidtcon0, fbi->io + S3C24XX_LCD_VIDTCON0); + writel(vidtcon1, fbi->io + S3C24XX_LCD_VIDTCON1); + writel(vidtcon2, fbi->io + S3C24XX_LCD_VIDTCON2); + writel(wincon0, fbi->io + S3C24XX_LCD_WINCON0); + writel(vidosd0a, fbi->io + S3C24XX_LCD_VIDOSD0A); + writel(vidosd0b, fbi->io + S3C24XX_LCD_VIDOSD0B); + + /* These are the configuration addresses for the frame buffer */ + writel(info->fix.smem_start, fbi->io + S3C24XX_LCD_VIDW00ADD0B0); + writel(info->fix.smem_start + info->fix.smem_len, + fbi->io + S3C24XX_LCD_VIDW00ADD1B0); + + printk_debug("vidcon0 0x%08x | vidcon1 0x%08x\n", + (unsigned int)vidcon0, + (unsigned int)vidcon1); + printk_debug("vidtcon0 0x%08x | vidtcon1 0x%08x | vidtcon2 0x%08x\n", + (unsigned int)vidtcon0, + (unsigned int)vidtcon1, + (unsigned int)vidtcon2); +} + + +/* Write the display configuration to the hardware */ +static int s3c2410fb_set_par(struct fb_info *info) +{ + struct fb_var_screeninfo *var = &info->var; + + printk_debug("Calling %s\n", __func__); + + switch (var->bits_per_pixel) { + case 32: + case 16: + case 12: + info->fix.visual = FB_VISUAL_TRUECOLOR; + break; + case 1: + info->fix.visual = FB_VISUAL_MONO01; + break; + default: + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + break; + } + + + info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8; + printk_debug("Line length is %i\n", info->fix.line_length); + + + /* activate this new configuration */ + s3c2410fb_activate_var(info); + return 0; +} + + +static void s3c2410fb_lcd_enable(struct s3c2410fb_info *fbi, int enable) +{ + unsigned long flags; + unsigned long vidcon0; + + local_irq_save(flags); + + printk_debug("%s the TFT LCD\n", enable ? "Enabling" : "Disabling"); + + vidcon0 = readl(fbi->io + S3C24XX_LCD_VIDCON0); + if (enable) + vidcon0 |= (S3C24XX_LCD_VIDCON0_ENVID | S3C24XX_LCD_VIDCON0_ENVID_F); + else + vidcon0 &= ~(S3C24XX_LCD_VIDCON0_ENVID | S3C24XX_LCD_VIDCON0_ENVID_F); + + writel(vidcon0, fbi->io + S3C24XX_LCD_VIDCON0); + local_irq_restore(flags); +} + + + +static int s3c2410fb_blank(int blank_mode, struct fb_info *info) +{ + struct s3c2410fb_info *fbi = info->par; + + printk_debug("Blank (mode=%d, info=%p, power down=%i)\n", + blank_mode, info, FB_BLANK_POWERDOWN); + + if (blank_mode == FB_BLANK_POWERDOWN) { + s3c2410fb_lcd_enable(fbi, 0); + } else { + s3c2410fb_lcd_enable(fbi, 1); + } + + return 0; +} + +static void schedule_palette_update(struct s3c2410fb_info *fbi, + unsigned int regno, unsigned int val) +{ + unsigned long flags; + unsigned long irqen; + void __iomem *irq_base = fbi->irq_base; + + printk_debug("Calling %s\n", __func__); + + local_irq_save(flags); + + fbi->palette_buffer[regno] = val; + + if (!fbi->palette_ready) { + fbi->palette_ready = 1; + /* enable IRQ */ + irqen = readl(irq_base + S3C24XX_LCDINTMSK); + irqen &= ~S3C2410_LCDINT_FRSYNC; + writel(irqen, irq_base + S3C24XX_LCDINTMSK); + } + + local_irq_restore(flags); +} + + +/* from pxafb.c */ +static inline unsigned int chan_to_field(unsigned int chan, + struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + + + +static int s3c2410fb_setcolreg(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + struct s3c2410fb_info *fbi = info->par; + void __iomem *regs = fbi->io; + unsigned int val; + + + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* true-colour, use pseudo-palette */ + if (regno < 16) { + u32 *pal = info->pseudo_palette; + + val = chan_to_field(red, &info->var.red); + val |= chan_to_field(green, &info->var.green); + val |= chan_to_field(blue, &info->var.blue); + + pal[regno] = val; + } + break; + + case FB_VISUAL_PSEUDOCOLOR: + if (regno < 256) { + /* currently assume RGB 5-6-5 mode */ + + val = (red >> 0) & 0xf800; + val |= (green >> 5) & 0x07e0; + val |= (blue >> 11) & 0x001f; + + writel(val, regs + S3C2410_TFTPAL(regno)); + schedule_palette_update(fbi, regno, val); + } + + break; + + default: + return 1; /* unknown type */ + } + + return 0; +} + + + +/* Function called when the frame buffer device (/dev/fbX) is being opened */ +static int s3c2410fb_tft_open(struct fb_info *info, int user) +{ + struct s3c2410fb_info *fbi; + + fbi = info->par; + + printk_debug("Calling %s\n", __func__); + s3c2410fb_lcd_enable(fbi, 1); + return 0; +} + + +static int s3c2410fb_tft_release(struct fb_info *info, int user) +{ + struct s3c2410fb_info *fbi; + + fbi = info->par; + printk_debug("Calling %s\n", __func__); + s3c2410fb_lcd_enable(fbi, 0); + return 0; +} + + +static int s3c2410fb_tft_pan(struct fb_var_screeninfo *var, struct fb_info *info) +{ + + + + return 0; +} + +/* These are the available device operations */ +static struct fb_ops s3cfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = s3c2410fb_check_var, + .fb_set_par = s3c2410fb_set_par, + .fb_blank = s3c2410fb_blank, + .fb_setcolreg = s3c2410fb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_open = s3c2410fb_tft_open, + .fb_release = s3c2410fb_tft_release, + .fb_pan_display = s3c2410fb_tft_pan, +}; + + + +static irqreturn_t s3cfb_irq(int irq, void *dev_id) +{ + printk_info("IRQ of the TFT LCD enabled?\n"); +/* struct s3c2410fb_info *fbi = dev_id; */ +/* void __iomem *irq_base = fbi->irq_base; */ +/* unsigned long lcdirq = readl(irq_base + S3C24XX_LCDINTPND); */ + +/* if (lcdirq & S3C2410_LCDINT_FRSYNC) { */ +/* if (fbi->palette_ready) */ +/* s3c2410fb_write_palette(fbi); */ + +/* writel(S3C2410_LCDINT_FRSYNC, irq_base + S3C24XX_LCDINTPND); */ +/* writel(S3C2410_LCDINT_FRSYNC, irq_base + S3C24XX_LCDSRCPND); */ +/* } */ + + return IRQ_HANDLED; +} + + +/* + * s3c2410fb_map_video_memory(): + * Allocates the DRAM memory for the frame buffer. This buffer is + * remapped into a non-cached, non-buffered, memory region to + * allow palette and pixel writes to occur without flushing the + * cache. Once this area is remapped, all virtual memory + * access to the video memory should occur at the new region. + */ +static int __init s3c2410fb_map_video_memory(struct fb_info *info) +{ + struct s3c2410fb_info *fbi = info->par; + dma_addr_t map_dma; + unsigned map_size = PAGE_ALIGN(info->fix.smem_len); + + printk_debug("map_video_memory(fbi=%p) map_size %u\n", fbi, map_size); + + info->screen_base = dma_alloc_writecombine(fbi->dev, map_size, + &map_dma, GFP_KERNEL); + + if (info->screen_base) { + /* prevent initial garbage on screen */ + printk_debug("map_video_memory: clear %p:%08x\n", + info->screen_base, map_size); + memset(info->screen_base, 0x00, map_size); + + info->fix.smem_start = map_dma; + info->fix.smem_len = map_size; + + printk_debug("map_video_memory: dma=%08lx cpu=%p size=%08x\n", + info->fix.smem_start, info->screen_base, map_size); + } + + return info->screen_base ? 0 : -ENOMEM; +} + + + +static inline void modify_gpio(void __iomem *reg, + unsigned long set, unsigned long mask) +{ + unsigned long tmp; + + tmp = readl(reg) & ~mask; + writel(tmp | set, reg); +} + + + +static int s3c2410fb_init_registers(struct fb_info *info) +{ + struct s3c2410fb_info *fbi = info->par; + struct s3c2410fb_mach_info *mach_info = fbi->dev->platform_data; + unsigned long flags; + + /* Initialise LCD with values from haret */ + + local_irq_save(flags); + + /* modify the gpio(s) with interrupts set (bjd) */ + + modify_gpio(S3C2410_GPCUP, mach_info->gpcup, mach_info->gpcup_mask); + modify_gpio(S3C2410_GPCCON, mach_info->gpccon, mach_info->gpccon_mask); + modify_gpio(S3C2410_GPDUP, mach_info->gpdup, mach_info->gpdup_mask); + modify_gpio(S3C2410_GPDCON, mach_info->gpdcon, mach_info->gpdcon_mask); + + local_irq_restore(flags); + +/* printk_debug("LPCSEL = 0x%08lx\n", mach_info->lpcsel); */ +/* writel(mach_info->lpcsel, lpcsel); */ + +/* printk_debug("replacing TPAL %08x\n", readl(tpal)); */ + + /* ensure temporary palette disabled */ +/* writel(0x00, tpal); */ + + return 0; +} + + + +static inline void s3c2410fb_unmap_video_memory(struct fb_info *info) +{ + struct s3c2410fb_info *fbi = info->par; + + dma_free_writecombine(fbi->dev, PAGE_ALIGN(info->fix.smem_len), + info->screen_base, info->fix.smem_start); +} + + + +int __init s3c_fb_probe(struct platform_device *pdev) +{ + struct s3c2410fb_info *info; + struct s3c2410fb_display *display; + struct fb_info *fbinfo; + struct s3c2410fb_mach_info *mach_info; + struct resource *res; + int ret; + int irq; + int i; + int size; + u32 vidcon0; + + /* @XXX: Use a higher probe for setting the type */ + enum s3c_drv_type drv_type = DRV_S3C2410; + + printk_debug("Probing a new device ID %i\n", pdev->id); + + + /* Get the user defined LCD controller configuration */ + mach_info = pdev->dev.platform_data; + if (mach_info == NULL) { + printk_err("No platform data for lcd, cannot attach\n"); + return -EINVAL; + } + + if (mach_info->default_display >= mach_info->num_displays) { + printk_err("Default is %d but only %d displays\n", + mach_info->default_display, mach_info->num_displays); + return -EINVAL; + } + + /* Get the user defined display configuration */ + display = mach_info->displays + mach_info->default_display; + + /* Get the IRQ for the display controller */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + printk_err("No irq for device.\n"); + return -ENOENT; + } + + /* Allocate the frame buffer for this device */ + fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev); + if (!fbinfo) + return -ENOMEM; + + platform_set_drvdata(pdev, fbinfo); + + /* Use the private data for our internal settings */ + info = fbinfo->par; + info->dev = &pdev->dev; + info->drv_type = drv_type; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "failed to get memory registers\n"); + ret = -ENXIO; + goto dealloc_fb; + } + + size = (res->end - res->start) + 1; + info->mem = request_mem_region(res->start, size, pdev->name); + if (info->mem == NULL) { + dev_err(&pdev->dev, "failed to get memory region\n"); + ret = -ENOENT; + goto dealloc_fb; + } + + info->io = ioremap(res->start, size); + if (info->io == NULL) { + dev_err(&pdev->dev, "ioremap() of registers failed\n"); + ret = -ENXIO; + goto release_mem; + } + + info->irq_base = info->io + + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE); + + /* @XXX: Do we really need the below code? */ + strcpy(fbinfo->fix.id, S3CFB_DRIVER_NAME); + + /* Stop the video first */ + vidcon0 = readl(info->io + S3C24XX_LCD_VIDCON0); + vidcon0 &= (S3C24XX_LCD_VIDCON0_ENVID | S3C24XX_LCD_VIDCON0_ENVID_F); + writel(vidcon0, info->io + S3C24XX_LCD_VIDCON0); + + /* Start the initial fix configuration */ + fbinfo->fix.type = FB_TYPE_PACKED_PIXELS; + fbinfo->fix.type_aux = 0; + fbinfo->fix.xpanstep = 0; + fbinfo->fix.ypanstep = 0; + fbinfo->fix.ywrapstep = 0; + fbinfo->fix.accel = FB_ACCEL_NONE; + + fbinfo->var.nonstd = 0; + fbinfo->var.activate = FB_ACTIVATE_NOW; + fbinfo->var.accel_flags = 0; + fbinfo->var.vmode = FB_VMODE_NONINTERLACED; + + fbinfo->fbops = &s3cfb_ops; + fbinfo->flags = FBINFO_FLAG_DEFAULT; + fbinfo->pseudo_palette = &info->pseudo_pal; + + for (i = 0; i < 256; i++) + info->palette_buffer[i] = PALETTE_BUFF_CLEAR; + + /* Request our IRQ */ + ret = request_irq(irq, s3cfb_irq, IRQF_DISABLED, pdev->name, info); + if (ret) { + printk_err("Cannot get irq %d - err %d\n", irq, ret); + ret = -EBUSY; + goto release_regs; + } + + /* + * We are using the HCLK, then the U-Boot is using this clock too + */ + if (display->vidcon0 & S3C24XX_LCD_VIDCON0_CLKSEL_LCD) { + printk_err("Invalid clock (only tested with the HCLK).\n"); + ret = -EINVAL; + goto release_irq; + } + + /* @FIXME: Select the clock depending on the passed display configuration */ + info->clk = clk_get(NULL, "hclk"); + if (!info->clk || IS_ERR(info->clk)) { + printk_err("Failed to get lcd clock source\n"); + ret = -ENOENT; + goto release_irq; + } + + clk_enable(info->clk); + printk_debug("Got and enabled the HCLK clock\n"); + + msleep(1); + + /* find maximum required memory size for display */ + for (i = 0; i < mach_info->num_displays; i++) { + unsigned long smem_len = mach_info->displays[i].xres; + + smem_len *= mach_info->displays[i].yres; + smem_len *= mach_info->displays[i].bpp; + smem_len >>= 3; + if (fbinfo->fix.smem_len < smem_len) + fbinfo->fix.smem_len = smem_len; + } + + /* Initialize video memory */ + ret = s3c2410fb_map_video_memory(fbinfo); + if (ret) { + printk_err("Failed to allocate video RAM: %d\n", ret); + ret = -ENOMEM; + goto release_clock; + } + + + fbinfo->var.xres = display->xres; + fbinfo->var.yres = display->yres; + fbinfo->var.bits_per_pixel = display->bpp; + + + s3c2410fb_init_registers(fbinfo); + + + s3c2410fb_check_var(&fbinfo->var, fbinfo); + + + ret = register_framebuffer(fbinfo); + if (ret) { + printk_err("Failed to register the frambuffer device, %i\n", ret); + goto free_video_memory; + } + + + printk_info("New frame buffer fb%d: %s frame buffer device\n", + fbinfo->node, fbinfo->fix.id); + return 0; + + free_video_memory: + s3c2410fb_unmap_video_memory(fbinfo); + + release_clock: + clk_disable(info->clk); + clk_put(info->clk); + + release_irq: + free_irq(irq, info); + + release_regs: + iounmap(info->io); + + release_mem: + release_resource(info->mem); + kfree(info->mem); + + dealloc_fb: + platform_set_drvdata(pdev, NULL); + framebuffer_release(fbinfo); + + return ret; +} + +/* s3c_fb_stop_lcd + * + * shutdown the lcd controller + */ +void s3c_fb_stop_lcd(void) +{ +/* unsigned long flags; */ +/* unsigned long tmp; */ +/* printk("s3c_fb_stop_lcd() called.\n"); */ + +/* local_irq_save(flags); */ + +/* #if defined (CONFIG_FB_LTV350QV ) || defined (CONFIG_FB_LMS430WQ ) || defined(CONFIG_ARCH_S3C64XX) */ +/* tmp = __raw_readl(S3C_VIDCON0); */ +/* __raw_writel(tmp & ~(ENVID|ENVID_F), S3C_VIDCON0); */ +/* #else */ +/* tmp = __raw_readl(S3C_LCDCON1); */ +/* __raw_writel(tmp & ~(ENVID|ENVID_F), S3C_LCDCON1); */ +/* #endif */ +/* local_irq_restore(flags); */ +} + + +void s3c_fb_start_lcd(void) { +/* unsigned long flags; */ +/* unsigned long tmp; */ +/* printk("s3c_fb_start_lcd() called.\n"); */ + +/* local_irq_save(flags); */ + +/* #if defined (CONFIG_FB_LTV350QV ) || defined (CONFIG_FB_LMS430WQ ) || defined(CONFIG_ARCH_S3C64XX) */ +/* tmp = __raw_readl(S3C_VIDCON0); */ +/* __raw_writel(tmp | ENVID | ENVID_F, S3C_VIDCON0); */ +/* #else */ +/* tmp = __raw_readl(S3C_LCDCON1); */ +/* __raw_writel(tmp | ENVID | ENVID_F, S3C_LCDCON1); */ +/* #endif */ +/* local_irq_restore(flags); */ +} + +/* + * Cleanup + */ +static int s3c_fb_remove(struct platform_device *pdev) +{ + struct fb_info *fbinfo; + struct s3c2410fb_info *info; + int irq; + + fbinfo = platform_get_drvdata(pdev); + info = fbinfo->par; + + unregister_framebuffer(fbinfo); + + /* Free the requested clock */ + if (info->clk) { + clk_disable(info->clk); + clk_put(info->clk); + info->clk = NULL; + } + + /* Free the requested IRQ */ + irq = platform_get_irq(pdev, 0); + free_irq(irq, info); + + /* Unamp que requested memory mapped registers */ + iounmap(info->io); + + + release_resource(info->mem); + kfree(info->mem); + + platform_set_drvdata(pdev, NULL); + + framebuffer_release(fbinfo); + +/* struct s3c_fb_info *info = fbinfo->par; */ +/* int irq; */ +/* int index=0; */ + +/* s3c_fb_stop_lcd(); */ +/* msleep(1); */ + +/* for(index=0; index<S3C_FB_NUM; index++){ */ +/* s3c_fb_unmap_video_memory((struct s3c_fb_info *)&info[index]); */ + +/* if (lcd_clock) { */ +/* clk_disable(lcd_clock); */ +/* clk_put(lcd_clock); */ +/* lcd_clock = NULL; */ +/* } */ + +/* irq = platform_get_irq(pdev, 0); */ +/* free_irq(irq,&info[index]); */ +/* release_mem_region((unsigned long)S3C_VA_LCD, S3C_SZ_LCD); */ +/* unregister_framebuffer(&(info[index].fb)); */ +/* } // for(index=0; index<CONFIG_FB_NUM; index++) */ + return 0; +} + +#ifdef CONFIG_PM +/* suspend and resume support for the lcd controller */ +static int s3c_fb_suspend(struct platform_device *dev, pm_message_t state) +{ +/* s3c_fb_stop_lcd(); */ + +/* /\* sleep before disabling the clock, we need to ensure */ +/* * the LCD DMA engine is not going to get back on the bus */ +/* * before the clock goes off again (bjd) *\/ */ + +/* msleep(1); */ +/* clk_disable(lcd_clock); */ + + return 0; +} + +static int s3c_fb_resume(struct platform_device *dev) +{ +/* clk_enable(lcd_clock); */ +/* msleep(1); */ + +/* Init_LDI(); */ +/* #if 0 */ +/* for(int index=0; index<S3C_FB_NUM; index++) */ +/* s3c_fb_init_registers(&info[index]); */ +/* #endif */ + + return 0; +} + +#else +#define s3c_fb_suspend NULL +#define s3c_fb_resume NULL +#endif + +static struct platform_driver s3c_fb_driver = { + .probe = s3c_fb_probe, + .remove = s3c_fb_remove, + .suspend = s3c_fb_suspend, + .resume = s3c_fb_resume, + .driver = { + .name = S3CFB_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + + +int __devinit s3c_fb_init(void) +{ + return platform_driver_register(&s3c_fb_driver); +} + + +static void __exit s3c_fb_cleanup(void) +{ + platform_driver_unregister(&s3c_fb_driver); +} + +EXPORT_SYMBOL(s3c_fb_stop_lcd); +EXPORT_SYMBOL(s3c_fb_start_lcd); + +module_init(s3c_fb_init); +module_exit(s3c_fb_cleanup); + +MODULE_AUTHOR(""); +MODULE_DESCRIPTION("Framebuffer driver for the S3C"); +MODULE_LICENSE("GPL"); |