/* Update initial VBUS override based on extcon state */ if (extcon_get_state(qcom->edev, EXTCON_USB) ||
!extcon_get_state(host_edev, EXTCON_USB_HOST))
dwc3_qcom_vbus_notifier(&qcom->vbus_nb, true, qcom->edev); else
dwc3_qcom_vbus_notifier(&qcom->vbus_nb, false, qcom->edev);
return 0;
}
staticint dwc3_qcom_interconnect_enable(struct dwc3_qcom *qcom)
{ int ret;
ret = icc_enable(qcom->icc_path_ddr); if (ret) return ret;
ret = icc_enable(qcom->icc_path_apps); if (ret)
icc_disable(qcom->icc_path_ddr);
return ret;
}
staticint dwc3_qcom_interconnect_disable(struct dwc3_qcom *qcom)
{ int ret;
ret = icc_disable(qcom->icc_path_ddr); if (ret) return ret;
ret = icc_disable(qcom->icc_path_apps); if (ret)
icc_enable(qcom->icc_path_ddr);
return ret;
}
/** * dwc3_qcom_interconnect_init() - Get interconnect path handles * and set bandwidth. * @qcom: Pointer to the concerned usb core. *
*/ staticint dwc3_qcom_interconnect_init(struct dwc3_qcom *qcom)
{ enum usb_device_speed max_speed; struct device *dev = qcom->dev; int ret;
qcom->icc_path_ddr = of_icc_get(dev, "usb-ddr"); if (IS_ERR(qcom->icc_path_ddr)) { return dev_err_probe(dev, PTR_ERR(qcom->icc_path_ddr), "failed to get usb-ddr path\n");
}
qcom->icc_path_apps = of_icc_get(dev, "apps-usb"); if (IS_ERR(qcom->icc_path_apps)) {
ret = dev_err_probe(dev, PTR_ERR(qcom->icc_path_apps), "failed to get apps-usb path\n"); goto put_path_ddr;
}
max_speed = usb_get_maximum_speed(&qcom->dwc3->dev); if (max_speed >= USB_SPEED_SUPER || max_speed == USB_SPEED_UNKNOWN) {
ret = icc_set_bw(qcom->icc_path_ddr,
USB_MEMORY_AVG_SS_BW, USB_MEMORY_PEAK_SS_BW);
} else {
ret = icc_set_bw(qcom->icc_path_ddr,
USB_MEMORY_AVG_HS_BW, USB_MEMORY_PEAK_HS_BW);
} if (ret) {
dev_err(dev, "failed to set bandwidth for usb-ddr path: %d\n", ret); goto put_path_apps;
}
ret = icc_set_bw(qcom->icc_path_apps, APPS_USB_AVG_BW, APPS_USB_PEAK_BW); if (ret) {
dev_err(dev, "failed to set bandwidth for apps-usb path: %d\n", ret); goto put_path_apps;
}
/** * dwc3_qcom_interconnect_exit() - Release interconnect path handles * @qcom: Pointer to the concerned usb core. * * This function is used to release interconnect path handle.
*/ staticvoid dwc3_qcom_interconnect_exit(struct dwc3_qcom *qcom)
{
icc_put(qcom->icc_path_ddr);
icc_put(qcom->icc_path_apps);
}
/* Only usable in contexts where the role can not change. */ staticbool dwc3_qcom_is_host(struct dwc3_qcom *qcom)
{ struct dwc3 *dwc;
/* * Configure DP/DM line interrupts based on the USB2 device attached to * the root hub port. When HS/FS device is connected, configure the DP line * as falling edge to detect both disconnect and remote wakeup scenarios. When * LS device is connected, configure DM line as falling edge to detect both * disconnect and remote wakeup. When no device is connected, configure both * DP and DM lines as rising edge to detect HS/HS/LS device connect scenario.
*/
staticvoid dwc3_qcom_disable_interrupts(struct dwc3_qcom *qcom)
{ int i;
for (i = 0; i < qcom->num_ports; i++)
dwc3_qcom_disable_port_interrupts(&qcom->ports[i]);
}
staticvoid dwc3_qcom_enable_interrupts(struct dwc3_qcom *qcom)
{ int i;
for (i = 0; i < qcom->num_ports; i++)
dwc3_qcom_enable_port_interrupts(&qcom->ports[i]);
}
staticint dwc3_qcom_suspend(struct dwc3_qcom *qcom, bool wakeup)
{
u32 val; int i, ret;
if (qcom->is_suspended) return 0;
for (i = 0; i < qcom->num_ports; i++) {
val = readl(qcom->qscratch_base + pwr_evnt_irq_stat_reg[i]); if (!(val & PWR_EVNT_LPM_IN_L2_MASK))
dev_err(qcom->dev, "port-%d HS-PHY not in L2\n", i + 1);
}
for (i = qcom->num_clocks - 1; i >= 0; i--)
clk_disable_unprepare(qcom->clks[i]);
ret = dwc3_qcom_interconnect_disable(qcom); if (ret)
dev_warn(qcom->dev, "failed to disable interconnect: %d\n", ret);
/* * The role is stable during suspend as role switching is done from a * freezable workqueue.
*/ if (dwc3_qcom_is_host(qcom) && wakeup) { for (i = 0; i < qcom->num_ports; i++)
qcom->ports[i].usb2_speed = dwc3_qcom_read_usb2_speed(qcom, i);
dwc3_qcom_enable_interrupts(qcom);
}
qcom->is_suspended = true;
return 0;
}
staticint dwc3_qcom_resume(struct dwc3_qcom *qcom, bool wakeup)
{ int ret; int i;
if (!qcom->is_suspended) return 0;
if (dwc3_qcom_is_host(qcom) && wakeup)
dwc3_qcom_disable_interrupts(qcom);
for (i = 0; i < qcom->num_clocks; i++) {
ret = clk_prepare_enable(qcom->clks[i]); if (ret < 0) { while (--i >= 0)
clk_disable_unprepare(qcom->clks[i]); return ret;
}
}
ret = dwc3_qcom_interconnect_enable(qcom); if (ret)
dev_warn(qcom->dev, "failed to enable interconnect: %d\n", ret);
/* Clear existing events from PHY related to L2 in/out */ for (i = 0; i < qcom->num_ports; i++) {
dwc3_qcom_setbits(qcom->qscratch_base,
pwr_evnt_irq_stat_reg[i],
PWR_EVNT_LPM_IN_L2_MASK | PWR_EVNT_LPM_OUT_L2_MASK);
}
/* If pm_suspended then let pm_resume take care of resuming h/w */ if (qcom->pm_suspended) return IRQ_HANDLED;
/* * This is safe as role switching is done from a freezable workqueue * and the wakeup interrupts are disabled as part of resume.
*/ if (dwc3_qcom_is_host(qcom))
pm_runtime_resume(&dwc->xhci->dev);
return IRQ_HANDLED;
}
staticvoid dwc3_qcom_select_utmi_clk(struct dwc3_qcom *qcom)
{ /* Configure dwc3 to use UTMI clock as PIPE clock not present */
dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
PIPE_UTMI_CLK_DIS);
struct device_node *dwc3_np __free(device_node) = of_get_compatible_child(np, "snps,dwc3"); if (!dwc3_np) {
dev_err(dev, "failed to find dwc3 core child\n"); return -ENODEV;
}
ret = of_platform_populate(np, NULL, NULL, dev); if (ret) {
dev_err(dev, "failed to register dwc3 core - %d\n", ret); return ret;
}
qcom->dwc3 = of_find_device_by_node(dwc3_np); if (!qcom->dwc3) {
ret = -ENODEV;
dev_err(dev, "failed to get dwc3 platform device\n");
of_platform_depopulate(dev);
}
qcom->resets = devm_reset_control_array_get_optional_exclusive(dev); if (IS_ERR(qcom->resets)) { return dev_err_probe(&pdev->dev, PTR_ERR(qcom->resets), "failed to get resets\n");
}
ret = reset_control_assert(qcom->resets); if (ret) {
dev_err(&pdev->dev, "failed to assert resets, err=%d\n", ret); return ret;
}
usleep_range(10, 1000);
ret = reset_control_deassert(qcom->resets); if (ret) {
dev_err(&pdev->dev, "failed to deassert resets, err=%d\n", ret); goto reset_assert;
}
ret = dwc3_qcom_clk_init(qcom, of_clk_get_parent_count(np)); if (ret) {
dev_err_probe(dev, ret, "failed to get clocks\n"); goto reset_assert;
}
qcom->qscratch_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(qcom->qscratch_base)) {
ret = PTR_ERR(qcom->qscratch_base); goto clk_disable;
}
ret = dwc3_qcom_setup_irq(pdev); if (ret) {
dev_err(dev, "failed to setup IRQs, err=%d\n", ret); goto clk_disable;
}
/* * Disable pipe_clk requirement if specified. Used when dwc3 * operates without SSPHY and only HS/FS/LS modes are supported.
*/
ignore_pipe_clk = device_property_read_bool(dev, "qcom,select-utmi-as-pipe-clk"); if (ignore_pipe_clk)
dwc3_qcom_select_utmi_clk(qcom);
ret = dwc3_qcom_of_register_core(pdev); if (ret) {
dev_err(dev, "failed to register DWC3 Core, err=%d\n", ret); goto clk_disable;
}
ret = dwc3_qcom_interconnect_init(qcom); if (ret) goto depopulate;
qcom->mode = usb_get_dr_mode(&qcom->dwc3->dev);
/* enable vbus override for device mode */ if (qcom->mode != USB_DR_MODE_HOST)
dwc3_qcom_vbus_override_enable(qcom, true);
/* register extcon to override sw_vbus on Vbus change later */
ret = dwc3_qcom_register_extcon(qcom); if (ret) goto interconnect_exit;
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.