struct qusb2_phy_init_tbl { unsignedint offset; unsignedint val; /* * register part of layout ? * if yes, then offset gives index in the reg-layout
*/ int in_layout;
};
struct qusb2_phy_cfg { conststruct qusb2_phy_init_tbl *tbl; /* number of entries in the table */ unsignedint tbl_num; /* offset to PHY_CLK_SCHEME register in TCSR map */ unsignedint clk_scheme_offset;
/* array of registers with different offsets */ constunsignedint *regs; unsignedint mask_core_ready; unsignedint disable_ctrl; unsignedint autoresume_en;
/* true if PHY has PLL_TEST register to select clk_scheme */ bool has_pll_test;
/* true if TUNE1 register must be updated by fused value, else TUNE2 */ bool update_tune1_with_efuse;
/* true if PHY has PLL_CORE_INPUT_OVERRIDE register to reset PLL */ bool has_pll_override;
/* true if PHY default clk scheme is single-ended */ bool se_clk_scheme_default;
};
/* struct override_param - structure holding qusb2 v2 phy overriding param * set override true if the device tree property exists and read and assign * to value
*/ struct override_param { bool override;
u8 value;
};
/*struct override_params - structure holding qusb2 v2 phy overriding params * @imp_res_offset: rescode offset to be updated in IMP_CTRL1 register * @hstx_trim: HSTX_TRIM to be updated in TUNE1 register * @preemphasis: Amplitude Pre-Emphasis to be updated in TUNE1 register * @preemphasis_width: half/full-width Pre-Emphasis updated via TUNE1 * @bias_ctrl: bias ctrl to be updated in BIAS_CONTROL_2 register * @charge_ctrl: charge ctrl to be updated in CHG_CTRL2 register * @hsdisc_trim: disconnect threshold to be updated in TUNE2 register
*/ struct override_params { struct override_param imp_res_offset; struct override_param hstx_trim; struct override_param preemphasis; struct override_param preemphasis_width; struct override_param bias_ctrl; struct override_param charge_ctrl; struct override_param hsdisc_trim;
};
/** * struct qusb2_phy - structure holding qusb2 phy attributes * * @phy: generic phy * @base: iomapped memory space for qubs2 phy * * @cfg_ahb_clk: AHB2PHY interface clock * @ref_clk: phy reference clock * @iface_clk: phy interface clock * @phy_reset: phy reset control * @vregs: regulator supplies bulk data * * @tcsr: TCSR syscon register map * @cell: nvmem cell containing phy tuning value * * @overrides: pointer to structure for all overriding tuning params * * @cfg: phy config data * @has_se_clk_scheme: indicate if PHY has single-ended ref clock scheme * @phy_initialized: indicate if PHY has been initialized * @mode: current PHY mode
*/ struct qusb2_phy { struct phy *phy; void __iomem *base;
if (or->imp_res_offset.override)
qusb2_write_mask(qphy->base, QUSB2PHY_IMP_CTRL1,
or->imp_res_offset.value << IMP_RES_OFFSET_SHIFT,
IMP_RES_OFFSET_MASK);
if (or->bias_ctrl.override)
qusb2_write_mask(qphy->base, QUSB2PHY_PLL_BIAS_CONTROL_2,
or->bias_ctrl.value << BIAS_CTRL2_RES_OFFSET_SHIFT,
BIAS_CTRL2_RES_OFFSET_MASK);
if (or->charge_ctrl.override)
qusb2_write_mask(qphy->base, QUSB2PHY_CHG_CTRL2,
or->charge_ctrl.value << CHG_CTRL2_OFFSET_SHIFT,
CHG_CTRL2_OFFSET_MASK);
if (or->hstx_trim.override)
qusb2_write_mask(qphy->base, cfg->regs[QUSB2PHY_PORT_TUNE1],
or->hstx_trim.value << HSTX_TRIM_SHIFT,
HSTX_TRIM_MASK);
if (or->preemphasis.override)
qusb2_write_mask(qphy->base, cfg->regs[QUSB2PHY_PORT_TUNE1],
or->preemphasis.value << PREEMPHASIS_EN_SHIFT,
PREEMPHASIS_EN_MASK);
if (or->preemphasis_width.override) { if (or->preemphasis_width.value ==
QUSB2_V2_PREEMPHASIS_WIDTH_HALF_BIT)
qusb2_setbits(qphy->base,
cfg->regs[QUSB2PHY_PORT_TUNE1],
PREEMPH_WIDTH_HALF_BIT); else
qusb2_clrbits(qphy->base,
cfg->regs[QUSB2PHY_PORT_TUNE1],
PREEMPH_WIDTH_HALF_BIT);
}
if (or->hsdisc_trim.override)
qusb2_write_mask(qphy->base, cfg->regs[QUSB2PHY_PORT_TUNE2],
or->hsdisc_trim.value << HSDISC_TRIM_SHIFT,
HSDISC_TRIM_MASK);
}
/* * Fetches HS Tx tuning value from nvmem and sets the * QUSB2PHY_PORT_TUNE1/2 register. * For error case, skip setting the value and use the default value.
*/ staticvoid qusb2_phy_set_tune2_param(struct qusb2_phy *qphy)
{ struct device *dev = &qphy->phy->dev; conststruct qusb2_phy_cfg *cfg = qphy->cfg;
u8 *val, hstx_trim;
/* efuse register is optional */ if (!qphy->cell) return;
/* * Read efuse register having TUNE2/1 parameter's high nibble. * If efuse register shows value as 0x0 (indicating value is not * fused), or if we fail to find a valid efuse register setting, * then use default value for high nibble that we have already * set while configuring the phy.
*/
val = nvmem_cell_read(qphy->cell, NULL); if (IS_ERR(val)) {
dev_dbg(dev, "failed to read a valid hs-tx trim value\n"); return;
}
hstx_trim = val[0];
kfree(val); if (!hstx_trim) {
dev_dbg(dev, "failed to read a valid hs-tx trim value\n"); return;
}
/* Fused TUNE1/2 value is the higher nibble only */ if (cfg->update_tune1_with_efuse)
qusb2_write_mask(qphy->base, cfg->regs[QUSB2PHY_PORT_TUNE1],
hstx_trim << HSTX_TRIM_SHIFT, HSTX_TRIM_MASK); else
qusb2_write_mask(qphy->base, cfg->regs[QUSB2PHY_PORT_TUNE2],
hstx_trim << HSTX_TRIM_SHIFT, HSTX_TRIM_MASK);
}
if (!qphy->phy_initialized) {
dev_vdbg(dev, "PHY not initialized, bailing out\n"); return 0;
}
/* * Enable DP/DM interrupts to detect line state changes based on current * speed. In other words, enable the triggers _opposite_ of what the * current D+/D- levels are e.g. if currently D+ high, D- low * (HS 'J'/Suspend), configure the mask to trigger on D+ low OR D- high
*/
intr_mask = DPSE_INTR_EN | DMSE_INTR_EN; switch (qphy->mode) { case PHY_MODE_USB_HOST_HS: case PHY_MODE_USB_HOST_FS: case PHY_MODE_USB_DEVICE_HS: case PHY_MODE_USB_DEVICE_FS:
intr_mask |= DMSE_INTR_HIGH_SEL; break; case PHY_MODE_USB_HOST_LS: case PHY_MODE_USB_DEVICE_LS:
intr_mask |= DPSE_INTR_HIGH_SEL; break; default: /* No device connected, enable both DP/DM high interrupt */
intr_mask |= DMSE_INTR_HIGH_SEL;
intr_mask |= DPSE_INTR_HIGH_SEL; break;
}
/* hold core PLL into reset */ if (cfg->has_pll_override) {
qusb2_setbits(qphy->base,
cfg->regs[QUSB2PHY_PLL_CORE_INPUT_OVERRIDE],
CORE_PLL_EN_FROM_RESET | CORE_RESET |
CORE_RESET_MUX);
}
/* enable phy auto-resume only if device is connected on bus */ if (qphy->mode != PHY_MODE_INVALID) {
qusb2_setbits(qphy->base, cfg->regs[QUSB2PHY_PORT_TEST1],
cfg->autoresume_en); /* Autoresume bit has to be toggled in order to enable it */
qusb2_clrbits(qphy->base, cfg->regs[QUSB2PHY_PORT_TEST1],
cfg->autoresume_en);
}
if (!qphy->has_se_clk_scheme)
clk_disable_unprepare(qphy->ref_clk);
/* Override board specific PHY tuning values */
qusb2_phy_override_phy_params(qphy);
/* Set efuse value for tuning the PHY */
qusb2_phy_set_tune2_param(qphy);
/* Enable the PHY */
qusb2_clrbits(qphy->base, cfg->regs[QUSB2PHY_PORT_POWERDOWN],
POWER_DOWN);
/* Required to get phy pll lock successfully */
usleep_range(150, 160);
/* * Not all the SoCs have got a readable TCSR_PHY_CLK_SCHEME * register in the TCSR so, if there's none, use the default * value hardcoded in the configuration.
*/
qphy->has_se_clk_scheme = cfg->se_clk_scheme_default;
/* * read TCSR_PHY_CLK_SCHEME register to check if single-ended * clock scheme is selected. If yes, then disable differential * ref_clk and use single-ended clock, otherwise use differential * ref_clk only.
*/ if (qphy->tcsr) {
ret = regmap_read(qphy->tcsr, qphy->cfg->clk_scheme_offset,
&clk_scheme); if (ret) {
dev_err(&phy->dev, "failed to read clk scheme reg\n"); goto assert_phy_reset;
}
/* is it a differential clock scheme ? */ if (!(clk_scheme & PHY_CLK_SCHEME_SEL)) {
dev_vdbg(&phy->dev, "%s(): select differential clk\n",
__func__);
qphy->has_se_clk_scheme = false;
} else {
dev_vdbg(&phy->dev, "%s(): select single-ended clk\n",
__func__);
}
}
if (!qphy->has_se_clk_scheme) {
ret = clk_prepare_enable(qphy->ref_clk); if (ret) {
dev_err(&phy->dev, "failed to enable ref clk, %d\n",
ret); goto assert_phy_reset;
}
}
if (cfg->has_pll_test) { if (!qphy->has_se_clk_scheme)
val &= ~CLK_REF_SEL; else
val |= CLK_REF_SEL;
writel(val, qphy->base + QUSB2PHY_PLL_TEST);
/* ensure above write is through */
readl(qphy->base + QUSB2PHY_PLL_TEST);
}
/* Required to get phy pll lock successfully */
usleep_range(100, 110);
val = readb(qphy->base + cfg->regs[QUSB2PHY_PLL_STATUS]); if (!(val & cfg->mask_core_ready)) {
dev_err(&phy->dev, "QUSB2PHY pll lock failed: status reg = %x\n", val);
ret = -EBUSY; goto disable_ref_clk;
}
qphy->phy_initialized = true;
qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL); if (!qphy) return -ENOMEM; or = &qphy->overrides;
qphy->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(qphy->base)) return PTR_ERR(qphy->base);
qphy->cfg_ahb_clk = devm_clk_get(dev, "cfg_ahb"); if (IS_ERR(qphy->cfg_ahb_clk)) return dev_err_probe(dev, PTR_ERR(qphy->cfg_ahb_clk), "failed to get cfg ahb clk\n");
qphy->ref_clk = devm_clk_get(dev, "ref"); if (IS_ERR(qphy->ref_clk)) return dev_err_probe(dev, PTR_ERR(qphy->ref_clk), "failed to get ref clk\n");
qphy->iface_clk = devm_clk_get_optional(dev, "iface"); if (IS_ERR(qphy->iface_clk)) return PTR_ERR(qphy->iface_clk);
qphy->phy_reset = devm_reset_control_get_by_index(&pdev->dev, 0); if (IS_ERR(qphy->phy_reset)) {
dev_err(dev, "failed to get phy core reset\n"); return PTR_ERR(qphy->phy_reset);
}
num = ARRAY_SIZE(qphy->vregs); for (i = 0; i < num; i++)
qphy->vregs[i].supply = qusb2_phy_vreg_names[i];
ret = devm_regulator_bulk_get(dev, num, qphy->vregs); if (ret) return dev_err_probe(dev, ret, "failed to get regulator supplies\n");
/* Get the specific init parameters of QMP phy */
qphy->cfg = of_device_get_match_data(dev);
qphy->tcsr = syscon_regmap_lookup_by_phandle(dev->of_node, "qcom,tcsr-syscon"); if (IS_ERR(qphy->tcsr)) {
dev_dbg(dev, "failed to lookup TCSR regmap\n");
qphy->tcsr = NULL;
}
qphy->cell = devm_nvmem_cell_get(dev, NULL); if (IS_ERR(qphy->cell)) { if (PTR_ERR(qphy->cell) == -EPROBE_DEFER) return -EPROBE_DEFER;
qphy->cell = NULL;
dev_dbg(dev, "failed to lookup tune2 hstx trim value\n");
}
pm_runtime_set_active(dev);
pm_runtime_enable(dev); /* * Prevent runtime pm from being ON by default. Users can enable * it using power/control in sysfs.
*/
pm_runtime_forbid(dev);
generic_phy = devm_phy_create(dev, NULL, &qusb2_phy_gen_ops); if (IS_ERR(generic_phy)) {
ret = PTR_ERR(generic_phy);
dev_err(dev, "failed to create phy, %d\n", ret);
pm_runtime_disable(dev); return ret;
}
qphy->phy = generic_phy;
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.