unsigned long base_addr_y, base_addr_c;
unsigned long c_offset;
- if (!var->nonstd)
- new_pan_offset = (var->yoffset * info->fix.line_length) +
- (var->xoffset * (info->var.bits_per_pixel / 8));
+ if (!info->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);
+ new_pan_offset = var->yoffset * info->fix.line_length
+ + var->xoffset;
if (new_pan_offset == ch->pan_offset)
return 0; /* No change, do nothing */
/* Set the source address for the next refresh */
base_addr_y = ch->dma_handle + new_pan_offset;
- if (var->nonstd) {
+ if (info->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;
+ c_offset = var->yoffset * info->fix.line_length
+ * (info->var.bits_per_pixel - 8) / 8;
+ base_addr_c = ch->dma_handle
+ + info->var.xres * info->var.yres_virtual
+ + c_offset;
/* Set x offset */
if (info->var.bits_per_pixel == 24)
base_addr_c += 2 * var->xoffset;
ch->base_addr_c = base_addr_c;
lcdc_write_chan_mirror(ch, LDSA1R, base_addr_y);
- if (var->nonstd)
+ if (info->var.nonstd)
lcdc_write_chan_mirror(ch, LDSA2R, base_addr_c);
if (lcdc_chan_is_sublcd(ch))
/* Couldn't reconfigure, hopefully, can continue as before */
return;
- 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
* FBINFO_MISC_USEREVENT flag is set. Since we do not want to fake a
{
struct sh_mobile_lcdc_chan *ch = info->par;
struct sh_mobile_lcdc_priv *p = ch->lcdc;
+ unsigned int best_dist = (unsigned int)-1;
+ unsigned int best_xres = 0;
+ unsigned int best_yres = 0;
+ unsigned int i;
- if (var->xres > MAX_XRES || var->yres > MAX_YRES ||
- var->xres * var->yres * (ch->cfg.bpp / 8) * 2 > info->fix.smem_len) {
- dev_warn(info->dev, "Invalid info: %u-%u-%u-%u x %u-%u-%u-%u @ %lukHz!\n",
- var->left_margin, var->xres, var->right_margin, var->hsync_len,
- var->upper_margin, var->yres, var->lower_margin, var->vsync_len,
- PICOS2KHZ(var->pixclock));
+ if (var->xres > MAX_XRES || var->yres > MAX_YRES)
return -EINVAL;
+
+ /* If board code provides us with a list of available modes, make sure
+ * we use one of them. Find the mode closest to the requested one. The
+ * distance between two modes is defined as the size of the
+ * non-overlapping parts of the two rectangles.
+ */
+ for (i = 0; i < ch->cfg.num_cfg; ++i) {
+ const struct fb_videomode *mode = &ch->cfg.lcd_cfg[i];
+ unsigned int dist;
+
+ /* We can only round up. */
+ if (var->xres > mode->xres || var->yres > mode->yres)
+ continue;
+
+ dist = var->xres * var->yres + mode->xres * mode->yres
+ - 2 * min(var->xres, mode->xres)
+ * min(var->yres, mode->yres);
+
+ if (dist < best_dist) {
+ best_xres = mode->xres;
+ best_yres = mode->yres;
+ best_dist = dist;
+ }
}
+ /* If no available mode can be used, return an error. */
+ if (ch->cfg.num_cfg != 0) {
+ if (best_dist == (unsigned int)-1)
+ return -EINVAL;
+
+ var->xres = best_xres;
+ var->yres = best_yres;
+ }
+
+ /* Make sure the virtual resolution is at least as big as the visible
+ * resolution.
+ */
+ if (var->xres_virtual < var->xres)
+ var->xres_virtual = var->xres;
+ if (var->yres_virtual < var->yres)
+ var->yres_virtual = var->yres;
+
+ if (var->bits_per_pixel <= 16) { /* RGB 565 */
+ var->bits_per_pixel = 16;
+ var->red.offset = 11;
+ var->red.length = 5;
+ var->green.offset = 5;
+ var->green.length = 6;
+ var->blue.offset = 0;
+ var->blue.length = 5;
+ var->transp.offset = 0;
+ var->transp.length = 0;
+ } else if (var->bits_per_pixel <= 24) { /* RGB 888 */
+ var->bits_per_pixel = 24;
+ var->red.offset = 16;
+ var->red.length = 8;
+ var->green.offset = 8;
+ var->green.length = 8;
+ var->blue.offset = 0;
+ var->blue.length = 8;
+ var->transp.offset = 0;
+ var->transp.length = 0;
+ } else if (var->bits_per_pixel <= 32) { /* RGBA 888 */
+ var->bits_per_pixel = 32;
+ var->red.offset = 16;
+ var->red.length = 8;
+ var->green.offset = 8;
+ var->green.length = 8;
+ var->blue.offset = 0;
+ var->blue.length = 8;
+ var->transp.offset = 24;
+ var->transp.length = 8;
+ } else
+ return -EINVAL;
+
+ var->red.msb_right = 0;
+ var->green.msb_right = 0;
+ var->blue.msb_right = 0;
+ var->transp.msb_right = 0;
+
+ /* Make sure we don't exceed our allocated memory. */
+ if (var->xres_virtual * var->yres_virtual * var->bits_per_pixel / 8 >
+ info->fix.smem_len)
+ return -EINVAL;
+
/* only accept the forced_bpp for dual channel configurations */
if (p->forced_bpp && p->forced_bpp != var->bits_per_pixel)
return -EINVAL;
- switch (var->bits_per_pixel) {
- case 16: /* PKF[4:0] = 00011 - RGB 565 */
- case 24: /* PKF[4:0] = 01011 - RGB 888 */
- case 32: /* PKF[4:0] = 00000 - RGBA 888 */
- break;
- default:
- return -EINVAL;
+ return 0;
+}
+
+static int sh_mobile_set_par(struct fb_info *info)
+{
+ struct sh_mobile_lcdc_chan *ch = info->par;
+ u32 line_length = info->fix.line_length;
+ int ret;
+
+ sh_mobile_lcdc_stop(ch->lcdc);
+
+ if (info->var.nonstd)
+ info->fix.line_length = info->var.xres;
+ else
+ info->fix.line_length = info->var.xres
+ * info->var.bits_per_pixel / 8;
+
+ ret = sh_mobile_lcdc_start(ch->lcdc);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: unable to restart LCDC\n", __func__);
+ info->fix.line_length = line_length;
}
- return 0;
+ return ret;
}
/*
.fb_open = sh_mobile_open,
.fb_release = sh_mobile_release,
.fb_check_var = sh_mobile_check_var,
+ .fb_set_par = sh_mobile_set_par,
};
static int sh_mobile_lcdc_update_bl(struct backlight_device *bdev)
backlight_device_unregister(bdev);
}
-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;
- var->red.length = 5;
- var->green.offset = 5;
- var->green.length = 6;
- var->blue.offset = 0;
- var->blue.length = 5;
- var->transp.offset = 0;
- var->transp.length = 0;
- break;
-
- case 24: /* PKF[4:0] = 01011 - RGB 888 */
- var->red.offset = 16;
- var->red.length = 8;
- var->green.offset = 8;
- var->green.length = 8;
- var->blue.offset = 0;
- var->blue.length = 8;
- var->transp.offset = 0;
- var->transp.length = 0;
- break;
-
- case 32: /* PKF[4:0] = 00000 - RGBA 888 */
- var->red.offset = 16;
- var->red.length = 8;
- var->green.offset = 8;
- var->green.length = 8;
- var->blue.offset = 0;
- var->blue.length = 8;
- var->transp.offset = 24;
- var->transp.length = 8;
- break;
- default:
- return -EINVAL;
- }
- var->bits_per_pixel = bpp;
- var->red.msb_right = 0;
- var->green.msb_right = 0;
- var->blue.msb_right = 0;
- var->transp.msb_right = 0;
- return 0;
-}
-
static int sh_mobile_lcdc_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
return NOTIFY_OK;
}
-static int sh_mobile_lcdc_remove(struct platform_device *pdev);
+static int sh_mobile_lcdc_remove(struct platform_device *pdev)
+{
+ struct sh_mobile_lcdc_priv *priv = platform_get_drvdata(pdev);
+ struct fb_info *info;
+ int i;
+
+ fb_unregister_client(&priv->notifier);
-static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev)
+ for (i = 0; i < ARRAY_SIZE(priv->ch); i++)
+ if (priv->ch[i].info && priv->ch[i].info->dev)
+ unregister_framebuffer(priv->ch[i].info);
+
+ sh_mobile_lcdc_stop(priv);
+
+ for (i = 0; i < ARRAY_SIZE(priv->ch); i++) {
+ info = priv->ch[i].info;
+
+ if (!info || !info->device)
+ continue;
+
+ if (priv->ch[i].sglist)
+ vfree(priv->ch[i].sglist);
+
+ if (info->screen_base)
+ dma_free_coherent(&pdev->dev, info->fix.smem_len,
+ info->screen_base,
+ priv->ch[i].dma_handle);
+ fb_dealloc_cmap(&info->cmap);
+ framebuffer_release(info);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(priv->ch); i++) {
+ if (priv->ch[i].bl)
+ sh_mobile_lcdc_bl_remove(priv->ch[i].bl);
+ }
+
+ if (priv->dot_clk)
+ clk_put(priv->dot_clk);
+
+ if (priv->dev)
+ pm_runtime_disable(priv->dev);
+
+ if (priv->base)
+ iounmap(priv->base);
+
+ if (priv->irq)
+ free_irq(priv->irq, priv);
+ kfree(priv);
+ return 0;
+}
+
+static int __devinit sh_mobile_lcdc_channel_init(struct sh_mobile_lcdc_chan *ch,
+ struct device *dev)
{
+ struct sh_mobile_lcdc_chan_cfg *cfg = &ch->cfg;
+ const struct fb_videomode *max_mode;
+ const struct fb_videomode *mode;
+ struct fb_var_screeninfo *var;
struct fb_info *info;
- struct sh_mobile_lcdc_priv *priv;
+ unsigned int max_size;
+ int num_cfg;
+ void *buf;
+ int ret;
+ int i;
+
+ mutex_init(&ch->open_lock);
+
+ /* Allocate the frame buffer device. */
+ ch->info = framebuffer_alloc(0, dev);
+ if (!ch->info) {
+ dev_err(dev, "unable to allocate fb_info\n");
+ return -ENOMEM;
+ }
+
+ info = ch->info;
+ info->fbops = &sh_mobile_lcdc_ops;
+ info->par = ch;
+ info->pseudo_palette = &ch->pseudo_palette;
+ info->flags = FBINFO_FLAG_DEFAULT;
+
+ /* Iterate through the modes to validate them and find the highest
+ * resolution.
+ */
+ max_mode = NULL;
+ max_size = 0;
+
+ for (i = 0, mode = cfg->lcd_cfg; i < cfg->num_cfg; i++, mode++) {
+ unsigned int size = mode->yres * mode->xres;
+
+ /* NV12 buffers must have even number of lines */
+ if ((cfg->nonstd) && cfg->bpp == 12 &&
+ (mode->yres & 0x1)) {
+ dev_err(dev, "yres must be multiple of 2 for YCbCr420 "
+ "mode.\n");
+ return -EINVAL;
+ }
+
+ if (size > max_size) {
+ max_mode = mode;
+ max_size = size;
+ }
+ }
+
+ if (!max_size)
+ max_size = MAX_XRES * MAX_YRES;
+ else
+ dev_dbg(dev, "Found largest videomode %ux%u\n",
+ max_mode->xres, max_mode->yres);
+
+ /* Initialize fixed screen information. Restrict pan to 2 lines steps
+ * for NV12.
+ */
+ info->fix = sh_mobile_lcdc_fix;
+ info->fix.smem_len = max_size * 2 * cfg->bpp / 8;
+ if (cfg->nonstd && cfg->bpp == 12)
+ info->fix.ypanstep = 2;
+
+ /* Create the mode list. */
+ if (cfg->lcd_cfg == NULL) {
+ mode = &default_720p;
+ num_cfg = 1;
+ } else {
+ mode = cfg->lcd_cfg;
+ num_cfg = cfg->num_cfg;
+ }
+
+ fb_videomode_to_modelist(mode, num_cfg, &info->modelist);
+
+ /* Initialize variable screen information using the first mode as
+ * default. The default Y virtual resolution is twice the panel size to
+ * allow for double-buffering.
+ */
+ var = &info->var;
+ fb_videomode_to_var(var, mode);
+ var->bits_per_pixel = cfg->bpp;
+ var->width = cfg->lcd_size_cfg.width;
+ var->height = cfg->lcd_size_cfg.height;
+ var->yres_virtual = var->yres * 2;
+ var->activate = FB_ACTIVATE_NOW;
+
+ ret = sh_mobile_check_var(var, info);
+ if (ret)
+ return ret;
+
+ /* Allocate frame buffer memory and color map. */
+ buf = dma_alloc_coherent(dev, info->fix.smem_len, &ch->dma_handle,
+ GFP_KERNEL);
+ if (!buf) {
+ dev_err(dev, "unable to allocate buffer\n");
+ return -ENOMEM;
+ }
+
+ ret = fb_alloc_cmap(&info->cmap, PALETTE_NR, 0);
+ if (ret < 0) {
+ dev_err(dev, "unable to allocate cmap\n");
+ dma_free_coherent(dev, info->fix.smem_len,
+ buf, ch->dma_handle);
+ return ret;
+ }
+
+ info->fix.smem_start = ch->dma_handle;
+ 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 = dev;
+ ch->display_var = *var;
+
+ return 0;
+}
+
+static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev)
+{
struct sh_mobile_lcdc_info *pdata = pdev->dev.platform_data;
+ struct sh_mobile_lcdc_priv *priv;
struct resource *res;
+ int num_channels;
int error;
- void *buf;
- int i, j;
+ int i;
if (!pdata) {
dev_err(&pdev->dev, "no platform data defined\n");
platform_set_drvdata(pdev, priv);
- error = request_irq(i, sh_mobile_lcdc_irq, IRQF_DISABLED,
+ error = request_irq(i, sh_mobile_lcdc_irq, 0,
dev_name(&pdev->dev), priv);
if (error) {
dev_err(&pdev->dev, "unable to request irq\n");
priv->irq = i;
atomic_set(&priv->hw_usecnt, -1);
- j = 0;
- for (i = 0; i < ARRAY_SIZE(pdata->ch); i++) {
- struct sh_mobile_lcdc_chan *ch = priv->ch + j;
+ for (i = 0, num_channels = 0; i < ARRAY_SIZE(pdata->ch); i++) {
+ struct sh_mobile_lcdc_chan *ch = priv->ch + num_channels;
ch->lcdc = priv;
memcpy(&ch->cfg, &pdata->ch[i], sizeof(pdata->ch[i]));
case LCDC_CHAN_MAINLCD:
ch->enabled = LDCNT2R_ME;
ch->reg_offs = lcdc_offs_mainlcd;
- j++;
+ num_channels++;
break;
case LCDC_CHAN_SUBLCD:
ch->enabled = LDCNT2R_SE;
ch->reg_offs = lcdc_offs_sublcd;
- j++;
+ num_channels++;
break;
}
}
- if (!j) {
+ if (!num_channels) {
dev_err(&pdev->dev, "no channels defined\n");
error = -EINVAL;
goto err1;
}
/* for dual channel LCDC (MAIN + SUB) force shared bpp setting */
- if (j == 2)
+ if (num_channels == 2)
priv->forced_bpp = pdata->ch[0].bpp;
priv->base = ioremap_nocache(res->start, resource_size(res));
priv->meram_dev = pdata->meram_dev;
- for (i = 0; i < j; i++) {
- struct fb_var_screeninfo *var;
- const struct fb_videomode *lcd_cfg, *max_cfg = NULL;
+ for (i = 0; i < num_channels; i++) {
struct sh_mobile_lcdc_chan *ch = priv->ch + i;
- struct sh_mobile_lcdc_chan_cfg *cfg = &ch->cfg;
- const struct fb_videomode *mode = cfg->lcd_cfg;
- unsigned long max_size = 0;
- int k;
- int num_cfg;
-
- ch->info = framebuffer_alloc(0, &pdev->dev);
- if (!ch->info) {
- dev_err(&pdev->dev, "unable to allocate fb_info\n");
- error = -ENOMEM;
- break;
- }
-
- info = ch->info;
- var = &info->var;
- info->fbops = &sh_mobile_lcdc_ops;
- info->par = ch;
-
- mutex_init(&ch->open_lock);
-
- for (k = 0, lcd_cfg = mode;
- 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;
- max_size = size;
- }
- }
-
- if (!mode)
- max_size = MAX_XRES * MAX_YRES;
- else if (max_cfg)
- dev_dbg(&pdev->dev, "Found largest videomode %ux%u\n",
- max_cfg->xres, max_cfg->yres);
-
- info->fix = sh_mobile_lcdc_fix;
- 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;
- num_cfg = 1;
- } else {
- num_cfg = cfg->num_cfg;
- }
-
- fb_videomode_to_modelist(mode, num_cfg, &info->modelist);
-
- fb_videomode_to_var(var, mode);
- var->width = cfg->lcd_size_cfg.width;
- var->height = cfg->lcd_size_cfg.height;
- /* Default Y virtual resolution is 2x panel size */
- var->yres_virtual = var->yres * 2;
- var->activate = FB_ACTIVATE_NOW;
-
- error = sh_mobile_lcdc_set_bpp(var, cfg->bpp, cfg->nonstd);
+ error = sh_mobile_lcdc_channel_init(ch, &pdev->dev);
if (error)
- break;
-
- buf = dma_alloc_coherent(&pdev->dev, info->fix.smem_len,
- &ch->dma_handle, GFP_KERNEL);
- if (!buf) {
- dev_err(&pdev->dev, "unable to allocate buffer\n");
- error = -ENOMEM;
- break;
- }
-
- info->pseudo_palette = &ch->pseudo_palette;
- info->flags = FBINFO_FLAG_DEFAULT;
-
- error = fb_alloc_cmap(&info->cmap, PALETTE_NR, 0);
- if (error < 0) {
- dev_err(&pdev->dev, "unable to allocate cmap\n");
- dma_free_coherent(&pdev->dev, info->fix.smem_len,
- buf, ch->dma_handle);
- break;
- }
-
- info->fix.smem_start = ch->dma_handle;
- 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;
+ goto err1;
}
- if (error)
- goto err1;
-
error = sh_mobile_lcdc_start(priv);
if (error) {
dev_err(&pdev->dev, "unable to start hardware\n");
goto err1;
}
- for (i = 0; i < j; i++) {
+ for (i = 0; i < num_channels; i++) {
struct sh_mobile_lcdc_chan *ch = priv->ch + i;
-
- info = ch->info;
+ struct fb_info *info = ch->info;
if (info->fbdefio) {
ch->sglist = vmalloc(sizeof(struct scatterlist) *
return error;
}
-static int sh_mobile_lcdc_remove(struct platform_device *pdev)
-{
- struct sh_mobile_lcdc_priv *priv = platform_get_drvdata(pdev);
- struct fb_info *info;
- int i;
-
- fb_unregister_client(&priv->notifier);
-
- for (i = 0; i < ARRAY_SIZE(priv->ch); i++)
- if (priv->ch[i].info && priv->ch[i].info->dev)
- unregister_framebuffer(priv->ch[i].info);
-
- sh_mobile_lcdc_stop(priv);
-
- for (i = 0; i < ARRAY_SIZE(priv->ch); i++) {
- info = priv->ch[i].info;
-
- if (!info || !info->device)
- continue;
-
- if (priv->ch[i].sglist)
- vfree(priv->ch[i].sglist);
-
- if (info->screen_base)
- dma_free_coherent(&pdev->dev, info->fix.smem_len,
- info->screen_base,
- priv->ch[i].dma_handle);
- fb_dealloc_cmap(&info->cmap);
- framebuffer_release(info);
- }
-
- for (i = 0; i < ARRAY_SIZE(priv->ch); i++) {
- if (priv->ch[i].bl)
- sh_mobile_lcdc_bl_remove(priv->ch[i].bl);
- }
-
- if (priv->dot_clk)
- clk_put(priv->dot_clk);
-
- if (priv->dev)
- pm_runtime_disable(priv->dev);
-
- if (priv->base)
- iounmap(priv->base);
-
- if (priv->irq)
- free_irq(priv->irq, priv);
- kfree(priv);
- return 0;
-}
-
static struct platform_driver sh_mobile_lcdc_driver = {
.driver = {
.name = "sh_mobile_lcdc_fb",