diff options
author | Glenn Elliott <gelliott@cs.unc.edu> | 2012-03-04 19:47:13 -0500 |
---|---|---|
committer | Glenn Elliott <gelliott@cs.unc.edu> | 2012-03-04 19:47:13 -0500 |
commit | c71c03bda1e86c9d5198c5d83f712e695c4f2a1e (patch) | |
tree | ecb166cb3e2b7e2adb3b5e292245fefd23381ac8 /drivers/video/sh_mobile_lcdcfb.c | |
parent | ea53c912f8a86a8567697115b6a0d8152beee5c8 (diff) | |
parent | 6a00f206debf8a5c8899055726ad127dbeeed098 (diff) |
Merge branch 'mpi-master' into wip-k-fmlpwip-k-fmlp
Conflicts:
litmus/sched_cedf.c
Diffstat (limited to 'drivers/video/sh_mobile_lcdcfb.c')
-rw-r--r-- | drivers/video/sh_mobile_lcdcfb.c | 754 |
1 files changed, 603 insertions, 151 deletions
diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c index d72075a9f01c..019dbd3f12b2 100644 --- a/drivers/video/sh_mobile_lcdcfb.c +++ b/drivers/video/sh_mobile_lcdcfb.c | |||
@@ -12,7 +12,6 @@ | |||
12 | #include <linux/init.h> | 12 | #include <linux/init.h> |
13 | #include <linux/delay.h> | 13 | #include <linux/delay.h> |
14 | #include <linux/mm.h> | 14 | #include <linux/mm.h> |
15 | #include <linux/fb.h> | ||
16 | #include <linux/clk.h> | 15 | #include <linux/clk.h> |
17 | #include <linux/pm_runtime.h> | 16 | #include <linux/pm_runtime.h> |
18 | #include <linux/platform_device.h> | 17 | #include <linux/platform_device.h> |
@@ -21,10 +20,15 @@ | |||
21 | #include <linux/vmalloc.h> | 20 | #include <linux/vmalloc.h> |
22 | #include <linux/ioctl.h> | 21 | #include <linux/ioctl.h> |
23 | #include <linux/slab.h> | 22 | #include <linux/slab.h> |
23 | #include <linux/console.h> | ||
24 | #include <linux/backlight.h> | ||
25 | #include <linux/gpio.h> | ||
24 | #include <video/sh_mobile_lcdc.h> | 26 | #include <video/sh_mobile_lcdc.h> |
25 | #include <asm/atomic.h> | 27 | #include <asm/atomic.h> |
26 | 28 | ||
27 | #define PALETTE_NR 16 | 29 | #include "sh_mobile_lcdcfb.h" |
30 | #include "sh_mobile_meram.h" | ||
31 | |||
28 | #define SIDE_B_OFFSET 0x1000 | 32 | #define SIDE_B_OFFSET 0x1000 |
29 | #define MIRROR_OFFSET 0x2000 | 33 | #define MIRROR_OFFSET 0x2000 |
30 | 34 | ||
@@ -53,11 +57,8 @@ static int lcdc_shared_regs[] = { | |||
53 | }; | 57 | }; |
54 | #define NR_SHARED_REGS ARRAY_SIZE(lcdc_shared_regs) | 58 | #define NR_SHARED_REGS ARRAY_SIZE(lcdc_shared_regs) |
55 | 59 | ||
56 | /* per-channel registers */ | 60 | #define MAX_XRES 1920 |
57 | enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R, | 61 | #define MAX_YRES 1080 |
58 | LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR, | ||
59 | LDHAJR, | ||
60 | NR_CH_REGS }; | ||
61 | 62 | ||
62 | static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { | 63 | static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { |
63 | [LDDCKPAT1R] = 0x400, | 64 | [LDDCKPAT1R] = 0x400, |
@@ -69,6 +70,7 @@ static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { | |||
69 | [LDSM1R] = 0x428, | 70 | [LDSM1R] = 0x428, |
70 | [LDSM2R] = 0x42c, | 71 | [LDSM2R] = 0x42c, |
71 | [LDSA1R] = 0x430, | 72 | [LDSA1R] = 0x430, |
73 | [LDSA2R] = 0x434, | ||
72 | [LDMLSR] = 0x438, | 74 | [LDMLSR] = 0x438, |
73 | [LDHCNR] = 0x448, | 75 | [LDHCNR] = 0x448, |
74 | [LDHSYNR] = 0x44c, | 76 | [LDHSYNR] = 0x44c, |
@@ -112,23 +114,22 @@ static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = { | |||
112 | #define LDRCNTR_MRC 0x00000001 | 114 | #define LDRCNTR_MRC 0x00000001 |
113 | #define LDSR_MRS 0x00000100 | 115 | #define LDSR_MRS 0x00000100 |
114 | 116 | ||
115 | struct sh_mobile_lcdc_priv; | 117 | static const struct fb_videomode default_720p = { |
116 | struct sh_mobile_lcdc_chan { | 118 | .name = "HDMI 720p", |
117 | struct sh_mobile_lcdc_priv *lcdc; | 119 | .xres = 1280, |
118 | unsigned long *reg_offs; | 120 | .yres = 720, |
119 | unsigned long ldmt1r_value; | 121 | |
120 | unsigned long enabled; /* ME and SE in LDCNT2R */ | 122 | .left_margin = 220, |
121 | struct sh_mobile_lcdc_chan_cfg cfg; | 123 | .right_margin = 110, |
122 | u32 pseudo_palette[PALETTE_NR]; | 124 | .hsync_len = 40, |
123 | unsigned long saved_ch_regs[NR_CH_REGS]; | 125 | |
124 | struct fb_info *info; | 126 | .upper_margin = 20, |
125 | dma_addr_t dma_handle; | 127 | .lower_margin = 5, |
126 | struct fb_deferred_io defio; | 128 | .vsync_len = 5, |
127 | struct scatterlist *sglist; | 129 | |
128 | unsigned long frame_end; | 130 | .pixclock = 13468, |
129 | unsigned long pan_offset; | 131 | .refresh = 60, |
130 | wait_queue_head_t frame_end_wait; | 132 | .sync = FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT, |
131 | struct completion vsync_completion; | ||
132 | }; | 133 | }; |
133 | 134 | ||
134 | struct sh_mobile_lcdc_priv { | 135 | struct sh_mobile_lcdc_priv { |
@@ -142,6 +143,8 @@ struct sh_mobile_lcdc_priv { | |||
142 | struct notifier_block notifier; | 143 | struct notifier_block notifier; |
143 | unsigned long saved_shared_regs[NR_SHARED_REGS]; | 144 | unsigned long saved_shared_regs[NR_SHARED_REGS]; |
144 | int started; | 145 | int started; |
146 | int forced_bpp; /* 2 channel LCDC must share bpp setting */ | ||
147 | struct sh_mobile_meram_info *meram_dev; | ||
145 | }; | 148 | }; |
146 | 149 | ||
147 | static bool banked(int reg_nr) | 150 | static bool banked(int reg_nr) |
@@ -153,6 +156,7 @@ static bool banked(int reg_nr) | |||
153 | case LDDFR: | 156 | case LDDFR: |
154 | case LDSM1R: | 157 | case LDSM1R: |
155 | case LDSA1R: | 158 | case LDSA1R: |
159 | case LDSA2R: | ||
156 | case LDMLSR: | 160 | case LDMLSR: |
157 | case LDHCNR: | 161 | case LDHCNR: |
158 | case LDHSYNR: | 162 | case LDHSYNR: |
@@ -409,8 +413,8 @@ static void sh_mobile_lcdc_start_stop(struct sh_mobile_lcdc_priv *priv, | |||
409 | 413 | ||
410 | static void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch) | 414 | static void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch) |
411 | { | 415 | { |
412 | struct fb_var_screeninfo *var = &ch->info->var; | 416 | struct fb_var_screeninfo *var = &ch->info->var, *display_var = &ch->display_var; |
413 | unsigned long h_total, hsync_pos; | 417 | unsigned long h_total, hsync_pos, display_h_total; |
414 | u32 tmp; | 418 | u32 tmp; |
415 | 419 | ||
416 | tmp = ch->ldmt1r_value; | 420 | tmp = ch->ldmt1r_value; |
@@ -428,31 +432,33 @@ static void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch) | |||
428 | lcdc_write_chan(ch, LDMT3R, ch->cfg.sys_bus_cfg.ldmt3r); | 432 | lcdc_write_chan(ch, LDMT3R, ch->cfg.sys_bus_cfg.ldmt3r); |
429 | 433 | ||
430 | /* horizontal configuration */ | 434 | /* horizontal configuration */ |
431 | h_total = var->xres + var->hsync_len + | 435 | h_total = display_var->xres + display_var->hsync_len + |
432 | var->left_margin + var->right_margin; | 436 | display_var->left_margin + display_var->right_margin; |
433 | tmp = h_total / 8; /* HTCN */ | 437 | tmp = h_total / 8; /* HTCN */ |
434 | tmp |= (var->xres / 8) << 16; /* HDCN */ | 438 | tmp |= (min(display_var->xres, var->xres) / 8) << 16; /* HDCN */ |
435 | lcdc_write_chan(ch, LDHCNR, tmp); | 439 | lcdc_write_chan(ch, LDHCNR, tmp); |
436 | 440 | ||
437 | hsync_pos = var->xres + var->right_margin; | 441 | hsync_pos = display_var->xres + display_var->right_margin; |
438 | tmp = hsync_pos / 8; /* HSYNP */ | 442 | tmp = hsync_pos / 8; /* HSYNP */ |
439 | tmp |= (var->hsync_len / 8) << 16; /* HSYNW */ | 443 | tmp |= (display_var->hsync_len / 8) << 16; /* HSYNW */ |
440 | lcdc_write_chan(ch, LDHSYNR, tmp); | 444 | lcdc_write_chan(ch, LDHSYNR, tmp); |
441 | 445 | ||
442 | /* vertical configuration */ | 446 | /* vertical configuration */ |
443 | tmp = var->yres + var->vsync_len + | 447 | tmp = display_var->yres + display_var->vsync_len + |
444 | var->upper_margin + var->lower_margin; /* VTLN */ | 448 | display_var->upper_margin + display_var->lower_margin; /* VTLN */ |
445 | tmp |= var->yres << 16; /* VDLN */ | 449 | tmp |= min(display_var->yres, var->yres) << 16; /* VDLN */ |
446 | lcdc_write_chan(ch, LDVLNR, tmp); | 450 | lcdc_write_chan(ch, LDVLNR, tmp); |
447 | 451 | ||
448 | tmp = var->yres + var->lower_margin; /* VSYNP */ | 452 | tmp = display_var->yres + display_var->lower_margin; /* VSYNP */ |
449 | tmp |= var->vsync_len << 16; /* VSYNW */ | 453 | tmp |= display_var->vsync_len << 16; /* VSYNW */ |
450 | lcdc_write_chan(ch, LDVSYNR, tmp); | 454 | lcdc_write_chan(ch, LDVSYNR, tmp); |
451 | 455 | ||
452 | /* Adjust horizontal synchronisation for HDMI */ | 456 | /* Adjust horizontal synchronisation for HDMI */ |
453 | tmp = ((var->xres & 7) << 24) | | 457 | display_h_total = display_var->xres + display_var->hsync_len + |
454 | ((h_total & 7) << 16) | | 458 | display_var->left_margin + display_var->right_margin; |
455 | ((var->hsync_len & 7) << 8) | | 459 | tmp = ((display_var->xres & 7) << 24) | |
460 | ((display_h_total & 7) << 16) | | ||
461 | ((display_var->hsync_len & 7) << 8) | | ||
456 | hsync_pos; | 462 | hsync_pos; |
457 | lcdc_write_chan(ch, LDHAJR, tmp); | 463 | lcdc_write_chan(ch, LDHAJR, tmp); |
458 | } | 464 | } |
@@ -460,16 +466,20 @@ static void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch) | |||
460 | static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) | 466 | static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) |
461 | { | 467 | { |
462 | struct sh_mobile_lcdc_chan *ch; | 468 | struct sh_mobile_lcdc_chan *ch; |
463 | struct fb_videomode *lcd_cfg; | ||
464 | struct sh_mobile_lcdc_board_cfg *board_cfg; | 469 | struct sh_mobile_lcdc_board_cfg *board_cfg; |
465 | unsigned long tmp; | 470 | unsigned long tmp; |
466 | int k, m; | 471 | int bpp = 0; |
467 | int ret = 0; | 472 | unsigned long ldddsr; |
473 | int k, m, ret; | ||
468 | 474 | ||
469 | /* enable clocks before accessing the hardware */ | 475 | /* enable clocks before accessing the hardware */ |
470 | for (k = 0; k < ARRAY_SIZE(priv->ch); k++) | 476 | for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { |
471 | if (priv->ch[k].enabled) | 477 | if (priv->ch[k].enabled) { |
472 | sh_mobile_lcdc_clk_on(priv); | 478 | sh_mobile_lcdc_clk_on(priv); |
479 | if (!bpp) | ||
480 | bpp = priv->ch[k].info->var.bits_per_pixel; | ||
481 | } | ||
482 | } | ||
473 | 483 | ||
474 | /* reset */ | 484 | /* reset */ |
475 | lcdc_write(priv, _LDCNT2R, lcdc_read(priv, _LDCNT2R) | LCDC_RESET); | 485 | lcdc_write(priv, _LDCNT2R, lcdc_read(priv, _LDCNT2R) | LCDC_RESET); |
@@ -503,7 +513,8 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) | |||
503 | m = 1 << 6; | 513 | m = 1 << 6; |
504 | tmp |= m << (lcdc_chan_is_sublcd(ch) ? 8 : 0); | 514 | tmp |= m << (lcdc_chan_is_sublcd(ch) ? 8 : 0); |
505 | 515 | ||
506 | lcdc_write_chan(ch, LDDCKPAT1R, 0x00000000); | 516 | /* FIXME: sh7724 can only use 42, 48, 54 and 60 for the divider denominator */ |
517 | lcdc_write_chan(ch, LDDCKPAT1R, 0); | ||
507 | lcdc_write_chan(ch, LDDCKPAT2R, (1 << (m/2)) - 1); | 518 | lcdc_write_chan(ch, LDDCKPAT2R, (1 << (m/2)) - 1); |
508 | } | 519 | } |
509 | 520 | ||
@@ -518,7 +529,6 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) | |||
518 | 529 | ||
519 | for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { | 530 | for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { |
520 | ch = &priv->ch[k]; | 531 | ch = &priv->ch[k]; |
521 | lcd_cfg = &ch->cfg.lcd_cfg; | ||
522 | 532 | ||
523 | if (!ch->enabled) | 533 | if (!ch->enabled) |
524 | continue; | 534 | continue; |
@@ -529,17 +539,36 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) | |||
529 | lcdc_write_chan(ch, LDPMR, 0); | 539 | lcdc_write_chan(ch, LDPMR, 0); |
530 | 540 | ||
531 | board_cfg = &ch->cfg.board_cfg; | 541 | board_cfg = &ch->cfg.board_cfg; |
532 | if (board_cfg->setup_sys) | 542 | if (board_cfg->setup_sys) { |
533 | ret = board_cfg->setup_sys(board_cfg->board_data, ch, | 543 | ret = board_cfg->setup_sys(board_cfg->board_data, |
534 | &sh_mobile_lcdc_sys_bus_ops); | 544 | ch, &sh_mobile_lcdc_sys_bus_ops); |
535 | if (ret) | 545 | if (ret) |
536 | return ret; | 546 | return ret; |
547 | } | ||
537 | } | 548 | } |
538 | 549 | ||
539 | /* word and long word swap */ | 550 | /* word and long word swap */ |
540 | lcdc_write(priv, _LDDDSR, lcdc_read(priv, _LDDDSR) | 6); | 551 | ldddsr = lcdc_read(priv, _LDDDSR); |
552 | if (priv->ch[0].info->var.nonstd) | ||
553 | lcdc_write(priv, _LDDDSR, ldddsr | 7); | ||
554 | else { | ||
555 | switch (bpp) { | ||
556 | case 16: | ||
557 | lcdc_write(priv, _LDDDSR, ldddsr | 6); | ||
558 | break; | ||
559 | case 24: | ||
560 | lcdc_write(priv, _LDDDSR, ldddsr | 7); | ||
561 | break; | ||
562 | case 32: | ||
563 | lcdc_write(priv, _LDDDSR, ldddsr | 4); | ||
564 | break; | ||
565 | } | ||
566 | } | ||
541 | 567 | ||
542 | for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { | 568 | for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { |
569 | unsigned long base_addr_y; | ||
570 | unsigned long base_addr_c = 0; | ||
571 | int pitch; | ||
543 | ch = &priv->ch[k]; | 572 | ch = &priv->ch[k]; |
544 | 573 | ||
545 | if (!priv->ch[k].enabled) | 574 | if (!priv->ch[k].enabled) |
@@ -547,15 +576,95 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) | |||
547 | 576 | ||
548 | /* set bpp format in PKF[4:0] */ | 577 | /* set bpp format in PKF[4:0] */ |
549 | tmp = lcdc_read_chan(ch, LDDFR); | 578 | tmp = lcdc_read_chan(ch, LDDFR); |
550 | tmp &= ~(0x0001001f); | 579 | tmp &= ~0x0003031f; |
551 | tmp |= (ch->info->var.bits_per_pixel == 16) ? 3 : 0; | 580 | if (ch->info->var.nonstd) { |
581 | tmp |= (ch->info->var.nonstd << 16); | ||
582 | switch (ch->info->var.bits_per_pixel) { | ||
583 | case 12: | ||
584 | break; | ||
585 | case 16: | ||
586 | tmp |= (0x1 << 8); | ||
587 | break; | ||
588 | case 24: | ||
589 | tmp |= (0x2 << 8); | ||
590 | break; | ||
591 | } | ||
592 | } else { | ||
593 | switch (ch->info->var.bits_per_pixel) { | ||
594 | case 16: | ||
595 | tmp |= 0x03; | ||
596 | break; | ||
597 | case 24: | ||
598 | tmp |= 0x0b; | ||
599 | break; | ||
600 | case 32: | ||
601 | break; | ||
602 | } | ||
603 | } | ||
552 | lcdc_write_chan(ch, LDDFR, tmp); | 604 | lcdc_write_chan(ch, LDDFR, tmp); |
553 | 605 | ||
606 | base_addr_y = ch->info->fix.smem_start; | ||
607 | base_addr_c = base_addr_y + | ||
608 | ch->info->var.xres * | ||
609 | ch->info->var.yres_virtual; | ||
610 | pitch = ch->info->fix.line_length; | ||
611 | |||
612 | /* test if we can enable meram */ | ||
613 | if (ch->cfg.meram_cfg && priv->meram_dev && | ||
614 | priv->meram_dev->ops) { | ||
615 | struct sh_mobile_meram_cfg *cfg; | ||
616 | struct sh_mobile_meram_info *mdev; | ||
617 | unsigned long icb_addr_y, icb_addr_c; | ||
618 | int icb_pitch; | ||
619 | int pf; | ||
620 | |||
621 | cfg = ch->cfg.meram_cfg; | ||
622 | mdev = priv->meram_dev; | ||
623 | /* we need to de-init configured ICBs before we | ||
624 | * we can re-initialize them. | ||
625 | */ | ||
626 | if (ch->meram_enabled) | ||
627 | mdev->ops->meram_unregister(mdev, cfg); | ||
628 | |||
629 | ch->meram_enabled = 0; | ||
630 | |||
631 | if (ch->info->var.nonstd) { | ||
632 | if (ch->info->var.bits_per_pixel == 24) | ||
633 | pf = SH_MOBILE_MERAM_PF_NV24; | ||
634 | else | ||
635 | pf = SH_MOBILE_MERAM_PF_NV; | ||
636 | } else { | ||
637 | pf = SH_MOBILE_MERAM_PF_RGB; | ||
638 | } | ||
639 | |||
640 | ret = mdev->ops->meram_register(mdev, cfg, pitch, | ||
641 | ch->info->var.yres, | ||
642 | pf, | ||
643 | base_addr_y, | ||
644 | base_addr_c, | ||
645 | &icb_addr_y, | ||
646 | &icb_addr_c, | ||
647 | &icb_pitch); | ||
648 | if (!ret) { | ||
649 | /* set LDSA1R value */ | ||
650 | base_addr_y = icb_addr_y; | ||
651 | pitch = icb_pitch; | ||
652 | |||
653 | /* set LDSA2R value if required */ | ||
654 | if (base_addr_c) | ||
655 | base_addr_c = icb_addr_c; | ||
656 | |||
657 | ch->meram_enabled = 1; | ||
658 | } | ||
659 | } | ||
660 | |||
554 | /* point out our frame buffer */ | 661 | /* point out our frame buffer */ |
555 | lcdc_write_chan(ch, LDSA1R, ch->info->fix.smem_start); | 662 | lcdc_write_chan(ch, LDSA1R, base_addr_y); |
663 | if (ch->info->var.nonstd) | ||
664 | lcdc_write_chan(ch, LDSA2R, base_addr_c); | ||
556 | 665 | ||
557 | /* set line size */ | 666 | /* set line size */ |
558 | lcdc_write_chan(ch, LDMLSR, ch->info->fix.line_length); | 667 | lcdc_write_chan(ch, LDMLSR, pitch); |
559 | 668 | ||
560 | /* setup deferred io if SYS bus */ | 669 | /* setup deferred io if SYS bus */ |
561 | tmp = ch->cfg.sys_bus_cfg.deferred_io_msec; | 670 | tmp = ch->cfg.sys_bus_cfg.deferred_io_msec; |
@@ -591,8 +700,15 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) | |||
591 | continue; | 700 | continue; |
592 | 701 | ||
593 | board_cfg = &ch->cfg.board_cfg; | 702 | board_cfg = &ch->cfg.board_cfg; |
594 | if (board_cfg->display_on) | 703 | if (board_cfg->display_on && try_module_get(board_cfg->owner)) { |
595 | board_cfg->display_on(board_cfg->board_data, ch->info); | 704 | board_cfg->display_on(board_cfg->board_data, ch->info); |
705 | module_put(board_cfg->owner); | ||
706 | } | ||
707 | |||
708 | if (ch->bl) { | ||
709 | ch->bl->props.power = FB_BLANK_UNBLANK; | ||
710 | backlight_update_status(ch->bl); | ||
711 | } | ||
596 | } | 712 | } |
597 | 713 | ||
598 | return 0; | 714 | return 0; |
@@ -614,7 +730,7 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv) | |||
614 | * flush frame, and wait for frame end interrupt | 730 | * flush frame, and wait for frame end interrupt |
615 | * clean up deferred io and enable clock | 731 | * clean up deferred io and enable clock |
616 | */ | 732 | */ |
617 | if (ch->info->fbdefio) { | 733 | if (ch->info && ch->info->fbdefio) { |
618 | ch->frame_end = 0; | 734 | ch->frame_end = 0; |
619 | schedule_delayed_work(&ch->info->deferred_work, 0); | 735 | schedule_delayed_work(&ch->info->deferred_work, 0); |
620 | wait_event(ch->frame_end_wait, ch->frame_end); | 736 | wait_event(ch->frame_end_wait, ch->frame_end); |
@@ -623,9 +739,27 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv) | |||
623 | sh_mobile_lcdc_clk_on(priv); | 739 | sh_mobile_lcdc_clk_on(priv); |
624 | } | 740 | } |
625 | 741 | ||
742 | if (ch->bl) { | ||
743 | ch->bl->props.power = FB_BLANK_POWERDOWN; | ||
744 | backlight_update_status(ch->bl); | ||
745 | } | ||
746 | |||
626 | board_cfg = &ch->cfg.board_cfg; | 747 | board_cfg = &ch->cfg.board_cfg; |
627 | if (board_cfg->display_off) | 748 | if (board_cfg->display_off && try_module_get(board_cfg->owner)) { |
628 | board_cfg->display_off(board_cfg->board_data); | 749 | board_cfg->display_off(board_cfg->board_data); |
750 | module_put(board_cfg->owner); | ||
751 | } | ||
752 | |||
753 | /* disable the meram */ | ||
754 | if (ch->meram_enabled) { | ||
755 | struct sh_mobile_meram_cfg *cfg; | ||
756 | struct sh_mobile_meram_info *mdev; | ||
757 | cfg = ch->cfg.meram_cfg; | ||
758 | mdev = priv->meram_dev; | ||
759 | mdev->ops->meram_unregister(mdev, cfg); | ||
760 | ch->meram_enabled = 0; | ||
761 | } | ||
762 | |||
629 | } | 763 | } |
630 | 764 | ||
631 | /* stop the lcdc */ | 765 | /* stop the lcdc */ |
@@ -704,7 +838,6 @@ static int sh_mobile_lcdc_setup_clocks(struct platform_device *pdev, | |||
704 | return PTR_ERR(priv->dot_clk); | 838 | return PTR_ERR(priv->dot_clk); |
705 | } | 839 | } |
706 | } | 840 | } |
707 | atomic_set(&priv->hw_usecnt, -1); | ||
708 | 841 | ||
709 | /* Runtime PM support involves two step for this driver: | 842 | /* Runtime PM support involves two step for this driver: |
710 | * 1) Enable Runtime PM | 843 | * 1) Enable Runtime PM |
@@ -778,9 +911,15 @@ static int sh_mobile_fb_pan_display(struct fb_var_screeninfo *var, | |||
778 | struct sh_mobile_lcdc_priv *priv = ch->lcdc; | 911 | struct sh_mobile_lcdc_priv *priv = ch->lcdc; |
779 | unsigned long ldrcntr; | 912 | unsigned long ldrcntr; |
780 | unsigned long new_pan_offset; | 913 | unsigned long new_pan_offset; |
914 | unsigned long base_addr_y, base_addr_c; | ||
915 | unsigned long c_offset; | ||
781 | 916 | ||
782 | new_pan_offset = (var->yoffset * info->fix.line_length) + | 917 | if (!var->nonstd) |
783 | (var->xoffset * (info->var.bits_per_pixel / 8)); | 918 | new_pan_offset = (var->yoffset * info->fix.line_length) + |
919 | (var->xoffset * (info->var.bits_per_pixel / 8)); | ||
920 | else | ||
921 | new_pan_offset = (var->yoffset * info->fix.line_length) + | ||
922 | (var->xoffset); | ||
784 | 923 | ||
785 | if (new_pan_offset == ch->pan_offset) | 924 | if (new_pan_offset == ch->pan_offset) |
786 | return 0; /* No change, do nothing */ | 925 | return 0; /* No change, do nothing */ |
@@ -788,7 +927,46 @@ static int sh_mobile_fb_pan_display(struct fb_var_screeninfo *var, | |||
788 | ldrcntr = lcdc_read(priv, _LDRCNTR); | 927 | ldrcntr = lcdc_read(priv, _LDRCNTR); |
789 | 928 | ||
790 | /* Set the source address for the next refresh */ | 929 | /* Set the source address for the next refresh */ |
791 | lcdc_write_chan_mirror(ch, LDSA1R, ch->dma_handle + new_pan_offset); | 930 | base_addr_y = ch->dma_handle + new_pan_offset; |
931 | if (var->nonstd) { | ||
932 | /* Set y offset */ | ||
933 | c_offset = (var->yoffset * | ||
934 | info->fix.line_length * | ||
935 | (info->var.bits_per_pixel - 8)) / 8; | ||
936 | base_addr_c = ch->dma_handle + var->xres * var->yres_virtual + | ||
937 | c_offset; | ||
938 | /* Set x offset */ | ||
939 | if (info->var.bits_per_pixel == 24) | ||
940 | base_addr_c += 2 * var->xoffset; | ||
941 | else | ||
942 | base_addr_c += var->xoffset; | ||
943 | } else | ||
944 | base_addr_c = 0; | ||
945 | |||
946 | if (!ch->meram_enabled) { | ||
947 | lcdc_write_chan_mirror(ch, LDSA1R, base_addr_y); | ||
948 | if (base_addr_c) | ||
949 | lcdc_write_chan_mirror(ch, LDSA2R, base_addr_c); | ||
950 | } else { | ||
951 | struct sh_mobile_meram_cfg *cfg; | ||
952 | struct sh_mobile_meram_info *mdev; | ||
953 | unsigned long icb_addr_y, icb_addr_c; | ||
954 | int ret; | ||
955 | |||
956 | cfg = ch->cfg.meram_cfg; | ||
957 | mdev = priv->meram_dev; | ||
958 | ret = mdev->ops->meram_update(mdev, cfg, | ||
959 | base_addr_y, base_addr_c, | ||
960 | &icb_addr_y, &icb_addr_c); | ||
961 | if (ret) | ||
962 | return ret; | ||
963 | |||
964 | lcdc_write_chan_mirror(ch, LDSA1R, icb_addr_y); | ||
965 | if (icb_addr_c) | ||
966 | lcdc_write_chan_mirror(ch, LDSA2R, icb_addr_c); | ||
967 | |||
968 | } | ||
969 | |||
792 | if (lcdc_chan_is_sublcd(ch)) | 970 | if (lcdc_chan_is_sublcd(ch)) |
793 | lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_SRS); | 971 | lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_SRS); |
794 | else | 972 | else |
@@ -837,6 +1015,153 @@ static int sh_mobile_ioctl(struct fb_info *info, unsigned int cmd, | |||
837 | return retval; | 1015 | return retval; |
838 | } | 1016 | } |
839 | 1017 | ||
1018 | static void sh_mobile_fb_reconfig(struct fb_info *info) | ||
1019 | { | ||
1020 | struct sh_mobile_lcdc_chan *ch = info->par; | ||
1021 | struct fb_videomode mode1, mode2; | ||
1022 | struct fb_event event; | ||
1023 | int evnt = FB_EVENT_MODE_CHANGE_ALL; | ||
1024 | |||
1025 | if (ch->use_count > 1 || (ch->use_count == 1 && !info->fbcon_par)) | ||
1026 | /* More framebuffer users are active */ | ||
1027 | return; | ||
1028 | |||
1029 | fb_var_to_videomode(&mode1, &ch->display_var); | ||
1030 | fb_var_to_videomode(&mode2, &info->var); | ||
1031 | |||
1032 | if (fb_mode_is_equal(&mode1, &mode2)) | ||
1033 | return; | ||
1034 | |||
1035 | /* Display has been re-plugged, framebuffer is free now, reconfigure */ | ||
1036 | if (fb_set_var(info, &ch->display_var) < 0) | ||
1037 | /* Couldn't reconfigure, hopefully, can continue as before */ | ||
1038 | return; | ||
1039 | |||
1040 | if (info->var.nonstd) | ||
1041 | info->fix.line_length = mode1.xres; | ||
1042 | else | ||
1043 | info->fix.line_length = mode1.xres * (ch->cfg.bpp / 8); | ||
1044 | |||
1045 | /* | ||
1046 | * fb_set_var() calls the notifier change internally, only if | ||
1047 | * FBINFO_MISC_USEREVENT flag is set. Since we do not want to fake a | ||
1048 | * user event, we have to call the chain ourselves. | ||
1049 | */ | ||
1050 | event.info = info; | ||
1051 | event.data = &mode1; | ||
1052 | fb_notifier_call_chain(evnt, &event); | ||
1053 | } | ||
1054 | |||
1055 | /* | ||
1056 | * Locking: both .fb_release() and .fb_open() are called with info->lock held if | ||
1057 | * user == 1, or with console sem held, if user == 0. | ||
1058 | */ | ||
1059 | static int sh_mobile_release(struct fb_info *info, int user) | ||
1060 | { | ||
1061 | struct sh_mobile_lcdc_chan *ch = info->par; | ||
1062 | |||
1063 | mutex_lock(&ch->open_lock); | ||
1064 | dev_dbg(info->dev, "%s(): %d users\n", __func__, ch->use_count); | ||
1065 | |||
1066 | ch->use_count--; | ||
1067 | |||
1068 | /* Nothing to reconfigure, when called from fbcon */ | ||
1069 | if (user) { | ||
1070 | console_lock(); | ||
1071 | sh_mobile_fb_reconfig(info); | ||
1072 | console_unlock(); | ||
1073 | } | ||
1074 | |||
1075 | mutex_unlock(&ch->open_lock); | ||
1076 | |||
1077 | return 0; | ||
1078 | } | ||
1079 | |||
1080 | static int sh_mobile_open(struct fb_info *info, int user) | ||
1081 | { | ||
1082 | struct sh_mobile_lcdc_chan *ch = info->par; | ||
1083 | |||
1084 | mutex_lock(&ch->open_lock); | ||
1085 | ch->use_count++; | ||
1086 | |||
1087 | dev_dbg(info->dev, "%s(): %d users\n", __func__, ch->use_count); | ||
1088 | mutex_unlock(&ch->open_lock); | ||
1089 | |||
1090 | return 0; | ||
1091 | } | ||
1092 | |||
1093 | static int sh_mobile_check_var(struct fb_var_screeninfo *var, struct fb_info *info) | ||
1094 | { | ||
1095 | struct sh_mobile_lcdc_chan *ch = info->par; | ||
1096 | struct sh_mobile_lcdc_priv *p = ch->lcdc; | ||
1097 | |||
1098 | if (var->xres > MAX_XRES || var->yres > MAX_YRES || | ||
1099 | var->xres * var->yres * (ch->cfg.bpp / 8) * 2 > info->fix.smem_len) { | ||
1100 | dev_warn(info->dev, "Invalid info: %u-%u-%u-%u x %u-%u-%u-%u @ %lukHz!\n", | ||
1101 | var->left_margin, var->xres, var->right_margin, var->hsync_len, | ||
1102 | var->upper_margin, var->yres, var->lower_margin, var->vsync_len, | ||
1103 | PICOS2KHZ(var->pixclock)); | ||
1104 | return -EINVAL; | ||
1105 | } | ||
1106 | |||
1107 | /* only accept the forced_bpp for dual channel configurations */ | ||
1108 | if (p->forced_bpp && p->forced_bpp != var->bits_per_pixel) | ||
1109 | return -EINVAL; | ||
1110 | |||
1111 | switch (var->bits_per_pixel) { | ||
1112 | case 16: /* PKF[4:0] = 00011 - RGB 565 */ | ||
1113 | case 24: /* PKF[4:0] = 01011 - RGB 888 */ | ||
1114 | case 32: /* PKF[4:0] = 00000 - RGBA 888 */ | ||
1115 | break; | ||
1116 | default: | ||
1117 | return -EINVAL; | ||
1118 | } | ||
1119 | |||
1120 | return 0; | ||
1121 | } | ||
1122 | |||
1123 | /* | ||
1124 | * Screen blanking. Behavior is as follows: | ||
1125 | * FB_BLANK_UNBLANK: screen unblanked, clocks enabled | ||
1126 | * FB_BLANK_NORMAL: screen blanked, clocks enabled | ||
1127 | * FB_BLANK_VSYNC, | ||
1128 | * FB_BLANK_HSYNC, | ||
1129 | * FB_BLANK_POWEROFF: screen blanked, clocks disabled | ||
1130 | */ | ||
1131 | static int sh_mobile_lcdc_blank(int blank, struct fb_info *info) | ||
1132 | { | ||
1133 | struct sh_mobile_lcdc_chan *ch = info->par; | ||
1134 | struct sh_mobile_lcdc_priv *p = ch->lcdc; | ||
1135 | |||
1136 | /* blank the screen? */ | ||
1137 | if (blank > FB_BLANK_UNBLANK && ch->blank_status == FB_BLANK_UNBLANK) { | ||
1138 | struct fb_fillrect rect = { | ||
1139 | .width = info->var.xres, | ||
1140 | .height = info->var.yres, | ||
1141 | }; | ||
1142 | sh_mobile_lcdc_fillrect(info, &rect); | ||
1143 | } | ||
1144 | /* turn clocks on? */ | ||
1145 | if (blank <= FB_BLANK_NORMAL && ch->blank_status > FB_BLANK_NORMAL) { | ||
1146 | sh_mobile_lcdc_clk_on(p); | ||
1147 | } | ||
1148 | /* turn clocks off? */ | ||
1149 | if (blank > FB_BLANK_NORMAL && ch->blank_status <= FB_BLANK_NORMAL) { | ||
1150 | /* make sure the screen is updated with the black fill before | ||
1151 | * switching the clocks off. one vsync is not enough since | ||
1152 | * blanking may occur in the middle of a refresh. deferred io | ||
1153 | * mode will reenable the clocks and update the screen in time, | ||
1154 | * so it does not need this. */ | ||
1155 | if (!info->fbdefio) { | ||
1156 | sh_mobile_wait_for_vsync(info); | ||
1157 | sh_mobile_wait_for_vsync(info); | ||
1158 | } | ||
1159 | sh_mobile_lcdc_clk_off(p); | ||
1160 | } | ||
1161 | |||
1162 | ch->blank_status = blank; | ||
1163 | return 0; | ||
1164 | } | ||
840 | 1165 | ||
841 | static struct fb_ops sh_mobile_lcdc_ops = { | 1166 | static struct fb_ops sh_mobile_lcdc_ops = { |
842 | .owner = THIS_MODULE, | 1167 | .owner = THIS_MODULE, |
@@ -846,12 +1171,89 @@ static struct fb_ops sh_mobile_lcdc_ops = { | |||
846 | .fb_fillrect = sh_mobile_lcdc_fillrect, | 1171 | .fb_fillrect = sh_mobile_lcdc_fillrect, |
847 | .fb_copyarea = sh_mobile_lcdc_copyarea, | 1172 | .fb_copyarea = sh_mobile_lcdc_copyarea, |
848 | .fb_imageblit = sh_mobile_lcdc_imageblit, | 1173 | .fb_imageblit = sh_mobile_lcdc_imageblit, |
1174 | .fb_blank = sh_mobile_lcdc_blank, | ||
849 | .fb_pan_display = sh_mobile_fb_pan_display, | 1175 | .fb_pan_display = sh_mobile_fb_pan_display, |
850 | .fb_ioctl = sh_mobile_ioctl, | 1176 | .fb_ioctl = sh_mobile_ioctl, |
1177 | .fb_open = sh_mobile_open, | ||
1178 | .fb_release = sh_mobile_release, | ||
1179 | .fb_check_var = sh_mobile_check_var, | ||
851 | }; | 1180 | }; |
852 | 1181 | ||
853 | static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp) | 1182 | static int sh_mobile_lcdc_update_bl(struct backlight_device *bdev) |
1183 | { | ||
1184 | struct sh_mobile_lcdc_chan *ch = bl_get_data(bdev); | ||
1185 | struct sh_mobile_lcdc_board_cfg *cfg = &ch->cfg.board_cfg; | ||
1186 | int brightness = bdev->props.brightness; | ||
1187 | |||
1188 | if (bdev->props.power != FB_BLANK_UNBLANK || | ||
1189 | bdev->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) | ||
1190 | brightness = 0; | ||
1191 | |||
1192 | return cfg->set_brightness(cfg->board_data, brightness); | ||
1193 | } | ||
1194 | |||
1195 | static int sh_mobile_lcdc_get_brightness(struct backlight_device *bdev) | ||
1196 | { | ||
1197 | struct sh_mobile_lcdc_chan *ch = bl_get_data(bdev); | ||
1198 | struct sh_mobile_lcdc_board_cfg *cfg = &ch->cfg.board_cfg; | ||
1199 | |||
1200 | return cfg->get_brightness(cfg->board_data); | ||
1201 | } | ||
1202 | |||
1203 | static int sh_mobile_lcdc_check_fb(struct backlight_device *bdev, | ||
1204 | struct fb_info *info) | ||
854 | { | 1205 | { |
1206 | return (info->bl_dev == bdev); | ||
1207 | } | ||
1208 | |||
1209 | static struct backlight_ops sh_mobile_lcdc_bl_ops = { | ||
1210 | .options = BL_CORE_SUSPENDRESUME, | ||
1211 | .update_status = sh_mobile_lcdc_update_bl, | ||
1212 | .get_brightness = sh_mobile_lcdc_get_brightness, | ||
1213 | .check_fb = sh_mobile_lcdc_check_fb, | ||
1214 | }; | ||
1215 | |||
1216 | static struct backlight_device *sh_mobile_lcdc_bl_probe(struct device *parent, | ||
1217 | struct sh_mobile_lcdc_chan *ch) | ||
1218 | { | ||
1219 | struct backlight_device *bl; | ||
1220 | |||
1221 | bl = backlight_device_register(ch->cfg.bl_info.name, parent, ch, | ||
1222 | &sh_mobile_lcdc_bl_ops, NULL); | ||
1223 | if (IS_ERR(bl)) { | ||
1224 | dev_err(parent, "unable to register backlight device: %ld\n", | ||
1225 | PTR_ERR(bl)); | ||
1226 | return NULL; | ||
1227 | } | ||
1228 | |||
1229 | bl->props.max_brightness = ch->cfg.bl_info.max_brightness; | ||
1230 | bl->props.brightness = bl->props.max_brightness; | ||
1231 | backlight_update_status(bl); | ||
1232 | |||
1233 | return bl; | ||
1234 | } | ||
1235 | |||
1236 | static void sh_mobile_lcdc_bl_remove(struct backlight_device *bdev) | ||
1237 | { | ||
1238 | backlight_device_unregister(bdev); | ||
1239 | } | ||
1240 | |||
1241 | static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp, | ||
1242 | int nonstd) | ||
1243 | { | ||
1244 | if (nonstd) { | ||
1245 | switch (bpp) { | ||
1246 | case 12: | ||
1247 | case 16: | ||
1248 | case 24: | ||
1249 | var->bits_per_pixel = bpp; | ||
1250 | var->nonstd = nonstd; | ||
1251 | return 0; | ||
1252 | default: | ||
1253 | return -EINVAL; | ||
1254 | } | ||
1255 | } | ||
1256 | |||
855 | switch (bpp) { | 1257 | switch (bpp) { |
856 | case 16: /* PKF[4:0] = 00011 - RGB 565 */ | 1258 | case 16: /* PKF[4:0] = 00011 - RGB 565 */ |
857 | var->red.offset = 11; | 1259 | var->red.offset = 11; |
@@ -864,19 +1266,27 @@ static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp) | |||
864 | var->transp.length = 0; | 1266 | var->transp.length = 0; |
865 | break; | 1267 | break; |
866 | 1268 | ||
867 | case 32: /* PKF[4:0] = 00000 - RGB 888 | 1269 | case 24: /* PKF[4:0] = 01011 - RGB 888 */ |
868 | * sh7722 pdf says 00RRGGBB but reality is GGBB00RR | 1270 | var->red.offset = 16; |
869 | * this may be because LDDDSR has word swap enabled.. | ||
870 | */ | ||
871 | var->red.offset = 0; | ||
872 | var->red.length = 8; | 1271 | var->red.length = 8; |
873 | var->green.offset = 24; | 1272 | var->green.offset = 8; |
874 | var->green.length = 8; | 1273 | var->green.length = 8; |
875 | var->blue.offset = 16; | 1274 | var->blue.offset = 0; |
876 | var->blue.length = 8; | 1275 | var->blue.length = 8; |
877 | var->transp.offset = 0; | 1276 | var->transp.offset = 0; |
878 | var->transp.length = 0; | 1277 | var->transp.length = 0; |
879 | break; | 1278 | break; |
1279 | |||
1280 | case 32: /* PKF[4:0] = 00000 - RGBA 888 */ | ||
1281 | var->red.offset = 16; | ||
1282 | var->red.length = 8; | ||
1283 | var->green.offset = 8; | ||
1284 | var->green.length = 8; | ||
1285 | var->blue.offset = 0; | ||
1286 | var->blue.length = 8; | ||
1287 | var->transp.offset = 24; | ||
1288 | var->transp.length = 8; | ||
1289 | break; | ||
880 | default: | 1290 | default: |
881 | return -EINVAL; | 1291 | return -EINVAL; |
882 | } | 1292 | } |
@@ -958,6 +1368,7 @@ static const struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = { | |||
958 | .runtime_resume = sh_mobile_lcdc_runtime_resume, | 1368 | .runtime_resume = sh_mobile_lcdc_runtime_resume, |
959 | }; | 1369 | }; |
960 | 1370 | ||
1371 | /* locking: called with info->lock held */ | ||
961 | static int sh_mobile_lcdc_notify(struct notifier_block *nb, | 1372 | static int sh_mobile_lcdc_notify(struct notifier_block *nb, |
962 | unsigned long action, void *data) | 1373 | unsigned long action, void *data) |
963 | { | 1374 | { |
@@ -965,53 +1376,36 @@ static int sh_mobile_lcdc_notify(struct notifier_block *nb, | |||
965 | struct fb_info *info = event->info; | 1376 | struct fb_info *info = event->info; |
966 | struct sh_mobile_lcdc_chan *ch = info->par; | 1377 | struct sh_mobile_lcdc_chan *ch = info->par; |
967 | struct sh_mobile_lcdc_board_cfg *board_cfg = &ch->cfg.board_cfg; | 1378 | struct sh_mobile_lcdc_board_cfg *board_cfg = &ch->cfg.board_cfg; |
968 | struct fb_var_screeninfo *var; | ||
969 | 1379 | ||
970 | if (&ch->lcdc->notifier != nb) | 1380 | if (&ch->lcdc->notifier != nb) |
971 | return 0; | 1381 | return NOTIFY_DONE; |
972 | 1382 | ||
973 | dev_dbg(info->dev, "%s(): action = %lu, data = %p\n", | 1383 | dev_dbg(info->dev, "%s(): action = %lu, data = %p\n", |
974 | __func__, action, event->data); | 1384 | __func__, action, event->data); |
975 | 1385 | ||
976 | switch(action) { | 1386 | switch(action) { |
977 | case FB_EVENT_SUSPEND: | 1387 | case FB_EVENT_SUSPEND: |
978 | if (board_cfg->display_off) | 1388 | if (board_cfg->display_off && try_module_get(board_cfg->owner)) { |
979 | board_cfg->display_off(board_cfg->board_data); | 1389 | board_cfg->display_off(board_cfg->board_data); |
980 | pm_runtime_put(info->device); | 1390 | module_put(board_cfg->owner); |
1391 | } | ||
1392 | sh_mobile_lcdc_stop(ch->lcdc); | ||
981 | break; | 1393 | break; |
982 | case FB_EVENT_RESUME: | 1394 | case FB_EVENT_RESUME: |
983 | var = &info->var; | 1395 | mutex_lock(&ch->open_lock); |
1396 | sh_mobile_fb_reconfig(info); | ||
1397 | mutex_unlock(&ch->open_lock); | ||
984 | 1398 | ||
985 | /* HDMI must be enabled before LCDC configuration */ | 1399 | /* HDMI must be enabled before LCDC configuration */ |
986 | if (board_cfg->display_on) | 1400 | if (board_cfg->display_on && try_module_get(board_cfg->owner)) { |
987 | board_cfg->display_on(board_cfg->board_data, ch->info); | 1401 | board_cfg->display_on(board_cfg->board_data, info); |
988 | 1402 | module_put(board_cfg->owner); | |
989 | /* Check if the new display is not in our modelist */ | ||
990 | if (ch->info->modelist.next && | ||
991 | !fb_match_mode(var, &ch->info->modelist)) { | ||
992 | struct fb_videomode mode; | ||
993 | int ret; | ||
994 | |||
995 | /* Can we handle this display? */ | ||
996 | if (var->xres > ch->cfg.lcd_cfg.xres || | ||
997 | var->yres > ch->cfg.lcd_cfg.yres) | ||
998 | return -ENOMEM; | ||
999 | |||
1000 | /* Add to the modelist */ | ||
1001 | fb_var_to_videomode(&mode, var); | ||
1002 | ret = fb_add_videomode(&mode, &ch->info->modelist); | ||
1003 | if (ret < 0) | ||
1004 | return ret; | ||
1005 | } | 1403 | } |
1006 | 1404 | ||
1007 | pm_runtime_get_sync(info->device); | 1405 | sh_mobile_lcdc_start(ch->lcdc); |
1008 | |||
1009 | sh_mobile_lcdc_geometry(ch); | ||
1010 | |||
1011 | break; | ||
1012 | } | 1406 | } |
1013 | 1407 | ||
1014 | return 0; | 1408 | return NOTIFY_OK; |
1015 | } | 1409 | } |
1016 | 1410 | ||
1017 | static int sh_mobile_lcdc_remove(struct platform_device *pdev); | 1411 | static int sh_mobile_lcdc_remove(struct platform_device *pdev); |
@@ -1020,14 +1414,13 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1020 | { | 1414 | { |
1021 | struct fb_info *info; | 1415 | struct fb_info *info; |
1022 | struct sh_mobile_lcdc_priv *priv; | 1416 | struct sh_mobile_lcdc_priv *priv; |
1023 | struct sh_mobile_lcdc_info *pdata; | 1417 | struct sh_mobile_lcdc_info *pdata = pdev->dev.platform_data; |
1024 | struct sh_mobile_lcdc_chan_cfg *cfg; | ||
1025 | struct resource *res; | 1418 | struct resource *res; |
1026 | int error; | 1419 | int error; |
1027 | void *buf; | 1420 | void *buf; |
1028 | int i, j; | 1421 | int i, j; |
1029 | 1422 | ||
1030 | if (!pdev->dev.platform_data) { | 1423 | if (!pdata) { |
1031 | dev_err(&pdev->dev, "no platform data defined\n"); | 1424 | dev_err(&pdev->dev, "no platform data defined\n"); |
1032 | return -EINVAL; | 1425 | return -EINVAL; |
1033 | } | 1426 | } |
@@ -1055,31 +1448,37 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1055 | } | 1448 | } |
1056 | 1449 | ||
1057 | priv->irq = i; | 1450 | priv->irq = i; |
1058 | pdata = pdev->dev.platform_data; | 1451 | atomic_set(&priv->hw_usecnt, -1); |
1059 | 1452 | ||
1060 | j = 0; | 1453 | j = 0; |
1061 | for (i = 0; i < ARRAY_SIZE(pdata->ch); i++) { | 1454 | for (i = 0; i < ARRAY_SIZE(pdata->ch); i++) { |
1062 | priv->ch[j].lcdc = priv; | 1455 | struct sh_mobile_lcdc_chan *ch = priv->ch + j; |
1063 | memcpy(&priv->ch[j].cfg, &pdata->ch[i], sizeof(pdata->ch[i])); | 1456 | |
1457 | ch->lcdc = priv; | ||
1458 | memcpy(&ch->cfg, &pdata->ch[i], sizeof(pdata->ch[i])); | ||
1064 | 1459 | ||
1065 | error = sh_mobile_lcdc_check_interface(&priv->ch[j]); | 1460 | error = sh_mobile_lcdc_check_interface(ch); |
1066 | if (error) { | 1461 | if (error) { |
1067 | dev_err(&pdev->dev, "unsupported interface type\n"); | 1462 | dev_err(&pdev->dev, "unsupported interface type\n"); |
1068 | goto err1; | 1463 | goto err1; |
1069 | } | 1464 | } |
1070 | init_waitqueue_head(&priv->ch[j].frame_end_wait); | 1465 | init_waitqueue_head(&ch->frame_end_wait); |
1071 | init_completion(&priv->ch[j].vsync_completion); | 1466 | init_completion(&ch->vsync_completion); |
1072 | priv->ch[j].pan_offset = 0; | 1467 | ch->pan_offset = 0; |
1468 | |||
1469 | /* probe the backlight is there is one defined */ | ||
1470 | if (ch->cfg.bl_info.max_brightness) | ||
1471 | ch->bl = sh_mobile_lcdc_bl_probe(&pdev->dev, ch); | ||
1073 | 1472 | ||
1074 | switch (pdata->ch[i].chan) { | 1473 | switch (pdata->ch[i].chan) { |
1075 | case LCDC_CHAN_MAINLCD: | 1474 | case LCDC_CHAN_MAINLCD: |
1076 | priv->ch[j].enabled = 1 << 1; | 1475 | ch->enabled = 1 << 1; |
1077 | priv->ch[j].reg_offs = lcdc_offs_mainlcd; | 1476 | ch->reg_offs = lcdc_offs_mainlcd; |
1078 | j++; | 1477 | j++; |
1079 | break; | 1478 | break; |
1080 | case LCDC_CHAN_SUBLCD: | 1479 | case LCDC_CHAN_SUBLCD: |
1081 | priv->ch[j].enabled = 1 << 2; | 1480 | ch->enabled = 1 << 2; |
1082 | priv->ch[j].reg_offs = lcdc_offs_sublcd; | 1481 | ch->reg_offs = lcdc_offs_sublcd; |
1083 | j++; | 1482 | j++; |
1084 | break; | 1483 | break; |
1085 | } | 1484 | } |
@@ -1091,6 +1490,10 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1091 | goto err1; | 1490 | goto err1; |
1092 | } | 1491 | } |
1093 | 1492 | ||
1493 | /* for dual channel LCDC (MAIN + SUB) force shared bpp setting */ | ||
1494 | if (j == 2) | ||
1495 | priv->forced_bpp = pdata->ch[0].bpp; | ||
1496 | |||
1094 | priv->base = ioremap_nocache(res->start, resource_size(res)); | 1497 | priv->base = ioremap_nocache(res->start, resource_size(res)); |
1095 | if (!priv->base) | 1498 | if (!priv->base) |
1096 | goto err1; | 1499 | goto err1; |
@@ -1101,71 +1504,112 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1101 | goto err1; | 1504 | goto err1; |
1102 | } | 1505 | } |
1103 | 1506 | ||
1507 | priv->meram_dev = pdata->meram_dev; | ||
1508 | |||
1104 | for (i = 0; i < j; i++) { | 1509 | for (i = 0; i < j; i++) { |
1105 | struct fb_var_screeninfo *var; | 1510 | struct fb_var_screeninfo *var; |
1106 | struct fb_videomode *lcd_cfg; | 1511 | const struct fb_videomode *lcd_cfg, *max_cfg = NULL; |
1107 | cfg = &priv->ch[i].cfg; | 1512 | struct sh_mobile_lcdc_chan *ch = priv->ch + i; |
1108 | 1513 | struct sh_mobile_lcdc_chan_cfg *cfg = &ch->cfg; | |
1109 | priv->ch[i].info = framebuffer_alloc(0, &pdev->dev); | 1514 | const struct fb_videomode *mode = cfg->lcd_cfg; |
1110 | if (!priv->ch[i].info) { | 1515 | unsigned long max_size = 0; |
1516 | int k; | ||
1517 | int num_cfg; | ||
1518 | |||
1519 | ch->info = framebuffer_alloc(0, &pdev->dev); | ||
1520 | if (!ch->info) { | ||
1111 | dev_err(&pdev->dev, "unable to allocate fb_info\n"); | 1521 | dev_err(&pdev->dev, "unable to allocate fb_info\n"); |
1112 | error = -ENOMEM; | 1522 | error = -ENOMEM; |
1113 | break; | 1523 | break; |
1114 | } | 1524 | } |
1115 | 1525 | ||
1116 | info = priv->ch[i].info; | 1526 | info = ch->info; |
1117 | var = &info->var; | 1527 | var = &info->var; |
1118 | lcd_cfg = &cfg->lcd_cfg; | ||
1119 | info->fbops = &sh_mobile_lcdc_ops; | 1528 | info->fbops = &sh_mobile_lcdc_ops; |
1120 | var->xres = var->xres_virtual = lcd_cfg->xres; | 1529 | info->par = ch; |
1121 | var->yres = lcd_cfg->yres; | 1530 | |
1122 | /* Default Y virtual resolution is 2x panel size */ | 1531 | mutex_init(&ch->open_lock); |
1123 | var->yres_virtual = var->yres * 2; | 1532 | |
1533 | for (k = 0, lcd_cfg = mode; | ||
1534 | k < cfg->num_cfg && lcd_cfg; | ||
1535 | k++, lcd_cfg++) { | ||
1536 | unsigned long size = lcd_cfg->yres * lcd_cfg->xres; | ||
1537 | /* NV12 buffers must have even number of lines */ | ||
1538 | if ((cfg->nonstd) && cfg->bpp == 12 && | ||
1539 | (lcd_cfg->yres & 0x1)) { | ||
1540 | dev_err(&pdev->dev, "yres must be multiple of 2" | ||
1541 | " for YCbCr420 mode.\n"); | ||
1542 | error = -EINVAL; | ||
1543 | goto err1; | ||
1544 | } | ||
1545 | |||
1546 | if (size > max_size) { | ||
1547 | max_cfg = lcd_cfg; | ||
1548 | max_size = size; | ||
1549 | } | ||
1550 | } | ||
1551 | |||
1552 | if (!mode) | ||
1553 | max_size = MAX_XRES * MAX_YRES; | ||
1554 | else if (max_cfg) | ||
1555 | dev_dbg(&pdev->dev, "Found largest videomode %ux%u\n", | ||
1556 | max_cfg->xres, max_cfg->yres); | ||
1557 | |||
1558 | info->fix = sh_mobile_lcdc_fix; | ||
1559 | info->fix.smem_len = max_size * 2 * cfg->bpp / 8; | ||
1560 | |||
1561 | /* Only pan in 2 line steps for NV12 */ | ||
1562 | if (cfg->nonstd && cfg->bpp == 12) | ||
1563 | info->fix.ypanstep = 2; | ||
1564 | |||
1565 | if (!mode) { | ||
1566 | mode = &default_720p; | ||
1567 | num_cfg = 1; | ||
1568 | } else { | ||
1569 | num_cfg = cfg->num_cfg; | ||
1570 | } | ||
1571 | |||
1572 | fb_videomode_to_modelist(mode, num_cfg, &info->modelist); | ||
1573 | |||
1574 | fb_videomode_to_var(var, mode); | ||
1124 | var->width = cfg->lcd_size_cfg.width; | 1575 | var->width = cfg->lcd_size_cfg.width; |
1125 | var->height = cfg->lcd_size_cfg.height; | 1576 | var->height = cfg->lcd_size_cfg.height; |
1577 | /* Default Y virtual resolution is 2x panel size */ | ||
1578 | var->yres_virtual = var->yres * 2; | ||
1126 | var->activate = FB_ACTIVATE_NOW; | 1579 | var->activate = FB_ACTIVATE_NOW; |
1127 | var->left_margin = lcd_cfg->left_margin; | 1580 | |
1128 | var->right_margin = lcd_cfg->right_margin; | 1581 | error = sh_mobile_lcdc_set_bpp(var, cfg->bpp, cfg->nonstd); |
1129 | var->upper_margin = lcd_cfg->upper_margin; | ||
1130 | var->lower_margin = lcd_cfg->lower_margin; | ||
1131 | var->hsync_len = lcd_cfg->hsync_len; | ||
1132 | var->vsync_len = lcd_cfg->vsync_len; | ||
1133 | var->sync = lcd_cfg->sync; | ||
1134 | var->pixclock = lcd_cfg->pixclock; | ||
1135 | |||
1136 | error = sh_mobile_lcdc_set_bpp(var, cfg->bpp); | ||
1137 | if (error) | 1582 | if (error) |
1138 | break; | 1583 | break; |
1139 | 1584 | ||
1140 | info->fix = sh_mobile_lcdc_fix; | ||
1141 | info->fix.line_length = lcd_cfg->xres * (cfg->bpp / 8); | ||
1142 | info->fix.smem_len = info->fix.line_length * | ||
1143 | var->yres_virtual; | ||
1144 | |||
1145 | buf = dma_alloc_coherent(&pdev->dev, info->fix.smem_len, | 1585 | buf = dma_alloc_coherent(&pdev->dev, info->fix.smem_len, |
1146 | &priv->ch[i].dma_handle, GFP_KERNEL); | 1586 | &ch->dma_handle, GFP_KERNEL); |
1147 | if (!buf) { | 1587 | if (!buf) { |
1148 | dev_err(&pdev->dev, "unable to allocate buffer\n"); | 1588 | dev_err(&pdev->dev, "unable to allocate buffer\n"); |
1149 | error = -ENOMEM; | 1589 | error = -ENOMEM; |
1150 | break; | 1590 | break; |
1151 | } | 1591 | } |
1152 | 1592 | ||
1153 | info->pseudo_palette = &priv->ch[i].pseudo_palette; | 1593 | info->pseudo_palette = &ch->pseudo_palette; |
1154 | info->flags = FBINFO_FLAG_DEFAULT; | 1594 | info->flags = FBINFO_FLAG_DEFAULT; |
1155 | 1595 | ||
1156 | error = fb_alloc_cmap(&info->cmap, PALETTE_NR, 0); | 1596 | error = fb_alloc_cmap(&info->cmap, PALETTE_NR, 0); |
1157 | if (error < 0) { | 1597 | if (error < 0) { |
1158 | dev_err(&pdev->dev, "unable to allocate cmap\n"); | 1598 | dev_err(&pdev->dev, "unable to allocate cmap\n"); |
1159 | dma_free_coherent(&pdev->dev, info->fix.smem_len, | 1599 | dma_free_coherent(&pdev->dev, info->fix.smem_len, |
1160 | buf, priv->ch[i].dma_handle); | 1600 | buf, ch->dma_handle); |
1161 | break; | 1601 | break; |
1162 | } | 1602 | } |
1163 | 1603 | ||
1164 | memset(buf, 0, info->fix.smem_len); | 1604 | info->fix.smem_start = ch->dma_handle; |
1165 | info->fix.smem_start = priv->ch[i].dma_handle; | 1605 | if (var->nonstd) |
1606 | info->fix.line_length = var->xres; | ||
1607 | else | ||
1608 | info->fix.line_length = var->xres * (cfg->bpp / 8); | ||
1609 | |||
1166 | info->screen_base = buf; | 1610 | info->screen_base = buf; |
1167 | info->device = &pdev->dev; | 1611 | info->device = &pdev->dev; |
1168 | info->par = &priv->ch[i]; | 1612 | ch->display_var = *var; |
1169 | } | 1613 | } |
1170 | 1614 | ||
1171 | if (error) | 1615 | if (error) |
@@ -1191,6 +1635,8 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1191 | } | 1635 | } |
1192 | } | 1636 | } |
1193 | 1637 | ||
1638 | info->bl_dev = ch->bl; | ||
1639 | |||
1194 | error = register_framebuffer(info); | 1640 | error = register_framebuffer(info); |
1195 | if (error < 0) | 1641 | if (error < 0) |
1196 | goto err1; | 1642 | goto err1; |
@@ -1200,8 +1646,7 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1200 | pdev->name, | 1646 | pdev->name, |
1201 | (ch->cfg.chan == LCDC_CHAN_MAINLCD) ? | 1647 | (ch->cfg.chan == LCDC_CHAN_MAINLCD) ? |
1202 | "mainlcd" : "sublcd", | 1648 | "mainlcd" : "sublcd", |
1203 | (int) ch->cfg.lcd_cfg.xres, | 1649 | info->var.xres, info->var.yres, |
1204 | (int) ch->cfg.lcd_cfg.yres, | ||
1205 | ch->cfg.bpp); | 1650 | ch->cfg.bpp); |
1206 | 1651 | ||
1207 | /* deferred io mode: disable clock to save power */ | 1652 | /* deferred io mode: disable clock to save power */ |
@@ -1243,12 +1688,19 @@ static int sh_mobile_lcdc_remove(struct platform_device *pdev) | |||
1243 | if (priv->ch[i].sglist) | 1688 | if (priv->ch[i].sglist) |
1244 | vfree(priv->ch[i].sglist); | 1689 | vfree(priv->ch[i].sglist); |
1245 | 1690 | ||
1246 | dma_free_coherent(&pdev->dev, info->fix.smem_len, | 1691 | if (info->screen_base) |
1247 | info->screen_base, priv->ch[i].dma_handle); | 1692 | dma_free_coherent(&pdev->dev, info->fix.smem_len, |
1693 | info->screen_base, | ||
1694 | priv->ch[i].dma_handle); | ||
1248 | fb_dealloc_cmap(&info->cmap); | 1695 | fb_dealloc_cmap(&info->cmap); |
1249 | framebuffer_release(info); | 1696 | framebuffer_release(info); |
1250 | } | 1697 | } |
1251 | 1698 | ||
1699 | for (i = 0; i < ARRAY_SIZE(priv->ch); i++) { | ||
1700 | if (priv->ch[i].bl) | ||
1701 | sh_mobile_lcdc_bl_remove(priv->ch[i].bl); | ||
1702 | } | ||
1703 | |||
1252 | if (priv->dot_clk) | 1704 | if (priv->dot_clk) |
1253 | clk_put(priv->dot_clk); | 1705 | clk_put(priv->dot_clk); |
1254 | 1706 | ||