/* * SuperH Mobile LCDC Framebuffer * * Copyright (c) 2008 Magnus Damm * * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive * for more details.
*/
staticbool banked(int reg_nr)
{ switch (reg_nr) { case LDMT1R: case LDMT2R: case LDMT3R: case LDDFR: case LDSM1R: case LDSA1R: case LDSA2R: case LDMLSR: case LDHCNR: case LDHSYNR: case LDVLNR: case LDVSYNR: returntrue;
} returnfalse;
}
/* enable clocks before accessing hardware */
sh_mobile_lcdc_clk_on(ch->lcdc);
/* * It's possible to get here without anything on the pagereflist via * sh_mobile_lcdc_deferred_io_touch() or via a userspace fsync() * invocation. In the former case, the acceleration routines are * stepped in to when using the framebuffer console causing the * workqueue to be scheduled without any dirty pages on the list. * * Despite this, a panel update is still needed given that the * acceleration routines have their own methods for writing in * that still need to be updated. * * The fsync() and empty pagereflist case could be optimized for, * but we don't bother, as any application exhibiting such * behaviour is fundamentally broken anyways.
*/ if (!list_empty(pagereflist)) { unsignedint nr_pages = sh_mobile_lcdc_sginit(info, pagereflist);
/* Acknowledge interrupts and disable further VSYNC End IRQs. */
ldintr = lcdc_read(priv, _LDINTR);
lcdc_write(priv, _LDINTR, (ldintr ^ LDINTR_STATUS_MASK) & ~LDINTR_VEE);
/* figure out if this interrupt is for main or sub lcd */
is_sub = (lcdc_read(priv, _LDSR) & LDSR_MSS) ? 1 : 0;
/* wake up channel and disable clocks */ for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
ch = &priv->ch[k];
if (!ch->enabled) continue;
/* Frame End */ if (ldintr & LDINTR_FS) { if (is_sub == lcdc_chan_is_sublcd(ch)) {
ch->frame_end = 1;
wake_up(&ch->frame_end_wait);
sh_mobile_lcdc_clk_off(priv);
}
}
/* VSYNC End */ if (ldintr & LDINTR_VES)
complete(&ch->vsync_completion);
}
return IRQ_HANDLED;
}
staticint sh_mobile_lcdc_wait_for_vsync(struct sh_mobile_lcdc_chan *ch)
{ unsignedlong ldintr; int ret;
/* Enable VSync End interrupt and be careful not to acknowledge any * pending interrupt.
*/
ldintr = lcdc_read(ch->lcdc, _LDINTR);
ldintr |= LDINTR_VEE | LDINTR_STATUS_MASK;
lcdc_write(ch->lcdc, _LDINTR, ldintr);
ret = wait_for_completion_interruptible_timeout(&ch->vsync_completion,
msecs_to_jiffies(100)); if (!ret) return -ETIMEDOUT;
return 0;
}
staticvoid sh_mobile_lcdc_start_stop(struct sh_mobile_lcdc_priv *priv, int start)
{ unsignedlong tmp = lcdc_read(priv, _LDCNT2R); int k;
/* start or stop the lcdc */ if (start)
lcdc_write(priv, _LDCNT2R, tmp | LDCNT2R_DO); else
lcdc_write(priv, _LDCNT2R, tmp & ~LDCNT2R_DO);
/* wait until power is applied/stopped on all channels */ for (k = 0; k < ARRAY_SIZE(priv->ch); k++) if (lcdc_read(priv, _LDCNT2R) & priv->ch[k].enabled) while (1) {
tmp = lcdc_read_chan(&priv->ch[k], LDPMR)
& LDPMR_LPS; if (start && tmp == LDPMR_LPS) break; if (!start && tmp == 0) break;
cpu_relax();
}
if (!start)
lcdc_write(priv, _LDDCKSTPR, 1); /* stop dotclock */
}
switch (ovl->mode) { case LCDC_OVERLAY_BLEND:
format = LDBBSIFR_EN | (ovl->alpha << LDBBSIFR_LAY_SHIFT); break;
case LCDC_OVERLAY_ROP3:
format = LDBBSIFR_EN | LDBBSIFR_BRSEL
| (ovl->rop3 << LDBBSIFR_ROP3_SHIFT); break;
}
switch (ovl->format->fourcc) { case V4L2_PIX_FMT_RGB565: case V4L2_PIX_FMT_NV21: case V4L2_PIX_FMT_NV61: case V4L2_PIX_FMT_NV42:
format |= LDBBSIFR_SWPL | LDBBSIFR_SWPW; break; case V4L2_PIX_FMT_BGR24: case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV16: case V4L2_PIX_FMT_NV24:
format |= LDBBSIFR_SWPL | LDBBSIFR_SWPW | LDBBSIFR_SWPB; break; case V4L2_PIX_FMT_BGR32: default:
format |= LDBBSIFR_SWPL; break;
}
switch (ovl->format->fourcc) { case V4L2_PIX_FMT_RGB565:
format |= LDBBSIFR_AL_1 | LDBBSIFR_RY | LDBBSIFR_RPKF_RGB16; break; case V4L2_PIX_FMT_BGR24:
format |= LDBBSIFR_AL_1 | LDBBSIFR_RY | LDBBSIFR_RPKF_RGB24; break; case V4L2_PIX_FMT_BGR32:
format |= LDBBSIFR_AL_PK | LDBBSIFR_RY | LDBBSIFR_RPKF_ARGB32; break; case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV21:
format |= LDBBSIFR_AL_1 | LDBBSIFR_CHRR_420; break; case V4L2_PIX_FMT_NV16: case V4L2_PIX_FMT_NV61:
format |= LDBBSIFR_AL_1 | LDBBSIFR_CHRR_422; break; case V4L2_PIX_FMT_NV24: case V4L2_PIX_FMT_NV42:
format |= LDBBSIFR_AL_1 | LDBBSIFR_CHRR_444; break;
}
/* * __sh_mobile_lcdc_start - Configure and start the LCDC * @priv: LCDC device * * Configure all enabled channels and start the LCDC device. All external * devices (clocks, MERAM, panels, ...) are not touched by this function.
*/ staticvoid __sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
{ struct sh_mobile_lcdc_chan *ch; unsignedlong tmp; int k, m;
/* Enable LCDC channels. Read data from external memory, avoid using the * BEU for now.
*/
lcdc_write(priv, _LDCNT2R, priv->ch[0].enabled | priv->ch[1].enabled);
/* Stop the LCDC first and disable all interrupts. */
sh_mobile_lcdc_start_stop(priv, 0);
lcdc_write(priv, _LDINTR, 0);
/* Configure power supply, dot clocks and start them. */
tmp = priv->lddckr; for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
ch = &priv->ch[k]; if (!ch->enabled) continue;
/* Power supply */
lcdc_write_chan(ch, LDPMR, 0);
m = ch->cfg->clock_divider; if (!m) continue;
/* FIXME: sh7724 can only use 42, 48, 54 and 60 for the divider * denominator.
*/
lcdc_write_chan(ch, LDDCKPAT1R, 0);
lcdc_write_chan(ch, LDDCKPAT2R, (1 << (m/2)) - 1);
if (m == 1)
m = LDDCKR_MOSEL;
tmp |= m << (lcdc_chan_is_sublcd(ch) ? 8 : 0);
}
/* When using deferred I/O mode, configure the LCDC for one-shot * operation and enable the frame end interrupt. Otherwise use * continuous read mode.
*/ if (ch->ldmt1r_value & LDMT1R_IFM &&
ch->cfg->sys_bus_cfg.deferred_io_msec) {
lcdc_write_chan(ch, LDSM1R, LDSM1R_OS);
lcdc_write(priv, _LDINTR, LDINTR_FE);
} else {
lcdc_write_chan(ch, LDSM1R, 0);
}
}
/* Word and long word swap. */ switch (priv->ch[0].format->fourcc) { case V4L2_PIX_FMT_RGB565: case V4L2_PIX_FMT_NV21: case V4L2_PIX_FMT_NV61: case V4L2_PIX_FMT_NV42:
tmp = LDDDSR_LS | LDDDSR_WS; break; case V4L2_PIX_FMT_BGR24: case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV16: case V4L2_PIX_FMT_NV24:
tmp = LDDDSR_LS | LDDDSR_WS | LDDDSR_BS; break; case V4L2_PIX_FMT_BGR32: default:
tmp = LDDDSR_LS; break;
}
lcdc_write(priv, _LDDDSR, tmp);
for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { conststruct sh_mobile_lcdc_panel_cfg *panel;
ch = &priv->ch[k]; if (!ch->enabled) continue;
panel = &ch->cfg->panel_cfg; if (panel->setup_sys) {
ret = panel->setup_sys(ch, &sh_mobile_lcdc_sys_bus_ops); if (ret) return ret;
}
}
/* Compute frame buffer base address and pitch for each channel. */ for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
ch = &priv->ch[k]; if (!ch->enabled) continue;
for (k = 0; k < ARRAY_SIZE(priv->overlays); ++k) { struct sh_mobile_lcdc_overlay *ovl = &priv->overlays[k];
sh_mobile_lcdc_overlay_setup(ovl);
}
/* Start the LCDC. */
__sh_mobile_lcdc_start(priv);
/* Setup deferred I/O, tell the board code to enable the panels, and * turn backlight on.
*/ for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
ch = &priv->ch[k]; if (!ch->enabled) continue;
if (ch->bl) {
ch->bl->props.power = BACKLIGHT_POWER_ON;
backlight_update_status(ch->bl);
}
}
return 0;
}
staticvoid sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv)
{ struct sh_mobile_lcdc_chan *ch; int k;
/* clean up deferred io and ask board code to disable panel */ for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
ch = &priv->ch[k]; if (!ch->enabled) continue;
/* deferred io mode: * flush frame, and wait for frame end interrupt * clean up deferred io and enable clock
*/ if (ch->info && ch->info->fbdefio) {
ch->frame_end = 0;
schedule_delayed_work(&ch->info->deferred_work, 0);
wait_event(ch->frame_end_wait, ch->frame_end);
fb_deferred_io_cleanup(ch->info);
ch->info->fbdefio = NULL;
sh_mobile_lcdc_clk_on(priv);
}
if (ch->bl) {
ch->bl->props.power = BACKLIGHT_POWER_OFF;
backlight_update_status(ch->bl);
}
sh_mobile_lcdc_display_off(ch);
}
/* stop the lcdc */ if (priv->started) {
sh_mobile_lcdc_start_stop(priv, 0);
priv->started = 0;
}
/* stop clocks */ for (k = 0; k < ARRAY_SIZE(priv->ch); k++) if (priv->ch[k].enabled)
sh_mobile_lcdc_clk_off(priv);
}
/* 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 (sh_mobile_format_is_fourcc(var)) { conststruct sh_mobile_lcdc_format_info *format;
format = sh_mobile_format_info(var->grayscale); if (format == NULL) return -EINVAL;
var->bits_per_pixel = format->bpp;
/* If the Y offset hasn't changed, the C offset hasn't either. There's * nothing to do in that case.
*/ if (y_offset == ovl->pan_y_offset) return 0;
/* Set the source address for the next refresh */
base_addr_y = ovl->dma_handle + y_offset;
base_addr_c = ovl->dma_handle + ovl->xres_virtual * ovl->yres_virtual
+ c_offset;
/* Initialize fixed screen information. Restrict pan to 2 lines steps * for NV12 and NV21.
*/
info->fix = sh_mobile_lcdc_overlay_fix;
snprintf(info->fix.id, sizeof(info->fix.id), "SHMobile ovl %u", ovl->index);
info->fix.smem_start = ovl->dma_handle;
info->fix.smem_len = ovl->fb_size;
info->fix.line_length = ovl->pitch;
if (ovl->format->yuv)
info->fix.visual = FB_VISUAL_FOURCC; else
info->fix.visual = FB_VISUAL_TRUECOLOR;
switch (ovl->format->fourcc) { case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV21:
info->fix.ypanstep = 2;
fallthrough; case V4L2_PIX_FMT_NV16: case V4L2_PIX_FMT_NV61:
info->fix.xpanstep = 2;
}
/* Use the legacy API by default for RGB formats, and the FOURCC API * for YUV formats.
*/ if (!ovl->format->yuv)
var->bits_per_pixel = ovl->format->bpp; else
var->grayscale = ovl->format->fourcc;
/* If the Y offset hasn't changed, the C offset hasn't either. There's * nothing to do in that case.
*/ if (y_offset == ch->pan_y_offset) return 0;
/* Set the source address for the next refresh */
base_addr_y = ch->dma_handle + y_offset;
base_addr_c = ch->dma_handle + ch->xres_virtual * ch->yres_virtual
+ c_offset;
if (ch->use_count > 1 || (ch->use_count == 1 && !info->fbcon_par)) /* More framebuffer users are active */ return;
fb_var_to_videomode(&mode, &info->var);
if (fb_mode_is_equal(&ch->display.mode, &mode)) return;
/* Display has been re-plugged, framebuffer is free now, reconfigure */
var = info->var;
fb_videomode_to_var(&var, &ch->display.mode);
var.width = ch->display.width;
var.height = ch->display.height;
var.activate = FB_ACTIVATE_NOW;
if (fb_set_var(info, &var) < 0) /* Couldn't reconfigure, hopefully, can continue as before */ return;
fbcon_update_vcs(info, true);
}
/* * Locking: both .fb_release() and .fb_open() are called with info->lock held if * user == 1, or with console sem held, if user == 0.
*/ staticint sh_mobile_lcdc_release(struct fb_info *info, int user)
{ struct sh_mobile_lcdc_chan *ch = info->par;
/* 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_modes; ++i) { conststruct fb_videomode *mode = &ch->cfg->lcd_modes[i]; unsignedint dist;
/* We can only round up. */ if (var->xres > mode->xres || var->yres > mode->yres) continue;
/* If no available mode can be used, return an error. */ if (ch->cfg->num_modes != 0) { if (best_dist == (unsignedint)-1) return -EINVAL;
var->xres = best_xres;
var->yres = best_yres;
}
ret = __sh_mobile_lcdc_check_var(var, info); if (ret < 0) return ret;
/* only accept the forced_fourcc for dual channel configurations */ if (p->forced_fourcc &&
p->forced_fourcc != sh_mobile_format_fourcc(var)) return -EINVAL;
/* blank the screen? */ if (blank > FB_BLANK_UNBLANK && ch->blank_status == FB_BLANK_UNBLANK) { struct fb_fillrect rect = {
.width = ch->xres,
.height = ch->yres,
};
sh_mobile_lcdc_fillrect(info, &rect);
} /* turn clocks on? */ if (blank <= FB_BLANK_NORMAL && ch->blank_status > FB_BLANK_NORMAL) {
sh_mobile_lcdc_clk_on(p);
} /* turn clocks off? */ if (blank > FB_BLANK_NORMAL && ch->blank_status <= FB_BLANK_NORMAL) { /* make sure the screen is updated with the black fill before * switching the clocks off. one vsync is not enough since * blanking may occur in the middle of a refresh. deferred io * mode will reenable the clocks and update the screen in time,
* so it does not need this. */ if (!info->fbdefio) {
sh_mobile_lcdc_wait_for_vsync(ch);
sh_mobile_lcdc_wait_for_vsync(ch);
}
sh_mobile_lcdc_clk_off(p);
}
/* Allocate and initialize the frame buffer device. Create the modes * list and allocate the color map.
*/
info = framebuffer_alloc(0, priv->dev); if (!info) return -ENOMEM;
ret = fb_alloc_cmap(&info->cmap, PALETTE_NR, 0); if (ret < 0) {
dev_err(priv->dev, "unable to allocate cmap\n"); return ret;
}
/* Initialize fixed screen information. Restrict pan to 2 lines steps * for NV12 and NV21.
*/
info->fix = sh_mobile_lcdc_fix;
info->fix.smem_start = ch->dma_handle;
info->fix.smem_len = ch->fb_size;
info->fix.line_length = ch->pitch;
if (ch->format->yuv)
info->fix.visual = FB_VISUAL_FOURCC; else
info->fix.visual = FB_VISUAL_TRUECOLOR;
switch (ch->format->fourcc) { case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV21:
info->fix.ypanstep = 2;
fallthrough; case V4L2_PIX_FMT_NV16: case V4L2_PIX_FMT_NV61:
info->fix.xpanstep = 2;
}
/* Initialize variable screen information using the first mode as * default.
*/
var = &info->var;
fb_videomode_to_var(var, modes);
var->width = ch->display.width;
var->height = ch->display.height;
var->xres_virtual = ch->xres_virtual;
var->yres_virtual = ch->yres_virtual;
var->activate = FB_ACTIVATE_NOW;
/* Use the legacy API by default for RGB formats, and the FOURCC API * for YUV formats.
*/ if (!ch->format->yuv)
var->bits_per_pixel = ch->format->bpp; else
var->grayscale = ch->format->fourcc;
ret = sh_mobile_lcdc_check_var(var, info); if (ret) return ret;
for (i = 0; i < ARRAY_SIZE(priv->overlays); i++)
sh_mobile_lcdc_overlay_fb_unregister(&priv->overlays[i]); for (i = 0; i < ARRAY_SIZE(priv->ch); i++)
sh_mobile_lcdc_channel_fb_unregister(&priv->ch[i]);
sh_mobile_lcdc_stop(priv);
for (i = 0; i < ARRAY_SIZE(priv->overlays); i++) { struct sh_mobile_lcdc_overlay *ovl = &priv->overlays[i];
sh_mobile_lcdc_overlay_fb_cleanup(ovl);
if (ovl->fb_mem)
dma_free_coherent(&pdev->dev, ovl->fb_size,
ovl->fb_mem, ovl->dma_handle);
}
for (i = 0; i < ARRAY_SIZE(priv->ch); i++) { struct sh_mobile_lcdc_chan *ch = &priv->ch[i];
if (ch->tx_dev) {
ch->tx_dev->lcdc = NULL;
module_put(ch->cfg->tx_dev->dev.driver->owner);
}
sh_mobile_lcdc_channel_fb_cleanup(ch);
if (ch->fb_mem)
dma_free_coherent(&pdev->dev, ch->fb_size,
ch->fb_mem, ch->dma_handle);
}
for (i = 0; i < ARRAY_SIZE(priv->ch); i++) { struct sh_mobile_lcdc_chan *ch = &priv->ch[i];
if (ch->bl)
sh_mobile_lcdc_bl_remove(ch->bl);
mutex_destroy(&ch->open_lock);
}
if (priv->dot_clk) {
pm_runtime_disable(&pdev->dev);
clk_put(priv->dot_clk);
}
if (priv->base)
iounmap(priv->base);
if (priv->irq)
free_irq(priv->irq, priv);
kfree(priv);
}
staticint sh_mobile_lcdc_check_interface(struct sh_mobile_lcdc_chan *ch)
{ int interface_type = ch->cfg->interface_type;
switch (interface_type) { case RGB8: case RGB9: case RGB12A: case RGB12B: case RGB16: case RGB18: case RGB24: case SYS8A: case SYS8B: case SYS8C: case SYS8D: case SYS9: case SYS12: case SYS16A: case SYS16B: case SYS16C: case SYS18: case SYS24: break; default: return -EINVAL;
}
/* SUBLCD only supports SYS interface */ if (lcdc_chan_is_sublcd(ch)) { if (!(interface_type & LDMT1R_IFM)) return -EINVAL;
/* Validate the format. */
format = sh_mobile_format_info(cfg->fourcc); if (format == NULL) {
dev_err(dev, "Invalid FOURCC %08x.\n", cfg->fourcc); return -EINVAL;
}
/* Iterate through the modes to validate them and find the highest * resolution.
*/
max_mode = NULL;
max_size = 0;
for (i = 0, mode = cfg->lcd_modes; i < cfg->num_modes; i++, mode++) { unsignedint size = mode->yres * mode->xres;
/* NV12/NV21 buffers must have even number of lines */ if ((cfg->fourcc == V4L2_PIX_FMT_NV12 ||
cfg->fourcc == V4L2_PIX_FMT_NV21) && (mode->yres & 0x1)) {
dev_err(dev, "yres must be multiple of 2 for " "YCbCr420 mode.\n"); return -EINVAL;
}
/* Use the first mode as default. The default Y virtual resolution is * twice the panel size to allow for double-buffering.
*/
ch->format = format;
ch->xres = mode->xres;
ch->xres_virtual = mode->xres;
ch->yres = mode->yres;
ch->yres_virtual = mode->yres * 2;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.