Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Damian Hobson-Garcia and committed by
Paul Mundt
53b50314 3b0fd9d7

+115 -29
+113 -28
drivers/video/sh_mobile_lcdcfb.c
··· 69 69 [LDSM1R] = 0x428, 70 70 [LDSM2R] = 0x42c, 71 71 [LDSA1R] = 0x430, 72 + [LDSA2R] = 0x434, 72 73 [LDMLSR] = 0x438, 73 74 [LDHCNR] = 0x448, 74 75 [LDHSYNR] = 0x44c, ··· 154 153 case LDDFR: 155 154 case LDSM1R: 156 155 case LDSA1R: 156 + case LDSA2R: 157 157 case LDMLSR: 158 158 case LDHCNR: 159 159 case LDHSYNR: ··· 467 465 struct sh_mobile_lcdc_board_cfg *board_cfg; 468 466 unsigned long tmp; 469 467 int bpp = 0; 468 + unsigned long ldddsr; 470 469 int k, m; 471 470 int ret = 0; 472 471 ··· 546 543 } 547 544 548 545 /* word and long word swap */ 549 - switch (bpp) { 550 - case 16: 551 - lcdc_write(priv, _LDDDSR, lcdc_read(priv, _LDDDSR) | 6); 552 - break; 553 - case 24: 554 - lcdc_write(priv, _LDDDSR, lcdc_read(priv, _LDDDSR) | 7); 555 - break; 556 - case 32: 557 - lcdc_write(priv, _LDDDSR, lcdc_read(priv, _LDDDSR) | 4); 558 - break; 546 + ldddsr = lcdc_read(priv, _LDDDSR); 547 + if (priv->ch[0].info->var.nonstd) 548 + lcdc_write(priv, _LDDDSR, ldddsr | 7); 549 + else { 550 + switch (bpp) { 551 + case 16: 552 + lcdc_write(priv, _LDDDSR, ldddsr | 6); 553 + break; 554 + case 24: 555 + lcdc_write(priv, _LDDDSR, ldddsr | 7); 556 + break; 557 + case 32: 558 + lcdc_write(priv, _LDDDSR, ldddsr | 4); 559 + break; 560 + } 559 561 } 560 562 561 563 for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { ··· 571 563 572 564 /* set bpp format in PKF[4:0] */ 573 565 tmp = lcdc_read_chan(ch, LDDFR); 574 - tmp &= ~0x0001001f; 575 - switch (ch->info->var.bits_per_pixel) { 576 - case 16: 577 - tmp |= 0x03; 578 - break; 579 - case 24: 580 - tmp |= 0x0b; 581 - break; 582 - case 32: 583 - break; 566 + tmp &= ~0x0003031f; 567 + if (ch->info->var.nonstd) { 568 + tmp |= (ch->info->var.nonstd << 16); 569 + switch (ch->info->var.bits_per_pixel) { 570 + case 12: 571 + break; 572 + case 16: 573 + tmp |= (0x1 << 8); 574 + break; 575 + case 24: 576 + tmp |= (0x2 << 8); 577 + break; 578 + } 579 + } else { 580 + switch (ch->info->var.bits_per_pixel) { 581 + case 16: 582 + tmp |= 0x03; 583 + break; 584 + case 24: 585 + tmp |= 0x0b; 586 + break; 587 + case 32: 588 + break; 589 + } 584 590 } 585 591 lcdc_write_chan(ch, LDDFR, tmp); 586 592 587 593 /* point out our frame buffer */ 588 594 lcdc_write_chan(ch, LDSA1R, ch->info->fix.smem_start); 595 + if (ch->info->var.nonstd) 596 + lcdc_write_chan(ch, LDSA2R, 597 + ch->info->fix.smem_start + 598 + ch->info->var.xres * 599 + ch->info->var.yres_virtual); 589 600 590 601 /* set line size */ 591 602 lcdc_write_chan(ch, LDMLSR, ch->info->fix.line_length); ··· 843 816 struct sh_mobile_lcdc_priv *priv = ch->lcdc; 844 817 unsigned long ldrcntr; 845 818 unsigned long new_pan_offset; 819 + unsigned long base_addr_y, base_addr_c; 820 + unsigned long c_offset; 846 821 847 - new_pan_offset = (var->yoffset * info->fix.line_length) + 848 - (var->xoffset * (info->var.bits_per_pixel / 8)); 822 + if (!var->nonstd) 823 + new_pan_offset = (var->yoffset * info->fix.line_length) + 824 + (var->xoffset * (info->var.bits_per_pixel / 8)); 825 + else 826 + new_pan_offset = (var->yoffset * info->fix.line_length) + 827 + (var->xoffset); 849 828 850 829 if (new_pan_offset == ch->pan_offset) 851 830 return 0; /* No change, do nothing */ ··· 859 826 ldrcntr = lcdc_read(priv, _LDRCNTR); 860 827 861 828 /* Set the source address for the next refresh */ 862 - lcdc_write_chan_mirror(ch, LDSA1R, ch->dma_handle + new_pan_offset); 829 + base_addr_y = ch->dma_handle + new_pan_offset; 830 + if (var->nonstd) { 831 + /* Set y offset */ 832 + c_offset = (var->yoffset * 833 + info->fix.line_length * 834 + (info->var.bits_per_pixel - 8)) / 8; 835 + base_addr_c = ch->dma_handle + var->xres * var->yres_virtual + 836 + c_offset; 837 + /* Set x offset */ 838 + if (info->var.bits_per_pixel == 24) 839 + base_addr_c += 2 * var->xoffset; 840 + else 841 + base_addr_c += var->xoffset; 842 + } else 843 + base_addr_c = 0; 844 + 845 + lcdc_write_chan_mirror(ch, LDSA1R, base_addr_y); 846 + if (base_addr_c) 847 + lcdc_write_chan_mirror(ch, LDSA2R, base_addr_c); 848 + 863 849 if (lcdc_chan_is_sublcd(ch)) 864 850 lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_SRS); 865 851 else ··· 949 897 /* Couldn't reconfigure, hopefully, can continue as before */ 950 898 return; 951 899 952 - info->fix.line_length = mode1.xres * (ch->cfg.bpp / 8); 900 + if (info->var.nonstd) 901 + info->fix.line_length = mode1.xres; 902 + else 903 + info->fix.line_length = mode1.xres * (ch->cfg.bpp / 8); 953 904 954 905 /* 955 906 * fb_set_var() calls the notifier change internally, only if ··· 1105 1050 backlight_device_unregister(bdev); 1106 1051 } 1107 1052 1108 - static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp) 1053 + static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp, 1054 + int nonstd) 1109 1055 { 1056 + if (nonstd) { 1057 + switch (bpp) { 1058 + case 12: 1059 + case 16: 1060 + case 24: 1061 + var->bits_per_pixel = bpp; 1062 + var->nonstd = nonstd; 1063 + return 0; 1064 + default: 1065 + return -EINVAL; 1066 + } 1067 + } 1068 + 1110 1069 switch (bpp) { 1111 1070 case 16: /* PKF[4:0] = 00011 - RGB 565 */ 1112 1071 var->red.offset = 11; ··· 1403 1334 k < cfg->num_cfg && lcd_cfg; 1404 1335 k++, lcd_cfg++) { 1405 1336 unsigned long size = lcd_cfg->yres * lcd_cfg->xres; 1337 + /* NV12 buffers must have even number of lines */ 1338 + if ((cfg->nonstd) && cfg->bpp == 12 && 1339 + (lcd_cfg->yres & 0x1)) { 1340 + dev_err(&pdev->dev, "yres must be multiple of 2" 1341 + " for YCbCr420 mode.\n"); 1342 + error = -EINVAL; 1343 + goto err1; 1344 + } 1406 1345 1407 1346 if (size > max_size) { 1408 1347 max_cfg = lcd_cfg; ··· 1425 1348 max_cfg->xres, max_cfg->yres); 1426 1349 1427 1350 info->fix = sh_mobile_lcdc_fix; 1428 - info->fix.smem_len = max_size * (cfg->bpp / 8) * 2; 1351 + info->fix.smem_len = max_size * 2 * cfg->bpp / 8; 1352 + 1353 + /* Only pan in 2 line steps for NV12 */ 1354 + if (cfg->nonstd && cfg->bpp == 12) 1355 + info->fix.ypanstep = 2; 1429 1356 1430 1357 if (!mode) { 1431 1358 mode = &default_720p; ··· 1447 1366 var->yres_virtual = var->yres * 2; 1448 1367 var->activate = FB_ACTIVATE_NOW; 1449 1368 1450 - error = sh_mobile_lcdc_set_bpp(var, cfg->bpp); 1369 + error = sh_mobile_lcdc_set_bpp(var, cfg->bpp, cfg->nonstd); 1451 1370 if (error) 1452 1371 break; 1453 1372 ··· 1471 1390 } 1472 1391 1473 1392 info->fix.smem_start = ch->dma_handle; 1474 - info->fix.line_length = var->xres * (cfg->bpp / 8); 1393 + if (var->nonstd) 1394 + info->fix.line_length = var->xres; 1395 + else 1396 + info->fix.line_length = var->xres * (cfg->bpp / 8); 1397 + 1475 1398 info->screen_base = buf; 1476 1399 info->device = &pdev->dev; 1477 1400 ch->display_var = *var;
+1 -1
drivers/video/sh_mobile_lcdcfb.h
··· 8 8 9 9 /* per-channel registers */ 10 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 12 LDHAJR, 13 13 NR_CH_REGS }; 14 14
+1
include/video/sh_mobile_lcdc.h
··· 86 86 struct sh_mobile_lcdc_board_cfg board_cfg; 87 87 struct sh_mobile_lcdc_bl_info bl_info; 88 88 struct sh_mobile_lcdc_sys_bus_cfg sys_bus_cfg; /* only for SYSn I/F */ 89 + int nonstd; 89 90 }; 90 91 91 92 struct sh_mobile_lcdc_info {