diff options
author | Magnus Damm <damm@igel.co.jp> | 2009-08-14 06:49:08 -0400 |
---|---|---|
committer | Paul Mundt <lethal@linux-sh.org> | 2009-08-23 05:03:19 -0400 |
commit | 0246c4712c40294bd5e8335f0c15a38c8e52709f (patch) | |
tree | 375b68994d72931b9ecd6e7b2e1ccb9716c75d73 /drivers | |
parent | f1a3b994f9dfd12111dc034402aed256fac66dfe (diff) |
video: Runtime PM for SuperH Mobile LCDC
This patch modifies the SuperH Mobile LCDC framebuffer driver
to support Runtime PM. The driver is using the functions
- pm_runtime_get_sync()
- pm_runtime_put_sync()
to inform the bus code if the hardware is idle or not. If the
hardware is idle then the bus code may call the runtime dev_pm_ops
callbacks to save and restore state. pm_runtime_resume() is used
to allow the driver to access the hardware from probe().
Signed-off-by: Magnus Damm <damm@igel.co.jp>
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/video/sh_mobile_lcdcfb.c | 156 |
1 files changed, 110 insertions, 46 deletions
diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c index fc3f9662ceae..1cb5213c1a03 100644 --- a/drivers/video/sh_mobile_lcdcfb.c +++ b/drivers/video/sh_mobile_lcdcfb.c | |||
@@ -14,6 +14,7 @@ | |||
14 | #include <linux/mm.h> | 14 | #include <linux/mm.h> |
15 | #include <linux/fb.h> | 15 | #include <linux/fb.h> |
16 | #include <linux/clk.h> | 16 | #include <linux/clk.h> |
17 | #include <linux/pm_runtime.h> | ||
17 | #include <linux/platform_device.h> | 18 | #include <linux/platform_device.h> |
18 | #include <linux/dma-mapping.h> | 19 | #include <linux/dma-mapping.h> |
19 | #include <linux/interrupt.h> | 20 | #include <linux/interrupt.h> |
@@ -23,33 +24,6 @@ | |||
23 | 24 | ||
24 | #define PALETTE_NR 16 | 25 | #define PALETTE_NR 16 |
25 | 26 | ||
26 | struct sh_mobile_lcdc_priv; | ||
27 | struct sh_mobile_lcdc_chan { | ||
28 | struct sh_mobile_lcdc_priv *lcdc; | ||
29 | unsigned long *reg_offs; | ||
30 | unsigned long ldmt1r_value; | ||
31 | unsigned long enabled; /* ME and SE in LDCNT2R */ | ||
32 | struct sh_mobile_lcdc_chan_cfg cfg; | ||
33 | u32 pseudo_palette[PALETTE_NR]; | ||
34 | struct fb_info *info; | ||
35 | dma_addr_t dma_handle; | ||
36 | struct fb_deferred_io defio; | ||
37 | struct scatterlist *sglist; | ||
38 | unsigned long frame_end; | ||
39 | wait_queue_head_t frame_end_wait; | ||
40 | }; | ||
41 | |||
42 | struct sh_mobile_lcdc_priv { | ||
43 | void __iomem *base; | ||
44 | int irq; | ||
45 | atomic_t clk_usecnt; | ||
46 | struct clk *dot_clk; | ||
47 | struct clk *clk; | ||
48 | unsigned long lddckr; | ||
49 | struct sh_mobile_lcdc_chan ch[2]; | ||
50 | int started; | ||
51 | }; | ||
52 | |||
53 | /* shared registers */ | 27 | /* shared registers */ |
54 | #define _LDDCKR 0x410 | 28 | #define _LDDCKR 0x410 |
55 | #define _LDDCKSTPR 0x414 | 29 | #define _LDDCKSTPR 0x414 |
@@ -63,11 +37,23 @@ struct sh_mobile_lcdc_priv { | |||
63 | #define _LDDWAR 0x900 | 37 | #define _LDDWAR 0x900 |
64 | #define _LDDRAR 0x904 | 38 | #define _LDDRAR 0x904 |
65 | 39 | ||
40 | /* shared registers and their order for context save/restore */ | ||
41 | static int lcdc_shared_regs[] = { | ||
42 | _LDDCKR, | ||
43 | _LDDCKSTPR, | ||
44 | _LDINTR, | ||
45 | _LDDDSR, | ||
46 | _LDCNT1R, | ||
47 | _LDCNT2R, | ||
48 | }; | ||
49 | #define NR_SHARED_REGS ARRAY_SIZE(lcdc_shared_regs) | ||
50 | |||
66 | /* per-channel registers */ | 51 | /* per-channel registers */ |
67 | enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R, | 52 | enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R, |
68 | LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR }; | 53 | LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR, |
54 | NR_CH_REGS }; | ||
69 | 55 | ||
70 | static unsigned long lcdc_offs_mainlcd[] = { | 56 | static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { |
71 | [LDDCKPAT1R] = 0x400, | 57 | [LDDCKPAT1R] = 0x400, |
72 | [LDDCKPAT2R] = 0x404, | 58 | [LDDCKPAT2R] = 0x404, |
73 | [LDMT1R] = 0x418, | 59 | [LDMT1R] = 0x418, |
@@ -85,7 +71,7 @@ static unsigned long lcdc_offs_mainlcd[] = { | |||
85 | [LDPMR] = 0x460, | 71 | [LDPMR] = 0x460, |
86 | }; | 72 | }; |
87 | 73 | ||
88 | static unsigned long lcdc_offs_sublcd[] = { | 74 | static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = { |
89 | [LDDCKPAT1R] = 0x408, | 75 | [LDDCKPAT1R] = 0x408, |
90 | [LDDCKPAT2R] = 0x40c, | 76 | [LDDCKPAT2R] = 0x40c, |
91 | [LDMT1R] = 0x600, | 77 | [LDMT1R] = 0x600, |
@@ -110,6 +96,35 @@ static unsigned long lcdc_offs_sublcd[] = { | |||
110 | #define LDINTR_FE 0x00000400 | 96 | #define LDINTR_FE 0x00000400 |
111 | #define LDINTR_FS 0x00000004 | 97 | #define LDINTR_FS 0x00000004 |
112 | 98 | ||
99 | struct sh_mobile_lcdc_priv; | ||
100 | struct sh_mobile_lcdc_chan { | ||
101 | struct sh_mobile_lcdc_priv *lcdc; | ||
102 | unsigned long *reg_offs; | ||
103 | unsigned long ldmt1r_value; | ||
104 | unsigned long enabled; /* ME and SE in LDCNT2R */ | ||
105 | struct sh_mobile_lcdc_chan_cfg cfg; | ||
106 | u32 pseudo_palette[PALETTE_NR]; | ||
107 | unsigned long saved_ch_regs[NR_CH_REGS]; | ||
108 | struct fb_info *info; | ||
109 | dma_addr_t dma_handle; | ||
110 | struct fb_deferred_io defio; | ||
111 | struct scatterlist *sglist; | ||
112 | unsigned long frame_end; | ||
113 | wait_queue_head_t frame_end_wait; | ||
114 | }; | ||
115 | |||
116 | struct sh_mobile_lcdc_priv { | ||
117 | void __iomem *base; | ||
118 | int irq; | ||
119 | atomic_t hw_usecnt; | ||
120 | struct device *dev; | ||
121 | struct clk *dot_clk; | ||
122 | unsigned long lddckr; | ||
123 | struct sh_mobile_lcdc_chan ch[2]; | ||
124 | unsigned long saved_shared_regs[NR_SHARED_REGS]; | ||
125 | int started; | ||
126 | }; | ||
127 | |||
113 | static void lcdc_write_chan(struct sh_mobile_lcdc_chan *chan, | 128 | static void lcdc_write_chan(struct sh_mobile_lcdc_chan *chan, |
114 | int reg_nr, unsigned long data) | 129 | int reg_nr, unsigned long data) |
115 | { | 130 | { |
@@ -188,8 +203,8 @@ struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = { | |||
188 | 203 | ||
189 | static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv) | 204 | static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv) |
190 | { | 205 | { |
191 | if (atomic_inc_and_test(&priv->clk_usecnt)) { | 206 | if (atomic_inc_and_test(&priv->hw_usecnt)) { |
192 | clk_enable(priv->clk); | 207 | pm_runtime_get_sync(priv->dev); |
193 | if (priv->dot_clk) | 208 | if (priv->dot_clk) |
194 | clk_enable(priv->dot_clk); | 209 | clk_enable(priv->dot_clk); |
195 | } | 210 | } |
@@ -197,10 +212,10 @@ static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv) | |||
197 | 212 | ||
198 | static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv) | 213 | static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv) |
199 | { | 214 | { |
200 | if (atomic_sub_return(1, &priv->clk_usecnt) == -1) { | 215 | if (atomic_sub_return(1, &priv->hw_usecnt) == -1) { |
201 | if (priv->dot_clk) | 216 | if (priv->dot_clk) |
202 | clk_disable(priv->dot_clk); | 217 | clk_disable(priv->dot_clk); |
203 | clk_disable(priv->clk); | 218 | pm_runtime_put(priv->dev); |
204 | } | 219 | } |
205 | } | 220 | } |
206 | 221 | ||
@@ -574,7 +589,6 @@ static int sh_mobile_lcdc_setup_clocks(struct platform_device *pdev, | |||
574 | int clock_source, | 589 | int clock_source, |
575 | struct sh_mobile_lcdc_priv *priv) | 590 | struct sh_mobile_lcdc_priv *priv) |
576 | { | 591 | { |
577 | char clk_name[8]; | ||
578 | char *str; | 592 | char *str; |
579 | int icksel; | 593 | int icksel; |
580 | 594 | ||
@@ -588,23 +602,21 @@ static int sh_mobile_lcdc_setup_clocks(struct platform_device *pdev, | |||
588 | 602 | ||
589 | priv->lddckr = icksel << 16; | 603 | priv->lddckr = icksel << 16; |
590 | 604 | ||
591 | atomic_set(&priv->clk_usecnt, -1); | ||
592 | snprintf(clk_name, sizeof(clk_name), "lcdc%d", pdev->id); | ||
593 | priv->clk = clk_get(&pdev->dev, clk_name); | ||
594 | if (IS_ERR(priv->clk)) { | ||
595 | dev_err(&pdev->dev, "cannot get clock \"%s\"\n", clk_name); | ||
596 | return PTR_ERR(priv->clk); | ||
597 | } | ||
598 | |||
599 | if (str) { | 605 | if (str) { |
600 | priv->dot_clk = clk_get(&pdev->dev, str); | 606 | priv->dot_clk = clk_get(&pdev->dev, str); |
601 | if (IS_ERR(priv->dot_clk)) { | 607 | if (IS_ERR(priv->dot_clk)) { |
602 | dev_err(&pdev->dev, "cannot get dot clock %s\n", str); | 608 | dev_err(&pdev->dev, "cannot get dot clock %s\n", str); |
603 | clk_put(priv->clk); | ||
604 | return PTR_ERR(priv->dot_clk); | 609 | return PTR_ERR(priv->dot_clk); |
605 | } | 610 | } |
606 | } | 611 | } |
607 | 612 | atomic_set(&priv->hw_usecnt, -1); | |
613 | |||
614 | /* Runtime PM support involves two step for this driver: | ||
615 | * 1) Enable Runtime PM | ||
616 | * 2) Force Runtime PM Resume since hardware is accessed from probe() | ||
617 | */ | ||
618 | pm_runtime_enable(priv->dev); | ||
619 | pm_runtime_resume(priv->dev); | ||
608 | return 0; | 620 | return 0; |
609 | } | 621 | } |
610 | 622 | ||
@@ -722,9 +734,59 @@ static int sh_mobile_lcdc_resume(struct device *dev) | |||
722 | return sh_mobile_lcdc_start(platform_get_drvdata(pdev)); | 734 | return sh_mobile_lcdc_start(platform_get_drvdata(pdev)); |
723 | } | 735 | } |
724 | 736 | ||
737 | static int sh_mobile_lcdc_runtime_suspend(struct device *dev) | ||
738 | { | ||
739 | struct platform_device *pdev = to_platform_device(dev); | ||
740 | struct sh_mobile_lcdc_priv *p = platform_get_drvdata(pdev); | ||
741 | struct sh_mobile_lcdc_chan *ch; | ||
742 | int k, n; | ||
743 | |||
744 | /* save per-channel registers */ | ||
745 | for (k = 0; k < ARRAY_SIZE(p->ch); k++) { | ||
746 | ch = &p->ch[k]; | ||
747 | if (!ch->enabled) | ||
748 | continue; | ||
749 | for (n = 0; n < NR_CH_REGS; n++) | ||
750 | ch->saved_ch_regs[n] = lcdc_read_chan(ch, n); | ||
751 | } | ||
752 | |||
753 | /* save shared registers */ | ||
754 | for (n = 0; n < NR_SHARED_REGS; n++) | ||
755 | p->saved_shared_regs[n] = lcdc_read(p, lcdc_shared_regs[n]); | ||
756 | |||
757 | /* turn off LCDC hardware */ | ||
758 | lcdc_write(p, _LDCNT1R, 0); | ||
759 | return 0; | ||
760 | } | ||
761 | |||
762 | static int sh_mobile_lcdc_runtime_resume(struct device *dev) | ||
763 | { | ||
764 | struct platform_device *pdev = to_platform_device(dev); | ||
765 | struct sh_mobile_lcdc_priv *p = platform_get_drvdata(pdev); | ||
766 | struct sh_mobile_lcdc_chan *ch; | ||
767 | int k, n; | ||
768 | |||
769 | /* restore per-channel registers */ | ||
770 | for (k = 0; k < ARRAY_SIZE(p->ch); k++) { | ||
771 | ch = &p->ch[k]; | ||
772 | if (!ch->enabled) | ||
773 | continue; | ||
774 | for (n = 0; n < NR_CH_REGS; n++) | ||
775 | lcdc_write_chan(ch, n, ch->saved_ch_regs[n]); | ||
776 | } | ||
777 | |||
778 | /* restore shared registers */ | ||
779 | for (n = 0; n < NR_SHARED_REGS; n++) | ||
780 | lcdc_write(p, lcdc_shared_regs[n], p->saved_shared_regs[n]); | ||
781 | |||
782 | return 0; | ||
783 | } | ||
784 | |||
725 | static struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = { | 785 | static struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = { |
726 | .suspend = sh_mobile_lcdc_suspend, | 786 | .suspend = sh_mobile_lcdc_suspend, |
727 | .resume = sh_mobile_lcdc_resume, | 787 | .resume = sh_mobile_lcdc_resume, |
788 | .runtime_suspend = sh_mobile_lcdc_runtime_suspend, | ||
789 | .runtime_resume = sh_mobile_lcdc_runtime_resume, | ||
728 | }; | 790 | }; |
729 | 791 | ||
730 | static int sh_mobile_lcdc_remove(struct platform_device *pdev); | 792 | static int sh_mobile_lcdc_remove(struct platform_device *pdev); |
@@ -769,6 +831,7 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
769 | } | 831 | } |
770 | 832 | ||
771 | priv->irq = i; | 833 | priv->irq = i; |
834 | priv->dev = &pdev->dev; | ||
772 | platform_set_drvdata(pdev, priv); | 835 | platform_set_drvdata(pdev, priv); |
773 | pdata = pdev->dev.platform_data; | 836 | pdata = pdev->dev.platform_data; |
774 | 837 | ||
@@ -940,7 +1003,8 @@ static int sh_mobile_lcdc_remove(struct platform_device *pdev) | |||
940 | 1003 | ||
941 | if (priv->dot_clk) | 1004 | if (priv->dot_clk) |
942 | clk_put(priv->dot_clk); | 1005 | clk_put(priv->dot_clk); |
943 | clk_put(priv->clk); | 1006 | |
1007 | pm_runtime_disable(priv->dev); | ||
944 | 1008 | ||
945 | if (priv->base) | 1009 | if (priv->base) |
946 | iounmap(priv->base); | 1010 | iounmap(priv->base); |