/** * hw_read_intr_enable: returns interrupt enable register * * @ci: the controller * * This function returns register data
*/
u32 hw_read_intr_enable(struct ci_hdrc *ci)
{ return hw_read(ci, OP_USBINTR, ~0);
}
/** * hw_read_intr_status: returns interrupt status register * * @ci: the controller * * This function returns register data
*/
u32 hw_read_intr_status(struct ci_hdrc *ci)
{ return hw_read(ci, OP_USBSTS, ~0);
}
/** * hw_port_test_set: writes port test mode (execute without interruption) * @ci: the controller * @mode: new value * * This function returns an error code
*/ int hw_port_test_set(struct ci_hdrc *ci, u8 mode)
{ const u8 TEST_MODE_MAX = 7;
/** * hw_port_test_get: reads port test mode value * * @ci: the controller * * This function returns port test mode value
*/
u8 hw_port_test_get(struct ci_hdrc *ci)
{ return hw_read(ci, OP_PORTSC, PORTSC_PTC) >> __ffs(PORTSC_PTC);
}
staticvoid hw_wait_phy_stable(void)
{ /* * The phy needs some delay to output the stable status from low * power mode. And for OTGSC, the status inputs are debounced * using a 1 ms time constant, so, delay 2ms for controller to get * the stable status, like vbus and id when the phy leaves low power.
*/
usleep_range(2000, 2500);
}
/** * _ci_usb_phy_init: initialize phy taking in account both phy and usb_phy * interfaces * @ci: the controller * * This function returns an error code if the phy failed to init
*/ staticint _ci_usb_phy_init(struct ci_hdrc *ci)
{ int ret;
if (ci->phy) {
ret = phy_init(ci->phy); if (ret) return ret;
ret = phy_power_on(ci->phy); if (ret) {
phy_exit(ci->phy); return ret;
}
} else {
ret = usb_phy_init(ci->usb_phy);
}
return ret;
}
/** * ci_usb_phy_exit: deinitialize phy taking in account both phy and usb_phy * interfaces * @ci: the controller
*/ staticvoid ci_usb_phy_exit(struct ci_hdrc *ci)
{ if (ci->platdata->flags & CI_HDRC_OVERRIDE_PHY_CONTROL) return;
/** * ci_usb_phy_init: initialize phy according to different phy type * @ci: the controller * * This function returns an error code if usb_phy_init has failed
*/ staticint ci_usb_phy_init(struct ci_hdrc *ci)
{ int ret;
if (ci->platdata->flags & CI_HDRC_OVERRIDE_PHY_CONTROL) return 0;
switch (ci->platdata->phy_mode) { case USBPHY_INTERFACE_MODE_UTMI: case USBPHY_INTERFACE_MODE_UTMIW: case USBPHY_INTERFACE_MODE_HSIC:
ret = _ci_usb_phy_init(ci); if (!ret)
hw_wait_phy_stable(); else return ret;
hw_phymode_configure(ci); break; case USBPHY_INTERFACE_MODE_ULPI: case USBPHY_INTERFACE_MODE_SERIAL:
hw_phymode_configure(ci);
ret = _ci_usb_phy_init(ci); if (ret) return ret; break; default:
ret = _ci_usb_phy_init(ci); if (!ret)
hw_wait_phy_stable();
}
return ret;
}
/** * ci_platform_configure: do controller configure * @ci: the controller *
*/ void ci_platform_configure(struct ci_hdrc *ci)
{ bool is_device_mode, is_host_mode;
if (ci->platdata->flags & CI_HDRC_OVERRIDE_AHB_BURST)
hw_write_id_reg(ci, ID_SBUSCFG, AHBBRST_MASK,
ci->platdata->ahb_burst_config);
/* override burst size, take effect only when ahb_burst_config is 0 */ if (!hw_read_id_reg(ci, ID_SBUSCFG, AHBBRST_MASK)) { if (ci->platdata->flags & CI_HDRC_OVERRIDE_TX_BURST)
hw_write(ci, OP_BURSTSIZE, TX_BURST_MASK,
ci->platdata->tx_burst_size << __ffs(TX_BURST_MASK));
if (ci->platdata->flags & CI_HDRC_OVERRIDE_RX_BURST)
hw_write(ci, OP_BURSTSIZE, RX_BURST_MASK,
ci->platdata->rx_burst_size);
}
}
/** * hw_controller_reset: do controller reset * @ci: the controller * * This function returns an error code
*/ staticint hw_controller_reset(struct ci_hdrc *ci)
{ int count = 0;
hw_write(ci, OP_USBCMD, USBCMD_RST, USBCMD_RST); while (hw_read(ci, OP_USBCMD, USBCMD_RST)) {
udelay(10); if (count++ > 1000) return -ETIMEDOUT;
}
return 0;
}
/** * hw_device_reset: resets chip (execute without interruption) * @ci: the controller * * This function returns an error code
*/ int hw_device_reset(struct ci_hdrc *ci)
{ int ret;
/* should flush & stop before reset */
hw_write(ci, OP_ENDPTFLUSH, ~0, ~0);
hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
ret = hw_controller_reset(ci); if (ret) {
dev_err(ci->dev, "error resetting controller, ret=%d\n", ret); return ret;
}
if (ci->platdata->notify_event) {
ret = ci->platdata->notify_event(ci,
CI_HDRC_CONTROLLER_RESET_EVENT); if (ret) return ret;
}
/* USBMODE should be configured step by step */
hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_IDLE);
hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_DC); /* HW >= 2.3 */
hw_write(ci, OP_USBMODE, USBMODE_SLOM, USBMODE_SLOM);
if (hw_read(ci, OP_USBMODE, USBMODE_CM) != USBMODE_CM_DC) {
dev_err(ci->dev, "cannot enter in %s device mode\n",
ci_role(ci)->name);
dev_err(ci->dev, "lpm = %i\n", ci->hw_bank.lpm); return -ENODEV;
}
if (ci->in_lpm) { /* * If we already have a wakeup irq pending there, * let's just return to wait resume finished firstly.
*/ if (ci->wakeup_int) return IRQ_HANDLED;
if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) { if (ci->is_otg) {
role = ci_otg_role(ci);
hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE);
} else { /* * If the controller is not OTG capable, but support * role switch, the defalt role is gadget, and the * user can switch it through debugfs.
*/
role = CI_ROLE_GADGET;
}
} else {
role = ci->roles[CI_ROLE_HOST] ? CI_ROLE_HOST
: CI_ROLE_GADGET;
}
if (!platdata->phy_mode)
platdata->phy_mode = of_usb_get_phy_mode(dev->of_node);
if (!platdata->dr_mode)
platdata->dr_mode = usb_get_dr_mode(dev);
if (platdata->dr_mode == USB_DR_MODE_UNKNOWN)
platdata->dr_mode = USB_DR_MODE_OTG;
if (platdata->dr_mode != USB_DR_MODE_PERIPHERAL) { /* Get the vbus regulator */
platdata->reg_vbus = devm_regulator_get_optional(dev, "vbus"); if (PTR_ERR(platdata->reg_vbus) == -EPROBE_DEFER) { return -EPROBE_DEFER;
} elseif (PTR_ERR(platdata->reg_vbus) == -ENODEV) { /* no vbus regulator is needed */
platdata->reg_vbus = NULL;
} elseif (IS_ERR(platdata->reg_vbus)) {
dev_err(dev, "Getting regulator error: %ld\n",
PTR_ERR(platdata->reg_vbus)); return PTR_ERR(platdata->reg_vbus);
} /* Get TPL support */ if (!platdata->tpl_support)
platdata->tpl_support =
of_usb_host_tpl_support(dev->of_node);
}
if (platdata->dr_mode == USB_DR_MODE_OTG) { /* We can support HNP and SRP of OTG 2.0 */
platdata->ci_otg_caps.otg_rev = 0x0200;
platdata->ci_otg_caps.hnp_support = true;
platdata->ci_otg_caps.srp_support = true;
/* Update otg capabilities by DT properties */
ret = of_usb_update_otg_caps(dev->of_node,
&platdata->ci_otg_caps); if (ret) return ret;
}
if (usb_get_maximum_speed(dev) == USB_SPEED_FULL)
platdata->flags |= CI_HDRC_FORCE_FULLSPEED;
ret = of_property_read_u32(dev->of_node, "ahb-burst-config",
&platdata->ahb_burst_config); if (!ret) {
platdata->flags |= CI_HDRC_OVERRIDE_AHB_BURST;
} elseif (ret != -EINVAL) {
dev_err(dev, "failed to get ahb-burst-config\n"); return ret;
}
ret = of_property_read_u32(dev->of_node, "tx-burst-size-dword",
&platdata->tx_burst_size); if (!ret) {
platdata->flags |= CI_HDRC_OVERRIDE_TX_BURST;
} elseif (ret != -EINVAL) {
dev_err(dev, "failed to get tx-burst-size-dword\n"); return ret;
}
ret = of_property_read_u32(dev->of_node, "rx-burst-size-dword",
&platdata->rx_burst_size); if (!ret) {
platdata->flags |= CI_HDRC_OVERRIDE_RX_BURST;
} elseif (ret != -EINVAL) {
dev_err(dev, "failed to get rx-burst-size-dword\n"); return ret;
}
if (of_property_read_bool(dev->of_node, "non-zero-ttctrl-ttha"))
platdata->flags |= CI_HDRC_SET_NON_ZERO_TTHA;
ext_id = ERR_PTR(-ENODEV);
ext_vbus = ERR_PTR(-ENODEV); if (of_property_present(dev->of_node, "extcon")) { /* Each one of them is not mandatory */
ext_vbus = extcon_get_edev_by_phandle(dev, 0); if (IS_ERR(ext_vbus) && PTR_ERR(ext_vbus) != -ENODEV) return PTR_ERR(ext_vbus);
void ci_hdrc_remove_device(struct platform_device *pdev)
{ int id = pdev->id;
platform_device_unregister(pdev);
ida_free(&ci_ida, id);
}
EXPORT_SYMBOL_GPL(ci_hdrc_remove_device);
/** * ci_hdrc_query_available_role: get runtime available operation mode * * The glue layer can get current operation mode (host/peripheral/otg) * This function should be called after ci core device has created. * * @pdev: the platform device of ci core. * * Return runtime usb_dr_mode.
*/ enum usb_dr_mode ci_hdrc_query_available_role(struct platform_device *pdev)
{ struct ci_hdrc *ci = platform_get_drvdata(pdev);
ret = hw_device_init(ci, base); if (ret < 0) {
dev_err(dev, "can't initialize hardware\n"); return -ENODEV;
}
ret = ci_ulpi_init(ci); if (ret) return ret;
if (ci->platdata->phy) {
ci->phy = ci->platdata->phy;
} elseif (ci->platdata->usb_phy) {
ci->usb_phy = ci->platdata->usb_phy;
} else { /* Look for a generic PHY first */
ci->phy = devm_phy_get(dev->parent, "usb-phy");
if (PTR_ERR(ci->phy) == -EPROBE_DEFER) {
ret = -EPROBE_DEFER; goto ulpi_exit;
} elseif (IS_ERR(ci->phy)) {
ci->phy = NULL;
}
/* Look for a legacy USB PHY from device-tree next */ if (!ci->phy) {
ci->usb_phy = devm_usb_get_phy_by_phandle(dev->parent, "phys", 0);
if (PTR_ERR(ci->usb_phy) == -EPROBE_DEFER) {
ret = -EPROBE_DEFER; goto ulpi_exit;
} elseif (IS_ERR(ci->usb_phy)) {
ci->usb_phy = NULL;
}
}
/* Look for any registered legacy USB PHY as last resort */ if (!ci->phy && !ci->usb_phy) {
ci->usb_phy = devm_usb_get_phy(dev->parent,
USB_PHY_TYPE_USB2);
if (PTR_ERR(ci->usb_phy) == -EPROBE_DEFER) {
ret = -EPROBE_DEFER; goto ulpi_exit;
} elseif (IS_ERR(ci->usb_phy)) {
ci->usb_phy = NULL;
}
}
/* No USB PHY was found in the end */ if (!ci->phy && !ci->usb_phy) {
ret = -ENXIO; goto ulpi_exit;
}
}
ret = ci_usb_phy_init(ci); if (ret) {
dev_err(dev, "unable to init phy: %d\n", ret); goto ulpi_exit;
}
ci->hw_bank.phys = res->start;
ci->irq = platform_get_irq(pdev, 0); if (ci->irq < 0) {
ret = ci->irq; goto deinit_phy;
}
ci_get_otg_capable(ci);
dr_mode = ci->platdata->dr_mode; /* initialize role(s) before the interrupt is requested */ if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
ret = ci_hdrc_host_init(ci); if (ret) { if (ret == -ENXIO)
dev_info(dev, "doesn't support host\n"); else goto deinit_phy;
}
}
if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
ret = ci_hdrc_gadget_init(ci); if (ret) { if (ret == -ENXIO)
dev_info(dev, "doesn't support gadget\n"); else goto deinit_host;
}
}
if (!ci->roles[CI_ROLE_HOST] && !ci->roles[CI_ROLE_GADGET]) {
dev_err(dev, "no supported roles\n");
ret = -ENODEV; goto deinit_gadget;
}
if (ci->is_otg && ci->roles[CI_ROLE_GADGET]) {
ret = ci_hdrc_otg_init(ci); if (ret) {
dev_err(dev, "init otg fails, ret = %d\n", ret); goto deinit_gadget;
}
}
if (ci_role_switch.fwnode) {
ci_role_switch.driver_data = ci;
ci->role_switch = usb_role_switch_register(dev,
&ci_role_switch); if (IS_ERR(ci->role_switch)) {
ret = PTR_ERR(ci->role_switch); goto deinit_otg;
}
}
ci->role = ci_get_role(ci); if (!ci_otg_is_fsm_mode(ci)) { /* only update vbus status for peripheral */ if (ci->role == CI_ROLE_GADGET) { /* Pull down DP for possible charger detection */
hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
ci_handle_vbus_change(ci);
}
ret = ci_role_start(ci, ci->role); if (ret) {
dev_err(dev, "can't start %s role\n",
ci_role(ci)->name); goto stop;
}
}
ret = devm_request_irq(dev, ci->irq, ci_irq_handler, IRQF_SHARED,
ci->platdata->name, ci); if (ret) goto stop;
ret = ci_extcon_register(ci); if (ret) goto stop;
if (ci->supports_runtime_pm) {
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
pm_runtime_set_autosuspend_delay(&pdev->dev, 2000);
pm_runtime_mark_last_busy(ci->dev);
pm_runtime_use_autosuspend(&pdev->dev);
}
if (ci_otg_is_fsm_mode(ci))
ci_hdrc_otg_fsm_start(ci);
/* * Handle the wakeup interrupt triggered by extcon connector * We need to call ci_irq again for extcon since the first * interrupt (wakeup int) only let the controller be out of * low power mode, but not handle any interrupts.
*/ staticvoid ci_extcon_wakeup_int(struct ci_hdrc *ci)
{ struct ci_hdrc_cable *cable_id, *cable_vbus;
u32 otgsc = hw_read_otgsc(ci, ~0);
if (ci->wq)
flush_workqueue(ci->wq); /* * Controller needs to be active during suspend, otherwise the core * may run resume when the parent is at suspend if other driver's * suspend fails, it occurs before parent's suspend has not started, * but the core suspend has finished.
*/ if (ci->in_lpm)
pm_runtime_resume(dev);
if (ci->in_lpm) {
WARN_ON(1); return 0;
}
/* Extra routine per role before system suspend */ if (ci->role != CI_ROLE_END && ci_role(ci)->suspend)
ci_role(ci)->suspend(ci);
if (device_may_wakeup(dev)) { if (ci_otg_is_fsm_mode(ci))
ci_otg_fsm_suspend_for_srp(ci);
/* Since ASYNCLISTADDR (host mode) and ENDPTLISTADDR (device * mode) share the same register address. We can check if * controller resume from power lost based on this address * due to this register will be reset after power lost.
*/
power_lost = !hw_read(ci, OP_ENDPTLISTADDR, ~0);
if (device_may_wakeup(dev))
disable_irq_wake(ci->irq);
ret = ci_controller_resume(dev); if (ret) return ret;
if (power_lost) { /* shutdown and re-init for phy */
ci_usb_phy_exit(ci);
ci_usb_phy_init(ci);
}
/* Extra routine per role after system resume */ if (ci->role != CI_ROLE_END && ci_role(ci)->resume)
ci_role(ci)->resume(ci, power_lost);
if (power_lost)
queue_work(system_freezable_wq, &ci->power_lost_work);
if (ci->supports_runtime_pm) {
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
}
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.