diff options
author | Damian Hobson-Garcia <dhobsong@igel.co.jp> | 2011-02-24 05:47:13 +0000 |
---|---|---|
committer | Paul Mundt <lethal@linux-sh.org> | 2011-03-16 17:27:10 +0900 |
commit | 53b5031430bb3a7941b340b453afe4eabeb5340c (patch) | |
tree | c6eff3b6345c251194c6a1e6f8ca60a5d8f20581 /drivers | |
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>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/video/sh_mobile_lcdcfb.c | 141 | ||||
-rw-r--r-- | drivers/video/sh_mobile_lcdcfb.h | 2 |
2 files changed, 114 insertions, 29 deletions
diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c index e040e46d7d9..bf2629f83f4 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] = { [LDSM1R] = 0x428, [LDSM2R] = 0x42c, [LDSA1R] = 0x430, + [LDSA2R] = 0x434, [LDMLSR] = 0x438, [LDHCNR] = 0x448, [LDHSYNR] = 0x44c, @@ -153,6 +154,7 @@ static bool banked(int reg_nr) case LDDFR: case LDSM1R: case LDSA1R: + case LDSA2R: case LDMLSR: case LDHCNR: case LDHSYNR: @@ -465,6 +467,7 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) struct sh_mobile_lcdc_board_cfg *board_cfg; unsigned long tmp; int bpp = 0; + unsigned long ldddsr; int k, m; int ret = 0; @@ -543,16 +546,21 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) } /* word and long word swap */ - switch (bpp) { - case 16: - lcdc_write(priv, _LDDDSR, lcdc_read(priv, _LDDDSR) | 6); - break; - case 24: - lcdc_write(priv, _LDDDSR, lcdc_read(priv, _LDDDSR) | 7); - break; - case 32: - lcdc_write(priv, _LDDDSR, lcdc_read(priv, _LDDDSR) | 4); - break; + ldddsr = lcdc_read(priv, _LDDDSR); + if (priv->ch[0].info->var.nonstd) + lcdc_write(priv, _LDDDSR, ldddsr | 7); + else { + switch (bpp) { + case 16: + lcdc_write(priv, _LDDDSR, ldddsr | 6); + break; + case 24: + lcdc_write(priv, _LDDDSR, ldddsr | 7); + break; + case 32: + lcdc_write(priv, _LDDDSR, ldddsr | 4); + break; + } } 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) /* set bpp format in PKF[4:0] */ tmp = lcdc_read_chan(ch, LDDFR); - tmp &= ~0x0001001f; - switch (ch->info->var.bits_per_pixel) { - case 16: - tmp |= 0x03; - break; - case 24: - tmp |= 0x0b; - break; - case 32: - break; + tmp &= ~0x0003031f; + if (ch->info->var.nonstd) { + tmp |= (ch->info->var.nonstd << 16); + switch (ch->info->var.bits_per_pixel) { + case 12: + break; + case 16: + tmp |= (0x1 << 8); + break; + case 24: + tmp |= (0x2 << 8); + break; + } + } else { + switch (ch->info->var.bits_per_pixel) { + case 16: + tmp |= 0x03; + break; + case 24: + tmp |= 0x0b; + break; + case 32: + break; + } } lcdc_write_chan(ch, LDDFR, tmp); /* point out our frame buffer */ lcdc_write_chan(ch, LDSA1R, ch->info->fix.smem_start); + if (ch->info->var.nonstd) + lcdc_write_chan(ch, LDSA2R, + ch->info->fix.smem_start + + ch->info->var.xres * + ch->info->var.yres_virtual); /* set line size */ 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, struct sh_mobile_lcdc_priv *priv = ch->lcdc; unsigned long ldrcntr; unsigned long new_pan_offset; + unsigned long base_addr_y, base_addr_c; + unsigned long c_offset; - new_pan_offset = (var->yoffset * info->fix.line_length) + - (var->xoffset * (info->var.bits_per_pixel / 8)); + if (!var->nonstd) + new_pan_offset = (var->yoffset * info->fix.line_length) + + (var->xoffset * (info->var.bits_per_pixel / 8)); + else + new_pan_offset = (var->yoffset * info->fix.line_length) + + (var->xoffset); if (new_pan_offset == ch->pan_offset) return 0; /* No change, do nothing */ @@ -826,7 +859,26 @@ static int sh_mobile_fb_pan_display(struct fb_var_screeninfo *var, ldrcntr = lcdc_read(priv, _LDRCNTR); /* Set the source address for the next refresh */ - lcdc_write_chan_mirror(ch, LDSA1R, ch->dma_handle + new_pan_offset); + base_addr_y = ch->dma_handle + new_pan_offset; + if (var->nonstd) { + /* Set y offset */ + c_offset = (var->yoffset * + info->fix.line_length * + (info->var.bits_per_pixel - 8)) / 8; + base_addr_c = ch->dma_handle + var->xres * var->yres_virtual + + c_offset; + /* Set x offset */ + if (info->var.bits_per_pixel == 24) + base_addr_c += 2 * var->xoffset; + else + base_addr_c += var->xoffset; + } else + base_addr_c = 0; + + lcdc_write_chan_mirror(ch, LDSA1R, base_addr_y); + if (base_addr_c) + lcdc_write_chan_mirror(ch, LDSA2R, base_addr_c); + if (lcdc_chan_is_sublcd(ch)) lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_SRS); else @@ -897,7 +949,10 @@ static void sh_mobile_fb_reconfig(struct fb_info *info) /* Couldn't reconfigure, hopefully, can continue as before */ return; - info->fix.line_length = mode1.xres * (ch->cfg.bpp / 8); + if (info->var.nonstd) + info->fix.line_length = mode1.xres; + else + info->fix.line_length = mode1.xres * (ch->cfg.bpp / 8); /* * 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) backlight_device_unregister(bdev); } -static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp) +static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp, + int nonstd) { + if (nonstd) { + switch (bpp) { + case 12: + case 16: + case 24: + var->bits_per_pixel = bpp; + var->nonstd = nonstd; + return 0; + default: + return -EINVAL; + } + } + switch (bpp) { case 16: /* PKF[4:0] = 00011 - RGB 565 */ var->red.offset = 11; @@ -1334,6 +1403,14 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) k < cfg->num_cfg && lcd_cfg; k++, lcd_cfg++) { unsigned long size = lcd_cfg->yres * lcd_cfg->xres; + /* NV12 buffers must have even number of lines */ + if ((cfg->nonstd) && cfg->bpp == 12 && + (lcd_cfg->yres & 0x1)) { + dev_err(&pdev->dev, "yres must be multiple of 2" + " for YCbCr420 mode.\n"); + error = -EINVAL; + goto err1; + } if (size > max_size) { max_cfg = lcd_cfg; @@ -1348,7 +1425,11 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) max_cfg->xres, max_cfg->yres); info->fix = sh_mobile_lcdc_fix; - info->fix.smem_len = max_size * (cfg->bpp / 8) * 2; + info->fix.smem_len = max_size * 2 * cfg->bpp / 8; + + /* Only pan in 2 line steps for NV12 */ + if (cfg->nonstd && cfg->bpp == 12) + info->fix.ypanstep = 2; if (!mode) { mode = &default_720p; @@ -1366,7 +1447,7 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) var->yres_virtual = var->yres * 2; var->activate = FB_ACTIVATE_NOW; - error = sh_mobile_lcdc_set_bpp(var, cfg->bpp); + error = sh_mobile_lcdc_set_bpp(var, cfg->bpp, cfg->nonstd); if (error) break; @@ -1390,7 +1471,11 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) } info->fix.smem_start = ch->dma_handle; - info->fix.line_length = var->xres * (cfg->bpp / 8); + if (var->nonstd) + info->fix.line_length = var->xres; + else + info->fix.line_length = var->xres * (cfg->bpp / 8); + info->screen_base = buf; info->device = &pdev->dev; ch->display_var = *var; diff --git a/drivers/video/sh_mobile_lcdcfb.h b/drivers/video/sh_mobile_lcdcfb.h index 03a22dcbcc5..4635eed63ee 100644 --- a/drivers/video/sh_mobile_lcdcfb.h +++ b/drivers/video/sh_mobile_lcdcfb.h @@ -8,7 +8,7 @@ /* per-channel registers */ enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R, - LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR, + LDSM2R, LDSA1R, LDSA2R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR, LDHAJR, NR_CH_REGS }; |