X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=drivers%2Fvideo%2Fatmel_lcdfb.c;h=5b3a15dffb5f536304eca7d1746b44c0e62c86ad;hb=968910bd03b226ed410d092c2da59dffe5bfe8de;hp=fc65c02306ddbd6c32c2be96b80fcd630be0258a;hpb=e5e54bc86a1fed9849b22fd736c30b23c4719046;p=linux-2.6 diff --git a/drivers/video/atmel_lcdfb.c b/drivers/video/atmel_lcdfb.c index fc65c02306..5b3a15dffb 100644 --- a/drivers/video/atmel_lcdfb.c +++ b/drivers/video/atmel_lcdfb.c @@ -31,7 +31,8 @@ #define ATMEL_LCDC_CVAL_DEFAULT 0xc8 #define ATMEL_LCDC_DMA_BURST_LEN 8 -#if defined(CONFIG_ARCH_AT91SAM9263) || defined(CONFIG_ARCH_AT91CAP9) +#if defined(CONFIG_ARCH_AT91SAM9263) || defined(CONFIG_ARCH_AT91CAP9) || \ + defined(CONFIG_ARCH_AT91SAM9RL) #define ATMEL_LCDC_FIFO_SIZE 2048 #else #define ATMEL_LCDC_FIFO_SIZE 512 @@ -250,9 +251,25 @@ static int atmel_lcdfb_alloc_video_memory(struct atmel_lcdfb_info *sinfo) return -ENOMEM; } + memset(info->screen_base, 0, info->fix.smem_len); + return 0; } +static const struct fb_videomode *atmel_lcdfb_choose_mode(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct fb_videomode varfbmode; + const struct fb_videomode *fbmode = NULL; + + fb_var_to_videomode(&varfbmode, var); + fbmode = fb_find_nearest_mode(&varfbmode, &info->modelist); + if (fbmode) + fb_videomode_to_var(var, fbmode); + return fbmode; +} + + /** * atmel_lcdfb_check_var - Validates a var passed in. * @var: frame buffer variable screen structure @@ -286,6 +303,15 @@ static int atmel_lcdfb_check_var(struct fb_var_screeninfo *var, clk_value_khz = clk_get_rate(sinfo->lcdc_clk) / 1000; dev_dbg(dev, "%s:\n", __func__); + + if (!(var->pixclock && var->bits_per_pixel)) { + /* choose a suitable mode if possible */ + if (!atmel_lcdfb_choose_mode(var, info)) { + dev_err(dev, "needed value not specified\n"); + return -EINVAL; + } + } + 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); @@ -296,6 +322,13 @@ static int atmel_lcdfb_check_var(struct fb_var_screeninfo *var, return -EINVAL; } + /* Do not allow to have real resoulution larger than virtual */ + if (var->xres > var->xres_virtual) + var->xres_virtual = var->xres; + + if (var->yres > var->yres_virtual) + var->yres_virtual = var->yres; + /* Force same alignment for each line */ var->xres = (var->xres + 3) & ~3UL; var->xres_virtual = (var->xres_virtual + 3) & ~3UL; @@ -336,19 +369,35 @@ static int atmel_lcdfb_check_var(struct fb_var_screeninfo *var, break; case 15: case 16: - var->red.offset = 0; + if (sinfo->lcd_wiring_mode == ATMEL_LCDC_WIRING_RGB) { + /* RGB:565 mode */ + var->red.offset = 11; + var->blue.offset = 0; + var->green.length = 6; + } else { + /* BGR:555 mode */ + var->red.offset = 0; + var->blue.offset = 10; + var->green.length = 5; + } var->green.offset = 5; - var->blue.offset = 10; - var->red.length = var->green.length = var->blue.length = 5; + var->red.length = var->blue.length = 5; break; case 32: var->transp.offset = 24; var->transp.length = 8; /* fall through */ case 24: - var->red.offset = 0; + if (sinfo->lcd_wiring_mode == ATMEL_LCDC_WIRING_RGB) { + /* RGB:888 mode */ + var->red.offset = 16; + var->blue.offset = 0; + } else { + /* BGR:888 mode */ + var->red.offset = 0; + var->blue.offset = 16; + } var->green.offset = 8; - var->blue.offset = 16; var->red.length = var->green.length = var->blue.length = 8; break; default: @@ -360,6 +409,35 @@ static int atmel_lcdfb_check_var(struct fb_var_screeninfo *var, return 0; } +/* + * LCD reset sequence + */ +static void atmel_lcdfb_reset(struct atmel_lcdfb_info *sinfo) +{ + might_sleep(); + + /* LCD power off */ + lcdc_writel(sinfo, ATMEL_LCDC_PWRCON, sinfo->guard_time << ATMEL_LCDC_GUARDT_OFFSET); + + /* wait for the LCDC core to become idle */ + while (lcdc_readl(sinfo, ATMEL_LCDC_PWRCON) & ATMEL_LCDC_BUSY) + msleep(10); + + /* DMA disable */ + lcdc_writel(sinfo, ATMEL_LCDC_DMACON, 0); + + /* wait for DMA engine to become idle */ + while (lcdc_readl(sinfo, ATMEL_LCDC_DMACON) & ATMEL_LCDC_DMABUSY) + msleep(10); + + /* LCD power on */ + lcdc_writel(sinfo, ATMEL_LCDC_PWRCON, + (sinfo->guard_time << ATMEL_LCDC_GUARDT_OFFSET) | ATMEL_LCDC_PWR); + + /* DMA enable */ + lcdc_writel(sinfo, ATMEL_LCDC_DMACON, sinfo->default_dmacon); +} + /** * atmel_lcdfb_set_par - Alters the hardware state. * @info: frame buffer structure that represents a single frame buffer @@ -382,6 +460,8 @@ static int atmel_lcdfb_set_par(struct fb_info *info) unsigned long clk_value_khz; unsigned long bits_per_line; + might_sleep(); + dev_dbg(info->device, "%s:\n", __func__); dev_dbg(info->device, " * resolution: %ux%u (%ux%u virtual)\n", info->var.xres, info->var.yres, @@ -422,14 +502,15 @@ static int atmel_lcdfb_set_par(struct fb_info *info) value = DIV_ROUND_UP(clk_value_khz, PICOS2KHZ(info->var.pixclock)); - value = (value / 2) - 1; - dev_dbg(info->device, " * programming CLKVAL = 0x%08lx\n", value); - - if (value <= 0) { + if (value < 2) { 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); + value = (value / 2) - 1; + dev_dbg(info->device, " * programming CLKVAL = 0x%08lx\n", + value); + lcdc_writel(sinfo, ATMEL_LCDC_LCDCON1, + value << ATMEL_LCDC_CLKVAL_OFFSET); info->var.pixclock = KHZ2PICOS(clk_value_khz / (2 * (value + 1))); dev_dbg(info->device, " updated pixclk: %lu KHz\n", PICOS2KHZ(info->var.pixclock)); @@ -491,6 +572,8 @@ static int atmel_lcdfb_set_par(struct fb_info *info) /* Disable all interrupts */ lcdc_writel(sinfo, ATMEL_LCDC_IDR, ~0UL); + /* Enable FIFO & DMA errors */ + lcdc_writel(sinfo, ATMEL_LCDC_IER, ATMEL_LCDC_UFLWI | ATMEL_LCDC_OWRI | ATMEL_LCDC_MERI); /* ...wait for DMA engine to become idle... */ while (lcdc_readl(sinfo, ATMEL_LCDC_DMACON) & ATMEL_LCDC_DMABUSY) @@ -625,16 +708,31 @@ static irqreturn_t atmel_lcdfb_interrupt(int irq, void *dev_id) u32 status; status = lcdc_readl(sinfo, ATMEL_LCDC_ISR); - lcdc_writel(sinfo, ATMEL_LCDC_IDR, status); + if (status & ATMEL_LCDC_UFLWI) { + dev_warn(info->device, "FIFO underflow %#x\n", status); + /* reset DMA and FIFO to avoid screen shifting */ + schedule_work(&sinfo->task); + } + lcdc_writel(sinfo, ATMEL_LCDC_ICR, status); return IRQ_HANDLED; } +/* + * LCD controller task (to reset the LCD) + */ +static void atmel_lcdfb_task(struct work_struct *work) +{ + struct atmel_lcdfb_info *sinfo = + container_of(work, struct atmel_lcdfb_info, task); + + atmel_lcdfb_reset(sinfo); +} + 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, @@ -672,6 +770,7 @@ static int __init atmel_lcdfb_probe(struct platform_device *pdev) struct fb_info *info; struct atmel_lcdfb_info *sinfo; struct atmel_lcdfb_info *pdata_sinfo; + struct fb_videomode fbmode; struct resource *regs = NULL; struct resource *map = NULL; int ret; @@ -696,6 +795,7 @@ static int __init atmel_lcdfb_probe(struct platform_device *pdev) sinfo->atmel_lcdfb_power_control = pdata_sinfo->atmel_lcdfb_power_control; sinfo->guard_time = pdata_sinfo->guard_time; sinfo->lcdcon_is_backlight = pdata_sinfo->lcdcon_is_backlight; + sinfo->lcd_wiring_mode = pdata_sinfo->lcd_wiring_mode; } else { dev_err(dev, "cannot get default configuration\n"); goto free_info; @@ -764,6 +864,11 @@ static int __init atmel_lcdfb_probe(struct platform_device *pdev) info->screen_base = ioremap(info->fix.smem_start, info->fix.smem_len); if (!info->screen_base) goto release_intmem; + + /* + * Don't clear the framebuffer -- someone may have set + * up a splash image. + */ } else { /* alocate memory buffer */ ret = atmel_lcdfb_alloc_video_memory(sinfo); @@ -799,6 +904,10 @@ static int __init atmel_lcdfb_probe(struct platform_device *pdev) goto unmap_mmio; } + /* Some operations on the LCDC might sleep and + * require a preemptible task context */ + INIT_WORK(&sinfo->task, atmel_lcdfb_task); + ret = atmel_lcdfb_init_fbinfo(sinfo); if (ret < 0) { dev_err(dev, "init fbinfo failed: %d\n", ret); @@ -828,6 +937,10 @@ static int __init atmel_lcdfb_probe(struct platform_device *pdev) goto free_cmap; } + /* add selected videomode to modelist */ + fb_var_to_videomode(&fbmode, &info->var); + fb_add_videomode(&fbmode, &info->modelist); + /* Power up the LCDC screen */ if (sinfo->atmel_lcdfb_power_control) sinfo->atmel_lcdfb_power_control(1); @@ -841,6 +954,7 @@ static int __init atmel_lcdfb_probe(struct platform_device *pdev) free_cmap: fb_dealloc_cmap(&info->cmap); unregister_irqs: + cancel_work_sync(&sinfo->task); free_irq(sinfo->irq_base, info); unmap_mmio: exit_backlight(sinfo); @@ -878,6 +992,7 @@ static int __exit atmel_lcdfb_remove(struct platform_device *pdev) if (!sinfo) return 0; + cancel_work_sync(&sinfo->task); exit_backlight(sinfo); if (sinfo->atmel_lcdfb_power_control) sinfo->atmel_lcdfb_power_control(0); @@ -903,10 +1018,42 @@ static int __exit atmel_lcdfb_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM + +static int atmel_lcdfb_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + struct fb_info *info = platform_get_drvdata(pdev); + struct atmel_lcdfb_info *sinfo = info->par; + + sinfo->saved_lcdcon = lcdc_readl(sinfo, ATMEL_LCDC_CONTRAST_VAL); + lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, 0); + if (sinfo->atmel_lcdfb_power_control) + sinfo->atmel_lcdfb_power_control(0); + atmel_lcdfb_stop_clock(sinfo); + return 0; +} + +static int atmel_lcdfb_resume(struct platform_device *pdev) +{ + struct fb_info *info = platform_get_drvdata(pdev); + struct atmel_lcdfb_info *sinfo = info->par; + + atmel_lcdfb_start_clock(sinfo); + if (sinfo->atmel_lcdfb_power_control) + sinfo->atmel_lcdfb_power_control(1); + lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, sinfo->saved_lcdcon); + return 0; +} + +#else +#define atmel_lcdfb_suspend NULL +#define atmel_lcdfb_resume NULL +#endif + static struct platform_driver atmel_lcdfb_driver = { .remove = __exit_p(atmel_lcdfb_remove), - -// FIXME need suspend, resume + .suspend = atmel_lcdfb_suspend, + .resume = atmel_lcdfb_resume, .driver = { .name = "atmel_lcdfb",