#define REG21_SEL_TX_CK_INV BIT(7) #define REG21_PMS_S_MASK GENMASK(3, 0) /* * REG33 does not match the ref manual. According to Sandor Yu from NXP, * "There is a doc issue on the i.MX8MP latest RM" * REG33 is being used per guidance from Sandor
*/ #define REG33_MODE_SET_DONE BIT(7) #define REG33_FIX_DA BIT(1)
/* * The calculated_phy_pll_cfg only handles integer divider for PMS, * meaning the last four entries will be fixed, but the first three will * be calculated by the PMS calculator.
*/ staticstruct phy_config calculated_phy_pll_cfg = {
.pixclk = 0,
.pll_div_regs = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00 },
};
/* * Calculation for the frequency lock detector target code (fld_tg_code) * is based on reference manual register description of PHY_REG13 * (13.10.3.1.14.2): * 1st) Calculate int_pllclk which is determinded by FLD_CK_DIV * 2nd) Increase resolution to avoid rounding issues * 3th) Do the div (256 / Freq. of int_pllclk) * 24 * 4th) Reduce the resolution and always round up since the NXP * settings rounding up always too. TODO: Check if that is * correct.
*/
/* * Figure 13-78 of the reference manual states the PLL should be TMDS x 5 * while the TMDS_CLKO should be the PLL / 5. So to calculate the PLL, * take the pix clock x 5, then return the value of the PLL / 5.
*/
fout *= 5;
/* The ref manual states the values of 'P' range from 1 to 11 */ for (_p = 1; _p <= 11; ++_p) { for (_s = 1; _s <= 16; ++_s) {
u64 tmp;
u32 delta;
/* s must be one or even */ if (_s > 1 && (_s & 0x01) == 1)
_s++;
/* _s cannot be 14 per the TRM */ if (_s == 14) continue;
/* * The Ref manual doesn't explicitly state the range of M, * but it does show it as an 8-bit value, so reject * any value above 255.
*/
tmp = (u64)fout * (_p * _s);
do_div(tmp, 24 * MHZ); if (tmp > 255) continue;
_m = tmp;
/* * Rev 2 of the Ref Manual states the * VCO can range between 750MHz and * 3GHz. The VCO is assumed to be * Fvco = (M * f_ref) / P, * where f_ref is 24MHz.
*/
tmp = div64_ul((u64)_m * 24 * MHZ, _p); if (tmp < 750 * MHZ ||
tmp > 3000 * MHZ) continue;
/* Final frequency after post-divider */
do_div(tmp, _s);
/* If we have an exact match, stop looking for a better value */ if (!delta) goto done;
}
}
done: if (best_freq) {
*p = best_p;
*m = best_m;
*s = best_s;
}
return best_freq / 5;
}
staticint fsl_samsung_hdmi_phy_configure(struct fsl_samsung_hdmi_phy *phy, conststruct phy_config *cfg)
{ int i, ret;
u8 val;
/* common PHY registers */ for (i = 0; i < ARRAY_SIZE(common_phy_cfg); i++)
writeb(common_phy_cfg[i].val, phy->regs + common_phy_cfg[i].reg);
/* set individual PLL registers PHY_REG1 ... PHY_REG7 */ for (i = 0; i < PHY_PLL_DIV_REGS_NUM; i++)
writeb(cfg->pll_div_regs[i], phy->regs + PHY_REG(1) + i * 4);
/* High nibble of PHY_REG3 and low nibble of PHY_REG21 both contain 'S' */
writeb(REG21_SEL_TX_CK_INV | FIELD_PREP(REG21_PMS_S_MASK,
cfg->pll_div_regs[2] >> 4), phy->regs + PHY_REG(21));
ret = fsl_samsung_hdmi_phy_configure_pll_lock_det(phy, cfg); if (ret) {
dev_err(phy->dev, "pixclock too large\n"); return ret;
}
/* Helper function to lookup the available fractional-divider rate */ staticconststruct phy_config *fsl_samsung_hdmi_phy_lookup_rate(unsignedlong rate)
{ int i;
/* Search the lookup table */ for (i = ARRAY_SIZE(phy_pll_cfg) - 1; i >= 0; i--) if (phy_pll_cfg[i].pixclk <= rate) break;
/* If there is an exact match, or the array has been searched, return the value*/ if (phy_pll_cfg[i].pixclk == rate || i + 1 > ARRAY_SIZE(phy_pll_cfg) - 1) return &phy_pll_cfg[i];
/* See if the next entry is closer to nominal than this one */ return (abs((long) rate - (long) phy_pll_cfg[i].pixclk) <
abs((long) rate - (long) phy_pll_cfg[i+1].pixclk) ?
&phy_pll_cfg[i] : &phy_pll_cfg[i+1]);
}
staticvoid fsl_samsung_hdmi_calculate_phy(struct phy_config *cal_phy, unsignedlong rate,
u8 p, u16 m, u8 s)
{
cal_phy->pixclk = rate;
cal_phy->pll_div_regs[0] = FIELD_PREP(REG01_PMS_P_MASK, p);
cal_phy->pll_div_regs[1] = m;
cal_phy->pll_div_regs[2] = FIELD_PREP(REG03_PMS_S_MASK, s-1); /* pll_div_regs 3-6 are fixed and pre-defined already */
}
/* If the clock is out of range return error instead of searching */ if (rate > 297000000 || rate < 22250000) return NULL;
/* Search the fractional divider lookup table */
fract_div_phy = fsl_samsung_hdmi_phy_lookup_rate(rate); if (fract_div_phy->pixclk == rate) {
dev_dbg(phy->dev, "fractional divider match = %u\n", fract_div_phy->pixclk); return fract_div_phy;
}
/* Calculate the integer divider */
int_div_clk = fsl_samsung_hdmi_phy_find_pms(rate, &p, &m, &s);
fsl_samsung_hdmi_calculate_phy(&calculated_phy_pll_cfg, int_div_clk, p, m, s); if (int_div_clk == rate) {
dev_dbg(phy->dev, "integer divider match = %u\n", calculated_phy_pll_cfg.pixclk); return &calculated_phy_pll_cfg;
}
/* Calculate the absolute value of the differences and return whichever is closest */ if (abs((long)rate - (long)int_div_clk) <
abs((long)rate - (long)fract_div_phy->pixclk)) {
dev_dbg(phy->dev, "integer divider = %u\n", calculated_phy_pll_cfg.pixclk); return &calculated_phy_pll_cfg;
}
phy->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(phy->regs)) return PTR_ERR(phy->regs);
phy->apbclk = devm_clk_get_enabled(phy->dev, "apb"); if (IS_ERR(phy->apbclk)) return dev_err_probe(phy->dev, PTR_ERR(phy->apbclk), "failed to get apb clk\n");
phy->refclk = devm_clk_get(phy->dev, "ref"); if (IS_ERR(phy->refclk)) return dev_err_probe(phy->dev, PTR_ERR(phy->refclk), "failed to get ref clk\n");
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.