/* * fin fvco fout fclkout * in --> [1/M] --> |PD| -> [LPF] -> [VCO] -> [1/P] -+-> [1/FDPLL] -> out * +-> | | | * | | * +---------------- [1/N] <------------+ * * fclkout = fvco / P / FDPLL -- (1) * * fin/M = fvco/P/N * * fvco = fin * P * N / M -- (2) * * (1) + (2) indicates * * fclkout = fin * N / M / FDPLL * * NOTES * N : (n + 1) * M : (m + 1) * FDPLL : (fdpll + 1) * P : 2 * 2kHz < fvco < 4096MHz * * To minimize the jitter, * N : as large as possible * M : as small as possible
*/ for (m = 0; m < 4; m++) { for (n = 119; n > 38; n--) { /* * This code only runs on 64-bit architectures, the * unsigned long type can thus be used for 64-bit * computation. It will still compile without any * warning on 32-bit architectures. * * To optimize calculations, use fout instead of fvco * to verify the VCO frequency constraint.
*/ unsignedlong fout = input * (n + 1) / (m + 1);
/* * If the target rate has already been achieved perfectly we can't do * better.
*/ if (params->diff == 0) return;
/* * Compute the input clock rate and internal divisor values to obtain * the clock rate closest to the target frequency.
*/
rate = clk_round_rate(clk, target);
div = clamp(DIV_ROUND_CLOSEST(rate, target), 1UL, 64UL) - 1;
diff = abs(rate / (div + 1) - target);
/* * Store the parameters if the resulting frequency is better than any * previously calculated value.
*/ if (diff < params->diff) {
params->clk = clk;
params->rate = rate;
params->diff = diff;
params->escr = escr | div;
}
}
/* * DU channels that have a display PLL can't use the internal * system clock, and have no internal clock divider.
*/
extclk = clk_get_rate(rcrtc->extclock);
rcar_du_dpll_divider(rcrtc, &dpll, extclk, target);
escr = ESCR_DCLKSEL_DCLKIN | div;
} elseif (rcdu->info->lvds_clk_mask & BIT(rcrtc->index) ||
rcdu->info->dsi_clk_mask & BIT(rcrtc->index)) { /* * Use the external LVDS or DSI PLL output as the dot clock when * outputting to the LVDS or DSI encoder on an SoC that supports * this clock routing option. We use the clock directly in that * case, without any additional divider.
*/
escr = ESCR_DCLKSEL_DCLKIN;
} else { struct du_clk_params params = { .diff = (unsignedlong)-1 };
rcar_du_escr_divider(rcrtc->clock, mode_clock,
ESCR_DCLKSEL_CLKS, ¶ms); if (rcrtc->extclock)
rcar_du_escr_divider(rcrtc->extclock, mode_clock,
ESCR_DCLKSEL_DCLKIN, ¶ms);
/* * The ESCR register only exists in DU channels that can output to an * LVDS or DPAT, and the OTAR register in DU channels that can output * to a DPAD.
*/ if ((rcdu->info->routes[RCAR_DU_OUTPUT_DPAD0].possible_crtcs |
rcdu->info->routes[RCAR_DU_OUTPUT_DPAD1].possible_crtcs |
rcdu->info->routes[RCAR_DU_OUTPUT_LVDS0].possible_crtcs |
rcdu->info->routes[RCAR_DU_OUTPUT_LVDS1].possible_crtcs) &
BIT(rcrtc->index)) {
dev_dbg(rcrtc->dev->dev, "%s: ESCR 0x%08x\n", __func__, escr);
/* * When the CMM is enabled, an additional offset of 25 pixels must be * subtracted from the HDS (horizontal display start) and HDE * (horizontal display end) registers.
*/
hdse_offset = 19; if (rcrtc->group->cmms_mask & BIT(rcrtc->index % 2))
hdse_offset += 25;
/* If VSP+DU integration is enabled the plane assignment is fixed. */ if (rcar_du_has(rcdu, RCAR_DU_FEATURE_VSP1_SOURCE)) { if (rcdu->info->gen < 3) {
dspr = (rcrtc->index % 2) + 1;
hwplanes = 1 << (rcrtc->index % 2);
} else {
dspr = (rcrtc->index % 2) ? 3 : 1;
hwplanes = 1 << ((rcrtc->index % 2) ? 2 : 0);
}
}
/* * Update the planes to display timing and dot clock generator * associations. * * Updating the DPTSR register requires restarting the CRTC group, * resulting in visible flicker. To mitigate the issue only update the * association if needed by enabled planes. Planes being disabled will * keep their current association.
*/
mutex_lock(&rcrtc->group->lock);
if (drm_lut)
cmm_config.lut.table = (struct drm_color_lut *)drm_lut->data;
rcar_cmm_setup(rcrtc->cmm, &cmm_config);
}
/* ----------------------------------------------------------------------------- * Start/Stop and Suspend/Resume
*/
staticvoid rcar_du_crtc_setup(struct rcar_du_crtc *rcrtc)
{ /* Set display off and background to black */
rcar_du_crtc_write(rcrtc, DOOR, DOOR_RGB(0, 0, 0));
rcar_du_crtc_write(rcrtc, BPOR, BPOR_RGB(0, 0, 0));
/* Configure display timings and output routing */
rcar_du_crtc_set_display_timing(rcrtc);
rcar_du_group_set_routing(rcrtc->group);
/* Start with all planes disabled. */
rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? DS2PR : DS1PR, 0);
/* Enable the VSP compositor. */ if (rcar_du_has(rcrtc->dev, RCAR_DU_FEATURE_VSP1_SOURCE))
rcar_du_vsp_enable(rcrtc);
/* Turn vertical blanking interrupt reporting on. */
drm_crtc_vblank_on(&rcrtc->crtc);
}
staticint rcar_du_crtc_get(struct rcar_du_crtc *rcrtc)
{ int ret;
/* * Guard against double-get, as the function is called from both the * .atomic_enable() and .atomic_begin() handlers.
*/ if (rcrtc->initialized) return 0;
ret = clk_prepare_enable(rcrtc->clock); if (ret < 0) return ret;
ret = clk_prepare_enable(rcrtc->extclock); if (ret < 0) goto error_clock;
ret = rcar_du_group_get(rcrtc->group); if (ret < 0) goto error_group;
/* Make sure vblank interrupts are enabled. */
drm_crtc_vblank_get(crtc);
/* * Disable planes and calculate how many vertical blanking interrupts we * have to wait for. If a vertical blanking interrupt has been triggered * but not processed yet, we don't know whether it occurred before or * after the planes got disabled. We thus have to wait for two vblank * interrupts in that case.
*/
spin_lock_irq(&rcrtc->vblank_lock);
rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? DS2PR : DS1PR, 0);
status = rcar_du_crtc_read(rcrtc, DSSR);
rcrtc->vblank_count = status & DSSR_VBK ? 2 : 1;
spin_unlock_irq(&rcrtc->vblank_lock);
if (!wait_event_timeout(rcrtc->vblank_wait, rcrtc->vblank_count == 0,
msecs_to_jiffies(100)))
dev_warn(rcdu->dev, "vertical blanking timeout\n");
/* * Disable all planes and wait for the change to take effect. This is * required as the plane enable registers are updated on vblank, and no * vblank will occur once the CRTC is stopped. Disabling planes when * starting the CRTC thus wouldn't be enough as it would start scanning * out immediately from old frame buffers until the next vblank. * * This increases the CRTC stop delay, especially when multiple CRTCs * are stopped in one operation as we now wait for one vblank per CRTC. * Whether this can be improved needs to be researched.
*/
rcar_du_crtc_disable_planes(rcrtc);
/* * Disable vertical blanking interrupt reporting. We first need to wait * for page flip completion before stopping the CRTC as userspace * expects page flips to eventually complete.
*/
rcar_du_crtc_wait_page_flip(rcrtc);
drm_crtc_vblank_off(crtc);
/* Disable the VSP compositor. */ if (rcar_du_has(rcrtc->dev, RCAR_DU_FEATURE_VSP1_SOURCE))
rcar_du_vsp_disable(rcrtc);
if (rcrtc->cmm)
rcar_cmm_disable(rcrtc->cmm);
/* * Select switch sync mode. This stops display operation and configures * the HSYNC and VSYNC signals as inputs. * * TODO: Find another way to stop the display for DUs that don't support * TVM sync.
*/ if (rcar_du_has(rcrtc->dev, RCAR_DU_FEATURE_TVM_SYNC))
rcar_du_crtc_dsysr_clr_set(rcrtc, DSYSR_TVM_MASK,
DSYSR_TVM_SWITCH);
if (rcrtc->cmm)
rcar_cmm_enable(rcrtc->cmm);
rcar_du_crtc_get(rcrtc);
/* * On D3/E3 the dot clock is provided by the LVDS encoder attached to * the DU channel. We need to enable its clock output explicitly before * starting the CRTC, as the bridge hasn't been enabled by the atomic * helpers yet.
*/ if (rcdu->info->lvds_clk_mask & BIT(rcrtc->index)) { bool dot_clk_only = rstate->outputs == BIT(RCAR_DU_OUTPUT_DPAD0); struct drm_bridge *bridge = rcdu->lvds[rcrtc->index]; conststruct drm_display_mode *mode =
&crtc->state->adjusted_mode;
/* * Similarly to LVDS, on V3U the dot clock is provided by the DSI * encoder, and we need to enable the DSI clocks before enabling the CRTC.
*/ if ((rcdu->info->dsi_clk_mask & BIT(rcrtc->index)) &&
(rstate->outputs &
(BIT(RCAR_DU_OUTPUT_DSI0) | BIT(RCAR_DU_OUTPUT_DSI1)))) { struct drm_bridge *bridge = rcdu->dsi[rcrtc->index];
rcar_mipi_dsi_pclk_enable(bridge, state);
}
rcar_du_crtc_start(rcrtc);
/* * TODO: The chip manual indicates that CMM tables should be written * after the DU channel has been activated. Investigate the impact * of this restriction on the first displayed frame.
*/
rcar_du_cmm_setup(crtc);
}
/* * Disable the LVDS clock output, see * rcar_du_crtc_atomic_enable(). When the LVDS output is used, * this also disables the LVDS encoder.
*/
rcar_lvds_pclk_disable(bridge, dot_clk_only);
}
/* * If a mode set is in progress we can be called with the CRTC disabled. * We thus need to first get and setup the CRTC in order to configure * planes. We must *not* put the CRTC in .atomic_flush(), as it must be * kept awake until the .atomic_enable() call that will follow. The get * operation in .atomic_enable() will in that case be a no-op, and the * CRTC will be put later in .atomic_disable(). * * If a mode set is not in progress the CRTC is enabled, and the * following get call will be a no-op. There is thus no need to balance * it in .atomic_flush() either.
*/
rcar_du_crtc_get(rcrtc);
/* If the active state changed, we let .atomic_enable handle CMM. */ if (crtc->state->color_mgmt_changed && !crtc->state->active_changed)
rcar_du_cmm_setup(crtc);
if (rcar_du_has(rcrtc->dev, RCAR_DU_FEATURE_VSP1_SOURCE))
rcar_du_vsp_atomic_begin(rcrtc);
}
if (interlaced && !rcar_du_has(rcdu, RCAR_DU_FEATURE_INTERLACED)) return MODE_NO_INTERLACE;
/* * The hardware requires a minimum combined horizontal sync and back * porch of 20 pixels (when CMM isn't used) or 45 pixels (when CMM is * used), and a minimum vertical back porch of 3 lines.
*/
min_sync_porch = 20; if (rcrtc->group->cmms_mask & BIT(rcrtc->index % 2))
min_sync_porch += 25;
if (mode->htotal - mode->hsync_start < min_sync_porch) return MODE_HBLANK_NARROW;
/* * Parse the source name. Supported values are "plane%u" to compute the * CRC on an input plane (%u is the plane ID), and "auto" to compute the * CRC on the composer (VSP) output.
*/
status = rcar_du_crtc_read(rcrtc, DSSR);
rcar_du_crtc_write(rcrtc, DSRCR, status & DSRCR_MASK);
if (status & DSSR_VBK) { /* * Wake up the vblank wait if the counter reaches 0. This must * be protected by the vblank_lock to avoid races in * rcar_du_crtc_disable_planes().
*/ if (rcrtc->vblank_count) { if (--rcrtc->vblank_count == 0)
wake_up(&rcrtc->vblank_wait);
}
}
spin_unlock(&rcrtc->vblank_lock);
if (status & DSSR_VBK) { if (rcdu->info->gen < 3) {
drm_crtc_handle_vblank(&rcrtc->crtc);
rcar_du_crtc_finish_page_flip(rcrtc);
}
/* Get the CRTC clock and the optional external clock. */ if (rcar_du_has(rcdu, RCAR_DU_FEATURE_CRTC_CLOCK)) {
sprintf(clk_name, "du.%u", hwindex);
name = clk_name;
} else {
name = NULL;
}
rcrtc->clock = devm_clk_get(rcdu->dev, name); if (IS_ERR(rcrtc->clock)) {
dev_err(rcdu->dev, "no clock for DU channel %u\n", hwindex); return PTR_ERR(rcrtc->clock);
}
sprintf(clk_name, "dclkin.%u", hwindex);
clk = devm_clk_get(rcdu->dev, clk_name); if (!IS_ERR(clk)) {
rcrtc->extclock = clk;
} elseif (PTR_ERR(clk) == -EPROBE_DEFER) { return -EPROBE_DEFER;
} elseif (rcdu->info->dpll_mask & BIT(hwindex)) { /* * DU channels that have a display PLL can't use the internal * system clock and thus require an external clock.
*/
ret = PTR_ERR(clk);
dev_err(rcdu->dev, "can't get dclkin.%u: %d\n", hwindex, ret); 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.