diff options
author | Nicolas Ferre <nicolas.ferre@atmel.com> | 2008-07-24 00:31:20 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2008-07-24 13:47:37 -0400 |
commit | d22579b837358cbef12ccca5adaf7e93ae09ab7a (patch) | |
tree | 9da030058cc894372e7d5d0356447d1f6a14804b | |
parent | 77a6e7abb09de0e85a15e2fe42c21ffc59847759 (diff) |
atmel_lcdfb: FIFO underflow management
Manage atmel_lcdfb FIFO underflow
Resetting the LCD and DMA allows to fix screen shifting after a FIFO
underflow. It follows reset sequence from errata "LCD Screen Shifting
After a Reset".
Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com>
Cc: Haavard Skinnemoen <hskinnemoen@atmel.com>
Cc: Andrew Victor <linux@maxim.org.za>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | drivers/video/atmel_lcdfb.c | 57 | ||||
-rw-r--r-- | include/video/atmel_lcdc.h | 1 |
2 files changed, 57 insertions, 1 deletions
diff --git a/drivers/video/atmel_lcdfb.c b/drivers/video/atmel_lcdfb.c index b004036d4087..d335bb96b03b 100644 --- a/drivers/video/atmel_lcdfb.c +++ b/drivers/video/atmel_lcdfb.c | |||
@@ -379,6 +379,35 @@ static int atmel_lcdfb_check_var(struct fb_var_screeninfo *var, | |||
379 | return 0; | 379 | return 0; |
380 | } | 380 | } |
381 | 381 | ||
382 | /* | ||
383 | * LCD reset sequence | ||
384 | */ | ||
385 | static void atmel_lcdfb_reset(struct atmel_lcdfb_info *sinfo) | ||
386 | { | ||
387 | might_sleep(); | ||
388 | |||
389 | /* LCD power off */ | ||
390 | lcdc_writel(sinfo, ATMEL_LCDC_PWRCON, sinfo->guard_time << ATMEL_LCDC_GUARDT_OFFSET); | ||
391 | |||
392 | /* wait for the LCDC core to become idle */ | ||
393 | while (lcdc_readl(sinfo, ATMEL_LCDC_PWRCON) & ATMEL_LCDC_BUSY) | ||
394 | msleep(10); | ||
395 | |||
396 | /* DMA disable */ | ||
397 | lcdc_writel(sinfo, ATMEL_LCDC_DMACON, 0); | ||
398 | |||
399 | /* wait for DMA engine to become idle */ | ||
400 | while (lcdc_readl(sinfo, ATMEL_LCDC_DMACON) & ATMEL_LCDC_DMABUSY) | ||
401 | msleep(10); | ||
402 | |||
403 | /* LCD power on */ | ||
404 | lcdc_writel(sinfo, ATMEL_LCDC_PWRCON, | ||
405 | (sinfo->guard_time << ATMEL_LCDC_GUARDT_OFFSET) | ATMEL_LCDC_PWR); | ||
406 | |||
407 | /* DMA enable */ | ||
408 | lcdc_writel(sinfo, ATMEL_LCDC_DMACON, sinfo->default_dmacon); | ||
409 | } | ||
410 | |||
382 | /** | 411 | /** |
383 | * atmel_lcdfb_set_par - Alters the hardware state. | 412 | * atmel_lcdfb_set_par - Alters the hardware state. |
384 | * @info: frame buffer structure that represents a single frame buffer | 413 | * @info: frame buffer structure that represents a single frame buffer |
@@ -401,6 +430,8 @@ static int atmel_lcdfb_set_par(struct fb_info *info) | |||
401 | unsigned long clk_value_khz; | 430 | unsigned long clk_value_khz; |
402 | unsigned long bits_per_line; | 431 | unsigned long bits_per_line; |
403 | 432 | ||
433 | might_sleep(); | ||
434 | |||
404 | dev_dbg(info->device, "%s:\n", __func__); | 435 | dev_dbg(info->device, "%s:\n", __func__); |
405 | dev_dbg(info->device, " * resolution: %ux%u (%ux%u virtual)\n", | 436 | dev_dbg(info->device, " * resolution: %ux%u (%ux%u virtual)\n", |
406 | info->var.xres, info->var.yres, | 437 | info->var.xres, info->var.yres, |
@@ -511,6 +542,8 @@ static int atmel_lcdfb_set_par(struct fb_info *info) | |||
511 | 542 | ||
512 | /* Disable all interrupts */ | 543 | /* Disable all interrupts */ |
513 | lcdc_writel(sinfo, ATMEL_LCDC_IDR, ~0UL); | 544 | lcdc_writel(sinfo, ATMEL_LCDC_IDR, ~0UL); |
545 | /* Enable FIFO & DMA errors */ | ||
546 | lcdc_writel(sinfo, ATMEL_LCDC_IER, ATMEL_LCDC_UFLWI | ATMEL_LCDC_OWRI | ATMEL_LCDC_MERI); | ||
514 | 547 | ||
515 | /* ...wait for DMA engine to become idle... */ | 548 | /* ...wait for DMA engine to become idle... */ |
516 | while (lcdc_readl(sinfo, ATMEL_LCDC_DMACON) & ATMEL_LCDC_DMABUSY) | 549 | while (lcdc_readl(sinfo, ATMEL_LCDC_DMACON) & ATMEL_LCDC_DMABUSY) |
@@ -645,10 +678,26 @@ static irqreturn_t atmel_lcdfb_interrupt(int irq, void *dev_id) | |||
645 | u32 status; | 678 | u32 status; |
646 | 679 | ||
647 | status = lcdc_readl(sinfo, ATMEL_LCDC_ISR); | 680 | status = lcdc_readl(sinfo, ATMEL_LCDC_ISR); |
648 | lcdc_writel(sinfo, ATMEL_LCDC_IDR, status); | 681 | if (status & ATMEL_LCDC_UFLWI) { |
682 | dev_warn(info->device, "FIFO underflow %#x\n", status); | ||
683 | /* reset DMA and FIFO to avoid screen shifting */ | ||
684 | schedule_work(&sinfo->task); | ||
685 | } | ||
686 | lcdc_writel(sinfo, ATMEL_LCDC_ICR, status); | ||
649 | return IRQ_HANDLED; | 687 | return IRQ_HANDLED; |
650 | } | 688 | } |
651 | 689 | ||
690 | /* | ||
691 | * LCD controller task (to reset the LCD) | ||
692 | */ | ||
693 | static void atmel_lcdfb_task(struct work_struct *work) | ||
694 | { | ||
695 | struct atmel_lcdfb_info *sinfo = | ||
696 | container_of(work, struct atmel_lcdfb_info, task); | ||
697 | |||
698 | atmel_lcdfb_reset(sinfo); | ||
699 | } | ||
700 | |||
652 | static int __init atmel_lcdfb_init_fbinfo(struct atmel_lcdfb_info *sinfo) | 701 | static int __init atmel_lcdfb_init_fbinfo(struct atmel_lcdfb_info *sinfo) |
653 | { | 702 | { |
654 | struct fb_info *info = sinfo->info; | 703 | struct fb_info *info = sinfo->info; |
@@ -824,6 +873,10 @@ static int __init atmel_lcdfb_probe(struct platform_device *pdev) | |||
824 | goto unmap_mmio; | 873 | goto unmap_mmio; |
825 | } | 874 | } |
826 | 875 | ||
876 | /* Some operations on the LCDC might sleep and | ||
877 | * require a preemptible task context */ | ||
878 | INIT_WORK(&sinfo->task, atmel_lcdfb_task); | ||
879 | |||
827 | ret = atmel_lcdfb_init_fbinfo(sinfo); | 880 | ret = atmel_lcdfb_init_fbinfo(sinfo); |
828 | if (ret < 0) { | 881 | if (ret < 0) { |
829 | dev_err(dev, "init fbinfo failed: %d\n", ret); | 882 | dev_err(dev, "init fbinfo failed: %d\n", ret); |
@@ -866,6 +919,7 @@ static int __init atmel_lcdfb_probe(struct platform_device *pdev) | |||
866 | free_cmap: | 919 | free_cmap: |
867 | fb_dealloc_cmap(&info->cmap); | 920 | fb_dealloc_cmap(&info->cmap); |
868 | unregister_irqs: | 921 | unregister_irqs: |
922 | cancel_work_sync(&sinfo->task); | ||
869 | free_irq(sinfo->irq_base, info); | 923 | free_irq(sinfo->irq_base, info); |
870 | unmap_mmio: | 924 | unmap_mmio: |
871 | exit_backlight(sinfo); | 925 | exit_backlight(sinfo); |
@@ -903,6 +957,7 @@ static int __exit atmel_lcdfb_remove(struct platform_device *pdev) | |||
903 | if (!sinfo) | 957 | if (!sinfo) |
904 | return 0; | 958 | return 0; |
905 | 959 | ||
960 | cancel_work_sync(&sinfo->task); | ||
906 | exit_backlight(sinfo); | 961 | exit_backlight(sinfo); |
907 | if (sinfo->atmel_lcdfb_power_control) | 962 | if (sinfo->atmel_lcdfb_power_control) |
908 | sinfo->atmel_lcdfb_power_control(0); | 963 | sinfo->atmel_lcdfb_power_control(0); |
diff --git a/include/video/atmel_lcdc.h b/include/video/atmel_lcdc.h index ed64862c4e18..1ccf462b433a 100644 --- a/include/video/atmel_lcdc.h +++ b/include/video/atmel_lcdc.h | |||
@@ -37,6 +37,7 @@ struct atmel_lcdfb_info { | |||
37 | struct fb_info *info; | 37 | struct fb_info *info; |
38 | void __iomem *mmio; | 38 | void __iomem *mmio; |
39 | unsigned long irq_base; | 39 | unsigned long irq_base; |
40 | struct work_struct task; | ||
40 | 41 | ||
41 | unsigned int guard_time; | 42 | unsigned int guard_time; |
42 | struct platform_device *pdev; | 43 | struct platform_device *pdev; |