10b56e9a7SVivek Gautam /* 20b56e9a7SVivek Gautam * Broadcom Northstar USB 3.0 PHY Driver 30b56e9a7SVivek Gautam * 40b56e9a7SVivek Gautam * Copyright (C) 2016 Rafał Miłecki <rafal@milecki.pl> 50b56e9a7SVivek Gautam * Copyright (C) 2016 Broadcom 60b56e9a7SVivek Gautam * 70b56e9a7SVivek Gautam * All magic values used for initialization (and related comments) were obtained 80b56e9a7SVivek Gautam * from Broadcom's SDK: 90b56e9a7SVivek Gautam * Copyright (c) Broadcom Corp, 2012 100b56e9a7SVivek Gautam * 110b56e9a7SVivek Gautam * This program is free software; you can redistribute it and/or modify 120b56e9a7SVivek Gautam * it under the terms of the GNU General Public License version 2 as 130b56e9a7SVivek Gautam * published by the Free Software Foundation. 140b56e9a7SVivek Gautam */ 150b56e9a7SVivek Gautam 160b56e9a7SVivek Gautam #include <linux/bcma/bcma.h> 170b56e9a7SVivek Gautam #include <linux/delay.h> 180b56e9a7SVivek Gautam #include <linux/err.h> 190b56e9a7SVivek Gautam #include <linux/module.h> 200b56e9a7SVivek Gautam #include <linux/of_platform.h> 210b56e9a7SVivek Gautam #include <linux/platform_device.h> 220b56e9a7SVivek Gautam #include <linux/phy/phy.h> 230b56e9a7SVivek Gautam #include <linux/slab.h> 240b56e9a7SVivek Gautam 250b56e9a7SVivek Gautam #define BCM_NS_USB3_MII_MNG_TIMEOUT_US 1000 /* usecs */ 260b56e9a7SVivek Gautam 270b56e9a7SVivek Gautam #define BCM_NS_USB3_PHY_BASE_ADDR_REG 0x1f 280b56e9a7SVivek Gautam #define BCM_NS_USB3_PHY_PLL30_BLOCK 0x8000 290b56e9a7SVivek Gautam #define BCM_NS_USB3_PHY_TX_PMD_BLOCK 0x8040 300b56e9a7SVivek Gautam #define BCM_NS_USB3_PHY_PIPE_BLOCK 0x8060 310b56e9a7SVivek Gautam 320b56e9a7SVivek Gautam /* Registers of PLL30 block */ 330b56e9a7SVivek Gautam #define BCM_NS_USB3_PLL_CONTROL 0x01 340b56e9a7SVivek Gautam #define BCM_NS_USB3_PLLA_CONTROL0 0x0a 350b56e9a7SVivek Gautam #define BCM_NS_USB3_PLLA_CONTROL1 0x0b 360b56e9a7SVivek Gautam 370b56e9a7SVivek Gautam /* Registers of TX PMD block */ 380b56e9a7SVivek Gautam #define BCM_NS_USB3_TX_PMD_CONTROL1 0x01 390b56e9a7SVivek Gautam 400b56e9a7SVivek Gautam /* Registers of PIPE block */ 410b56e9a7SVivek Gautam #define BCM_NS_USB3_LFPS_CMP 0x02 420b56e9a7SVivek Gautam #define BCM_NS_USB3_LFPS_DEGLITCH 0x03 430b56e9a7SVivek Gautam 440b56e9a7SVivek Gautam enum bcm_ns_family { 450b56e9a7SVivek Gautam BCM_NS_UNKNOWN, 460b56e9a7SVivek Gautam BCM_NS_AX, 470b56e9a7SVivek Gautam BCM_NS_BX, 480b56e9a7SVivek Gautam }; 490b56e9a7SVivek Gautam 500b56e9a7SVivek Gautam struct bcm_ns_usb3 { 510b56e9a7SVivek Gautam struct device *dev; 520b56e9a7SVivek Gautam enum bcm_ns_family family; 530b56e9a7SVivek Gautam void __iomem *dmp; 540b56e9a7SVivek Gautam void __iomem *ccb_mii; 550b56e9a7SVivek Gautam struct phy *phy; 56b20f506fSRafał Miłecki 57b20f506fSRafał Miłecki int (*phy_write)(struct bcm_ns_usb3 *usb3, u16 reg, u16 value); 580b56e9a7SVivek Gautam }; 590b56e9a7SVivek Gautam 600b56e9a7SVivek Gautam static const struct of_device_id bcm_ns_usb3_id_table[] = { 610b56e9a7SVivek Gautam { 620b56e9a7SVivek Gautam .compatible = "brcm,ns-ax-usb3-phy", 630b56e9a7SVivek Gautam .data = (int *)BCM_NS_AX, 640b56e9a7SVivek Gautam }, 650b56e9a7SVivek Gautam { 660b56e9a7SVivek Gautam .compatible = "brcm,ns-bx-usb3-phy", 670b56e9a7SVivek Gautam .data = (int *)BCM_NS_BX, 680b56e9a7SVivek Gautam }, 690b56e9a7SVivek Gautam {}, 700b56e9a7SVivek Gautam }; 710b56e9a7SVivek Gautam MODULE_DEVICE_TABLE(of, bcm_ns_usb3_id_table); 720b56e9a7SVivek Gautam 730b56e9a7SVivek Gautam static int bcm_ns_usb3_mdio_phy_write(struct bcm_ns_usb3 *usb3, u16 reg, 740b56e9a7SVivek Gautam u16 value) 750b56e9a7SVivek Gautam { 76b20f506fSRafał Miłecki return usb3->phy_write(usb3, reg, value); 770b56e9a7SVivek Gautam } 780b56e9a7SVivek Gautam 790b56e9a7SVivek Gautam static int bcm_ns_usb3_phy_init_ns_bx(struct bcm_ns_usb3 *usb3) 800b56e9a7SVivek Gautam { 810b56e9a7SVivek Gautam int err; 820b56e9a7SVivek Gautam 830b56e9a7SVivek Gautam /* Enable MDIO. Setting MDCDIV as 26 */ 840b56e9a7SVivek Gautam writel(0x0000009a, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL); 850b56e9a7SVivek Gautam 860b56e9a7SVivek Gautam /* Wait for MDIO? */ 870b56e9a7SVivek Gautam udelay(2); 880b56e9a7SVivek Gautam 890b56e9a7SVivek Gautam /* USB3 PLL Block */ 900b56e9a7SVivek Gautam err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, 910b56e9a7SVivek Gautam BCM_NS_USB3_PHY_PLL30_BLOCK); 920b56e9a7SVivek Gautam if (err < 0) 930b56e9a7SVivek Gautam return err; 940b56e9a7SVivek Gautam 950b56e9a7SVivek Gautam /* Assert Ana_Pllseq start */ 960b56e9a7SVivek Gautam bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLL_CONTROL, 0x1000); 970b56e9a7SVivek Gautam 980b56e9a7SVivek Gautam /* Assert CML Divider ratio to 26 */ 990b56e9a7SVivek Gautam bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL0, 0x6400); 1000b56e9a7SVivek Gautam 1010b56e9a7SVivek Gautam /* Asserting PLL Reset */ 1020b56e9a7SVivek Gautam bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL1, 0xc000); 1030b56e9a7SVivek Gautam 1040b56e9a7SVivek Gautam /* Deaaserting PLL Reset */ 1050b56e9a7SVivek Gautam bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL1, 0x8000); 1060b56e9a7SVivek Gautam 1070b56e9a7SVivek Gautam /* Deasserting USB3 system reset */ 1080b56e9a7SVivek Gautam writel(0, usb3->dmp + BCMA_RESET_CTL); 1090b56e9a7SVivek Gautam 1100b56e9a7SVivek Gautam /* PLL frequency monitor enable */ 1110b56e9a7SVivek Gautam bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLL_CONTROL, 0x9000); 1120b56e9a7SVivek Gautam 1130b56e9a7SVivek Gautam /* PIPE Block */ 1140b56e9a7SVivek Gautam bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, 1150b56e9a7SVivek Gautam BCM_NS_USB3_PHY_PIPE_BLOCK); 1160b56e9a7SVivek Gautam 1170b56e9a7SVivek Gautam /* CMPMAX & CMPMINTH setting */ 1180b56e9a7SVivek Gautam bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_LFPS_CMP, 0xf30d); 1190b56e9a7SVivek Gautam 1200b56e9a7SVivek Gautam /* DEGLITCH MIN & MAX setting */ 1210b56e9a7SVivek Gautam bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_LFPS_DEGLITCH, 0x6302); 1220b56e9a7SVivek Gautam 1230b56e9a7SVivek Gautam /* TXPMD block */ 1240b56e9a7SVivek Gautam bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, 1250b56e9a7SVivek Gautam BCM_NS_USB3_PHY_TX_PMD_BLOCK); 1260b56e9a7SVivek Gautam 1270b56e9a7SVivek Gautam /* Enabling SSC */ 1280b56e9a7SVivek Gautam bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003); 1290b56e9a7SVivek Gautam 1300b56e9a7SVivek Gautam return 0; 1310b56e9a7SVivek Gautam } 1320b56e9a7SVivek Gautam 1330b56e9a7SVivek Gautam static int bcm_ns_usb3_phy_init_ns_ax(struct bcm_ns_usb3 *usb3) 1340b56e9a7SVivek Gautam { 1350b56e9a7SVivek Gautam int err; 1360b56e9a7SVivek Gautam 1370b56e9a7SVivek Gautam /* Enable MDIO. Setting MDCDIV as 26 */ 1380b56e9a7SVivek Gautam writel(0x0000009a, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL); 1390b56e9a7SVivek Gautam 1400b56e9a7SVivek Gautam /* Wait for MDIO? */ 1410b56e9a7SVivek Gautam udelay(2); 1420b56e9a7SVivek Gautam 1430b56e9a7SVivek Gautam /* PLL30 block */ 1440b56e9a7SVivek Gautam err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, 1450b56e9a7SVivek Gautam BCM_NS_USB3_PHY_PLL30_BLOCK); 1460b56e9a7SVivek Gautam if (err < 0) 1470b56e9a7SVivek Gautam return err; 1480b56e9a7SVivek Gautam 1490b56e9a7SVivek Gautam bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL0, 0x6400); 1500b56e9a7SVivek Gautam 1510b56e9a7SVivek Gautam bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, 0x80e0); 1520b56e9a7SVivek Gautam 1530b56e9a7SVivek Gautam bcm_ns_usb3_mdio_phy_write(usb3, 0x02, 0x009c); 1540b56e9a7SVivek Gautam 1550b56e9a7SVivek Gautam /* Enable SSC */ 1560b56e9a7SVivek Gautam bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, 1570b56e9a7SVivek Gautam BCM_NS_USB3_PHY_TX_PMD_BLOCK); 1580b56e9a7SVivek Gautam 1590b56e9a7SVivek Gautam bcm_ns_usb3_mdio_phy_write(usb3, 0x02, 0x21d3); 1600b56e9a7SVivek Gautam 1610b56e9a7SVivek Gautam bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003); 1620b56e9a7SVivek Gautam 1630b56e9a7SVivek Gautam /* Deasserting USB3 system reset */ 1640b56e9a7SVivek Gautam writel(0, usb3->dmp + BCMA_RESET_CTL); 1650b56e9a7SVivek Gautam 1660b56e9a7SVivek Gautam return 0; 1670b56e9a7SVivek Gautam } 1680b56e9a7SVivek Gautam 1690b56e9a7SVivek Gautam static int bcm_ns_usb3_phy_init(struct phy *phy) 1700b56e9a7SVivek Gautam { 1710b56e9a7SVivek Gautam struct bcm_ns_usb3 *usb3 = phy_get_drvdata(phy); 1720b56e9a7SVivek Gautam int err; 1730b56e9a7SVivek Gautam 1740b56e9a7SVivek Gautam /* Perform USB3 system soft reset */ 1750b56e9a7SVivek Gautam writel(BCMA_RESET_CTL_RESET, usb3->dmp + BCMA_RESET_CTL); 1760b56e9a7SVivek Gautam 1770b56e9a7SVivek Gautam switch (usb3->family) { 1780b56e9a7SVivek Gautam case BCM_NS_AX: 1790b56e9a7SVivek Gautam err = bcm_ns_usb3_phy_init_ns_ax(usb3); 1800b56e9a7SVivek Gautam break; 1810b56e9a7SVivek Gautam case BCM_NS_BX: 1820b56e9a7SVivek Gautam err = bcm_ns_usb3_phy_init_ns_bx(usb3); 1830b56e9a7SVivek Gautam break; 1840b56e9a7SVivek Gautam default: 1850b56e9a7SVivek Gautam WARN_ON(1); 1860b56e9a7SVivek Gautam err = -ENOTSUPP; 1870b56e9a7SVivek Gautam } 1880b56e9a7SVivek Gautam 1890b56e9a7SVivek Gautam return err; 1900b56e9a7SVivek Gautam } 1910b56e9a7SVivek Gautam 1920b56e9a7SVivek Gautam static const struct phy_ops ops = { 1930b56e9a7SVivek Gautam .init = bcm_ns_usb3_phy_init, 1940b56e9a7SVivek Gautam .owner = THIS_MODULE, 1950b56e9a7SVivek Gautam }; 1960b56e9a7SVivek Gautam 197b20f506fSRafał Miłecki /************************************************** 198b20f506fSRafał Miłecki * Platform driver code 199b20f506fSRafał Miłecki **************************************************/ 200b20f506fSRafał Miłecki 201b20f506fSRafał Miłecki static int bcm_ns_usb3_wait_reg(struct bcm_ns_usb3 *usb3, void __iomem *addr, 202b20f506fSRafał Miłecki u32 mask, u32 value, unsigned long timeout) 203b20f506fSRafał Miłecki { 204b20f506fSRafał Miłecki unsigned long deadline = jiffies + timeout; 205b20f506fSRafał Miłecki u32 val; 206b20f506fSRafał Miłecki 207b20f506fSRafał Miłecki do { 208b20f506fSRafał Miłecki val = readl(addr); 209b20f506fSRafał Miłecki if ((val & mask) == value) 210b20f506fSRafał Miłecki return 0; 211b20f506fSRafał Miłecki cpu_relax(); 212b20f506fSRafał Miłecki udelay(10); 213b20f506fSRafał Miłecki } while (!time_after_eq(jiffies, deadline)); 214b20f506fSRafał Miłecki 215b20f506fSRafał Miłecki dev_err(usb3->dev, "Timeout waiting for register %p\n", addr); 216b20f506fSRafał Miłecki 217b20f506fSRafał Miłecki return -EBUSY; 218b20f506fSRafał Miłecki } 219b20f506fSRafał Miłecki 220b20f506fSRafał Miłecki static inline int bcm_ns_usb3_mii_mng_wait_idle(struct bcm_ns_usb3 *usb3) 221b20f506fSRafał Miłecki { 222b20f506fSRafał Miłecki return bcm_ns_usb3_wait_reg(usb3, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL, 223b20f506fSRafał Miłecki 0x0100, 0x0000, 224b20f506fSRafał Miłecki usecs_to_jiffies(BCM_NS_USB3_MII_MNG_TIMEOUT_US)); 225b20f506fSRafał Miłecki } 226b20f506fSRafał Miłecki 227b20f506fSRafał Miłecki static int bcm_ns_usb3_platform_phy_write(struct bcm_ns_usb3 *usb3, u16 reg, 228b20f506fSRafał Miłecki u16 value) 229b20f506fSRafał Miłecki { 230b20f506fSRafał Miłecki u32 tmp = 0; 231b20f506fSRafał Miłecki int err; 232b20f506fSRafał Miłecki 233b20f506fSRafał Miłecki err = bcm_ns_usb3_mii_mng_wait_idle(usb3); 234b20f506fSRafał Miłecki if (err < 0) { 235b20f506fSRafał Miłecki dev_err(usb3->dev, "Couldn't write 0x%08x value\n", value); 236b20f506fSRafał Miłecki return err; 237b20f506fSRafał Miłecki } 238b20f506fSRafał Miłecki 239b20f506fSRafał Miłecki /* TODO: Use a proper MDIO bus layer */ 240b20f506fSRafał Miłecki tmp |= 0x58020000; /* Magic value for MDIO PHY write */ 241b20f506fSRafał Miłecki tmp |= reg << 18; 242b20f506fSRafał Miłecki tmp |= value; 243b20f506fSRafał Miłecki writel(tmp, usb3->ccb_mii + BCMA_CCB_MII_MNG_CMD_DATA); 244b20f506fSRafał Miłecki 245b20f506fSRafał Miłecki return bcm_ns_usb3_mii_mng_wait_idle(usb3); 246b20f506fSRafał Miłecki } 247b20f506fSRafał Miłecki 2480b56e9a7SVivek Gautam static int bcm_ns_usb3_probe(struct platform_device *pdev) 2490b56e9a7SVivek Gautam { 2500b56e9a7SVivek Gautam struct device *dev = &pdev->dev; 2510b56e9a7SVivek Gautam const struct of_device_id *of_id; 2520b56e9a7SVivek Gautam struct bcm_ns_usb3 *usb3; 2530b56e9a7SVivek Gautam struct resource *res; 2540b56e9a7SVivek Gautam struct phy_provider *phy_provider; 2550b56e9a7SVivek Gautam 2560b56e9a7SVivek Gautam usb3 = devm_kzalloc(dev, sizeof(*usb3), GFP_KERNEL); 2570b56e9a7SVivek Gautam if (!usb3) 2580b56e9a7SVivek Gautam return -ENOMEM; 2590b56e9a7SVivek Gautam 2600b56e9a7SVivek Gautam usb3->dev = dev; 2610b56e9a7SVivek Gautam 2620b56e9a7SVivek Gautam of_id = of_match_device(bcm_ns_usb3_id_table, dev); 2630b56e9a7SVivek Gautam if (!of_id) 2640b56e9a7SVivek Gautam return -EINVAL; 2650b56e9a7SVivek Gautam usb3->family = (enum bcm_ns_family)of_id->data; 2660b56e9a7SVivek Gautam 2670b56e9a7SVivek Gautam res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dmp"); 2680b56e9a7SVivek Gautam usb3->dmp = devm_ioremap_resource(dev, res); 2690b56e9a7SVivek Gautam if (IS_ERR(usb3->dmp)) { 2700b56e9a7SVivek Gautam dev_err(dev, "Failed to map DMP regs\n"); 2710b56e9a7SVivek Gautam return PTR_ERR(usb3->dmp); 2720b56e9a7SVivek Gautam } 2730b56e9a7SVivek Gautam 2740b56e9a7SVivek Gautam res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ccb-mii"); 2750b56e9a7SVivek Gautam usb3->ccb_mii = devm_ioremap_resource(dev, res); 2760b56e9a7SVivek Gautam if (IS_ERR(usb3->ccb_mii)) { 2770b56e9a7SVivek Gautam dev_err(dev, "Failed to map ChipCommon B MII regs\n"); 2780b56e9a7SVivek Gautam return PTR_ERR(usb3->ccb_mii); 2790b56e9a7SVivek Gautam } 2800b56e9a7SVivek Gautam 281b20f506fSRafał Miłecki usb3->phy_write = bcm_ns_usb3_platform_phy_write; 282b20f506fSRafał Miłecki 2830b56e9a7SVivek Gautam usb3->phy = devm_phy_create(dev, NULL, &ops); 2840b56e9a7SVivek Gautam if (IS_ERR(usb3->phy)) { 2850b56e9a7SVivek Gautam dev_err(dev, "Failed to create PHY\n"); 2860b56e9a7SVivek Gautam return PTR_ERR(usb3->phy); 2870b56e9a7SVivek Gautam } 2880b56e9a7SVivek Gautam 2890b56e9a7SVivek Gautam phy_set_drvdata(usb3->phy, usb3); 2900b56e9a7SVivek Gautam platform_set_drvdata(pdev, usb3); 2910b56e9a7SVivek Gautam 2920b56e9a7SVivek Gautam phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); 2930b56e9a7SVivek Gautam if (!IS_ERR(phy_provider)) 2940b56e9a7SVivek Gautam dev_info(dev, "Registered Broadcom Northstar USB 3.0 PHY driver\n"); 2950b56e9a7SVivek Gautam 2960b56e9a7SVivek Gautam return PTR_ERR_OR_ZERO(phy_provider); 2970b56e9a7SVivek Gautam } 2980b56e9a7SVivek Gautam 2990b56e9a7SVivek Gautam static struct platform_driver bcm_ns_usb3_driver = { 3000b56e9a7SVivek Gautam .probe = bcm_ns_usb3_probe, 3010b56e9a7SVivek Gautam .driver = { 3020b56e9a7SVivek Gautam .name = "bcm_ns_usb3", 3030b56e9a7SVivek Gautam .of_match_table = bcm_ns_usb3_id_table, 3040b56e9a7SVivek Gautam }, 3050b56e9a7SVivek Gautam }; 3060b56e9a7SVivek Gautam module_platform_driver(bcm_ns_usb3_driver); 3070b56e9a7SVivek Gautam 3080b56e9a7SVivek Gautam MODULE_LICENSE("GPL v2"); 309