phy-armada38x-comphy.c (6183B)
1// SPDX-License-Identifier: GPL-2.0 2/* 3 * Copyright (C) 2018 Russell King, Deep Blue Solutions Ltd. 4 * 5 * Partly derived from CP110 comphy driver by Antoine Tenart 6 * <antoine.tenart@bootlin.com> 7 */ 8#include <linux/delay.h> 9#include <linux/iopoll.h> 10#include <linux/module.h> 11#include <linux/phy/phy.h> 12#include <linux/phy.h> 13#include <linux/platform_device.h> 14 15#define MAX_A38X_COMPHY 6 16#define MAX_A38X_PORTS 3 17 18#define COMPHY_CFG1 0x00 19#define COMPHY_CFG1_GEN_TX(x) ((x) << 26) 20#define COMPHY_CFG1_GEN_TX_MSK COMPHY_CFG1_GEN_TX(15) 21#define COMPHY_CFG1_GEN_RX(x) ((x) << 22) 22#define COMPHY_CFG1_GEN_RX_MSK COMPHY_CFG1_GEN_RX(15) 23#define GEN_SGMII_1_25GBPS 6 24#define GEN_SGMII_3_125GBPS 8 25 26#define COMPHY_STAT1 0x18 27#define COMPHY_STAT1_PLL_RDY_TX BIT(3) 28#define COMPHY_STAT1_PLL_RDY_RX BIT(2) 29 30#define COMPHY_SELECTOR 0xfc 31 32struct a38x_comphy; 33 34struct a38x_comphy_lane { 35 void __iomem *base; 36 struct a38x_comphy *priv; 37 unsigned int n; 38 39 int port; 40}; 41 42struct a38x_comphy { 43 void __iomem *base; 44 void __iomem *conf; 45 struct device *dev; 46 struct a38x_comphy_lane lane[MAX_A38X_COMPHY]; 47}; 48 49static const u8 gbe_mux[MAX_A38X_COMPHY][MAX_A38X_PORTS] = { 50 { 0, 0, 0 }, 51 { 4, 5, 0 }, 52 { 0, 4, 0 }, 53 { 0, 0, 4 }, 54 { 0, 3, 0 }, 55 { 0, 0, 3 }, 56}; 57 58static void a38x_set_conf(struct a38x_comphy_lane *lane, bool enable) 59{ 60 struct a38x_comphy *priv = lane->priv; 61 u32 conf; 62 63 if (priv->conf) { 64 conf = readl_relaxed(priv->conf); 65 if (enable) 66 conf |= BIT(lane->port); 67 else 68 conf &= ~BIT(lane->port); 69 writel(conf, priv->conf); 70 } 71} 72 73static void a38x_comphy_set_reg(struct a38x_comphy_lane *lane, 74 unsigned int offset, u32 mask, u32 value) 75{ 76 u32 val; 77 78 val = readl_relaxed(lane->base + offset) & ~mask; 79 writel(val | value, lane->base + offset); 80} 81 82static void a38x_comphy_set_speed(struct a38x_comphy_lane *lane, 83 unsigned int gen_tx, unsigned int gen_rx) 84{ 85 a38x_comphy_set_reg(lane, COMPHY_CFG1, 86 COMPHY_CFG1_GEN_TX_MSK | COMPHY_CFG1_GEN_RX_MSK, 87 COMPHY_CFG1_GEN_TX(gen_tx) | 88 COMPHY_CFG1_GEN_RX(gen_rx)); 89} 90 91static int a38x_comphy_poll(struct a38x_comphy_lane *lane, 92 unsigned int offset, u32 mask, u32 value) 93{ 94 u32 val; 95 int ret; 96 97 ret = readl_relaxed_poll_timeout_atomic(lane->base + offset, val, 98 (val & mask) == value, 99 1000, 150000); 100 101 if (ret) 102 dev_err(lane->priv->dev, 103 "comphy%u: timed out waiting for status\n", lane->n); 104 105 return ret; 106} 107 108/* 109 * We only support changing the speed for comphys configured for GBE. 110 * Since that is all we do, we only poll for PLL ready status. 111 */ 112static int a38x_comphy_set_mode(struct phy *phy, enum phy_mode mode, int sub) 113{ 114 struct a38x_comphy_lane *lane = phy_get_drvdata(phy); 115 unsigned int gen; 116 int ret; 117 118 if (mode != PHY_MODE_ETHERNET) 119 return -EINVAL; 120 121 switch (sub) { 122 case PHY_INTERFACE_MODE_SGMII: 123 case PHY_INTERFACE_MODE_1000BASEX: 124 gen = GEN_SGMII_1_25GBPS; 125 break; 126 127 case PHY_INTERFACE_MODE_2500BASEX: 128 gen = GEN_SGMII_3_125GBPS; 129 break; 130 131 default: 132 return -EINVAL; 133 } 134 135 a38x_set_conf(lane, false); 136 137 a38x_comphy_set_speed(lane, gen, gen); 138 139 ret = a38x_comphy_poll(lane, COMPHY_STAT1, 140 COMPHY_STAT1_PLL_RDY_TX | 141 COMPHY_STAT1_PLL_RDY_RX, 142 COMPHY_STAT1_PLL_RDY_TX | 143 COMPHY_STAT1_PLL_RDY_RX); 144 145 if (ret == 0) 146 a38x_set_conf(lane, true); 147 148 return ret; 149} 150 151static const struct phy_ops a38x_comphy_ops = { 152 .set_mode = a38x_comphy_set_mode, 153 .owner = THIS_MODULE, 154}; 155 156static struct phy *a38x_comphy_xlate(struct device *dev, 157 struct of_phandle_args *args) 158{ 159 struct a38x_comphy_lane *lane; 160 struct phy *phy; 161 u32 val; 162 163 if (WARN_ON(args->args[0] >= MAX_A38X_PORTS)) 164 return ERR_PTR(-EINVAL); 165 166 phy = of_phy_simple_xlate(dev, args); 167 if (IS_ERR(phy)) 168 return phy; 169 170 lane = phy_get_drvdata(phy); 171 if (lane->port >= 0) 172 return ERR_PTR(-EBUSY); 173 174 lane->port = args->args[0]; 175 176 val = readl_relaxed(lane->priv->base + COMPHY_SELECTOR); 177 val = (val >> (4 * lane->n)) & 0xf; 178 179 if (!gbe_mux[lane->n][lane->port] || 180 val != gbe_mux[lane->n][lane->port]) { 181 dev_warn(lane->priv->dev, 182 "comphy%u: not configured for GBE\n", lane->n); 183 phy = ERR_PTR(-EINVAL); 184 } 185 186 return phy; 187} 188 189static int a38x_comphy_probe(struct platform_device *pdev) 190{ 191 struct phy_provider *provider; 192 struct device_node *child; 193 struct a38x_comphy *priv; 194 struct resource *res; 195 void __iomem *base; 196 197 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 198 if (!priv) 199 return -ENOMEM; 200 201 base = devm_platform_ioremap_resource(pdev, 0); 202 if (IS_ERR(base)) 203 return PTR_ERR(base); 204 205 priv->dev = &pdev->dev; 206 priv->base = base; 207 208 /* Optional */ 209 res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "conf"); 210 if (res) { 211 priv->conf = devm_ioremap_resource(&pdev->dev, res); 212 if (IS_ERR(priv->conf)) 213 return PTR_ERR(priv->conf); 214 } 215 216 for_each_available_child_of_node(pdev->dev.of_node, child) { 217 struct phy *phy; 218 int ret; 219 u32 val; 220 221 ret = of_property_read_u32(child, "reg", &val); 222 if (ret < 0) { 223 dev_err(&pdev->dev, "missing 'reg' property (%d)\n", 224 ret); 225 continue; 226 } 227 228 if (val >= MAX_A38X_COMPHY || priv->lane[val].base) { 229 dev_err(&pdev->dev, "invalid 'reg' property\n"); 230 continue; 231 } 232 233 phy = devm_phy_create(&pdev->dev, child, &a38x_comphy_ops); 234 if (IS_ERR(phy)) { 235 of_node_put(child); 236 return PTR_ERR(phy); 237 } 238 239 priv->lane[val].base = base + 0x28 * val; 240 priv->lane[val].priv = priv; 241 priv->lane[val].n = val; 242 priv->lane[val].port = -1; 243 phy_set_drvdata(phy, &priv->lane[val]); 244 } 245 246 dev_set_drvdata(&pdev->dev, priv); 247 248 provider = devm_of_phy_provider_register(&pdev->dev, a38x_comphy_xlate); 249 250 return PTR_ERR_OR_ZERO(provider); 251} 252 253static const struct of_device_id a38x_comphy_of_match_table[] = { 254 { .compatible = "marvell,armada-380-comphy" }, 255 { }, 256}; 257MODULE_DEVICE_TABLE(of, a38x_comphy_of_match_table); 258 259static struct platform_driver a38x_comphy_driver = { 260 .probe = a38x_comphy_probe, 261 .driver = { 262 .name = "armada-38x-comphy", 263 .of_match_table = a38x_comphy_of_match_table, 264 }, 265}; 266module_platform_driver(a38x_comphy_driver); 267 268MODULE_AUTHOR("Russell King <rmk+kernel@armlinux.org.uk>"); 269MODULE_DESCRIPTION("Common PHY driver for Armada 38x SoCs"); 270MODULE_LICENSE("GPL v2");