diff options
author | Nicolas Ferre <nicolas.ferre@rfo.atmel.com> | 2007-05-11 09:23:26 +0400 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-05-11 19:29:37 +0400 |
commit | 14340586148e7c88d7b1b752876f5b5227b200b9 (patch) | |
tree | 91a0d77c94b3d62e83e9cea0b5e3adedd91244fd /drivers/video/atmel_lcdfb.c | |
parent | f23a06f076173c1f56572556169bf9e1793c5d59 (diff) | |
download | linux-14340586148e7c88d7b1b752876f5b5227b200b9.tar.xz |
atmel_lcdfb: AT91/AT32 LCD Controller framebuffer driver
Adds a framebuffer driver to ATMEL AT91SAM9x and AT32 aka AVR32 platforms.
Those chips share quite the same IP and this code is suitable for both
architectures.
Signed-off-by: Nicolas Ferre <nicolas.ferre@rfo.atmel.com>
Signed-off-by: Antonino Daplas <adaplas@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/video/atmel_lcdfb.c')
-rw-r--r-- | drivers/video/atmel_lcdfb.c | 752 |
1 files changed, 752 insertions, 0 deletions
diff --git a/drivers/video/atmel_lcdfb.c b/drivers/video/atmel_lcdfb.c new file mode 100644 index 000000000000..e1d5bd0c98c4 --- /dev/null +++ b/drivers/video/atmel_lcdfb.c @@ -0,0 +1,752 @@ +/* + * Driver for AT91/AT32 LCD Controller + * + * Copyright (C) 2007 Atmel Corporation + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/clk.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/delay.h> + +#include <asm/arch/board.h> +#include <asm/arch/cpu.h> +#include <asm/arch/gpio.h> + +#include <video/atmel_lcdc.h> + +#define lcdc_readl(sinfo, reg) __raw_readl((sinfo)->mmio+(reg)) +#define lcdc_writel(sinfo, reg, val) __raw_writel((val), (sinfo)->mmio+(reg)) + +/* configurable parameters */ +#define ATMEL_LCDC_CVAL_DEFAULT 0xc8 +#define ATMEL_LCDC_DMA_BURST_LEN 8 + +#if defined(CONFIG_ARCH_AT91SAM9263) +#define ATMEL_LCDC_FIFO_SIZE 2048 +#else +#define ATMEL_LCDC_FIFO_SIZE 512 +#endif + +#if defined(CONFIG_ARCH_AT91) +#define ATMEL_LCDFB_FBINFO_DEFAULT FBINFO_DEFAULT + +static inline void atmel_lcdfb_update_dma2d(struct atmel_lcdfb_info *sinfo, + struct fb_var_screeninfo *var) +{ + +} +#elif defined(CONFIG_AVR32) +#define ATMEL_LCDFB_FBINFO_DEFAULT (FBINFO_DEFAULT \ + | FBINFO_PARTIAL_PAN_OK \ + | FBINFO_HWACCEL_XPAN \ + | FBINFO_HWACCEL_YPAN) + +static void atmel_lcdfb_update_dma2d(struct atmel_lcdfb_info *sinfo, + struct fb_var_screeninfo *var) +{ + u32 dma2dcfg; + u32 pixeloff; + + pixeloff = (var->xoffset * var->bits_per_pixel) & 0x1f; + + dma2dcfg = ((var->xres_virtual - var->xres) * var->bits_per_pixel) / 8; + dma2dcfg |= pixeloff << ATMEL_LCDC_PIXELOFF_OFFSET; + lcdc_writel(sinfo, ATMEL_LCDC_DMA2DCFG, dma2dcfg); + + /* Update configuration */ + lcdc_writel(sinfo, ATMEL_LCDC_DMACON, + lcdc_readl(sinfo, ATMEL_LCDC_DMACON) + | ATMEL_LCDC_DMAUPDT); +} +#endif + + +static struct fb_fix_screeninfo atmel_lcdfb_fix __initdata = { + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .xpanstep = 0, + .ypanstep = 0, + .ywrapstep = 0, + .accel = FB_ACCEL_NONE, +}; + + +static void atmel_lcdfb_update_dma(struct fb_info *info, + struct fb_var_screeninfo *var) +{ + struct atmel_lcdfb_info *sinfo = info->par; + struct fb_fix_screeninfo *fix = &info->fix; + unsigned long dma_addr; + + dma_addr = (fix->smem_start + var->yoffset * fix->line_length + + var->xoffset * var->bits_per_pixel / 8); + + dma_addr &= ~3UL; + + /* Set framebuffer DMA base address and pixel offset */ + lcdc_writel(sinfo, ATMEL_LCDC_DMABADDR1, dma_addr); + + atmel_lcdfb_update_dma2d(sinfo, var); +} + +static inline void atmel_lcdfb_free_video_memory(struct atmel_lcdfb_info *sinfo) +{ + struct fb_info *info = sinfo->info; + + dma_free_writecombine(info->device, info->fix.smem_len, + info->screen_base, info->fix.smem_start); +} + +/** + * atmel_lcdfb_alloc_video_memory - Allocate framebuffer memory + * @sinfo: the frame buffer to allocate memory for + */ +static int atmel_lcdfb_alloc_video_memory(struct atmel_lcdfb_info *sinfo) +{ + struct fb_info *info = sinfo->info; + struct fb_var_screeninfo *var = &info->var; + + info->fix.smem_len = (var->xres_virtual * var->yres_virtual + * ((var->bits_per_pixel + 7) / 8)); + + info->screen_base = dma_alloc_writecombine(info->device, info->fix.smem_len, + (dma_addr_t *)&info->fix.smem_start, GFP_KERNEL); + + if (!info->screen_base) { + return -ENOMEM; + } + + return 0; +} + +/** + * atmel_lcdfb_check_var - Validates a var passed in. + * @var: frame buffer variable screen structure + * @info: frame buffer structure that represents a single frame buffer + * + * Checks to see if the hardware supports the state requested by + * var passed in. This function does not alter the hardware + * state!!! This means the data stored in struct fb_info and + * struct atmel_lcdfb_info do not change. This includes the var + * inside of struct fb_info. Do NOT change these. This function + * can be called on its own if we intent to only test a mode and + * not actually set it. The stuff in modedb.c is a example of + * this. If the var passed in is slightly off by what the + * hardware can support then we alter the var PASSED in to what + * we can do. If the hardware doesn't support mode change a + * -EINVAL will be returned by the upper layers. You don't need + * to implement this function then. If you hardware doesn't + * support changing the resolution then this function is not + * needed. In this case the driver would just provide a var that + * represents the static state the screen is in. + * + * Returns negative errno on error, or zero on success. + */ +static int atmel_lcdfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct device *dev = info->device; + struct atmel_lcdfb_info *sinfo = info->par; + unsigned long clk_value_khz; + + clk_value_khz = clk_get_rate(sinfo->lcdc_clk) / 1000; + + dev_dbg(dev, "%s:\n", __func__); + dev_dbg(dev, " resolution: %ux%u\n", var->xres, var->yres); + dev_dbg(dev, " pixclk: %lu KHz\n", PICOS2KHZ(var->pixclock)); + dev_dbg(dev, " bpp: %u\n", var->bits_per_pixel); + dev_dbg(dev, " clk: %lu KHz\n", clk_value_khz); + + if ((PICOS2KHZ(var->pixclock) * var->bits_per_pixel / 8) > clk_value_khz) { + dev_err(dev, "%lu KHz pixel clock is too fast\n", PICOS2KHZ(var->pixclock)); + return -EINVAL; + } + + /* Force same alignment for each line */ + var->xres = (var->xres + 3) & ~3UL; + var->xres_virtual = (var->xres_virtual + 3) & ~3UL; + + var->red.msb_right = var->green.msb_right = var->blue.msb_right = 0; + var->transp.msb_right = 0; + var->transp.offset = var->transp.length = 0; + var->xoffset = var->yoffset = 0; + + switch (var->bits_per_pixel) { + case 2: + case 4: + case 8: + var->red.offset = var->green.offset = var->blue.offset = 0; + var->red.length = var->green.length = var->blue.length + = var->bits_per_pixel; + break; + case 15: + case 16: + var->red.offset = 0; + var->green.offset = 5; + var->blue.offset = 10; + var->red.length = var->green.length = var->blue.length = 5; + break; + case 24: + case 32: + var->red.offset = 0; + var->green.offset = 8; + var->blue.offset = 16; + var->red.length = var->green.length = var->blue.length = 8; + break; + default: + dev_err(dev, "color depth %d not supported\n", + var->bits_per_pixel); + return -EINVAL; + } + + return 0; +} + +/** + * atmel_lcdfb_set_par - Alters the hardware state. + * @info: frame buffer structure that represents a single frame buffer + * + * Using the fb_var_screeninfo in fb_info we set the resolution + * of the this particular framebuffer. This function alters the + * par AND the fb_fix_screeninfo stored in fb_info. It doesn't + * not alter var in fb_info since we are using that data. This + * means we depend on the data in var inside fb_info to be + * supported by the hardware. atmel_lcdfb_check_var is always called + * before atmel_lcdfb_set_par to ensure this. Again if you can't + * change the resolution you don't need this function. + * + */ +static int atmel_lcdfb_set_par(struct fb_info *info) +{ + struct atmel_lcdfb_info *sinfo = info->par; + unsigned long value; + unsigned long clk_value_khz; + + dev_dbg(info->device, "%s:\n", __func__); + dev_dbg(info->device, " * resolution: %ux%u (%ux%u virtual)\n", + info->var.xres, info->var.yres, + info->var.xres_virtual, info->var.yres_virtual); + + /* Turn off the LCD controller and the DMA controller */ + lcdc_writel(sinfo, ATMEL_LCDC_PWRCON, sinfo->guard_time << ATMEL_LCDC_GUARDT_OFFSET); + + lcdc_writel(sinfo, ATMEL_LCDC_DMACON, 0); + + if (info->var.bits_per_pixel <= 8) + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + else + info->fix.visual = FB_VISUAL_TRUECOLOR; + + info->fix.line_length = info->var.xres_virtual * (info->var.bits_per_pixel / 8); + + /* Re-initialize the DMA engine... */ + dev_dbg(info->device, " * update DMA engine\n"); + atmel_lcdfb_update_dma(info, &info->var); + + /* ...set frame size and burst length = 8 words (?) */ + value = (info->var.yres * info->var.xres * info->var.bits_per_pixel) / 32; + value |= ((ATMEL_LCDC_DMA_BURST_LEN - 1) << ATMEL_LCDC_BLENGTH_OFFSET); + lcdc_writel(sinfo, ATMEL_LCDC_DMAFRMCFG, value); + + /* Now, the LCDC core... */ + + /* Set pixel clock */ + clk_value_khz = clk_get_rate(sinfo->lcdc_clk) / 1000; + + value = clk_value_khz / PICOS2KHZ(info->var.pixclock); + + if (clk_value_khz % PICOS2KHZ(info->var.pixclock)) + value++; + + value = (value / 2) - 1; + + if (value <= 0) { + dev_notice(info->device, "Bypassing pixel clock divider\n"); + lcdc_writel(sinfo, ATMEL_LCDC_LCDCON1, ATMEL_LCDC_BYPASS); + } else + lcdc_writel(sinfo, ATMEL_LCDC_LCDCON1, value << ATMEL_LCDC_CLKVAL_OFFSET); + + /* Initialize control register 2 */ + value = sinfo->default_lcdcon2; + + if (!(info->var.sync & FB_SYNC_HOR_HIGH_ACT)) + value |= ATMEL_LCDC_INVLINE_INVERTED; + if (!(info->var.sync & FB_SYNC_VERT_HIGH_ACT)) + value |= ATMEL_LCDC_INVFRAME_INVERTED; + + switch (info->var.bits_per_pixel) { + case 1: value |= ATMEL_LCDC_PIXELSIZE_1; break; + case 2: value |= ATMEL_LCDC_PIXELSIZE_2; break; + case 4: value |= ATMEL_LCDC_PIXELSIZE_4; break; + case 8: value |= ATMEL_LCDC_PIXELSIZE_8; break; + case 15: /* fall through */ + case 16: value |= ATMEL_LCDC_PIXELSIZE_16; break; + case 24: value |= ATMEL_LCDC_PIXELSIZE_24; break; + case 32: value |= ATMEL_LCDC_PIXELSIZE_32; break; + default: BUG(); break; + } + dev_dbg(info->device, " * LCDCON2 = %08lx\n", value); + lcdc_writel(sinfo, ATMEL_LCDC_LCDCON2, value); + + /* Vertical timing */ + value = (info->var.vsync_len - 1) << ATMEL_LCDC_VPW_OFFSET; + value |= info->var.upper_margin << ATMEL_LCDC_VBP_OFFSET; + value |= info->var.lower_margin; + dev_dbg(info->device, " * LCDTIM1 = %08lx\n", value); + lcdc_writel(sinfo, ATMEL_LCDC_TIM1, value); + + /* Horizontal timing */ + value = (info->var.right_margin - 1) << ATMEL_LCDC_HFP_OFFSET; + value |= (info->var.hsync_len - 1) << ATMEL_LCDC_HPW_OFFSET; + value |= (info->var.left_margin - 1); + dev_dbg(info->device, " * LCDTIM2 = %08lx\n", value); + lcdc_writel(sinfo, ATMEL_LCDC_TIM2, value); + + /* Display size */ + value = (info->var.xres - 1) << ATMEL_LCDC_HOZVAL_OFFSET; + value |= info->var.yres - 1; + lcdc_writel(sinfo, ATMEL_LCDC_LCDFRMCFG, value); + + /* FIFO Threshold: Use formula from data sheet */ + value = ATMEL_LCDC_FIFO_SIZE - (2 * ATMEL_LCDC_DMA_BURST_LEN + 3); + lcdc_writel(sinfo, ATMEL_LCDC_FIFO, value); + + /* Toggle LCD_MODE every frame */ + lcdc_writel(sinfo, ATMEL_LCDC_MVAL, 0); + + /* Disable all interrupts */ + lcdc_writel(sinfo, ATMEL_LCDC_IDR, ~0UL); + + /* Set contrast */ + value = ATMEL_LCDC_PS_DIV8 | ATMEL_LCDC_POL_POSITIVE | ATMEL_LCDC_ENA_PWMENABLE; + lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, value); + lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_VAL, ATMEL_LCDC_CVAL_DEFAULT); + /* ...wait for DMA engine to become idle... */ + while (lcdc_readl(sinfo, ATMEL_LCDC_DMACON) & ATMEL_LCDC_DMABUSY) + msleep(10); + + dev_dbg(info->device, " * re-enable DMA engine\n"); + /* ...and enable it with updated configuration */ + lcdc_writel(sinfo, ATMEL_LCDC_DMACON, sinfo->default_dmacon); + + dev_dbg(info->device, " * re-enable LCDC core\n"); + lcdc_writel(sinfo, ATMEL_LCDC_PWRCON, + (sinfo->guard_time << ATMEL_LCDC_GUARDT_OFFSET) | ATMEL_LCDC_PWR); + + dev_dbg(info->device, " * DONE\n"); + + return 0; +} + +static inline unsigned int chan_to_field(unsigned int chan, const struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +/** + * atmel_lcdfb_setcolreg - Optional function. Sets a color register. + * @regno: Which register in the CLUT we are programming + * @red: The red value which can be up to 16 bits wide + * @green: The green value which can be up to 16 bits wide + * @blue: The blue value which can be up to 16 bits wide. + * @transp: If supported the alpha value which can be up to 16 bits wide. + * @info: frame buffer info structure + * + * Set a single color register. The values supplied have a 16 bit + * magnitude which needs to be scaled in this function for the hardware. + * Things to take into consideration are how many color registers, if + * any, are supported with the current color visual. With truecolor mode + * no color palettes are supported. Here a psuedo palette is created + * which we store the value in pseudo_palette in struct fb_info. For + * pseudocolor mode we have a limited color palette. To deal with this + * we can program what color is displayed for a particular pixel value. + * DirectColor is similar in that we can program each color field. If + * we have a static colormap we don't need to implement this function. + * + * Returns negative errno on error, or zero on success. In an + * ideal world, this would have been the case, but as it turns + * out, the other drivers return 1 on failure, so that's what + * we're going to do. + */ +static int atmel_lcdfb_setcolreg(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + unsigned int transp, struct fb_info *info) +{ + struct atmel_lcdfb_info *sinfo = info->par; + unsigned int val; + u32 *pal; + int ret = 1; + + if (info->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + if (regno < 16) { + 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; + ret = 0; + } + break; + + case FB_VISUAL_PSEUDOCOLOR: + if (regno < 256) { + val = ((red >> 11) & 0x001f); + val |= ((green >> 6) & 0x03e0); + val |= ((blue >> 1) & 0x7c00); + + /* + * TODO: intensity bit. Maybe something like + * ~(red[10] ^ green[10] ^ blue[10]) & 1 + */ + + lcdc_writel(sinfo, ATMEL_LCDC_LUT(regno), val); + ret = 0; + } + break; + } + + return ret; +} + +static int atmel_lcdfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + dev_dbg(info->device, "%s\n", __func__); + + atmel_lcdfb_update_dma(info, var); + + return 0; +} + +static struct fb_ops atmel_lcdfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = atmel_lcdfb_check_var, + .fb_set_par = atmel_lcdfb_set_par, + .fb_setcolreg = atmel_lcdfb_setcolreg, + .fb_pan_display = atmel_lcdfb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static irqreturn_t atmel_lcdfb_interrupt(int irq, void *dev_id) +{ + struct fb_info *info = dev_id; + struct atmel_lcdfb_info *sinfo = info->par; + u32 status; + + status = lcdc_readl(sinfo, ATMEL_LCDC_ISR); + lcdc_writel(sinfo, ATMEL_LCDC_IDR, status); + return IRQ_HANDLED; +} + +static int __init atmel_lcdfb_init_fbinfo(struct atmel_lcdfb_info *sinfo) +{ + struct fb_info *info = sinfo->info; + int ret = 0; + + memset_io(info->screen_base, 0, info->fix.smem_len); + info->var.activate |= FB_ACTIVATE_FORCE | FB_ACTIVATE_NOW; + + dev_info(info->device, + "%luKiB frame buffer at %08lx (mapped at %p)\n", + (unsigned long)info->fix.smem_len / 1024, + (unsigned long)info->fix.smem_start, + info->screen_base); + + /* Allocate colormap */ + ret = fb_alloc_cmap(&info->cmap, 256, 0); + if (ret < 0) + dev_err(info->device, "Alloc color map failed\n"); + + return ret; +} + +static void atmel_lcdfb_start_clock(struct atmel_lcdfb_info *sinfo) +{ + if (sinfo->bus_clk) + clk_enable(sinfo->bus_clk); + clk_enable(sinfo->lcdc_clk); +} + +static void atmel_lcdfb_stop_clock(struct atmel_lcdfb_info *sinfo) +{ + if (sinfo->bus_clk) + clk_disable(sinfo->bus_clk); + clk_disable(sinfo->lcdc_clk); +} + + +static int __init atmel_lcdfb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct fb_info *info; + struct atmel_lcdfb_info *sinfo; + struct atmel_lcdfb_info *pdata_sinfo; + struct resource *regs = NULL; + struct resource *map = NULL; + int ret; + + dev_dbg(dev, "%s BEGIN\n", __func__); + + ret = -ENOMEM; + info = framebuffer_alloc(sizeof(struct atmel_lcdfb_info), dev); + if (!info) { + dev_err(dev, "cannot allocate memory\n"); + goto out; + } + + sinfo = info->par; + + if (dev->platform_data) { + pdata_sinfo = (struct atmel_lcdfb_info *)dev->platform_data; + sinfo->default_bpp = pdata_sinfo->default_bpp; + sinfo->default_dmacon = pdata_sinfo->default_dmacon; + sinfo->default_lcdcon2 = pdata_sinfo->default_lcdcon2; + sinfo->default_monspecs = pdata_sinfo->default_monspecs; + sinfo->atmel_lcdfb_power_control = pdata_sinfo->atmel_lcdfb_power_control; + sinfo->guard_time = pdata_sinfo->guard_time; + } else { + dev_err(dev, "cannot get default configuration\n"); + goto free_info; + } + sinfo->info = info; + sinfo->pdev = pdev; + + strcpy(info->fix.id, sinfo->pdev->name); + info->flags = ATMEL_LCDFB_FBINFO_DEFAULT; + info->pseudo_palette = sinfo->pseudo_palette; + info->fbops = &atmel_lcdfb_ops; + + memcpy(&info->monspecs, sinfo->default_monspecs, sizeof(info->monspecs)); + info->fix = atmel_lcdfb_fix; + + /* Enable LCDC Clocks */ + if (cpu_is_at91sam9261() || cpu_is_at32ap7000()) { + sinfo->bus_clk = clk_get(dev, "hck1"); + if (IS_ERR(sinfo->bus_clk)) { + ret = PTR_ERR(sinfo->bus_clk); + goto free_info; + } + } + sinfo->lcdc_clk = clk_get(dev, "lcdc_clk"); + if (IS_ERR(sinfo->lcdc_clk)) { + ret = PTR_ERR(sinfo->lcdc_clk); + goto put_bus_clk; + } + atmel_lcdfb_start_clock(sinfo); + + ret = fb_find_mode(&info->var, info, NULL, info->monspecs.modedb, + info->monspecs.modedb_len, info->monspecs.modedb, + sinfo->default_bpp); + if (!ret) { + dev_err(dev, "no suitable video mode found\n"); + goto stop_clk; + } + + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) { + dev_err(dev, "resources unusable\n"); + ret = -ENXIO; + goto stop_clk; + } + + sinfo->irq_base = platform_get_irq(pdev, 0); + if (sinfo->irq_base < 0) { + dev_err(dev, "unable to get irq\n"); + ret = sinfo->irq_base; + goto stop_clk; + } + + /* Initialize video memory */ + map = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (map) { + /* use a pre-allocated memory buffer */ + info->fix.smem_start = map->start; + info->fix.smem_len = map->end - map->start + 1; + if (!request_mem_region(info->fix.smem_start, + info->fix.smem_len, pdev->name)) { + ret = -EBUSY; + goto stop_clk; + } + + info->screen_base = ioremap(info->fix.smem_start, info->fix.smem_len); + if (!info->screen_base) + goto release_intmem; + } else { + /* alocate memory buffer */ + ret = atmel_lcdfb_alloc_video_memory(sinfo); + if (ret < 0) { + dev_err(dev, "cannot allocate framebuffer: %d\n", ret); + goto stop_clk; + } + } + + /* LCDC registers */ + info->fix.mmio_start = regs->start; + info->fix.mmio_len = regs->end - regs->start + 1; + + if (!request_mem_region(info->fix.mmio_start, + info->fix.mmio_len, pdev->name)) { + ret = -EBUSY; + goto free_fb; + } + + sinfo->mmio = ioremap(info->fix.mmio_start, info->fix.mmio_len); + if (!sinfo->mmio) { + dev_err(dev, "cannot map LCDC registers\n"); + goto release_mem; + } + + /* interrupt */ + ret = request_irq(sinfo->irq_base, atmel_lcdfb_interrupt, 0, pdev->name, info); + if (ret) { + dev_err(dev, "request_irq failed: %d\n", ret); + goto unmap_mmio; + } + + ret = atmel_lcdfb_init_fbinfo(sinfo); + if (ret < 0) { + dev_err(dev, "init fbinfo failed: %d\n", ret); + goto unregister_irqs; + } + + /* + * This makes sure that our colour bitfield + * descriptors are correctly initialised. + */ + atmel_lcdfb_check_var(&info->var, info); + + ret = fb_set_var(info, &info->var); + if (ret) { + dev_warn(dev, "unable to set display parameters\n"); + goto free_cmap; + } + + dev_set_drvdata(dev, info); + + /* + * Tell the world that we're ready to go + */ + ret = register_framebuffer(info); + if (ret < 0) { + dev_err(dev, "failed to register framebuffer device: %d\n", ret); + goto free_cmap; + } + + /* Power up the LCDC screen */ + if (sinfo->atmel_lcdfb_power_control) + sinfo->atmel_lcdfb_power_control(1); + + dev_info(dev, "fb%d: Atmel LCDC at 0x%08lx (mapped at %p), irq %lu\n", + info->node, info->fix.mmio_start, sinfo->mmio, sinfo->irq_base); + + return 0; + + +free_cmap: + fb_dealloc_cmap(&info->cmap); +unregister_irqs: + free_irq(sinfo->irq_base, info); +unmap_mmio: + iounmap(sinfo->mmio); +release_mem: + release_mem_region(info->fix.mmio_start, info->fix.mmio_len); +free_fb: + if (map) + iounmap(info->screen_base); + else + atmel_lcdfb_free_video_memory(sinfo); + +release_intmem: + if (map) + release_mem_region(info->fix.smem_start, info->fix.smem_len); +stop_clk: + atmel_lcdfb_stop_clock(sinfo); + clk_put(sinfo->lcdc_clk); +put_bus_clk: + if (sinfo->bus_clk) + clk_put(sinfo->bus_clk); +free_info: + framebuffer_release(info); +out: + dev_dbg(dev, "%s FAILED\n", __func__); + return ret; +} + +static int __exit atmel_lcdfb_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct fb_info *info = dev_get_drvdata(dev); + struct atmel_lcdfb_info *sinfo = info->par; + + if (!sinfo) + return 0; + + if (sinfo->atmel_lcdfb_power_control) + sinfo->atmel_lcdfb_power_control(0); + unregister_framebuffer(info); + atmel_lcdfb_stop_clock(sinfo); + clk_put(sinfo->lcdc_clk); + if (sinfo->bus_clk) + clk_put(sinfo->bus_clk); + fb_dealloc_cmap(&info->cmap); + free_irq(sinfo->irq_base, info); + iounmap(sinfo->mmio); + release_mem_region(info->fix.mmio_start, info->fix.mmio_len); + if (platform_get_resource(pdev, IORESOURCE_MEM, 1)) { + iounmap(info->screen_base); + release_mem_region(info->fix.smem_start, info->fix.smem_len); + } else { + atmel_lcdfb_free_video_memory(sinfo); + } + + dev_set_drvdata(dev, NULL); + framebuffer_release(info); + + return 0; +} + +static struct platform_driver atmel_lcdfb_driver = { + .remove = __exit_p(atmel_lcdfb_remove), + .driver = { + .name = "atmel_lcdfb", + .owner = THIS_MODULE, + }, +}; + +static int __init atmel_lcdfb_init(void) +{ + return platform_driver_probe(&atmel_lcdfb_driver, atmel_lcdfb_probe); +} + +static void __exit atmel_lcdfb_exit(void) +{ + platform_driver_unregister(&atmel_lcdfb_driver); +} + +module_init(atmel_lcdfb_init); +module_exit(atmel_lcdfb_exit); + +MODULE_DESCRIPTION("AT91/AT32 LCD Controller framebuffer driver"); +MODULE_AUTHOR("Nicolas Ferre <nicolas.ferre@rfo.atmel.com>"); +MODULE_LICENSE("GPL"); |