diff options
Diffstat (limited to 'drivers/video')
-rw-r--r-- | drivers/video/Kconfig | 2 | ||||
-rw-r--r-- | drivers/video/sh_mobile_lcdcfb.c | 175 |
2 files changed, 115 insertions, 62 deletions
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 3b54b394017..7e2c9774f08 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -1867,7 +1867,7 @@ config FB_W100 config FB_SH_MOBILE_LCDC tristate "SuperH Mobile LCDC framebuffer support" - depends on FB && SUPERH + depends on FB && SUPERH && HAVE_CLK select FB_SYS_FILLRECT select FB_SYS_COPYAREA select FB_SYS_IMAGEBLIT diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c index 07f22b62563..1cb5213c1a0 100644 --- a/drivers/video/sh_mobile_lcdcfb.c +++ b/drivers/video/sh_mobile_lcdcfb.c @@ -14,6 +14,7 @@ #include <linux/mm.h> #include <linux/fb.h> #include <linux/clk.h> +#include <linux/pm_runtime.h> #include <linux/platform_device.h> #include <linux/dma-mapping.h> #include <linux/interrupt.h> @@ -23,35 +24,6 @@ #define PALETTE_NR 16 -struct sh_mobile_lcdc_priv; -struct sh_mobile_lcdc_chan { - struct sh_mobile_lcdc_priv *lcdc; - unsigned long *reg_offs; - unsigned long ldmt1r_value; - unsigned long enabled; /* ME and SE in LDCNT2R */ - struct sh_mobile_lcdc_chan_cfg cfg; - u32 pseudo_palette[PALETTE_NR]; - struct fb_info *info; - dma_addr_t dma_handle; - struct fb_deferred_io defio; - struct scatterlist *sglist; - unsigned long frame_end; - wait_queue_head_t frame_end_wait; -}; - -struct sh_mobile_lcdc_priv { - void __iomem *base; - int irq; -#ifdef CONFIG_HAVE_CLK - atomic_t clk_usecnt; - struct clk *dot_clk; - struct clk *clk; -#endif - unsigned long lddckr; - struct sh_mobile_lcdc_chan ch[2]; - int started; -}; - /* shared registers */ #define _LDDCKR 0x410 #define _LDDCKSTPR 0x414 @@ -65,11 +37,23 @@ struct sh_mobile_lcdc_priv { #define _LDDWAR 0x900 #define _LDDRAR 0x904 +/* shared registers and their order for context save/restore */ +static int lcdc_shared_regs[] = { + _LDDCKR, + _LDDCKSTPR, + _LDINTR, + _LDDDSR, + _LDCNT1R, + _LDCNT2R, +}; +#define NR_SHARED_REGS ARRAY_SIZE(lcdc_shared_regs) + /* per-channel registers */ enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R, - LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR }; + LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR, + NR_CH_REGS }; -static unsigned long lcdc_offs_mainlcd[] = { +static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { [LDDCKPAT1R] = 0x400, [LDDCKPAT2R] = 0x404, [LDMT1R] = 0x418, @@ -87,7 +71,7 @@ static unsigned long lcdc_offs_mainlcd[] = { [LDPMR] = 0x460, }; -static unsigned long lcdc_offs_sublcd[] = { +static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = { [LDDCKPAT1R] = 0x408, [LDDCKPAT2R] = 0x40c, [LDMT1R] = 0x600, @@ -112,6 +96,35 @@ static unsigned long lcdc_offs_sublcd[] = { #define LDINTR_FE 0x00000400 #define LDINTR_FS 0x00000004 +struct sh_mobile_lcdc_priv; +struct sh_mobile_lcdc_chan { + struct sh_mobile_lcdc_priv *lcdc; + unsigned long *reg_offs; + unsigned long ldmt1r_value; + unsigned long enabled; /* ME and SE in LDCNT2R */ + struct sh_mobile_lcdc_chan_cfg cfg; + u32 pseudo_palette[PALETTE_NR]; + unsigned long saved_ch_regs[NR_CH_REGS]; + struct fb_info *info; + dma_addr_t dma_handle; + struct fb_deferred_io defio; + struct scatterlist *sglist; + unsigned long frame_end; + wait_queue_head_t frame_end_wait; +}; + +struct sh_mobile_lcdc_priv { + void __iomem *base; + int irq; + atomic_t hw_usecnt; + struct device *dev; + struct clk *dot_clk; + unsigned long lddckr; + struct sh_mobile_lcdc_chan ch[2]; + unsigned long saved_shared_regs[NR_SHARED_REGS]; + int started; +}; + static void lcdc_write_chan(struct sh_mobile_lcdc_chan *chan, int reg_nr, unsigned long data) { @@ -156,6 +169,7 @@ static void lcdc_sys_write_index(void *handle, unsigned long data) lcdc_write(ch->lcdc, _LDDWD0R, data | 0x10000000); lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0); lcdc_write(ch->lcdc, _LDDWAR, 1 | (lcdc_chan_is_sublcd(ch) ? 2 : 0)); + lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0); } static void lcdc_sys_write_data(void *handle, unsigned long data) @@ -165,6 +179,7 @@ static void lcdc_sys_write_data(void *handle, unsigned long data) lcdc_write(ch->lcdc, _LDDWD0R, data | 0x11000000); lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0); lcdc_write(ch->lcdc, _LDDWAR, 1 | (lcdc_chan_is_sublcd(ch) ? 2 : 0)); + lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0); } static unsigned long lcdc_sys_read_data(void *handle) @@ -175,8 +190,9 @@ static unsigned long lcdc_sys_read_data(void *handle) lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0); lcdc_write(ch->lcdc, _LDDRAR, 1 | (lcdc_chan_is_sublcd(ch) ? 2 : 0)); udelay(1); + lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0); - return lcdc_read(ch->lcdc, _LDDRDR) & 0xffff; + return lcdc_read(ch->lcdc, _LDDRDR) & 0x3ffff; } struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = { @@ -185,11 +201,10 @@ struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = { lcdc_sys_read_data, }; -#ifdef CONFIG_HAVE_CLK static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv) { - if (atomic_inc_and_test(&priv->clk_usecnt)) { - clk_enable(priv->clk); + if (atomic_inc_and_test(&priv->hw_usecnt)) { + pm_runtime_get_sync(priv->dev); if (priv->dot_clk) clk_enable(priv->dot_clk); } @@ -197,16 +212,12 @@ static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv) static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv) { - if (atomic_sub_return(1, &priv->clk_usecnt) == -1) { + if (atomic_sub_return(1, &priv->hw_usecnt) == -1) { if (priv->dot_clk) clk_disable(priv->dot_clk); - clk_disable(priv->clk); + pm_runtime_put(priv->dev); } } -#else -static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv) {} -static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv) {} -#endif static int sh_mobile_lcdc_sginit(struct fb_info *info, struct list_head *pagelist) @@ -520,7 +531,6 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv) board_cfg = &ch->cfg.board_cfg; if (board_cfg->display_off) board_cfg->display_off(board_cfg->board_data); - } /* stop the lcdc */ @@ -579,9 +589,6 @@ static int sh_mobile_lcdc_setup_clocks(struct platform_device *pdev, int clock_source, struct sh_mobile_lcdc_priv *priv) { -#ifdef CONFIG_HAVE_CLK - char clk_name[8]; -#endif char *str; int icksel; @@ -595,25 +602,21 @@ static int sh_mobile_lcdc_setup_clocks(struct platform_device *pdev, priv->lddckr = icksel << 16; -#ifdef CONFIG_HAVE_CLK - atomic_set(&priv->clk_usecnt, -1); - snprintf(clk_name, sizeof(clk_name), "lcdc%d", pdev->id); - priv->clk = clk_get(&pdev->dev, clk_name); - if (IS_ERR(priv->clk)) { - dev_err(&pdev->dev, "cannot get clock \"%s\"\n", clk_name); - return PTR_ERR(priv->clk); - } - if (str) { priv->dot_clk = clk_get(&pdev->dev, str); if (IS_ERR(priv->dot_clk)) { dev_err(&pdev->dev, "cannot get dot clock %s\n", str); - clk_put(priv->clk); return PTR_ERR(priv->dot_clk); } } -#endif - + atomic_set(&priv->hw_usecnt, -1); + + /* Runtime PM support involves two step for this driver: + * 1) Enable Runtime PM + * 2) Force Runtime PM Resume since hardware is accessed from probe() + */ + pm_runtime_enable(priv->dev); + pm_runtime_resume(priv->dev); return 0; } @@ -731,9 +734,59 @@ static int sh_mobile_lcdc_resume(struct device *dev) return sh_mobile_lcdc_start(platform_get_drvdata(pdev)); } +static int sh_mobile_lcdc_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct sh_mobile_lcdc_priv *p = platform_get_drvdata(pdev); + struct sh_mobile_lcdc_chan *ch; + int k, n; + + /* save per-channel registers */ + for (k = 0; k < ARRAY_SIZE(p->ch); k++) { + ch = &p->ch[k]; + if (!ch->enabled) + continue; + for (n = 0; n < NR_CH_REGS; n++) + ch->saved_ch_regs[n] = lcdc_read_chan(ch, n); + } + + /* save shared registers */ + for (n = 0; n < NR_SHARED_REGS; n++) + p->saved_shared_regs[n] = lcdc_read(p, lcdc_shared_regs[n]); + + /* turn off LCDC hardware */ + lcdc_write(p, _LDCNT1R, 0); + return 0; +} + +static int sh_mobile_lcdc_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct sh_mobile_lcdc_priv *p = platform_get_drvdata(pdev); + struct sh_mobile_lcdc_chan *ch; + int k, n; + + /* restore per-channel registers */ + for (k = 0; k < ARRAY_SIZE(p->ch); k++) { + ch = &p->ch[k]; + if (!ch->enabled) + continue; + for (n = 0; n < NR_CH_REGS; n++) + lcdc_write_chan(ch, n, ch->saved_ch_regs[n]); + } + + /* restore shared registers */ + for (n = 0; n < NR_SHARED_REGS; n++) + lcdc_write(p, lcdc_shared_regs[n], p->saved_shared_regs[n]); + + return 0; +} + static struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = { .suspend = sh_mobile_lcdc_suspend, .resume = sh_mobile_lcdc_resume, + .runtime_suspend = sh_mobile_lcdc_runtime_suspend, + .runtime_resume = sh_mobile_lcdc_runtime_resume, }; static int sh_mobile_lcdc_remove(struct platform_device *pdev); @@ -778,6 +831,7 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev) } priv->irq = i; + priv->dev = &pdev->dev; platform_set_drvdata(pdev, priv); pdata = pdev->dev.platform_data; @@ -947,11 +1001,10 @@ static int sh_mobile_lcdc_remove(struct platform_device *pdev) framebuffer_release(info); } -#ifdef CONFIG_HAVE_CLK if (priv->dot_clk) clk_put(priv->dot_clk); - clk_put(priv->clk); -#endif + + pm_runtime_disable(priv->dev); if (priv->base) iounmap(priv->base); |