diff options
| author | Magnus Damm <damm@igel.co.jp> | 2009-03-13 11:36:55 -0400 |
|---|---|---|
| committer | Paul Mundt <lethal@linux-sh.org> | 2009-03-16 06:54:17 -0400 |
| commit | 2feb075a33905c2f6ef6be484c75ba6a35f6d12d (patch) | |
| tree | ef297298600e8fcbc10d30126bfd583264b721c4 | |
| parent | 93356d07474b1f16f25e79e81597c2a6b8c2a783 (diff) | |
video: sh_mobile_lcdcfb suspend/resume support
This patch adds suspend/resume support to the LCDC
driver for SuperH Mobile - sh_mobile_lcdcfb.
We simply stop hardware on suspend and start it again
on resume. For RGB panels this is trivial, but for SYS
panels in deferred io mode this becomes a bit more
difficult - we need to wait for a frame end interrupt
to make sure the clocks are balanced before stopping
the actual hardware.
Signed-off-by: Magnus Damm <damm@igel.co.jp>
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
| -rw-r--r-- | drivers/video/sh_mobile_lcdcfb.c | 66 |
1 files changed, 59 insertions, 7 deletions
diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c index 2c5d069e5f06..b433b8ac76d9 100644 --- a/drivers/video/sh_mobile_lcdcfb.c +++ b/drivers/video/sh_mobile_lcdcfb.c | |||
| @@ -33,6 +33,8 @@ struct sh_mobile_lcdc_chan { | |||
| 33 | struct fb_info info; | 33 | struct fb_info info; |
| 34 | dma_addr_t dma_handle; | 34 | dma_addr_t dma_handle; |
| 35 | struct fb_deferred_io defio; | 35 | struct fb_deferred_io defio; |
| 36 | unsigned long frame_end; | ||
| 37 | wait_queue_head_t frame_end_wait; | ||
| 36 | }; | 38 | }; |
| 37 | 39 | ||
| 38 | struct sh_mobile_lcdc_priv { | 40 | struct sh_mobile_lcdc_priv { |
| @@ -226,7 +228,10 @@ static void sh_mobile_lcdc_deferred_io_touch(struct fb_info *info) | |||
| 226 | static irqreturn_t sh_mobile_lcdc_irq(int irq, void *data) | 228 | static irqreturn_t sh_mobile_lcdc_irq(int irq, void *data) |
| 227 | { | 229 | { |
| 228 | struct sh_mobile_lcdc_priv *priv = data; | 230 | struct sh_mobile_lcdc_priv *priv = data; |
| 231 | struct sh_mobile_lcdc_chan *ch; | ||
| 229 | unsigned long tmp; | 232 | unsigned long tmp; |
| 233 | int is_sub; | ||
| 234 | int k; | ||
| 230 | 235 | ||
| 231 | /* acknowledge interrupt */ | 236 | /* acknowledge interrupt */ |
| 232 | tmp = lcdc_read(priv, _LDINTR); | 237 | tmp = lcdc_read(priv, _LDINTR); |
| @@ -234,8 +239,24 @@ static irqreturn_t sh_mobile_lcdc_irq(int irq, void *data) | |||
| 234 | tmp |= 0x000000ff ^ LDINTR_FS; /* status in low 8 */ | 239 | tmp |= 0x000000ff ^ LDINTR_FS; /* status in low 8 */ |
| 235 | lcdc_write(priv, _LDINTR, tmp); | 240 | lcdc_write(priv, _LDINTR, tmp); |
| 236 | 241 | ||
| 237 | /* disable clocks */ | 242 | /* figure out if this interrupt is for main or sub lcd */ |
| 238 | sh_mobile_lcdc_clk_off(priv); | 243 | is_sub = (lcdc_read(priv, _LDSR) & (1 << 10)) ? 1 : 0; |
| 244 | |||
| 245 | /* wake up channel and disable clocks*/ | ||
| 246 | for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { | ||
| 247 | ch = &priv->ch[k]; | ||
| 248 | |||
| 249 | if (!ch->enabled) | ||
| 250 | continue; | ||
| 251 | |||
| 252 | if (is_sub == lcdc_chan_is_sublcd(ch)) { | ||
| 253 | ch->frame_end = 1; | ||
| 254 | wake_up(&ch->frame_end_wait); | ||
| 255 | |||
| 256 | sh_mobile_lcdc_clk_off(priv); | ||
| 257 | } | ||
| 258 | } | ||
| 259 | |||
| 239 | return IRQ_HANDLED; | 260 | return IRQ_HANDLED; |
| 240 | } | 261 | } |
| 241 | 262 | ||
| @@ -448,18 +469,27 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv) | |||
| 448 | struct sh_mobile_lcdc_board_cfg *board_cfg; | 469 | struct sh_mobile_lcdc_board_cfg *board_cfg; |
| 449 | int k; | 470 | int k; |
| 450 | 471 | ||
| 451 | /* tell the board code to disable the panel */ | 472 | /* clean up deferred io and ask board code to disable panel */ |
| 452 | for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { | 473 | for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { |
| 453 | ch = &priv->ch[k]; | 474 | ch = &priv->ch[k]; |
| 454 | board_cfg = &ch->cfg.board_cfg; | ||
| 455 | if (board_cfg->display_off) | ||
| 456 | board_cfg->display_off(board_cfg->board_data); | ||
| 457 | 475 | ||
| 458 | /* cleanup deferred io if enabled */ | 476 | /* deferred io mode: |
| 477 | * flush frame, and wait for frame end interrupt | ||
| 478 | * clean up deferred io and enable clock | ||
| 479 | */ | ||
| 459 | if (ch->info.fbdefio) { | 480 | if (ch->info.fbdefio) { |
| 481 | ch->frame_end = 0; | ||
| 482 | schedule_delayed_work(&ch->info.deferred_work, 0); | ||
| 483 | wait_event(ch->frame_end_wait, ch->frame_end); | ||
| 460 | fb_deferred_io_cleanup(&ch->info); | 484 | fb_deferred_io_cleanup(&ch->info); |
| 461 | ch->info.fbdefio = NULL; | 485 | ch->info.fbdefio = NULL; |
| 486 | sh_mobile_lcdc_clk_on(priv); | ||
| 462 | } | 487 | } |
| 488 | |||
| 489 | board_cfg = &ch->cfg.board_cfg; | ||
| 490 | if (board_cfg->display_off) | ||
| 491 | board_cfg->display_off(board_cfg->board_data); | ||
| 492 | |||
| 463 | } | 493 | } |
| 464 | 494 | ||
| 465 | /* stop the lcdc */ | 495 | /* stop the lcdc */ |
| @@ -652,6 +682,26 @@ static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp) | |||
| 652 | return 0; | 682 | return 0; |
| 653 | } | 683 | } |
| 654 | 684 | ||
| 685 | static int sh_mobile_lcdc_suspend(struct device *dev) | ||
| 686 | { | ||
| 687 | struct platform_device *pdev = to_platform_device(dev); | ||
| 688 | |||
| 689 | sh_mobile_lcdc_stop(platform_get_drvdata(pdev)); | ||
| 690 | return 0; | ||
| 691 | } | ||
| 692 | |||
| 693 | static int sh_mobile_lcdc_resume(struct device *dev) | ||
| 694 | { | ||
| 695 | struct platform_device *pdev = to_platform_device(dev); | ||
| 696 | |||
| 697 | return sh_mobile_lcdc_start(platform_get_drvdata(pdev)); | ||
| 698 | } | ||
| 699 | |||
| 700 | static struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = { | ||
| 701 | .suspend = sh_mobile_lcdc_suspend, | ||
| 702 | .resume = sh_mobile_lcdc_resume, | ||
| 703 | }; | ||
| 704 | |||
| 655 | static int sh_mobile_lcdc_remove(struct platform_device *pdev); | 705 | static int sh_mobile_lcdc_remove(struct platform_device *pdev); |
| 656 | 706 | ||
| 657 | static int __init sh_mobile_lcdc_probe(struct platform_device *pdev) | 707 | static int __init sh_mobile_lcdc_probe(struct platform_device *pdev) |
| @@ -707,6 +757,7 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
| 707 | dev_err(&pdev->dev, "unsupported interface type\n"); | 757 | dev_err(&pdev->dev, "unsupported interface type\n"); |
| 708 | goto err1; | 758 | goto err1; |
| 709 | } | 759 | } |
| 760 | init_waitqueue_head(&priv->ch[i].frame_end_wait); | ||
| 710 | 761 | ||
| 711 | switch (pdata->ch[i].chan) { | 762 | switch (pdata->ch[i].chan) { |
| 712 | case LCDC_CHAN_MAINLCD: | 763 | case LCDC_CHAN_MAINLCD: |
| @@ -860,6 +911,7 @@ static struct platform_driver sh_mobile_lcdc_driver = { | |||
| 860 | .driver = { | 911 | .driver = { |
| 861 | .name = "sh_mobile_lcdc_fb", | 912 | .name = "sh_mobile_lcdc_fb", |
| 862 | .owner = THIS_MODULE, | 913 | .owner = THIS_MODULE, |
| 914 | .pm = &sh_mobile_lcdc_dev_pm_ops, | ||
| 863 | }, | 915 | }, |
| 864 | .probe = sh_mobile_lcdc_probe, | 916 | .probe = sh_mobile_lcdc_probe, |
| 865 | .remove = sh_mobile_lcdc_remove, | 917 | .remove = sh_mobile_lcdc_remove, |
