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