diff options
author | Damian Hobson-Garcia <dhobsong@igel.co.jp> | 2011-02-24 00:47:13 -0500 |
---|---|---|
committer | Paul Mundt <lethal@linux-sh.org> | 2011-03-16 04:27:10 -0400 |
commit | 53b5031430bb3a7941b340b453afe4eabeb5340c (patch) | |
tree | c6eff3b6345c251194c6a1e6f8ca60a5d8f20581 | |
parent | 3b0fd9d75598584478d1d3f6551f8a8a9696c34e (diff) |
fbdev: sh_mobile_lcdc: Add YUV framebuffer support
Supports YCbCr420sp, YCbCr422sp, and YCbCr44sp, formats
(bpp = 12, 16, and 24) respectively.
When double-buffering both Y planes appear before the C planes (Y-Y-C-C),
as opposed to Y-C-Y-C.
Set .nonstd in struct sh_mobile_lcdc_chan_cfg to enable YUV mode, and use
.bpp to distiguish between the 3 modes.
The value of .nonstd is copied to bits 16-31 of LDDFR in the LCDC and
should be set accordingly.
.nonstd must be set to 0 for RGB mode.
Due to the encoding of YUV data, the framebuffer will clear to green
instead of black.
In YUV 420 mode, panning is only possible in 2 line increments.
Additionally in YUV 420 mode the vertical resolution of the framebuffer
must be an even number.
Signed-off-by: Damian Hobson-Garcia <dhobsong@igel.co.jp>
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
-rw-r--r-- | drivers/video/sh_mobile_lcdcfb.c | 141 | ||||
-rw-r--r-- | drivers/video/sh_mobile_lcdcfb.h | 2 | ||||
-rw-r--r-- | include/video/sh_mobile_lcdc.h | 1 |
3 files changed, 115 insertions, 29 deletions
diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c index e040e46d7d91..bf2629f83f40 100644 --- a/drivers/video/sh_mobile_lcdcfb.c +++ b/drivers/video/sh_mobile_lcdcfb.c | |||
@@ -69,6 +69,7 @@ static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { | |||
69 | [LDSM1R] = 0x428, | 69 | [LDSM1R] = 0x428, |
70 | [LDSM2R] = 0x42c, | 70 | [LDSM2R] = 0x42c, |
71 | [LDSA1R] = 0x430, | 71 | [LDSA1R] = 0x430, |
72 | [LDSA2R] = 0x434, | ||
72 | [LDMLSR] = 0x438, | 73 | [LDMLSR] = 0x438, |
73 | [LDHCNR] = 0x448, | 74 | [LDHCNR] = 0x448, |
74 | [LDHSYNR] = 0x44c, | 75 | [LDHSYNR] = 0x44c, |
@@ -153,6 +154,7 @@ static bool banked(int reg_nr) | |||
153 | case LDDFR: | 154 | case LDDFR: |
154 | case LDSM1R: | 155 | case LDSM1R: |
155 | case LDSA1R: | 156 | case LDSA1R: |
157 | case LDSA2R: | ||
156 | case LDMLSR: | 158 | case LDMLSR: |
157 | case LDHCNR: | 159 | case LDHCNR: |
158 | case LDHSYNR: | 160 | case LDHSYNR: |
@@ -465,6 +467,7 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) | |||
465 | struct sh_mobile_lcdc_board_cfg *board_cfg; | 467 | struct sh_mobile_lcdc_board_cfg *board_cfg; |
466 | unsigned long tmp; | 468 | unsigned long tmp; |
467 | int bpp = 0; | 469 | int bpp = 0; |
470 | unsigned long ldddsr; | ||
468 | int k, m; | 471 | int k, m; |
469 | int ret = 0; | 472 | int ret = 0; |
470 | 473 | ||
@@ -543,16 +546,21 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) | |||
543 | } | 546 | } |
544 | 547 | ||
545 | /* word and long word swap */ | 548 | /* word and long word swap */ |
546 | switch (bpp) { | 549 | ldddsr = lcdc_read(priv, _LDDDSR); |
547 | case 16: | 550 | if (priv->ch[0].info->var.nonstd) |
548 | lcdc_write(priv, _LDDDSR, lcdc_read(priv, _LDDDSR) | 6); | 551 | lcdc_write(priv, _LDDDSR, ldddsr | 7); |
549 | break; | 552 | else { |
550 | case 24: | 553 | switch (bpp) { |
551 | lcdc_write(priv, _LDDDSR, lcdc_read(priv, _LDDDSR) | 7); | 554 | case 16: |
552 | break; | 555 | lcdc_write(priv, _LDDDSR, ldddsr | 6); |
553 | case 32: | 556 | break; |
554 | lcdc_write(priv, _LDDDSR, lcdc_read(priv, _LDDDSR) | 4); | 557 | case 24: |
555 | break; | 558 | lcdc_write(priv, _LDDDSR, ldddsr | 7); |
559 | break; | ||
560 | case 32: | ||
561 | lcdc_write(priv, _LDDDSR, ldddsr | 4); | ||
562 | break; | ||
563 | } | ||
556 | } | 564 | } |
557 | 565 | ||
558 | for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { | 566 | for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { |
@@ -563,21 +571,40 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) | |||
563 | 571 | ||
564 | /* set bpp format in PKF[4:0] */ | 572 | /* set bpp format in PKF[4:0] */ |
565 | tmp = lcdc_read_chan(ch, LDDFR); | 573 | tmp = lcdc_read_chan(ch, LDDFR); |
566 | tmp &= ~0x0001001f; | 574 | tmp &= ~0x0003031f; |
567 | switch (ch->info->var.bits_per_pixel) { | 575 | if (ch->info->var.nonstd) { |
568 | case 16: | 576 | tmp |= (ch->info->var.nonstd << 16); |
569 | tmp |= 0x03; | 577 | switch (ch->info->var.bits_per_pixel) { |
570 | break; | 578 | case 12: |
571 | case 24: | 579 | break; |
572 | tmp |= 0x0b; | 580 | case 16: |
573 | break; | 581 | tmp |= (0x1 << 8); |
574 | case 32: | 582 | break; |
575 | break; | 583 | case 24: |
584 | tmp |= (0x2 << 8); | ||
585 | break; | ||
586 | } | ||
587 | } else { | ||
588 | switch (ch->info->var.bits_per_pixel) { | ||
589 | case 16: | ||
590 | tmp |= 0x03; | ||
591 | break; | ||
592 | case 24: | ||
593 | tmp |= 0x0b; | ||
594 | break; | ||
595 | case 32: | ||
596 | break; | ||
597 | } | ||
576 | } | 598 | } |
577 | lcdc_write_chan(ch, LDDFR, tmp); | 599 | lcdc_write_chan(ch, LDDFR, tmp); |
578 | 600 | ||
579 | /* point out our frame buffer */ | 601 | /* point out our frame buffer */ |
580 | lcdc_write_chan(ch, LDSA1R, ch->info->fix.smem_start); | 602 | lcdc_write_chan(ch, LDSA1R, ch->info->fix.smem_start); |
603 | if (ch->info->var.nonstd) | ||
604 | lcdc_write_chan(ch, LDSA2R, | ||
605 | ch->info->fix.smem_start + | ||
606 | ch->info->var.xres * | ||
607 | ch->info->var.yres_virtual); | ||
581 | 608 | ||
582 | /* set line size */ | 609 | /* set line size */ |
583 | lcdc_write_chan(ch, LDMLSR, ch->info->fix.line_length); | 610 | lcdc_write_chan(ch, LDMLSR, ch->info->fix.line_length); |
@@ -816,9 +843,15 @@ static int sh_mobile_fb_pan_display(struct fb_var_screeninfo *var, | |||
816 | struct sh_mobile_lcdc_priv *priv = ch->lcdc; | 843 | struct sh_mobile_lcdc_priv *priv = ch->lcdc; |
817 | unsigned long ldrcntr; | 844 | unsigned long ldrcntr; |
818 | unsigned long new_pan_offset; | 845 | unsigned long new_pan_offset; |
846 | unsigned long base_addr_y, base_addr_c; | ||
847 | unsigned long c_offset; | ||
819 | 848 | ||
820 | new_pan_offset = (var->yoffset * info->fix.line_length) + | 849 | if (!var->nonstd) |
821 | (var->xoffset * (info->var.bits_per_pixel / 8)); | 850 | new_pan_offset = (var->yoffset * info->fix.line_length) + |
851 | (var->xoffset * (info->var.bits_per_pixel / 8)); | ||
852 | else | ||
853 | new_pan_offset = (var->yoffset * info->fix.line_length) + | ||
854 | (var->xoffset); | ||
822 | 855 | ||
823 | if (new_pan_offset == ch->pan_offset) | 856 | if (new_pan_offset == ch->pan_offset) |
824 | return 0; /* No change, do nothing */ | 857 | return 0; /* No change, do nothing */ |
@@ -826,7 +859,26 @@ static int sh_mobile_fb_pan_display(struct fb_var_screeninfo *var, | |||
826 | ldrcntr = lcdc_read(priv, _LDRCNTR); | 859 | ldrcntr = lcdc_read(priv, _LDRCNTR); |
827 | 860 | ||
828 | /* Set the source address for the next refresh */ | 861 | /* Set the source address for the next refresh */ |
829 | lcdc_write_chan_mirror(ch, LDSA1R, ch->dma_handle + new_pan_offset); | 862 | base_addr_y = ch->dma_handle + new_pan_offset; |
863 | if (var->nonstd) { | ||
864 | /* Set y offset */ | ||
865 | c_offset = (var->yoffset * | ||
866 | info->fix.line_length * | ||
867 | (info->var.bits_per_pixel - 8)) / 8; | ||
868 | base_addr_c = ch->dma_handle + var->xres * var->yres_virtual + | ||
869 | c_offset; | ||
870 | /* Set x offset */ | ||
871 | if (info->var.bits_per_pixel == 24) | ||
872 | base_addr_c += 2 * var->xoffset; | ||
873 | else | ||
874 | base_addr_c += var->xoffset; | ||
875 | } else | ||
876 | base_addr_c = 0; | ||
877 | |||
878 | lcdc_write_chan_mirror(ch, LDSA1R, base_addr_y); | ||
879 | if (base_addr_c) | ||
880 | lcdc_write_chan_mirror(ch, LDSA2R, base_addr_c); | ||
881 | |||
830 | if (lcdc_chan_is_sublcd(ch)) | 882 | if (lcdc_chan_is_sublcd(ch)) |
831 | lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_SRS); | 883 | lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_SRS); |
832 | else | 884 | else |
@@ -897,7 +949,10 @@ static void sh_mobile_fb_reconfig(struct fb_info *info) | |||
897 | /* Couldn't reconfigure, hopefully, can continue as before */ | 949 | /* Couldn't reconfigure, hopefully, can continue as before */ |
898 | return; | 950 | return; |
899 | 951 | ||
900 | info->fix.line_length = mode1.xres * (ch->cfg.bpp / 8); | 952 | if (info->var.nonstd) |
953 | info->fix.line_length = mode1.xres; | ||
954 | else | ||
955 | info->fix.line_length = mode1.xres * (ch->cfg.bpp / 8); | ||
901 | 956 | ||
902 | /* | 957 | /* |
903 | * fb_set_var() calls the notifier change internally, only if | 958 | * fb_set_var() calls the notifier change internally, only if |
@@ -1050,8 +1105,22 @@ static void sh_mobile_lcdc_bl_remove(struct backlight_device *bdev) | |||
1050 | backlight_device_unregister(bdev); | 1105 | backlight_device_unregister(bdev); |
1051 | } | 1106 | } |
1052 | 1107 | ||
1053 | static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp) | 1108 | static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp, |
1109 | int nonstd) | ||
1054 | { | 1110 | { |
1111 | if (nonstd) { | ||
1112 | switch (bpp) { | ||
1113 | case 12: | ||
1114 | case 16: | ||
1115 | case 24: | ||
1116 | var->bits_per_pixel = bpp; | ||
1117 | var->nonstd = nonstd; | ||
1118 | return 0; | ||
1119 | default: | ||
1120 | return -EINVAL; | ||
1121 | } | ||
1122 | } | ||
1123 | |||
1055 | switch (bpp) { | 1124 | switch (bpp) { |
1056 | case 16: /* PKF[4:0] = 00011 - RGB 565 */ | 1125 | case 16: /* PKF[4:0] = 00011 - RGB 565 */ |
1057 | var->red.offset = 11; | 1126 | var->red.offset = 11; |
@@ -1334,6 +1403,14 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1334 | k < cfg->num_cfg && lcd_cfg; | 1403 | k < cfg->num_cfg && lcd_cfg; |
1335 | k++, lcd_cfg++) { | 1404 | k++, lcd_cfg++) { |
1336 | unsigned long size = lcd_cfg->yres * lcd_cfg->xres; | 1405 | unsigned long size = lcd_cfg->yres * lcd_cfg->xres; |
1406 | /* NV12 buffers must have even number of lines */ | ||
1407 | if ((cfg->nonstd) && cfg->bpp == 12 && | ||
1408 | (lcd_cfg->yres & 0x1)) { | ||
1409 | dev_err(&pdev->dev, "yres must be multiple of 2" | ||
1410 | " for YCbCr420 mode.\n"); | ||
1411 | error = -EINVAL; | ||
1412 | goto err1; | ||
1413 | } | ||
1337 | 1414 | ||
1338 | if (size > max_size) { | 1415 | if (size > max_size) { |
1339 | max_cfg = lcd_cfg; | 1416 | max_cfg = lcd_cfg; |
@@ -1348,7 +1425,11 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1348 | max_cfg->xres, max_cfg->yres); | 1425 | max_cfg->xres, max_cfg->yres); |
1349 | 1426 | ||
1350 | info->fix = sh_mobile_lcdc_fix; | 1427 | info->fix = sh_mobile_lcdc_fix; |
1351 | info->fix.smem_len = max_size * (cfg->bpp / 8) * 2; | 1428 | info->fix.smem_len = max_size * 2 * cfg->bpp / 8; |
1429 | |||
1430 | /* Only pan in 2 line steps for NV12 */ | ||
1431 | if (cfg->nonstd && cfg->bpp == 12) | ||
1432 | info->fix.ypanstep = 2; | ||
1352 | 1433 | ||
1353 | if (!mode) { | 1434 | if (!mode) { |
1354 | mode = &default_720p; | 1435 | mode = &default_720p; |
@@ -1366,7 +1447,7 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1366 | var->yres_virtual = var->yres * 2; | 1447 | var->yres_virtual = var->yres * 2; |
1367 | var->activate = FB_ACTIVATE_NOW; | 1448 | var->activate = FB_ACTIVATE_NOW; |
1368 | 1449 | ||
1369 | error = sh_mobile_lcdc_set_bpp(var, cfg->bpp); | 1450 | error = sh_mobile_lcdc_set_bpp(var, cfg->bpp, cfg->nonstd); |
1370 | if (error) | 1451 | if (error) |
1371 | break; | 1452 | break; |
1372 | 1453 | ||
@@ -1390,7 +1471,11 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1390 | } | 1471 | } |
1391 | 1472 | ||
1392 | info->fix.smem_start = ch->dma_handle; | 1473 | info->fix.smem_start = ch->dma_handle; |
1393 | info->fix.line_length = var->xres * (cfg->bpp / 8); | 1474 | if (var->nonstd) |
1475 | info->fix.line_length = var->xres; | ||
1476 | else | ||
1477 | info->fix.line_length = var->xres * (cfg->bpp / 8); | ||
1478 | |||
1394 | info->screen_base = buf; | 1479 | info->screen_base = buf; |
1395 | info->device = &pdev->dev; | 1480 | info->device = &pdev->dev; |
1396 | ch->display_var = *var; | 1481 | ch->display_var = *var; |
diff --git a/drivers/video/sh_mobile_lcdcfb.h b/drivers/video/sh_mobile_lcdcfb.h index 03a22dcbcc59..4635eed63eee 100644 --- a/drivers/video/sh_mobile_lcdcfb.h +++ b/drivers/video/sh_mobile_lcdcfb.h | |||
@@ -8,7 +8,7 @@ | |||
8 | 8 | ||
9 | /* per-channel registers */ | 9 | /* per-channel registers */ |
10 | enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R, | 10 | enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R, |
11 | LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR, | 11 | LDSM2R, LDSA1R, LDSA2R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR, |
12 | LDHAJR, | 12 | LDHAJR, |
13 | NR_CH_REGS }; | 13 | NR_CH_REGS }; |
14 | 14 | ||
diff --git a/include/video/sh_mobile_lcdc.h b/include/video/sh_mobile_lcdc.h index f2e6ab857fa8..2c8d369190b3 100644 --- a/include/video/sh_mobile_lcdc.h +++ b/include/video/sh_mobile_lcdc.h | |||
@@ -86,6 +86,7 @@ struct sh_mobile_lcdc_chan_cfg { | |||
86 | struct sh_mobile_lcdc_board_cfg board_cfg; | 86 | struct sh_mobile_lcdc_board_cfg board_cfg; |
87 | struct sh_mobile_lcdc_bl_info bl_info; | 87 | struct sh_mobile_lcdc_bl_info bl_info; |
88 | struct sh_mobile_lcdc_sys_bus_cfg sys_bus_cfg; /* only for SYSn I/F */ | 88 | struct sh_mobile_lcdc_sys_bus_cfg sys_bus_cfg; /* only for SYSn I/F */ |
89 | int nonstd; | ||
89 | }; | 90 | }; |
90 | 91 | ||
91 | struct sh_mobile_lcdc_info { | 92 | struct sh_mobile_lcdc_info { |