phy-bcm-cygnus-pcie.c (5193B)
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 const 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 unsigned cnt = 0; 130 int ret; 131 132 if (of_get_child_count(node) == 0) { 133 dev_err(dev, "PHY no child node\n"); 134 return -ENODEV; 135 } 136 137 core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL); 138 if (!core) 139 return -ENOMEM; 140 141 core->dev = dev; 142 143 core->base = devm_platform_ioremap_resource(pdev, 0); 144 if (IS_ERR(core->base)) 145 return PTR_ERR(core->base); 146 147 mutex_init(&core->lock); 148 149 for_each_available_child_of_node(node, child) { 150 unsigned int id; 151 struct cygnus_pcie_phy *p; 152 153 if (of_property_read_u32(child, "reg", &id)) { 154 dev_err(dev, "missing reg property for %pOFn\n", 155 child); 156 ret = -EINVAL; 157 goto put_child; 158 } 159 160 if (id >= MAX_NUM_PHYS) { 161 dev_err(dev, "invalid PHY id: %u\n", id); 162 ret = -EINVAL; 163 goto put_child; 164 } 165 166 if (core->phys[id].phy) { 167 dev_err(dev, "duplicated PHY id: %u\n", id); 168 ret = -EINVAL; 169 goto put_child; 170 } 171 172 p = &core->phys[id]; 173 p->phy = devm_phy_create(dev, child, &cygnus_pcie_phy_ops); 174 if (IS_ERR(p->phy)) { 175 dev_err(dev, "failed to create PHY\n"); 176 ret = PTR_ERR(p->phy); 177 goto put_child; 178 } 179 180 p->core = core; 181 p->id = id; 182 phy_set_drvdata(p->phy, p); 183 cnt++; 184 } 185 186 dev_set_drvdata(dev, core); 187 188 provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); 189 if (IS_ERR(provider)) { 190 dev_err(dev, "failed to register PHY provider\n"); 191 return PTR_ERR(provider); 192 } 193 194 dev_dbg(dev, "registered %u PCIe PHY(s)\n", cnt); 195 196 return 0; 197put_child: 198 of_node_put(child); 199 return ret; 200} 201 202static const struct of_device_id cygnus_pcie_phy_match_table[] = { 203 { .compatible = "brcm,cygnus-pcie-phy" }, 204 { /* sentinel */ } 205}; 206MODULE_DEVICE_TABLE(of, cygnus_pcie_phy_match_table); 207 208static struct platform_driver cygnus_pcie_phy_driver = { 209 .driver = { 210 .name = "cygnus-pcie-phy", 211 .of_match_table = cygnus_pcie_phy_match_table, 212 }, 213 .probe = cygnus_pcie_phy_probe, 214}; 215module_platform_driver(cygnus_pcie_phy_driver); 216 217MODULE_AUTHOR("Ray Jui <rjui@broadcom.com>"); 218MODULE_DESCRIPTION("Broadcom Cygnus PCIe PHY driver"); 219MODULE_LICENSE("GPL v2");