/* * FIMD stands for Fully Interactive Mobile Display and * as a display controller, it transfers contents drawn on memory * to a LCD Panel through Display Interfaces such as RGB or * CPU Interface.
*/
#define MIN_FB_WIDTH_FOR_16WORD_BURST 128
/* position control register for hardware window 0, 2 ~ 4.*/ #define VIDOSD_A(win) (VIDOSD_BASE + 0x00 + (win) * 16) #define VIDOSD_B(win) (VIDOSD_BASE + 0x04 + (win) * 16) /* * size control register for hardware windows 0 and alpha control register * for hardware windows 1 ~ 4
*/ #define VIDOSD_C(win) (VIDOSD_BASE + 0x08 + (win) * 16) /* size control register for hardware windows 1 ~ 2. */ #define VIDOSD_D(win) (VIDOSD_BASE + 0x0C + (win) * 16)
if (!test_and_set_bit(0, &ctx->irq_flags)) {
val = readl(ctx->regs + VIDINTCON0);
val |= VIDINTCON0_INT_ENABLE;
if (ctx->i80_if) {
val |= VIDINTCON0_INT_I80IFDONE;
val |= VIDINTCON0_INT_SYSMAINCON;
val &= ~VIDINTCON0_INT_SYSSUBCON;
} else {
val |= VIDINTCON0_INT_FRAME;
val &= ~VIDINTCON0_FRAMESEL0_MASK;
val |= VIDINTCON0_FRAMESEL0_FRONTPORCH;
val &= ~VIDINTCON0_FRAMESEL1_MASK;
val |= VIDINTCON0_FRAMESEL1_NONE;
}
if (test_and_clear_bit(0, &ctx->irq_flags)) {
val = readl(ctx->regs + VIDINTCON0);
val &= ~VIDINTCON0_INT_ENABLE;
if (ctx->i80_if) {
val &= ~VIDINTCON0_INT_I80IFDONE;
val &= ~VIDINTCON0_INT_SYSMAINCON;
val &= ~VIDINTCON0_INT_SYSSUBCON;
} else
val &= ~VIDINTCON0_INT_FRAME;
/* * wait for FIMD to signal VSYNC interrupt or return after * timeout which is set to 50ms (refresh rate of 20).
*/ if (!wait_event_timeout(ctx->wait_vsync_queue,
!atomic_read(&ctx->wait_vsync_event),
HZ/20))
DRM_DEV_DEBUG_KMS(ctx->dev, "vblank wait timed out.\n");
}
/* Hardware is in unknown state, so ensure it gets enabled properly */
ret = pm_runtime_resume_and_get(ctx->dev); if (ret < 0) {
dev_err(ctx->dev, "failed to enable FIMD device.\n"); return ret;
}
if (mode->clock == 0) {
DRM_DEV_ERROR(ctx->dev, "Mode has zero clock value.\n"); return -EINVAL;
}
ideal_clk = mode->clock * 1000;
if (ctx->i80_if) { /* * The frame done interrupt should be occurred prior to the * next TE signal.
*/
ideal_clk *= 2;
}
lcd_rate = clk_get_rate(ctx->lcd_clk); if (2 * lcd_rate < ideal_clk) {
DRM_DEV_ERROR(ctx->dev, "sclk_fimd clock too low(%lu) for requested pixel clock(%lu)\n",
lcd_rate, ideal_clk); return -EINVAL;
}
/* Find the clock divider value that gets us closest to ideal_clk */
clkdiv = DIV_ROUND_CLOSEST(lcd_rate, ideal_clk); if (clkdiv >= 0x200) {
DRM_DEV_ERROR(ctx->dev, "requested pixel clock(%lu) too low\n",
ideal_clk); return -EINVAL;
}
if (trg_type == I80_HW_TRG) { if (ctx->driver_data->has_hw_trigger)
val |= HWTRGEN_ENABLE | HWTRGMASK_ENABLE; if (ctx->driver_data->has_trigger_per_te)
val |= HWTRIGEN_PER_ENABLE;
} else {
val |= TRGMODE_ENABLE;
}
if (driver_data->has_vidoutcon)
writel(ctx->vidout_con, timing_base + VIDOUT_CON);
/* set bypass selection */ if (ctx->sysreg && regmap_update_bits(ctx->sysreg,
driver_data->lcdblk_offset,
0x1 << driver_data->lcdblk_bypass_shift,
0x1 << driver_data->lcdblk_bypass_shift)) {
DRM_DEV_ERROR(ctx->dev, "Failed to update sysreg for bypass setting.\n"); return;
}
/* TODO: When MIC is enabled for display path, the lcdblk_mic_bypass * bit should be cleared.
*/ if (driver_data->has_mic_bypass && ctx->sysreg &&
regmap_update_bits(ctx->sysreg,
driver_data->lcdblk_offset,
0x1 << driver_data->lcdblk_mic_bypass_shift,
0x1 << driver_data->lcdblk_mic_bypass_shift)) {
DRM_DEV_ERROR(ctx->dev, "Failed to update sysreg for bypass mic.\n"); return;
}
switch (pixel_alpha) { case DRM_MODE_BLEND_PIXEL_NONE: case DRM_MODE_BLEND_COVERAGE:
val |= BLENDEQ_A_FUNC_F(BLENDEQ_ALPHA_A);
val |= BLENDEQ_B_FUNC_F(BLENDEQ_ONE_MINUS_ALPHA_A); break; case DRM_MODE_BLEND_PREMULTI: default: if (alpha != DRM_BLEND_ALPHA_OPAQUE) {
val |= BLENDEQ_A_FUNC_F(BLENDEQ_ALPHA0);
val |= BLENDEQ_B_FUNC_F(BLENDEQ_ONE_MINUS_ALPHA_A);
} else {
val |= BLENDEQ_A_FUNC_F(BLENDEQ_ONE);
val |= BLENDEQ_B_FUNC_F(BLENDEQ_ONE_MINUS_ALPHA_A);
} break;
}
fimd_set_bits(ctx, BLENDEQx(win), mask, val);
}
switch (pixel_alpha) { case DRM_MODE_BLEND_PIXEL_NONE: break; case DRM_MODE_BLEND_COVERAGE: case DRM_MODE_BLEND_PREMULTI: default:
val |= WINCON1_ALPHA_SEL;
val |= WINCON1_BLD_PIX;
val |= WINCON1_ALPHA_MUL; break;
}
fimd_set_bits(ctx, WINCON(win), WINCONx_BLEND_MODE_MASK, val);
if (fb->format->has_alpha)
pixel_alpha = state->base.pixel_blend_mode; else
pixel_alpha = DRM_MODE_BLEND_PIXEL_NONE;
/* * In case of s3c64xx, window 0 doesn't support alpha channel. * So the request format is ARGB8888 then change it to XRGB8888.
*/ if (ctx->driver_data->has_limited_fmt && !win) { if (pixel_format == DRM_FORMAT_ARGB8888)
pixel_format = DRM_FORMAT_XRGB8888;
}
switch (pixel_format) { case DRM_FORMAT_C8:
val |= WINCON0_BPPMODE_8BPP_PALETTE;
val |= WINCONx_BURSTLEN_8WORD;
val |= WINCONx_BYTSWP; break; case DRM_FORMAT_XRGB1555: case DRM_FORMAT_XBGR1555:
val |= WINCON0_BPPMODE_16BPP_1555;
val |= WINCONx_HAWSWP;
val |= WINCONx_BURSTLEN_16WORD; break; case DRM_FORMAT_RGB565: case DRM_FORMAT_BGR565:
val |= WINCON0_BPPMODE_16BPP_565;
val |= WINCONx_HAWSWP;
val |= WINCONx_BURSTLEN_16WORD; break; case DRM_FORMAT_XRGB8888: case DRM_FORMAT_XBGR8888:
val |= WINCON0_BPPMODE_24BPP_888;
val |= WINCONx_WSWP;
val |= WINCONx_BURSTLEN_16WORD; break; case DRM_FORMAT_ARGB8888: case DRM_FORMAT_ABGR8888: default:
val |= WINCON1_BPPMODE_25BPP_A1888;
val |= WINCONx_WSWP;
val |= WINCONx_BURSTLEN_16WORD; break;
}
switch (pixel_format) { case DRM_FORMAT_XBGR1555: case DRM_FORMAT_XBGR8888: case DRM_FORMAT_ABGR8888: case DRM_FORMAT_BGR565:
writel(WIN_RGB_ORDER_REVERSE, ctx->regs + WIN_RGB_ORDER(win)); break; default:
writel(WIN_RGB_ORDER_FORWARD, ctx->regs + WIN_RGB_ORDER(win)); break;
}
/* * Setting dma-burst to 16Word causes permanent tearing for very small * buffers, e.g. cursor buffer. Burst Mode switching which based on * plane size is not recommended as plane size varies a lot towards the * end of the screen and rapid movement causes unstable DMA, but it is * still better to change dma-burst than displaying garbage.
*/
if (width < MIN_FB_WIDTH_FOR_16WORD_BURST) {
val &= ~WINCONx_BURSTLEN_MASK;
val |= WINCONx_BURSTLEN_4WORD;
}
fimd_set_bits(ctx, WINCON(win), ~WINCONx_BLEND_MODE_MASK, val);
/** * fimd_shadow_protect_win() - disable updating values from shadow registers at vsync * * @ctx: local driver data * @win: window to protect registers for * @protect: 1 to protect (disable updates)
*/ staticvoid fimd_shadow_protect_win(struct fimd_context *ctx, unsignedint win, bool protect)
{
u32 reg, bits, val;
/* * SHADOWCON/PRTCON register is used for enabling timing. * * for example, once only width value of a register is set, * if the dma is started then fimd hardware could malfunction so * with protect window setting, the register fields with prefix '_F' * wouldn't be updated at vsync also but updated once unprotect window * is set.
*/
/* * We need to make sure that all windows are disabled before we * suspend that connector. Otherwise we might try to scan from * a destroyed buffer later.
*/ for (i = 0; i < WINDOWS_NR; i++)
fimd_disable_plane(crtc, &ctx->planes[i]);
/* * Skips triggering if in triggering state, because multiple triggering * requests can cause panel reset.
*/ if (atomic_read(&ctx->triggering)) return;
/* * Exits triggering mode if vblank is not enabled yet, because when the * VIDINTCON0 register is not set, it can not exit from triggering mode.
*/ if (!test_bit(0, &ctx->irq_flags))
atomic_set(&ctx->triggering, 0);
}
/* Checks the crtc is detached already from encoder */ if (!ctx->drm_dev) return;
if (trg_type == I80_HW_TRG) goto out;
/* * If there is a page flip request, triggers and handles the page flip * event so that current fb can be updated into panel GRAM.
*/ if (atomic_add_unless(&ctx->win_updated, -1, 0))
fimd_trigger(ctx->dev);
out: /* Wakes up vsync event queue */ if (atomic_read(&ctx->wait_vsync_event)) {
atomic_set(&ctx->wait_vsync_event, 0);
wake_up(&ctx->wait_vsync_queue);
}
if (test_bit(0, &ctx->irq_flags))
drm_crtc_handle_vblank(&ctx->crtc->base);
}
/* check the crtc is detached already from encoder */ if (!ctx->drm_dev) goto out;
if (!ctx->i80_if)
drm_crtc_handle_vblank(&ctx->crtc->base);
if (ctx->i80_if) { /* Exits triggering mode */
atomic_set(&ctx->triggering, 0);
} else { /* set wait vsync event to zero and wake up queue. */ if (atomic_read(&ctx->wait_vsync_event)) {
atomic_set(&ctx->wait_vsync_event, 0);
wake_up(&ctx->wait_vsync_queue);
}
}
if (of_property_read_bool(dev->of_node, "samsung,invert-vden"))
ctx->vidcon1 |= VIDCON1_INV_VDEN; if (of_property_read_bool(dev->of_node, "samsung,invert-vclk"))
ctx->vidcon1 |= VIDCON1_INV_VCLK;
i80_if_timings = of_get_child_by_name(dev->of_node, "i80-if-timings"); if (i80_if_timings) {
u32 val;
ctx->i80_if = true;
if (ctx->driver_data->has_vidoutcon)
ctx->vidout_con |= VIDOUT_CON_F_I80_LDI0; else
ctx->vidcon0 |= VIDCON0_VIDOUT_I80_LDI0; /* * The user manual describes that this "DSI_EN" bit is required * to enable I80 24-bit data interface.
*/
ctx->vidcon0 |= VIDCON0_DSI_EN;
if (of_property_read_u32(i80_if_timings, "cs-setup", &val))
val = 0;
ctx->i80ifcon = LCD_CS_SETUP(val); if (of_property_read_u32(i80_if_timings, "wr-setup", &val))
val = 0;
ctx->i80ifcon |= LCD_WR_SETUP(val); if (of_property_read_u32(i80_if_timings, "wr-active", &val))
val = 1;
ctx->i80ifcon |= LCD_WR_ACTIVE(val); if (of_property_read_u32(i80_if_timings, "wr-hold", &val))
val = 0;
ctx->i80ifcon |= LCD_WR_HOLD(val);
}
of_node_put(i80_if_timings);
ctx->sysreg = syscon_regmap_lookup_by_phandle(dev->of_node, "samsung,sysreg"); if (IS_ERR(ctx->sysreg)) {
dev_warn(dev, "failed to get system register.\n");
ctx->sysreg = NULL;
}
ctx->bus_clk = devm_clk_get(dev, "fimd"); if (IS_ERR(ctx->bus_clk)) {
dev_err(dev, "failed to get bus clock\n"); return PTR_ERR(ctx->bus_clk);
}
ctx->lcd_clk = devm_clk_get(dev, "sclk_fimd"); if (IS_ERR(ctx->lcd_clk)) {
dev_err(dev, "failed to get lcd clock\n"); return PTR_ERR(ctx->lcd_clk);
}
ctx->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(ctx->regs)) return PTR_ERR(ctx->regs);
ret = platform_get_irq_byname(pdev, ctx->i80_if ? "lcd_sys" : "vsync"); if (ret < 0) return ret;
ret = devm_request_irq(dev, ret, fimd_irq_handler, 0, "drm_fimd", ctx); if (ret) {
dev_err(dev, "irq request failed.\n"); return ret;
}
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.