// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * Copyright (c) 2019 Amlogic, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../xhci_amlogic/xhci-meson.h" #include "../xhci_amlogic/xhci-plat-meson.h" #define CRG_DEFAULT_AUTOSUSPEND_DELAY 5000 /* ms */ #define CRG_XHCI_RESOURCES_NUM 2 #define CRG_GCTL_PRTCAP_HOST 1 #define CRG_GCTL_PRTCAP_DEVICE 2 #define CRG_GCTL_PRTCAP_OTG 3 #define CRG_XHCI_REGS_END 0x7fff struct crg { struct device *dev; struct platform_device *xhci; struct resource xhci_resources[CRG_XHCI_RESOURCES_NUM]; struct usb_phy *usb2_phy; struct usb_phy *usb3_phy; void __iomem *regs; size_t regs_size; enum usb_dr_mode dr_mode; u32 maximum_speed; void *mem; unsigned super_speed_support:1; struct clk *general_clk; }; static struct aml_xhci_plat_priv crg_xhci_plat_priv = { .quirks = XHCI_NO_64BIT_SUPPORT | XHCI_RESET_ON_RESUME, }; /* -------------------------------------------------------------------------- */ void crg_set_mode(struct crg *crg, u32 mode) { //TODO } static int crg_core_soft_reset(struct crg *crg) { usb_phy_init(crg->usb2_phy); usb_phy_init(crg->usb3_phy); return 0; } static void crg_core_exit(struct crg *crg) { usb_phy_shutdown(crg->usb2_phy); usb_phy_shutdown(crg->usb3_phy); usb_phy_set_suspend(crg->usb2_phy, 1); usb_phy_set_suspend(crg->usb3_phy, 1); } static int crg_core_init(struct crg *crg) { int ret; ret = crg_core_soft_reset(crg); if (ret) return ret; usb_phy_set_suspend(crg->usb2_phy, 0); usb_phy_set_suspend(crg->usb3_phy, 0); switch (crg->dr_mode) { case USB_DR_MODE_PERIPHERAL: crg_set_mode(crg, CRG_GCTL_PRTCAP_DEVICE); break; case USB_DR_MODE_HOST: crg_set_mode(crg, CRG_GCTL_PRTCAP_HOST); break; case USB_DR_MODE_OTG: crg_set_mode(crg, CRG_GCTL_PRTCAP_OTG); break; default: dev_warn(crg->dev, "Unsupported mode %d\n", crg->dr_mode); break; } return 0; } static int crg_core_get_phy(struct crg *crg) { struct device *dev = crg->dev; struct device_node *node = dev->of_node; int ret; if (node) { crg->usb2_phy = devm_usb_get_phy_by_phandle(dev, "usb-phy", 0); crg->usb3_phy = devm_usb_get_phy_by_phandle(dev, "usb-phy", 1); } else { crg->usb2_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); crg->usb3_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB3); } if (IS_ERR(crg->usb2_phy)) { ret = PTR_ERR(crg->usb2_phy); if (ret == -ENXIO || ret == -ENODEV) crg->usb2_phy = NULL; else if (ret == -EPROBE_DEFER) return ret; dev_err(dev, "no usb2 phy configured\n"); return ret; } if (IS_ERR(crg->usb3_phy)) { ret = PTR_ERR(crg->usb3_phy); if (ret == -ENXIO || ret == -ENODEV) crg->usb3_phy = NULL; else if (ret == -EPROBE_DEFER) return ret; dev_err(dev, "no usb3 phy configured\n"); return ret; } crg->super_speed_support = 0; if (crg->usb3_phy) if (crg->usb3_phy->flags == AML_USB3_PHY_ENABLE) crg->super_speed_support = 1; return 0; } static void crg_host_fill_xhci_irq_res(struct crg *crg, int irq, char *name) { struct platform_device *pdev = to_platform_device(crg->dev); struct device_node *np = dev_of_node(&pdev->dev); crg->xhci_resources[1].start = irq; crg->xhci_resources[1].end = irq; crg->xhci_resources[1].flags = IORESOURCE_IRQ | irq_get_trigger_type(irq); if (!name && np) crg->xhci_resources[1].name = of_node_full_name(pdev->dev.of_node); else crg->xhci_resources[1].name = name; } static int crg_host_get_irq(struct crg *crg) { struct platform_device *crg_pdev = to_platform_device(crg->dev); int irq; irq = platform_get_irq_byname_optional(crg_pdev, "host"); if (irq > 0) { crg_host_fill_xhci_irq_res(crg, irq, "host"); goto out; } if (irq == -EPROBE_DEFER) goto out; irq = platform_get_irq_byname_optional(crg_pdev, "crg_usb3"); if (irq > 0) { crg_host_fill_xhci_irq_res(crg, irq, "crg_usb3"); goto out; } if (irq == -EPROBE_DEFER) goto out; irq = platform_get_irq(crg_pdev, 0); if (irq > 0) { crg_host_fill_xhci_irq_res(crg, irq, NULL); goto out; } if (!irq) irq = -EINVAL; out: return irq; } static struct property_entry props[64]; int crg_host_init(struct crg *crg) { //struct property_entry props[4]; struct platform_device *xhci; int ret, irq; //struct resource *res; //struct platform_device *crg_pdev = to_platform_device(crg->dev); int prop_idx = 0; int cpu_type; irq = crg_host_get_irq(crg); if (irq < 0) return irq; xhci = platform_device_alloc("xhci-hcd-meson", PLATFORM_DEVID_AUTO); if (!xhci) { dev_err(crg->dev, "couldn't allocate xHCI device\n"); return -ENOMEM; } dma_set_coherent_mask(&xhci->dev, crg->dev->coherent_dma_mask); xhci->dev.parent = crg->dev; xhci->dev.dma_mask = crg->dev->dma_mask; xhci->dev.dma_parms = crg->dev->dma_parms; crg->xhci = xhci; ret = platform_device_add_resources(xhci, crg->xhci_resources, CRG_XHCI_RESOURCES_NUM); if (ret) { dev_err(crg->dev, "couldn't add resources to xHCI device\n"); goto err1; } memset(props, 0, sizeof(struct property_entry) * ARRAY_SIZE(props)); if (crg->super_speed_support) props[prop_idx++] = PROPERTY_ENTRY_BOOL("super_speed_support"); props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host"); props[prop_idx++] = PROPERTY_ENTRY_BOOL("usb2-lpm-disable"); cpu_type = get_cpu_type(); switch (cpu_type) { case MESON_CPU_MAJOR_ID_T5: props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-003"); props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-008"); props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-011"); break; case MESON_CPU_MAJOR_ID_T5D: props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-003"); props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-008"); props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-011"); break; case MESON_CPU_MAJOR_ID_T7: props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-007"); props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-010"); props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-014"); props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-008"); break; case MESON_CPU_MAJOR_ID_S4: props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-008"); props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-009"); props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-011"); break; case MESON_CPU_MAJOR_ID_T3: props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-007"); props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-010"); props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-014"); props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-008"); props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-011"); break; case MESON_CPU_MAJOR_ID_S4D: props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-008"); props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-009"); props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-011"); break; case MESON_CPU_MAJOR_ID_T5W: props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-007"); props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-011"); break; case MESON_CPU_MAJOR_ID_S6: props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-008"); break; default: break; } props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-plug-died"); props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-eproto"); props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host-016"); if (prop_idx) { ret = device_create_managed_software_node(&xhci->dev, props, NULL); if (ret) { dev_err(crg->dev, "failed to add properties to xHCI\n"); goto err1; } } ret = platform_device_add_data(xhci, &crg_xhci_plat_priv, sizeof(crg_xhci_plat_priv)); if (ret) goto err1; ret = platform_device_add(xhci); if (ret) { dev_err(crg->dev, "failed to register xHCI device\n"); goto err1; } return 0; err1: platform_device_put(xhci); return ret; } static void crg_host_exit(struct crg *crg) { /* amlogic_crg_otg_work may call crg_exit (which calls crg_remove) * after crg_shutdown. It is err-prone to dereference crg->xhci member and * unregister crg->xhci. Set crg->xhci to NULL to avoid this. * Since the xhci plat dev has been removed by crg_shutdown, this function * should have been called. It is safe to return here. */ if (!crg->xhci) { dev_warn(crg->dev, "%s: crg->xhci already removed, exit...\n", __func__); return; } /*crg_reset_thread_stop(crg->xhci);*/ platform_device_unregister(crg->xhci); crg->xhci = NULL; } static int crg_core_init_mode(struct crg *crg) { struct device *dev = crg->dev; int ret; switch (crg->dr_mode) { case USB_DR_MODE_PERIPHERAL: case USB_DR_MODE_OTG: break; case USB_DR_MODE_HOST: ret = crg_host_init(crg); if (ret) { if (ret != -EPROBE_DEFER) dev_err(dev, "failed to initialize host\n"); return ret; } break; default: dev_err(dev, "Unsupported mode of operation %d\n", crg->dr_mode); return -EINVAL; } return 0; } #define CRG_ALIGN_MASK (16 - 1) static int crg_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct resource *res; struct crg *crg; int ret; void *mem; const void *prop; mem = devm_kzalloc(dev, sizeof(*crg) + CRG_ALIGN_MASK, GFP_KERNEL); if (!mem) return -ENOMEM; crg = PTR_ALIGN(mem, CRG_ALIGN_MASK + 1); crg->mem = mem; crg->dev = dev; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(dev, "missing memory resource\n"); return -ENODEV; } crg->xhci_resources[0].start = res->start; crg->xhci_resources[0].end = crg->xhci_resources[0].start + CRG_XHCI_REGS_END; crg->xhci_resources[0].flags = res->flags; crg->xhci_resources[0].name = res->name; crg->general_clk = devm_clk_get(dev, "crg_general"); if (IS_ERR(crg->general_clk)) { ret = PTR_ERR(crg->general_clk); goto err0; } else { clk_prepare_enable(crg->general_clk); } platform_set_drvdata(pdev, crg); ret = crg_core_get_phy(crg); if (ret) goto err0; if (!dev->dma_mask) { dev->dma_mask = dev->parent->dma_mask; dev->dma_parms = dev->parent->dma_parms; dma_set_coherent_mask(dev, dev->parent->coherent_dma_mask); } pm_runtime_set_active(dev); pm_runtime_use_autosuspend(dev); pm_runtime_set_autosuspend_delay(dev, CRG_DEFAULT_AUTOSUSPEND_DELAY); pm_runtime_enable(dev); ret = pm_runtime_get_sync(dev); if (ret < 0) goto err0; pm_runtime_forbid(dev); ret = crg_core_init(crg); if (ret) { dev_err(dev, "failed to initialize core\n"); goto err0; } crg->maximum_speed = USB_SPEED_HIGH; crg->dr_mode = USB_DR_MODE_HOST; /* Check the maximum_speed parameter */ switch (crg->maximum_speed) { case USB_SPEED_LOW: case USB_SPEED_FULL: case USB_SPEED_HIGH: case USB_SPEED_SUPER: case USB_SPEED_SUPER_PLUS: break; default: dev_err(dev, "invalid maximum_speed parameter %d\n", crg->maximum_speed); break; } prop = of_get_property(pdev->dev.of_node, "dma-64bit-support", NULL); if (prop) if (of_read_ulong(prop, 1)) crg_xhci_plat_priv.quirks &= (~XHCI_NO_64BIT_SUPPORT); ret = crg_core_init_mode(crg); if (ret) goto err0; pm_runtime_put(dev); return 0; err0: return ret; } void crg_shutdown(struct platform_device *pdev) { struct crg *crg = platform_get_drvdata(pdev); pm_runtime_get_sync(&pdev->dev); crg_host_exit(crg); crg_core_exit(crg); if (crg->regs) iounmap(crg->regs); pm_runtime_put_sync(&pdev->dev); pm_runtime_allow(&pdev->dev); pm_runtime_disable(&pdev->dev); clk_disable_unprepare(crg->general_clk); } static void crg_remove(struct platform_device *pdev) { struct crg *crg = platform_get_drvdata(pdev); pm_runtime_get_sync(&pdev->dev); crg_core_exit(crg); pm_runtime_put_sync(&pdev->dev); pm_runtime_allow(&pdev->dev); pm_runtime_disable(&pdev->dev); clk_disable_unprepare(crg->general_clk); return; } #ifdef CONFIG_PM static int crg_suspend_common(struct crg *crg) { switch (crg->dr_mode) { case USB_DR_MODE_PERIPHERAL: case USB_DR_MODE_OTG: break; case USB_DR_MODE_HOST: default: /* do nothing */ break; } crg_core_exit(crg); return 0; } static int crg_resume_common(struct crg *crg) { int ret; ret = crg_core_init(crg); if (ret) return ret; switch (crg->dr_mode) { case USB_DR_MODE_PERIPHERAL: case USB_DR_MODE_OTG: case USB_DR_MODE_HOST: default: /* do nothing */ break; } return 0; } static int crg_runtime_checks(struct crg *crg) { switch (crg->dr_mode) { case USB_DR_MODE_PERIPHERAL: case USB_DR_MODE_OTG: case USB_DR_MODE_HOST: default: /* do nothing */ break; } return 0; } static int crg_runtime_suspend(struct device *dev) { struct crg *crg = dev_get_drvdata(dev); int ret; if (crg_runtime_checks(crg)) return -EBUSY; ret = crg_suspend_common(crg); if (ret) return ret; device_init_wakeup(dev, true); return 0; } static int crg_runtime_resume(struct device *dev) { struct crg *crg = dev_get_drvdata(dev); int ret; device_init_wakeup(dev, false); ret = crg_resume_common(crg); if (ret) return ret; switch (crg->dr_mode) { case USB_DR_MODE_PERIPHERAL: case USB_DR_MODE_OTG: case USB_DR_MODE_HOST: default: /* do nothing */ break; } pm_runtime_mark_last_busy(dev); pm_runtime_put(dev); return 0; } static int crg_runtime_idle(struct device *dev) { struct crg *crg = dev_get_drvdata(dev); switch (crg->dr_mode) { case USB_DR_MODE_PERIPHERAL: case USB_DR_MODE_OTG: if (crg_runtime_checks(crg)) return -EBUSY; break; case USB_DR_MODE_HOST: default: /* do nothing */ break; } pm_runtime_mark_last_busy(dev); pm_runtime_autosuspend(dev); return 0; } #endif /* CONFIG_PM */ #ifdef CONFIG_PM_SLEEP static int crg_suspend(struct device *dev) { struct crg *crg = dev_get_drvdata(dev); int ret; ret = crg_suspend_common(crg); if (ret) return ret; clk_disable_unprepare(crg->general_clk); pinctrl_pm_select_sleep_state(dev); return 0; } static int crg_resume(struct device *dev) { struct crg *crg = dev_get_drvdata(dev); int ret; pinctrl_pm_select_default_state(dev); clk_prepare_enable(crg->general_clk); ret = crg_resume_common(crg); if (ret) return ret; pm_runtime_disable(dev); pm_runtime_set_active(dev); pm_runtime_enable(dev); return 0; } #endif /* CONFIG_PM_SLEEP */ static const struct dev_pm_ops crg_dev_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(crg_suspend, crg_resume) SET_RUNTIME_PM_OPS(crg_runtime_suspend, crg_runtime_resume, crg_runtime_idle) }; #ifdef CONFIG_OF static const struct of_device_id of_crg_match[] = { { .compatible = "amlogic, crg" }, { }, }; MODULE_DEVICE_TABLE(of, of_crg_match); #endif static struct platform_driver crg_driver = { .remove = crg_remove, .shutdown = crg_shutdown, .driver = { .name = "crg", .of_match_table = of_match_ptr(of_crg_match), .pm = &crg_dev_pm_ops, }, }; /* AMLOGIC corigine driver does not allow module unload */ int __init amlogic_crg_init(void) { platform_driver_probe(&crg_driver, crg_probe); return 0; } #if 0 late_initcall(amlogic_crg_init); MODULE_ALIAS("platform:crg"); MODULE_AUTHOR("yue wang "); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("corigine USB3 DRD Controller Driver"); #endif