162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Cadence USBSS and USBSSP DRD Driver - host side 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2018-2019 Cadence Design Systems. 662306a36Sopenharmony_ci * Copyright (C) 2017-2018 NXP 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Authors: Peter Chen <peter.chen@nxp.com> 962306a36Sopenharmony_ci * Pawel Laszczak <pawell@cadence.com> 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/platform_device.h> 1362306a36Sopenharmony_ci#include <linux/slab.h> 1462306a36Sopenharmony_ci#include "core.h" 1562306a36Sopenharmony_ci#include "drd.h" 1662306a36Sopenharmony_ci#include "host-export.h" 1762306a36Sopenharmony_ci#include <linux/usb/hcd.h> 1862306a36Sopenharmony_ci#include "../host/xhci.h" 1962306a36Sopenharmony_ci#include "../host/xhci-plat.h" 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci/* 2262306a36Sopenharmony_ci * The XECP_PORT_CAP_REG and XECP_AUX_CTRL_REG1 exist only 2362306a36Sopenharmony_ci * in Cadence USB3 dual-role controller, so it can't be used 2462306a36Sopenharmony_ci * with Cadence CDNSP dual-role controller. 2562306a36Sopenharmony_ci */ 2662306a36Sopenharmony_ci#define XECP_PORT_CAP_REG 0x8000 2762306a36Sopenharmony_ci#define XECP_AUX_CTRL_REG1 0x8120 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#define CFG_RXDET_P3_EN BIT(15) 3062306a36Sopenharmony_ci#define LPM_2_STB_SWITCH_EN BIT(25) 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistatic void xhci_cdns3_plat_start(struct usb_hcd *hcd) 3362306a36Sopenharmony_ci{ 3462306a36Sopenharmony_ci struct xhci_hcd *xhci = hcd_to_xhci(hcd); 3562306a36Sopenharmony_ci u32 value; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci /* set usbcmd.EU3S */ 3862306a36Sopenharmony_ci value = readl(&xhci->op_regs->command); 3962306a36Sopenharmony_ci value |= CMD_PM_INDEX; 4062306a36Sopenharmony_ci writel(value, &xhci->op_regs->command); 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci if (hcd->regs) { 4362306a36Sopenharmony_ci value = readl(hcd->regs + XECP_AUX_CTRL_REG1); 4462306a36Sopenharmony_ci value |= CFG_RXDET_P3_EN; 4562306a36Sopenharmony_ci writel(value, hcd->regs + XECP_AUX_CTRL_REG1); 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci value = readl(hcd->regs + XECP_PORT_CAP_REG); 4862306a36Sopenharmony_ci value |= LPM_2_STB_SWITCH_EN; 4962306a36Sopenharmony_ci writel(value, hcd->regs + XECP_PORT_CAP_REG); 5062306a36Sopenharmony_ci } 5162306a36Sopenharmony_ci} 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistatic int xhci_cdns3_resume_quirk(struct usb_hcd *hcd) 5462306a36Sopenharmony_ci{ 5562306a36Sopenharmony_ci xhci_cdns3_plat_start(hcd); 5662306a36Sopenharmony_ci return 0; 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic const struct xhci_plat_priv xhci_plat_cdns3_xhci = { 6062306a36Sopenharmony_ci .quirks = XHCI_SKIP_PHY_INIT | XHCI_AVOID_BEI, 6162306a36Sopenharmony_ci .plat_start = xhci_cdns3_plat_start, 6262306a36Sopenharmony_ci .resume_quirk = xhci_cdns3_resume_quirk, 6362306a36Sopenharmony_ci}; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistatic const struct xhci_plat_priv xhci_plat_cdnsp_xhci; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistatic int __cdns_host_init(struct cdns *cdns) 6862306a36Sopenharmony_ci{ 6962306a36Sopenharmony_ci struct platform_device *xhci; 7062306a36Sopenharmony_ci int ret; 7162306a36Sopenharmony_ci struct usb_hcd *hcd; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci cdns_drd_host_on(cdns); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci xhci = platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO); 7662306a36Sopenharmony_ci if (!xhci) { 7762306a36Sopenharmony_ci dev_err(cdns->dev, "couldn't allocate xHCI device\n"); 7862306a36Sopenharmony_ci return -ENOMEM; 7962306a36Sopenharmony_ci } 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci xhci->dev.parent = cdns->dev; 8262306a36Sopenharmony_ci cdns->host_dev = xhci; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci ret = platform_device_add_resources(xhci, cdns->xhci_res, 8562306a36Sopenharmony_ci CDNS_XHCI_RESOURCES_NUM); 8662306a36Sopenharmony_ci if (ret) { 8762306a36Sopenharmony_ci dev_err(cdns->dev, "couldn't add resources to xHCI device\n"); 8862306a36Sopenharmony_ci goto err1; 8962306a36Sopenharmony_ci } 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci if (cdns->version < CDNSP_CONTROLLER_V2) 9262306a36Sopenharmony_ci cdns->xhci_plat_data = kmemdup(&xhci_plat_cdns3_xhci, 9362306a36Sopenharmony_ci sizeof(struct xhci_plat_priv), GFP_KERNEL); 9462306a36Sopenharmony_ci else 9562306a36Sopenharmony_ci cdns->xhci_plat_data = kmemdup(&xhci_plat_cdnsp_xhci, 9662306a36Sopenharmony_ci sizeof(struct xhci_plat_priv), GFP_KERNEL); 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci if (!cdns->xhci_plat_data) { 9962306a36Sopenharmony_ci ret = -ENOMEM; 10062306a36Sopenharmony_ci goto err1; 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci if (cdns->pdata && (cdns->pdata->quirks & CDNS3_DEFAULT_PM_RUNTIME_ALLOW)) 10462306a36Sopenharmony_ci cdns->xhci_plat_data->quirks |= XHCI_DEFAULT_PM_RUNTIME_ALLOW; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci ret = platform_device_add_data(xhci, cdns->xhci_plat_data, 10762306a36Sopenharmony_ci sizeof(struct xhci_plat_priv)); 10862306a36Sopenharmony_ci if (ret) 10962306a36Sopenharmony_ci goto free_memory; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci ret = platform_device_add(xhci); 11262306a36Sopenharmony_ci if (ret) { 11362306a36Sopenharmony_ci dev_err(cdns->dev, "failed to register xHCI device\n"); 11462306a36Sopenharmony_ci goto free_memory; 11562306a36Sopenharmony_ci } 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci /* Glue needs to access xHCI region register for Power management */ 11862306a36Sopenharmony_ci hcd = platform_get_drvdata(xhci); 11962306a36Sopenharmony_ci if (hcd) 12062306a36Sopenharmony_ci cdns->xhci_regs = hcd->regs; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci return 0; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_cifree_memory: 12562306a36Sopenharmony_ci kfree(cdns->xhci_plat_data); 12662306a36Sopenharmony_cierr1: 12762306a36Sopenharmony_ci platform_device_put(xhci); 12862306a36Sopenharmony_ci return ret; 12962306a36Sopenharmony_ci} 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_cistatic void cdns_host_exit(struct cdns *cdns) 13262306a36Sopenharmony_ci{ 13362306a36Sopenharmony_ci kfree(cdns->xhci_plat_data); 13462306a36Sopenharmony_ci platform_device_unregister(cdns->host_dev); 13562306a36Sopenharmony_ci cdns->host_dev = NULL; 13662306a36Sopenharmony_ci cdns_drd_host_off(cdns); 13762306a36Sopenharmony_ci} 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ciint cdns_host_init(struct cdns *cdns) 14062306a36Sopenharmony_ci{ 14162306a36Sopenharmony_ci struct cdns_role_driver *rdrv; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL); 14462306a36Sopenharmony_ci if (!rdrv) 14562306a36Sopenharmony_ci return -ENOMEM; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci rdrv->start = __cdns_host_init; 14862306a36Sopenharmony_ci rdrv->stop = cdns_host_exit; 14962306a36Sopenharmony_ci rdrv->state = CDNS_ROLE_STATE_INACTIVE; 15062306a36Sopenharmony_ci rdrv->name = "host"; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci cdns->roles[USB_ROLE_HOST] = rdrv; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci return 0; 15562306a36Sopenharmony_ci} 156