| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * 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 | 4 | */ |
|---|
| 13 | 5 | |
|---|
| 14 | 6 | /* Broadcom Cygnus SoC internal transceivers support. */ |
|---|
| .. | .. |
|---|
| 17 | 9 | #include <linux/module.h> |
|---|
| 18 | 10 | #include <linux/netdevice.h> |
|---|
| 19 | 11 | #include <linux/phy.h> |
|---|
| 12 | + |
|---|
| 13 | +struct bcm_omega_phy_priv { |
|---|
| 14 | + u64 *stats; |
|---|
| 15 | +}; |
|---|
| 20 | 16 | |
|---|
| 21 | 17 | /* Broadcom Cygnus Phy specific registers */ |
|---|
| 22 | 18 | #define MII_BCM_CYGNUS_AFE_VDAC_ICTRL_0 0x91E5 /* VDAL Control register */ |
|---|
| .. | .. |
|---|
| 129 | 125 | return genphy_config_aneg(phydev); |
|---|
| 130 | 126 | } |
|---|
| 131 | 127 | |
|---|
| 128 | +static int bcm_omega_config_init(struct phy_device *phydev) |
|---|
| 129 | +{ |
|---|
| 130 | + u8 count, rev; |
|---|
| 131 | + int ret = 0; |
|---|
| 132 | + |
|---|
| 133 | + rev = phydev->phy_id & ~phydev->drv->phy_id_mask; |
|---|
| 134 | + |
|---|
| 135 | + pr_info_once("%s: %s PHY revision: 0x%02x\n", |
|---|
| 136 | + phydev_name(phydev), phydev->drv->name, rev); |
|---|
| 137 | + |
|---|
| 138 | + /* Dummy read to a register to workaround an issue upon reset where the |
|---|
| 139 | + * internal inverter may not allow the first MDIO transaction to pass |
|---|
| 140 | + * the MDIO management controller and make us return 0xffff for such |
|---|
| 141 | + * reads. |
|---|
| 142 | + */ |
|---|
| 143 | + phy_read(phydev, MII_BMSR); |
|---|
| 144 | + |
|---|
| 145 | + switch (rev) { |
|---|
| 146 | + case 0x00: |
|---|
| 147 | + ret = bcm_phy_28nm_a0b0_afe_config_init(phydev); |
|---|
| 148 | + break; |
|---|
| 149 | + default: |
|---|
| 150 | + break; |
|---|
| 151 | + } |
|---|
| 152 | + |
|---|
| 153 | + if (ret) |
|---|
| 154 | + return ret; |
|---|
| 155 | + |
|---|
| 156 | + ret = bcm_phy_downshift_get(phydev, &count); |
|---|
| 157 | + if (ret) |
|---|
| 158 | + return ret; |
|---|
| 159 | + |
|---|
| 160 | + /* Only enable EEE if Wirespeed/downshift is disabled */ |
|---|
| 161 | + ret = bcm_phy_set_eee(phydev, count == DOWNSHIFT_DEV_DISABLE); |
|---|
| 162 | + if (ret) |
|---|
| 163 | + return ret; |
|---|
| 164 | + |
|---|
| 165 | + return bcm_phy_enable_apd(phydev, true); |
|---|
| 166 | +} |
|---|
| 167 | + |
|---|
| 168 | +static int bcm_omega_resume(struct phy_device *phydev) |
|---|
| 169 | +{ |
|---|
| 170 | + int ret; |
|---|
| 171 | + |
|---|
| 172 | + /* Re-apply workarounds coming out suspend/resume */ |
|---|
| 173 | + ret = bcm_omega_config_init(phydev); |
|---|
| 174 | + if (ret) |
|---|
| 175 | + return ret; |
|---|
| 176 | + |
|---|
| 177 | + /* 28nm Gigabit PHYs come out of reset without any half-duplex |
|---|
| 178 | + * or "hub" compliant advertised mode, fix that. This does not |
|---|
| 179 | + * cause any problems with the PHY library since genphy_config_aneg() |
|---|
| 180 | + * gracefully handles auto-negotiated and forced modes. |
|---|
| 181 | + */ |
|---|
| 182 | + return genphy_config_aneg(phydev); |
|---|
| 183 | +} |
|---|
| 184 | + |
|---|
| 185 | +static int bcm_omega_get_tunable(struct phy_device *phydev, |
|---|
| 186 | + struct ethtool_tunable *tuna, void *data) |
|---|
| 187 | +{ |
|---|
| 188 | + switch (tuna->id) { |
|---|
| 189 | + case ETHTOOL_PHY_DOWNSHIFT: |
|---|
| 190 | + return bcm_phy_downshift_get(phydev, (u8 *)data); |
|---|
| 191 | + default: |
|---|
| 192 | + return -EOPNOTSUPP; |
|---|
| 193 | + } |
|---|
| 194 | +} |
|---|
| 195 | + |
|---|
| 196 | +static int bcm_omega_set_tunable(struct phy_device *phydev, |
|---|
| 197 | + struct ethtool_tunable *tuna, |
|---|
| 198 | + const void *data) |
|---|
| 199 | +{ |
|---|
| 200 | + u8 count = *(u8 *)data; |
|---|
| 201 | + int ret; |
|---|
| 202 | + |
|---|
| 203 | + switch (tuna->id) { |
|---|
| 204 | + case ETHTOOL_PHY_DOWNSHIFT: |
|---|
| 205 | + ret = bcm_phy_downshift_set(phydev, count); |
|---|
| 206 | + break; |
|---|
| 207 | + default: |
|---|
| 208 | + return -EOPNOTSUPP; |
|---|
| 209 | + } |
|---|
| 210 | + |
|---|
| 211 | + if (ret) |
|---|
| 212 | + return ret; |
|---|
| 213 | + |
|---|
| 214 | + /* Disable EEE advertisement since this prevents the PHY |
|---|
| 215 | + * from successfully linking up, trigger auto-negotiation restart |
|---|
| 216 | + * to let the MAC decide what to do. |
|---|
| 217 | + */ |
|---|
| 218 | + ret = bcm_phy_set_eee(phydev, count == DOWNSHIFT_DEV_DISABLE); |
|---|
| 219 | + if (ret) |
|---|
| 220 | + return ret; |
|---|
| 221 | + |
|---|
| 222 | + return genphy_restart_aneg(phydev); |
|---|
| 223 | +} |
|---|
| 224 | + |
|---|
| 225 | +static void bcm_omega_get_phy_stats(struct phy_device *phydev, |
|---|
| 226 | + struct ethtool_stats *stats, u64 *data) |
|---|
| 227 | +{ |
|---|
| 228 | + struct bcm_omega_phy_priv *priv = phydev->priv; |
|---|
| 229 | + |
|---|
| 230 | + bcm_phy_get_stats(phydev, priv->stats, stats, data); |
|---|
| 231 | +} |
|---|
| 232 | + |
|---|
| 233 | +static int bcm_omega_probe(struct phy_device *phydev) |
|---|
| 234 | +{ |
|---|
| 235 | + struct bcm_omega_phy_priv *priv; |
|---|
| 236 | + |
|---|
| 237 | + priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL); |
|---|
| 238 | + if (!priv) |
|---|
| 239 | + return -ENOMEM; |
|---|
| 240 | + |
|---|
| 241 | + phydev->priv = priv; |
|---|
| 242 | + |
|---|
| 243 | + priv->stats = devm_kcalloc(&phydev->mdio.dev, |
|---|
| 244 | + bcm_phy_get_sset_count(phydev), sizeof(u64), |
|---|
| 245 | + GFP_KERNEL); |
|---|
| 246 | + if (!priv->stats) |
|---|
| 247 | + return -ENOMEM; |
|---|
| 248 | + |
|---|
| 249 | + return 0; |
|---|
| 250 | +} |
|---|
| 251 | + |
|---|
| 132 | 252 | static struct phy_driver bcm_cygnus_phy_driver[] = { |
|---|
| 133 | 253 | { |
|---|
| 134 | 254 | .phy_id = PHY_ID_BCM_CYGNUS, |
|---|
| 135 | 255 | .phy_id_mask = 0xfffffff0, |
|---|
| 136 | 256 | .name = "Broadcom Cygnus PHY", |
|---|
| 137 | | - .features = PHY_GBIT_FEATURES, |
|---|
| 257 | + /* PHY_GBIT_FEATURES */ |
|---|
| 138 | 258 | .config_init = bcm_cygnus_config_init, |
|---|
| 139 | 259 | .ack_interrupt = bcm_phy_ack_intr, |
|---|
| 140 | 260 | .config_intr = bcm_phy_config_intr, |
|---|
| 141 | 261 | .suspend = genphy_suspend, |
|---|
| 142 | 262 | .resume = bcm_cygnus_resume, |
|---|
| 143 | | -} }; |
|---|
| 263 | +}, { |
|---|
| 264 | + .phy_id = PHY_ID_BCM_OMEGA, |
|---|
| 265 | + .phy_id_mask = 0xfffffff0, |
|---|
| 266 | + .name = "Broadcom Omega Combo GPHY", |
|---|
| 267 | + /* PHY_GBIT_FEATURES */ |
|---|
| 268 | + .flags = PHY_IS_INTERNAL, |
|---|
| 269 | + .config_init = bcm_omega_config_init, |
|---|
| 270 | + .suspend = genphy_suspend, |
|---|
| 271 | + .resume = bcm_omega_resume, |
|---|
| 272 | + .get_tunable = bcm_omega_get_tunable, |
|---|
| 273 | + .set_tunable = bcm_omega_set_tunable, |
|---|
| 274 | + .get_sset_count = bcm_phy_get_sset_count, |
|---|
| 275 | + .get_strings = bcm_phy_get_strings, |
|---|
| 276 | + .get_stats = bcm_omega_get_phy_stats, |
|---|
| 277 | + .probe = bcm_omega_probe, |
|---|
| 278 | +} |
|---|
| 279 | +}; |
|---|
| 144 | 280 | |
|---|
| 145 | 281 | static struct mdio_device_id __maybe_unused bcm_cygnus_phy_tbl[] = { |
|---|
| 146 | 282 | { PHY_ID_BCM_CYGNUS, 0xfffffff0, }, |
|---|
| 283 | + { PHY_ID_BCM_OMEGA, 0xfffffff0, }, |
|---|
| 147 | 284 | { } |
|---|
| 148 | 285 | }; |
|---|
| 149 | 286 | MODULE_DEVICE_TABLE(mdio, bcm_cygnus_phy_tbl); |
|---|