18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/** 38c2ecf20Sopenharmony_ci * cdns3-ti.c - TI specific Glue layer for Cadence USB Controller 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2019 Texas Instruments Incorporated - https://www.ti.com 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/bits.h> 98c2ecf20Sopenharmony_ci#include <linux/clk.h> 108c2ecf20Sopenharmony_ci#include <linux/module.h> 118c2ecf20Sopenharmony_ci#include <linux/kernel.h> 128c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 138c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 148c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h> 158c2ecf20Sopenharmony_ci#include <linux/io.h> 168c2ecf20Sopenharmony_ci#include <linux/of_platform.h> 178c2ecf20Sopenharmony_ci#include <linux/pm_runtime.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci/* USB Wrapper register offsets */ 208c2ecf20Sopenharmony_ci#define USBSS_PID 0x0 218c2ecf20Sopenharmony_ci#define USBSS_W1 0x4 228c2ecf20Sopenharmony_ci#define USBSS_STATIC_CONFIG 0x8 238c2ecf20Sopenharmony_ci#define USBSS_PHY_TEST 0xc 248c2ecf20Sopenharmony_ci#define USBSS_DEBUG_CTRL 0x10 258c2ecf20Sopenharmony_ci#define USBSS_DEBUG_INFO 0x14 268c2ecf20Sopenharmony_ci#define USBSS_DEBUG_LINK_STATE 0x18 278c2ecf20Sopenharmony_ci#define USBSS_DEVICE_CTRL 0x1c 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci/* Wrapper 1 register bits */ 308c2ecf20Sopenharmony_ci#define USBSS_W1_PWRUP_RST BIT(0) 318c2ecf20Sopenharmony_ci#define USBSS_W1_OVERCURRENT_SEL BIT(8) 328c2ecf20Sopenharmony_ci#define USBSS_W1_MODESTRAP_SEL BIT(9) 338c2ecf20Sopenharmony_ci#define USBSS_W1_OVERCURRENT BIT(16) 348c2ecf20Sopenharmony_ci#define USBSS_W1_MODESTRAP_MASK GENMASK(18, 17) 358c2ecf20Sopenharmony_ci#define USBSS_W1_MODESTRAP_SHIFT 17 368c2ecf20Sopenharmony_ci#define USBSS_W1_USB2_ONLY BIT(19) 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci/* Static config register bits */ 398c2ecf20Sopenharmony_ci#define USBSS1_STATIC_PLL_REF_SEL_MASK GENMASK(8, 5) 408c2ecf20Sopenharmony_ci#define USBSS1_STATIC_PLL_REF_SEL_SHIFT 5 418c2ecf20Sopenharmony_ci#define USBSS1_STATIC_LOOPBACK_MODE_MASK GENMASK(4, 3) 428c2ecf20Sopenharmony_ci#define USBSS1_STATIC_LOOPBACK_MODE_SHIFT 3 438c2ecf20Sopenharmony_ci#define USBSS1_STATIC_VBUS_SEL_MASK GENMASK(2, 1) 448c2ecf20Sopenharmony_ci#define USBSS1_STATIC_VBUS_SEL_SHIFT 1 458c2ecf20Sopenharmony_ci#define USBSS1_STATIC_LANE_REVERSE BIT(0) 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci/* Modestrap modes */ 488c2ecf20Sopenharmony_cienum modestrap_mode { USBSS_MODESTRAP_MODE_NONE, 498c2ecf20Sopenharmony_ci USBSS_MODESTRAP_MODE_HOST, 508c2ecf20Sopenharmony_ci USBSS_MODESTRAP_MODE_PERIPHERAL}; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistruct cdns_ti { 538c2ecf20Sopenharmony_ci struct device *dev; 548c2ecf20Sopenharmony_ci void __iomem *usbss; 558c2ecf20Sopenharmony_ci unsigned usb2_only:1; 568c2ecf20Sopenharmony_ci unsigned vbus_divider:1; 578c2ecf20Sopenharmony_ci struct clk *usb2_refclk; 588c2ecf20Sopenharmony_ci struct clk *lpm_clk; 598c2ecf20Sopenharmony_ci}; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_cistatic const int cdns_ti_rate_table[] = { /* in KHZ */ 628c2ecf20Sopenharmony_ci 9600, 638c2ecf20Sopenharmony_ci 10000, 648c2ecf20Sopenharmony_ci 12000, 658c2ecf20Sopenharmony_ci 19200, 668c2ecf20Sopenharmony_ci 20000, 678c2ecf20Sopenharmony_ci 24000, 688c2ecf20Sopenharmony_ci 25000, 698c2ecf20Sopenharmony_ci 26000, 708c2ecf20Sopenharmony_ci 38400, 718c2ecf20Sopenharmony_ci 40000, 728c2ecf20Sopenharmony_ci 58000, 738c2ecf20Sopenharmony_ci 50000, 748c2ecf20Sopenharmony_ci 52000, 758c2ecf20Sopenharmony_ci}; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_cistatic inline u32 cdns_ti_readl(struct cdns_ti *data, u32 offset) 788c2ecf20Sopenharmony_ci{ 798c2ecf20Sopenharmony_ci return readl(data->usbss + offset); 808c2ecf20Sopenharmony_ci} 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_cistatic inline void cdns_ti_writel(struct cdns_ti *data, u32 offset, u32 value) 838c2ecf20Sopenharmony_ci{ 848c2ecf20Sopenharmony_ci writel(value, data->usbss + offset); 858c2ecf20Sopenharmony_ci} 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_cistatic int cdns_ti_probe(struct platform_device *pdev) 888c2ecf20Sopenharmony_ci{ 898c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 908c2ecf20Sopenharmony_ci struct device_node *node = pdev->dev.of_node; 918c2ecf20Sopenharmony_ci struct cdns_ti *data; 928c2ecf20Sopenharmony_ci int error; 938c2ecf20Sopenharmony_ci u32 reg; 948c2ecf20Sopenharmony_ci int rate_code, i; 958c2ecf20Sopenharmony_ci unsigned long rate; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); 988c2ecf20Sopenharmony_ci if (!data) 998c2ecf20Sopenharmony_ci return -ENOMEM; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, data); 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci data->dev = dev; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci data->usbss = devm_platform_ioremap_resource(pdev, 0); 1068c2ecf20Sopenharmony_ci if (IS_ERR(data->usbss)) { 1078c2ecf20Sopenharmony_ci dev_err(dev, "can't map IOMEM resource\n"); 1088c2ecf20Sopenharmony_ci return PTR_ERR(data->usbss); 1098c2ecf20Sopenharmony_ci } 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci data->usb2_refclk = devm_clk_get(dev, "ref"); 1128c2ecf20Sopenharmony_ci if (IS_ERR(data->usb2_refclk)) { 1138c2ecf20Sopenharmony_ci dev_err(dev, "can't get usb2_refclk\n"); 1148c2ecf20Sopenharmony_ci return PTR_ERR(data->usb2_refclk); 1158c2ecf20Sopenharmony_ci } 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci data->lpm_clk = devm_clk_get(dev, "lpm"); 1188c2ecf20Sopenharmony_ci if (IS_ERR(data->lpm_clk)) { 1198c2ecf20Sopenharmony_ci dev_err(dev, "can't get lpm_clk\n"); 1208c2ecf20Sopenharmony_ci return PTR_ERR(data->lpm_clk); 1218c2ecf20Sopenharmony_ci } 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci rate = clk_get_rate(data->usb2_refclk); 1248c2ecf20Sopenharmony_ci rate /= 1000; /* To KHz */ 1258c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(cdns_ti_rate_table); i++) { 1268c2ecf20Sopenharmony_ci if (cdns_ti_rate_table[i] == rate) 1278c2ecf20Sopenharmony_ci break; 1288c2ecf20Sopenharmony_ci } 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci if (i == ARRAY_SIZE(cdns_ti_rate_table)) { 1318c2ecf20Sopenharmony_ci dev_err(dev, "unsupported usb2_refclk rate: %lu KHz\n", rate); 1328c2ecf20Sopenharmony_ci return -EINVAL; 1338c2ecf20Sopenharmony_ci } 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci rate_code = i; 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci pm_runtime_enable(dev); 1388c2ecf20Sopenharmony_ci error = pm_runtime_get_sync(dev); 1398c2ecf20Sopenharmony_ci if (error < 0) { 1408c2ecf20Sopenharmony_ci dev_err(dev, "pm_runtime_get_sync failed: %d\n", error); 1418c2ecf20Sopenharmony_ci goto err; 1428c2ecf20Sopenharmony_ci } 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci /* assert RESET */ 1458c2ecf20Sopenharmony_ci reg = cdns_ti_readl(data, USBSS_W1); 1468c2ecf20Sopenharmony_ci reg &= ~USBSS_W1_PWRUP_RST; 1478c2ecf20Sopenharmony_ci cdns_ti_writel(data, USBSS_W1, reg); 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci /* set static config */ 1508c2ecf20Sopenharmony_ci reg = cdns_ti_readl(data, USBSS_STATIC_CONFIG); 1518c2ecf20Sopenharmony_ci reg &= ~USBSS1_STATIC_PLL_REF_SEL_MASK; 1528c2ecf20Sopenharmony_ci reg |= rate_code << USBSS1_STATIC_PLL_REF_SEL_SHIFT; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci reg &= ~USBSS1_STATIC_VBUS_SEL_MASK; 1558c2ecf20Sopenharmony_ci data->vbus_divider = device_property_read_bool(dev, "ti,vbus-divider"); 1568c2ecf20Sopenharmony_ci if (data->vbus_divider) 1578c2ecf20Sopenharmony_ci reg |= 1 << USBSS1_STATIC_VBUS_SEL_SHIFT; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci cdns_ti_writel(data, USBSS_STATIC_CONFIG, reg); 1608c2ecf20Sopenharmony_ci reg = cdns_ti_readl(data, USBSS_STATIC_CONFIG); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci /* set USB2_ONLY mode if requested */ 1638c2ecf20Sopenharmony_ci reg = cdns_ti_readl(data, USBSS_W1); 1648c2ecf20Sopenharmony_ci data->usb2_only = device_property_read_bool(dev, "ti,usb2-only"); 1658c2ecf20Sopenharmony_ci if (data->usb2_only) 1668c2ecf20Sopenharmony_ci reg |= USBSS_W1_USB2_ONLY; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci /* set default modestrap */ 1698c2ecf20Sopenharmony_ci reg |= USBSS_W1_MODESTRAP_SEL; 1708c2ecf20Sopenharmony_ci reg &= ~USBSS_W1_MODESTRAP_MASK; 1718c2ecf20Sopenharmony_ci reg |= USBSS_MODESTRAP_MODE_NONE << USBSS_W1_MODESTRAP_SHIFT; 1728c2ecf20Sopenharmony_ci cdns_ti_writel(data, USBSS_W1, reg); 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci /* de-assert RESET */ 1758c2ecf20Sopenharmony_ci reg |= USBSS_W1_PWRUP_RST; 1768c2ecf20Sopenharmony_ci cdns_ti_writel(data, USBSS_W1, reg); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci error = of_platform_populate(node, NULL, NULL, dev); 1798c2ecf20Sopenharmony_ci if (error) { 1808c2ecf20Sopenharmony_ci dev_err(dev, "failed to create children: %d\n", error); 1818c2ecf20Sopenharmony_ci goto err; 1828c2ecf20Sopenharmony_ci } 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci return 0; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_cierr: 1878c2ecf20Sopenharmony_ci pm_runtime_put_sync(data->dev); 1888c2ecf20Sopenharmony_ci pm_runtime_disable(data->dev); 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci return error; 1918c2ecf20Sopenharmony_ci} 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_cistatic int cdns_ti_remove_core(struct device *dev, void *c) 1948c2ecf20Sopenharmony_ci{ 1958c2ecf20Sopenharmony_ci struct platform_device *pdev = to_platform_device(dev); 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci platform_device_unregister(pdev); 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci return 0; 2008c2ecf20Sopenharmony_ci} 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_cistatic int cdns_ti_remove(struct platform_device *pdev) 2038c2ecf20Sopenharmony_ci{ 2048c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci device_for_each_child(dev, NULL, cdns_ti_remove_core); 2078c2ecf20Sopenharmony_ci pm_runtime_put_sync(dev); 2088c2ecf20Sopenharmony_ci pm_runtime_disable(dev); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, NULL); 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci return 0; 2138c2ecf20Sopenharmony_ci} 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_cistatic const struct of_device_id cdns_ti_of_match[] = { 2168c2ecf20Sopenharmony_ci { .compatible = "ti,j721e-usb", }, 2178c2ecf20Sopenharmony_ci {}, 2188c2ecf20Sopenharmony_ci}; 2198c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, cdns_ti_of_match); 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_cistatic struct platform_driver cdns_ti_driver = { 2228c2ecf20Sopenharmony_ci .probe = cdns_ti_probe, 2238c2ecf20Sopenharmony_ci .remove = cdns_ti_remove, 2248c2ecf20Sopenharmony_ci .driver = { 2258c2ecf20Sopenharmony_ci .name = "cdns3-ti", 2268c2ecf20Sopenharmony_ci .of_match_table = cdns_ti_of_match, 2278c2ecf20Sopenharmony_ci }, 2288c2ecf20Sopenharmony_ci}; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_cimodule_platform_driver(cdns_ti_driver); 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:cdns3-ti"); 2338c2ecf20Sopenharmony_ciMODULE_AUTHOR("Roger Quadros <rogerq@ti.com>"); 2348c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 2358c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Cadence USB3 TI Glue Layer"); 236