1*0b56e9a7SVivek Gautam /* 2*0b56e9a7SVivek Gautam * Copyright (C) 2015 Broadcom Corporation 3*0b56e9a7SVivek Gautam * 4*0b56e9a7SVivek Gautam * This program is free software; you can redistribute it and/or 5*0b56e9a7SVivek Gautam * modify it under the terms of the GNU General Public License as 6*0b56e9a7SVivek Gautam * published by the Free Software Foundation version 2. 7*0b56e9a7SVivek Gautam * 8*0b56e9a7SVivek Gautam * This program is distributed "as is" WITHOUT ANY WARRANTY of any 9*0b56e9a7SVivek Gautam * kind, whether express or implied; without even the implied warranty 10*0b56e9a7SVivek Gautam * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11*0b56e9a7SVivek Gautam * GNU General Public License for more details. 12*0b56e9a7SVivek Gautam */ 13*0b56e9a7SVivek Gautam 14*0b56e9a7SVivek Gautam #include <linux/delay.h> 15*0b56e9a7SVivek Gautam #include <linux/io.h> 16*0b56e9a7SVivek Gautam #include <linux/module.h> 17*0b56e9a7SVivek Gautam #include <linux/of.h> 18*0b56e9a7SVivek Gautam #include <linux/phy/phy.h> 19*0b56e9a7SVivek Gautam #include <linux/platform_device.h> 20*0b56e9a7SVivek Gautam 21*0b56e9a7SVivek Gautam #define PCIE_CFG_OFFSET 0x00 22*0b56e9a7SVivek Gautam #define PCIE1_PHY_IDDQ_SHIFT 10 23*0b56e9a7SVivek Gautam #define PCIE0_PHY_IDDQ_SHIFT 2 24*0b56e9a7SVivek Gautam 25*0b56e9a7SVivek Gautam enum cygnus_pcie_phy_id { 26*0b56e9a7SVivek Gautam CYGNUS_PHY_PCIE0 = 0, 27*0b56e9a7SVivek Gautam CYGNUS_PHY_PCIE1, 28*0b56e9a7SVivek Gautam MAX_NUM_PHYS, 29*0b56e9a7SVivek Gautam }; 30*0b56e9a7SVivek Gautam 31*0b56e9a7SVivek Gautam struct cygnus_pcie_phy_core; 32*0b56e9a7SVivek Gautam 33*0b56e9a7SVivek Gautam /** 34*0b56e9a7SVivek Gautam * struct cygnus_pcie_phy - Cygnus PCIe PHY device 35*0b56e9a7SVivek Gautam * @core: pointer to the Cygnus PCIe PHY core control 36*0b56e9a7SVivek Gautam * @id: internal ID to identify the Cygnus PCIe PHY 37*0b56e9a7SVivek Gautam * @phy: pointer to the kernel PHY device 38*0b56e9a7SVivek Gautam */ 39*0b56e9a7SVivek Gautam struct cygnus_pcie_phy { 40*0b56e9a7SVivek Gautam struct cygnus_pcie_phy_core *core; 41*0b56e9a7SVivek Gautam enum cygnus_pcie_phy_id id; 42*0b56e9a7SVivek Gautam struct phy *phy; 43*0b56e9a7SVivek Gautam }; 44*0b56e9a7SVivek Gautam 45*0b56e9a7SVivek Gautam /** 46*0b56e9a7SVivek Gautam * struct cygnus_pcie_phy_core - Cygnus PCIe PHY core control 47*0b56e9a7SVivek Gautam * @dev: pointer to device 48*0b56e9a7SVivek Gautam * @base: base register 49*0b56e9a7SVivek Gautam * @lock: mutex to protect access to individual PHYs 50*0b56e9a7SVivek Gautam * @phys: pointer to Cygnus PHY device 51*0b56e9a7SVivek Gautam */ 52*0b56e9a7SVivek Gautam struct cygnus_pcie_phy_core { 53*0b56e9a7SVivek Gautam struct device *dev; 54*0b56e9a7SVivek Gautam void __iomem *base; 55*0b56e9a7SVivek Gautam struct mutex lock; 56*0b56e9a7SVivek Gautam struct cygnus_pcie_phy phys[MAX_NUM_PHYS]; 57*0b56e9a7SVivek Gautam }; 58*0b56e9a7SVivek Gautam 59*0b56e9a7SVivek Gautam static int cygnus_pcie_power_config(struct cygnus_pcie_phy *phy, bool enable) 60*0b56e9a7SVivek Gautam { 61*0b56e9a7SVivek Gautam struct cygnus_pcie_phy_core *core = phy->core; 62*0b56e9a7SVivek Gautam unsigned shift; 63*0b56e9a7SVivek Gautam u32 val; 64*0b56e9a7SVivek Gautam 65*0b56e9a7SVivek Gautam mutex_lock(&core->lock); 66*0b56e9a7SVivek Gautam 67*0b56e9a7SVivek Gautam switch (phy->id) { 68*0b56e9a7SVivek Gautam case CYGNUS_PHY_PCIE0: 69*0b56e9a7SVivek Gautam shift = PCIE0_PHY_IDDQ_SHIFT; 70*0b56e9a7SVivek Gautam break; 71*0b56e9a7SVivek Gautam 72*0b56e9a7SVivek Gautam case CYGNUS_PHY_PCIE1: 73*0b56e9a7SVivek Gautam shift = PCIE1_PHY_IDDQ_SHIFT; 74*0b56e9a7SVivek Gautam break; 75*0b56e9a7SVivek Gautam 76*0b56e9a7SVivek Gautam default: 77*0b56e9a7SVivek Gautam mutex_unlock(&core->lock); 78*0b56e9a7SVivek Gautam dev_err(core->dev, "PCIe PHY %d invalid\n", phy->id); 79*0b56e9a7SVivek Gautam return -EINVAL; 80*0b56e9a7SVivek Gautam } 81*0b56e9a7SVivek Gautam 82*0b56e9a7SVivek Gautam if (enable) { 83*0b56e9a7SVivek Gautam val = readl(core->base + PCIE_CFG_OFFSET); 84*0b56e9a7SVivek Gautam val &= ~BIT(shift); 85*0b56e9a7SVivek Gautam writel(val, core->base + PCIE_CFG_OFFSET); 86*0b56e9a7SVivek Gautam /* 87*0b56e9a7SVivek Gautam * Wait 50 ms for the PCIe Serdes to stabilize after the analog 88*0b56e9a7SVivek Gautam * front end is brought up 89*0b56e9a7SVivek Gautam */ 90*0b56e9a7SVivek Gautam msleep(50); 91*0b56e9a7SVivek Gautam } else { 92*0b56e9a7SVivek Gautam val = readl(core->base + PCIE_CFG_OFFSET); 93*0b56e9a7SVivek Gautam val |= BIT(shift); 94*0b56e9a7SVivek Gautam writel(val, core->base + PCIE_CFG_OFFSET); 95*0b56e9a7SVivek Gautam } 96*0b56e9a7SVivek Gautam 97*0b56e9a7SVivek Gautam mutex_unlock(&core->lock); 98*0b56e9a7SVivek Gautam dev_dbg(core->dev, "PCIe PHY %d %s\n", phy->id, 99*0b56e9a7SVivek Gautam enable ? "enabled" : "disabled"); 100*0b56e9a7SVivek Gautam return 0; 101*0b56e9a7SVivek Gautam } 102*0b56e9a7SVivek Gautam 103*0b56e9a7SVivek Gautam static int cygnus_pcie_phy_power_on(struct phy *p) 104*0b56e9a7SVivek Gautam { 105*0b56e9a7SVivek Gautam struct cygnus_pcie_phy *phy = phy_get_drvdata(p); 106*0b56e9a7SVivek Gautam 107*0b56e9a7SVivek Gautam return cygnus_pcie_power_config(phy, true); 108*0b56e9a7SVivek Gautam } 109*0b56e9a7SVivek Gautam 110*0b56e9a7SVivek Gautam static int cygnus_pcie_phy_power_off(struct phy *p) 111*0b56e9a7SVivek Gautam { 112*0b56e9a7SVivek Gautam struct cygnus_pcie_phy *phy = phy_get_drvdata(p); 113*0b56e9a7SVivek Gautam 114*0b56e9a7SVivek Gautam return cygnus_pcie_power_config(phy, false); 115*0b56e9a7SVivek Gautam } 116*0b56e9a7SVivek Gautam 117*0b56e9a7SVivek Gautam static const struct phy_ops cygnus_pcie_phy_ops = { 118*0b56e9a7SVivek Gautam .power_on = cygnus_pcie_phy_power_on, 119*0b56e9a7SVivek Gautam .power_off = cygnus_pcie_phy_power_off, 120*0b56e9a7SVivek Gautam .owner = THIS_MODULE, 121*0b56e9a7SVivek Gautam }; 122*0b56e9a7SVivek Gautam 123*0b56e9a7SVivek Gautam static int cygnus_pcie_phy_probe(struct platform_device *pdev) 124*0b56e9a7SVivek Gautam { 125*0b56e9a7SVivek Gautam struct device *dev = &pdev->dev; 126*0b56e9a7SVivek Gautam struct device_node *node = dev->of_node, *child; 127*0b56e9a7SVivek Gautam struct cygnus_pcie_phy_core *core; 128*0b56e9a7SVivek Gautam struct phy_provider *provider; 129*0b56e9a7SVivek Gautam struct resource *res; 130*0b56e9a7SVivek Gautam unsigned cnt = 0; 131*0b56e9a7SVivek Gautam int ret; 132*0b56e9a7SVivek Gautam 133*0b56e9a7SVivek Gautam if (of_get_child_count(node) == 0) { 134*0b56e9a7SVivek Gautam dev_err(dev, "PHY no child node\n"); 135*0b56e9a7SVivek Gautam return -ENODEV; 136*0b56e9a7SVivek Gautam } 137*0b56e9a7SVivek Gautam 138*0b56e9a7SVivek Gautam core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL); 139*0b56e9a7SVivek Gautam if (!core) 140*0b56e9a7SVivek Gautam return -ENOMEM; 141*0b56e9a7SVivek Gautam 142*0b56e9a7SVivek Gautam core->dev = dev; 143*0b56e9a7SVivek Gautam 144*0b56e9a7SVivek Gautam res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 145*0b56e9a7SVivek Gautam core->base = devm_ioremap_resource(dev, res); 146*0b56e9a7SVivek Gautam if (IS_ERR(core->base)) 147*0b56e9a7SVivek Gautam return PTR_ERR(core->base); 148*0b56e9a7SVivek Gautam 149*0b56e9a7SVivek Gautam mutex_init(&core->lock); 150*0b56e9a7SVivek Gautam 151*0b56e9a7SVivek Gautam for_each_available_child_of_node(node, child) { 152*0b56e9a7SVivek Gautam unsigned int id; 153*0b56e9a7SVivek Gautam struct cygnus_pcie_phy *p; 154*0b56e9a7SVivek Gautam 155*0b56e9a7SVivek Gautam if (of_property_read_u32(child, "reg", &id)) { 156*0b56e9a7SVivek Gautam dev_err(dev, "missing reg property for %s\n", 157*0b56e9a7SVivek Gautam child->name); 158*0b56e9a7SVivek Gautam ret = -EINVAL; 159*0b56e9a7SVivek Gautam goto put_child; 160*0b56e9a7SVivek Gautam } 161*0b56e9a7SVivek Gautam 162*0b56e9a7SVivek Gautam if (id >= MAX_NUM_PHYS) { 163*0b56e9a7SVivek Gautam dev_err(dev, "invalid PHY id: %u\n", id); 164*0b56e9a7SVivek Gautam ret = -EINVAL; 165*0b56e9a7SVivek Gautam goto put_child; 166*0b56e9a7SVivek Gautam } 167*0b56e9a7SVivek Gautam 168*0b56e9a7SVivek Gautam if (core->phys[id].phy) { 169*0b56e9a7SVivek Gautam dev_err(dev, "duplicated PHY id: %u\n", id); 170*0b56e9a7SVivek Gautam ret = -EINVAL; 171*0b56e9a7SVivek Gautam goto put_child; 172*0b56e9a7SVivek Gautam } 173*0b56e9a7SVivek Gautam 174*0b56e9a7SVivek Gautam p = &core->phys[id]; 175*0b56e9a7SVivek Gautam p->phy = devm_phy_create(dev, child, &cygnus_pcie_phy_ops); 176*0b56e9a7SVivek Gautam if (IS_ERR(p->phy)) { 177*0b56e9a7SVivek Gautam dev_err(dev, "failed to create PHY\n"); 178*0b56e9a7SVivek Gautam ret = PTR_ERR(p->phy); 179*0b56e9a7SVivek Gautam goto put_child; 180*0b56e9a7SVivek Gautam } 181*0b56e9a7SVivek Gautam 182*0b56e9a7SVivek Gautam p->core = core; 183*0b56e9a7SVivek Gautam p->id = id; 184*0b56e9a7SVivek Gautam phy_set_drvdata(p->phy, p); 185*0b56e9a7SVivek Gautam cnt++; 186*0b56e9a7SVivek Gautam } 187*0b56e9a7SVivek Gautam 188*0b56e9a7SVivek Gautam dev_set_drvdata(dev, core); 189*0b56e9a7SVivek Gautam 190*0b56e9a7SVivek Gautam provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); 191*0b56e9a7SVivek Gautam if (IS_ERR(provider)) { 192*0b56e9a7SVivek Gautam dev_err(dev, "failed to register PHY provider\n"); 193*0b56e9a7SVivek Gautam return PTR_ERR(provider); 194*0b56e9a7SVivek Gautam } 195*0b56e9a7SVivek Gautam 196*0b56e9a7SVivek Gautam dev_dbg(dev, "registered %u PCIe PHY(s)\n", cnt); 197*0b56e9a7SVivek Gautam 198*0b56e9a7SVivek Gautam return 0; 199*0b56e9a7SVivek Gautam put_child: 200*0b56e9a7SVivek Gautam of_node_put(child); 201*0b56e9a7SVivek Gautam return ret; 202*0b56e9a7SVivek Gautam } 203*0b56e9a7SVivek Gautam 204*0b56e9a7SVivek Gautam static const struct of_device_id cygnus_pcie_phy_match_table[] = { 205*0b56e9a7SVivek Gautam { .compatible = "brcm,cygnus-pcie-phy" }, 206*0b56e9a7SVivek Gautam { /* sentinel */ } 207*0b56e9a7SVivek Gautam }; 208*0b56e9a7SVivek Gautam MODULE_DEVICE_TABLE(of, cygnus_pcie_phy_match_table); 209*0b56e9a7SVivek Gautam 210*0b56e9a7SVivek Gautam static struct platform_driver cygnus_pcie_phy_driver = { 211*0b56e9a7SVivek Gautam .driver = { 212*0b56e9a7SVivek Gautam .name = "cygnus-pcie-phy", 213*0b56e9a7SVivek Gautam .of_match_table = cygnus_pcie_phy_match_table, 214*0b56e9a7SVivek Gautam }, 215*0b56e9a7SVivek Gautam .probe = cygnus_pcie_phy_probe, 216*0b56e9a7SVivek Gautam }; 217*0b56e9a7SVivek Gautam module_platform_driver(cygnus_pcie_phy_driver); 218*0b56e9a7SVivek Gautam 219*0b56e9a7SVivek Gautam MODULE_AUTHOR("Ray Jui <rjui@broadcom.com>"); 220*0b56e9a7SVivek Gautam MODULE_DESCRIPTION("Broadcom Cygnus PCIe PHY driver"); 221*0b56e9a7SVivek Gautam MODULE_LICENSE("GPL v2"); 222