aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/video/sh_mobile_lcdcfb.c
diff options
context:
space:
mode:
authorMagnus Damm <damm@igel.co.jp>2008-12-19 01:34:41 -0500
committerPaul Mundt <lethal@linux-sh.org>2008-12-22 04:44:48 -0500
commit8564557a03c12adb9c4b76ae1e86db4113a04d13 (patch)
tree680fbc6f67382b76262477259fff4601c5e7a45e /drivers/video/sh_mobile_lcdcfb.c
parent37b4837959cb9aa60686ca0d85f73d819251abad (diff)
video: sh_mobile_lcdcfb deferred io support
This patch adds sh_mobile_lcdcfb deferred io support for SYS panels. The LCDC hardware block managed by the sh_mobile_lcdcfb driver supports RGB or SYS panel configurations. SYS panels come with an external display controller that is resposible for refreshing the actual LCD panel. RGB panels are controlled directly by the LCDC and they need to be refreshed by the LCDC hardware. In the case of SYS panels we can save some power by configuring the LCDC hardware block in one-shot mode. In this one-shot mode panel refresh is managed by software. This works well together with deferred io since it allows us to stop clocks for most of the time and only enable clocks when we actually want to trigger an update. When there is no fbdev activity the clocks are kept stopped which allows us to deep sleep. The refresh rate in deferred io mode is set using platform data. The same platform data can also be used to disable deferred io mode. As with other deferred io frame buffers user space code should use fsync() on the frame buffer device to trigger an update. Signed-off-by: Magnus Damm <damm@igel.co.jp> Signed-off-by: Paul Mundt <lethal@linux-sh.org>
Diffstat (limited to 'drivers/video/sh_mobile_lcdcfb.c')
-rw-r--r--drivers/video/sh_mobile_lcdcfb.c170
1 files changed, 148 insertions, 22 deletions
diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c
index e339d829183..0e2b8fd24df 100644
--- a/drivers/video/sh_mobile_lcdcfb.c
+++ b/drivers/video/sh_mobile_lcdcfb.c
@@ -16,7 +16,9 @@
16#include <linux/clk.h> 16#include <linux/clk.h>
17#include <linux/platform_device.h> 17#include <linux/platform_device.h>
18#include <linux/dma-mapping.h> 18#include <linux/dma-mapping.h>
19#include <linux/interrupt.h>
19#include <video/sh_mobile_lcdc.h> 20#include <video/sh_mobile_lcdc.h>
21#include <asm/atomic.h>
20 22
21#define PALETTE_NR 16 23#define PALETTE_NR 16
22 24
@@ -30,11 +32,14 @@ struct sh_mobile_lcdc_chan {
30 u32 pseudo_palette[PALETTE_NR]; 32 u32 pseudo_palette[PALETTE_NR];
31 struct fb_info info; 33 struct fb_info info;
32 dma_addr_t dma_handle; 34 dma_addr_t dma_handle;
35 struct fb_deferred_io defio;
33}; 36};
34 37
35struct sh_mobile_lcdc_priv { 38struct sh_mobile_lcdc_priv {
36 void __iomem *base; 39 void __iomem *base;
40 int irq;
37#ifdef CONFIG_HAVE_CLK 41#ifdef CONFIG_HAVE_CLK
42 atomic_t clk_usecnt;
38 struct clk *dot_clk; 43 struct clk *dot_clk;
39 struct clk *clk; 44 struct clk *clk;
40#endif 45#endif
@@ -57,7 +62,7 @@ struct sh_mobile_lcdc_priv {
57 62
58/* per-channel registers */ 63/* per-channel registers */
59enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R, 64enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R,
60 LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR }; 65 LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR };
61 66
62static unsigned long lcdc_offs_mainlcd[] = { 67static unsigned long lcdc_offs_mainlcd[] = {
63 [LDDCKPAT1R] = 0x400, 68 [LDDCKPAT1R] = 0x400,
@@ -67,6 +72,7 @@ static unsigned long lcdc_offs_mainlcd[] = {
67 [LDMT3R] = 0x420, 72 [LDMT3R] = 0x420,
68 [LDDFR] = 0x424, 73 [LDDFR] = 0x424,
69 [LDSM1R] = 0x428, 74 [LDSM1R] = 0x428,
75 [LDSM2R] = 0x42c,
70 [LDSA1R] = 0x430, 76 [LDSA1R] = 0x430,
71 [LDMLSR] = 0x438, 77 [LDMLSR] = 0x438,
72 [LDHCNR] = 0x448, 78 [LDHCNR] = 0x448,
@@ -84,6 +90,7 @@ static unsigned long lcdc_offs_sublcd[] = {
84 [LDMT3R] = 0x608, 90 [LDMT3R] = 0x608,
85 [LDDFR] = 0x60c, 91 [LDDFR] = 0x60c,
86 [LDSM1R] = 0x610, 92 [LDSM1R] = 0x610,
93 [LDSM2R] = 0x614,
87 [LDSA1R] = 0x618, 94 [LDSA1R] = 0x618,
88 [LDMLSR] = 0x620, 95 [LDMLSR] = 0x620,
89 [LDHCNR] = 0x624, 96 [LDHCNR] = 0x624,
@@ -97,6 +104,8 @@ static unsigned long lcdc_offs_sublcd[] = {
97#define LCDC_RESET 0x00000100 104#define LCDC_RESET 0x00000100
98#define DISPLAY_BEU 0x00000008 105#define DISPLAY_BEU 0x00000008
99#define LCDC_ENABLE 0x00000001 106#define LCDC_ENABLE 0x00000001
107#define LDINTR_FE 0x00000400
108#define LDINTR_FS 0x00000004
100 109
101static void lcdc_write_chan(struct sh_mobile_lcdc_chan *chan, 110static void lcdc_write_chan(struct sh_mobile_lcdc_chan *chan,
102 int reg_nr, unsigned long data) 111 int reg_nr, unsigned long data)
@@ -171,6 +180,65 @@ struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = {
171 lcdc_sys_read_data, 180 lcdc_sys_read_data,
172}; 181};
173 182
183#ifdef CONFIG_HAVE_CLK
184static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv)
185{
186 if (atomic_inc_and_test(&priv->clk_usecnt)) {
187 clk_enable(priv->clk);
188 if (priv->dot_clk)
189 clk_enable(priv->dot_clk);
190 }
191}
192
193static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv)
194{
195 if (atomic_sub_return(1, &priv->clk_usecnt) == -1) {
196 if (priv->dot_clk)
197 clk_disable(priv->dot_clk);
198 clk_disable(priv->clk);
199 }
200}
201#else
202static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv) {}
203static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv) {}
204#endif
205
206static void sh_mobile_lcdc_deferred_io(struct fb_info *info,
207 struct list_head *pagelist)
208{
209 struct sh_mobile_lcdc_chan *ch = info->par;
210
211 /* enable clocks before accessing hardware */
212 sh_mobile_lcdc_clk_on(ch->lcdc);
213
214 /* trigger panel update */
215 lcdc_write_chan(ch, LDSM2R, 1);
216}
217
218static void sh_mobile_lcdc_deferred_io_touch(struct fb_info *info)
219{
220 struct fb_deferred_io *fbdefio = info->fbdefio;
221
222 if (fbdefio)
223 schedule_delayed_work(&info->deferred_work, fbdefio->delay);
224}
225
226static irqreturn_t sh_mobile_lcdc_irq(int irq, void *data)
227{
228 struct sh_mobile_lcdc_priv *priv = data;
229 unsigned long tmp;
230
231 /* acknowledge interrupt */
232 tmp = lcdc_read(priv, _LDINTR);
233 tmp &= 0xffffff00; /* mask in high 24 bits */
234 tmp |= 0x000000ff ^ LDINTR_FS; /* status in low 8 */
235 lcdc_write(priv, _LDINTR, tmp);
236
237 /* disable clocks */
238 sh_mobile_lcdc_clk_off(priv);
239 return IRQ_HANDLED;
240}
241
174static void sh_mobile_lcdc_start_stop(struct sh_mobile_lcdc_priv *priv, 242static void sh_mobile_lcdc_start_stop(struct sh_mobile_lcdc_priv *priv,
175 int start) 243 int start)
176{ 244{
@@ -208,11 +276,11 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
208 int k, m; 276 int k, m;
209 int ret = 0; 277 int ret = 0;
210 278
211#ifdef CONFIG_HAVE_CLK 279 /* enable clocks before accessing the hardware */
212 clk_enable(priv->clk); 280 for (k = 0; k < ARRAY_SIZE(priv->ch); k++)
213 if (priv->dot_clk) 281 if (priv->ch[k].enabled)
214 clk_enable(priv->dot_clk); 282 sh_mobile_lcdc_clk_on(priv);
215#endif 283
216 /* reset */ 284 /* reset */
217 lcdc_write(priv, _LDCNT2R, lcdc_read(priv, _LDCNT2R) | LCDC_RESET); 285 lcdc_write(priv, _LDCNT2R, lcdc_read(priv, _LDCNT2R) | LCDC_RESET);
218 lcdc_wait_bit(priv, _LDCNT2R, LCDC_RESET, 0); 286 lcdc_wait_bit(priv, _LDCNT2R, LCDC_RESET, 0);
@@ -255,7 +323,7 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
255 lcdc_write(priv, _LDDCKSTPR, 0); 323 lcdc_write(priv, _LDDCKSTPR, 0);
256 lcdc_wait_bit(priv, _LDDCKSTPR, ~0, 0); 324 lcdc_wait_bit(priv, _LDDCKSTPR, ~0, 0);
257 325
258 /* interrupts are disabled */ 326 /* interrupts are disabled to begin with */
259 lcdc_write(priv, _LDINTR, 0); 327 lcdc_write(priv, _LDINTR, 0);
260 328
261 for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { 329 for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
@@ -316,9 +384,6 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
316 return ret; 384 return ret;
317 } 385 }
318 386
319 /* --- display_lcdc_data() --- */
320 lcdc_write(priv, _LDINTR, 0x00000f00);
321
322 /* word and long word swap */ 387 /* word and long word swap */
323 lcdc_write(priv, _LDDDSR, lcdc_read(priv, _LDDDSR) | 6); 388 lcdc_write(priv, _LDDDSR, lcdc_read(priv, _LDDDSR) | 6);
324 389
@@ -340,8 +405,24 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
340 /* set line size */ 405 /* set line size */
341 lcdc_write_chan(ch, LDMLSR, ch->info.fix.line_length); 406 lcdc_write_chan(ch, LDMLSR, ch->info.fix.line_length);
342 407
343 /* continuous read mode */ 408 /* setup deferred io if SYS bus */
344 lcdc_write_chan(ch, LDSM1R, 0); 409 tmp = ch->cfg.sys_bus_cfg.deferred_io_msec;
410 if (ch->ldmt1r_value & (1 << 12) && tmp) {
411 ch->defio.deferred_io = sh_mobile_lcdc_deferred_io;
412 ch->defio.delay = msecs_to_jiffies(tmp);
413 ch->info.fbdefio = &ch->defio;
414 fb_deferred_io_init(&ch->info);
415
416 /* one-shot mode */
417 lcdc_write_chan(ch, LDSM1R, 1);
418
419 /* enable "Frame End Interrupt Enable" bit */
420 lcdc_write(priv, _LDINTR, LDINTR_FE);
421
422 } else {
423 /* continuous read mode */
424 lcdc_write_chan(ch, LDSM1R, 0);
425 }
345 } 426 }
346 427
347 /* display output */ 428 /* display output */
@@ -365,6 +446,7 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv)
365{ 446{
366 struct sh_mobile_lcdc_chan *ch; 447 struct sh_mobile_lcdc_chan *ch;
367 struct sh_mobile_lcdc_board_cfg *board_cfg; 448 struct sh_mobile_lcdc_board_cfg *board_cfg;
449 unsigned long tmp;
368 int k; 450 int k;
369 451
370 /* tell the board code to disable the panel */ 452 /* tell the board code to disable the panel */
@@ -373,16 +455,22 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv)
373 board_cfg = &ch->cfg.board_cfg; 455 board_cfg = &ch->cfg.board_cfg;
374 if (board_cfg->display_off) 456 if (board_cfg->display_off)
375 board_cfg->display_off(board_cfg->board_data); 457 board_cfg->display_off(board_cfg->board_data);
458
459 /* cleanup deferred io if SYS bus */
460 tmp = ch->cfg.sys_bus_cfg.deferred_io_msec;
461 if (ch->ldmt1r_value & (1 << 12) && tmp) {
462 fb_deferred_io_cleanup(&ch->info);
463 ch->info.fbdefio = NULL;
464 }
376 } 465 }
377 466
378 /* stop the lcdc */ 467 /* stop the lcdc */
379 sh_mobile_lcdc_start_stop(priv, 0); 468 sh_mobile_lcdc_start_stop(priv, 0);
380 469
381#ifdef CONFIG_HAVE_CLK 470 /* stop clocks */
382 if (priv->dot_clk) 471 for (k = 0; k < ARRAY_SIZE(priv->ch); k++)
383 clk_disable(priv->dot_clk); 472 if (priv->ch[k].enabled)
384 clk_disable(priv->clk); 473 sh_mobile_lcdc_clk_off(priv);
385#endif
386} 474}
387 475
388static int sh_mobile_lcdc_check_interface(struct sh_mobile_lcdc_chan *ch) 476static int sh_mobile_lcdc_check_interface(struct sh_mobile_lcdc_chan *ch)
@@ -446,6 +534,7 @@ static int sh_mobile_lcdc_setup_clocks(struct platform_device *pdev,
446 priv->lddckr = icksel << 16; 534 priv->lddckr = icksel << 16;
447 535
448#ifdef CONFIG_HAVE_CLK 536#ifdef CONFIG_HAVE_CLK
537 atomic_set(&priv->clk_usecnt, -1);
449 snprintf(clk_name, sizeof(clk_name), "lcdc%d", pdev->id); 538 snprintf(clk_name, sizeof(clk_name), "lcdc%d", pdev->id);
450 priv->clk = clk_get(&pdev->dev, clk_name); 539 priv->clk = clk_get(&pdev->dev, clk_name);
451 if (IS_ERR(priv->clk)) { 540 if (IS_ERR(priv->clk)) {
@@ -497,13 +586,34 @@ static struct fb_fix_screeninfo sh_mobile_lcdc_fix = {
497 .accel = FB_ACCEL_NONE, 586 .accel = FB_ACCEL_NONE,
498}; 587};
499 588
589static void sh_mobile_lcdc_fillrect(struct fb_info *info,
590 const struct fb_fillrect *rect)
591{
592 sys_fillrect(info, rect);
593 sh_mobile_lcdc_deferred_io_touch(info);
594}
595
596static void sh_mobile_lcdc_copyarea(struct fb_info *info,
597 const struct fb_copyarea *area)
598{
599 sys_copyarea(info, area);
600 sh_mobile_lcdc_deferred_io_touch(info);
601}
602
603static void sh_mobile_lcdc_imageblit(struct fb_info *info,
604 const struct fb_image *image)
605{
606 sys_imageblit(info, image);
607 sh_mobile_lcdc_deferred_io_touch(info);
608}
609
500static struct fb_ops sh_mobile_lcdc_ops = { 610static struct fb_ops sh_mobile_lcdc_ops = {
501 .fb_setcolreg = sh_mobile_lcdc_setcolreg, 611 .fb_setcolreg = sh_mobile_lcdc_setcolreg,
502 .fb_read = fb_sys_read, 612 .fb_read = fb_sys_read,
503 .fb_write = fb_sys_write, 613 .fb_write = fb_sys_write,
504 .fb_fillrect = sys_fillrect, 614 .fb_fillrect = sh_mobile_lcdc_fillrect,
505 .fb_copyarea = sys_copyarea, 615 .fb_copyarea = sh_mobile_lcdc_copyarea,
506 .fb_imageblit = sys_imageblit, 616 .fb_imageblit = sh_mobile_lcdc_imageblit,
507}; 617};
508 618
509static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp) 619static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp)
@@ -564,8 +674,9 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev)
564 } 674 }
565 675
566 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 676 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
567 if (res == NULL) { 677 i = platform_get_irq(pdev, 0);
568 dev_err(&pdev->dev, "cannot find IO resource\n"); 678 if (!res || i < 0) {
679 dev_err(&pdev->dev, "cannot get platform resources\n");
569 error = -ENOENT; 680 error = -ENOENT;
570 goto err0; 681 goto err0;
571 } 682 }
@@ -577,6 +688,14 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev)
577 goto err0; 688 goto err0;
578 } 689 }
579 690
691 error = request_irq(i, sh_mobile_lcdc_irq, IRQF_DISABLED,
692 pdev->dev.bus_id, priv);
693 if (error) {
694 dev_err(&pdev->dev, "unable to request irq\n");
695 goto err1;
696 }
697
698 priv->irq = i;
580 platform_set_drvdata(pdev, priv); 699 platform_set_drvdata(pdev, priv);
581 pdata = pdev->dev.platform_data; 700 pdata = pdev->dev.platform_data;
582 701
@@ -660,6 +779,7 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev)
660 info->fix.smem_start = priv->ch[i].dma_handle; 779 info->fix.smem_start = priv->ch[i].dma_handle;
661 info->screen_base = buf; 780 info->screen_base = buf;
662 info->device = &pdev->dev; 781 info->device = &pdev->dev;
782 info->par = &priv->ch[i];
663 } 783 }
664 784
665 if (error) 785 if (error)
@@ -687,6 +807,10 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev)
687 (int) priv->ch[i].cfg.lcd_cfg.xres, 807 (int) priv->ch[i].cfg.lcd_cfg.xres,
688 (int) priv->ch[i].cfg.lcd_cfg.yres, 808 (int) priv->ch[i].cfg.lcd_cfg.yres,
689 priv->ch[i].cfg.bpp); 809 priv->ch[i].cfg.bpp);
810
811 /* deferred io mode: disable clock to save power */
812 if (info->fbdefio)
813 sh_mobile_lcdc_clk_off(priv);
690 } 814 }
691 815
692 return 0; 816 return 0;
@@ -728,6 +852,8 @@ static int sh_mobile_lcdc_remove(struct platform_device *pdev)
728 if (priv->base) 852 if (priv->base)
729 iounmap(priv->base); 853 iounmap(priv->base);
730 854
855 if (priv->irq)
856 free_irq(priv->irq, priv);
731 kfree(priv); 857 kfree(priv);
732 return 0; 858 return 0;
733} 859}