1 /** 2 * drivers/net/phy/rockchip.c 3 * 4 * Driver for ROCKCHIP Ethernet PHYs 5 * 6 * Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd 7 * 8 * David Wu <david.wu@rock-chips.com> 9 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License as published by 12 * the Free Software Foundation; either version 2 of the License, or 13 * (at your option) any later version. 14 * 15 */ 16 17 #include <linux/ethtool.h> 18 #include <linux/kernel.h> 19 #include <linux/module.h> 20 #include <linux/mii.h> 21 #include <linux/netdevice.h> 22 #include <linux/phy.h> 23 24 #define INTERNAL_EPHY_ID 0x1234d400 25 26 #define MII_INTERNAL_CTRL_STATUS 17 27 #define SMI_ADDR_TSTCNTL 20 28 #define SMI_ADDR_TSTREAD1 21 29 #define SMI_ADDR_TSTREAD2 22 30 #define SMI_ADDR_TSTWRITE 23 31 #define MII_SPECIAL_CONTROL_STATUS 31 32 33 #define MII_AUTO_MDIX_EN BIT(7) 34 #define MII_MDIX_EN BIT(6) 35 36 #define MII_SPEED_10 BIT(2) 37 #define MII_SPEED_100 BIT(3) 38 39 #define TSTCNTL_RD (BIT(15) | BIT(10)) 40 #define TSTCNTL_WR (BIT(14) | BIT(10)) 41 42 #define TSTMODE_ENABLE 0x400 43 #define TSTMODE_DISABLE 0x0 44 45 #define WR_ADDR_A7CFG 0x18 46 47 static int rockchip_init_tstmode(struct phy_device *phydev) 48 { 49 int ret; 50 51 /* Enable access to Analog and DSP register banks */ 52 ret = phy_write(phydev, SMI_ADDR_TSTCNTL, TSTMODE_ENABLE); 53 if (ret) 54 return ret; 55 56 ret = phy_write(phydev, SMI_ADDR_TSTCNTL, TSTMODE_DISABLE); 57 if (ret) 58 return ret; 59 60 return phy_write(phydev, SMI_ADDR_TSTCNTL, TSTMODE_ENABLE); 61 } 62 63 static int rockchip_close_tstmode(struct phy_device *phydev) 64 { 65 /* Back to basic register bank */ 66 return phy_write(phydev, SMI_ADDR_TSTCNTL, TSTMODE_DISABLE); 67 } 68 69 static int rockchip_integrated_phy_analog_init(struct phy_device *phydev) 70 { 71 int ret; 72 73 ret = rockchip_init_tstmode(phydev); 74 if (ret) 75 return ret; 76 77 /* 78 * Adjust tx amplitude to make sginal better, 79 * the default value is 0x8. 80 */ 81 ret = phy_write(phydev, SMI_ADDR_TSTWRITE, 0xB); 82 if (ret) 83 return ret; 84 ret = phy_write(phydev, SMI_ADDR_TSTCNTL, TSTCNTL_WR | WR_ADDR_A7CFG); 85 if (ret) 86 return ret; 87 88 return rockchip_close_tstmode(phydev); 89 } 90 91 static int rockchip_integrated_phy_config_init(struct phy_device *phydev) 92 { 93 int val, ret; 94 95 /* 96 * The auto MIDX has linked problem on some board, 97 * workround to disable auto MDIX. 98 */ 99 val = phy_read(phydev, MII_INTERNAL_CTRL_STATUS); 100 if (val < 0) 101 return val; 102 val &= ~MII_AUTO_MDIX_EN; 103 ret = phy_write(phydev, MII_INTERNAL_CTRL_STATUS, val); 104 if (ret) 105 return ret; 106 107 return rockchip_integrated_phy_analog_init(phydev); 108 } 109 110 static void rockchip_link_change_notify(struct phy_device *phydev) 111 { 112 int speed = SPEED_10; 113 114 if (phydev->autoneg == AUTONEG_ENABLE) { 115 int reg = phy_read(phydev, MII_SPECIAL_CONTROL_STATUS); 116 117 if (reg < 0) { 118 phydev_err(phydev, "phy_read err: %d.\n", reg); 119 return; 120 } 121 122 if (reg & MII_SPEED_100) 123 speed = SPEED_100; 124 else if (reg & MII_SPEED_10) 125 speed = SPEED_10; 126 } else { 127 int bmcr = phy_read(phydev, MII_BMCR); 128 129 if (bmcr < 0) { 130 phydev_err(phydev, "phy_read err: %d.\n", bmcr); 131 return; 132 } 133 134 if (bmcr & BMCR_SPEED100) 135 speed = SPEED_100; 136 else 137 speed = SPEED_10; 138 } 139 140 /* 141 * If mode switch happens from 10BT to 100BT, all DSP/AFE 142 * registers are set to default values. So any AFE/DSP 143 * registers have to be re-initialized in this case. 144 */ 145 if ((phydev->speed == SPEED_10) && (speed == SPEED_100)) { 146 int ret = rockchip_integrated_phy_analog_init(phydev); 147 if (ret) 148 phydev_err(phydev, "rockchip_integrated_phy_analog_init err: %d.\n", 149 ret); 150 } 151 } 152 153 static int rockchip_set_polarity(struct phy_device *phydev, int polarity) 154 { 155 int reg, err, val; 156 157 /* get the current settings */ 158 reg = phy_read(phydev, MII_INTERNAL_CTRL_STATUS); 159 if (reg < 0) 160 return reg; 161 162 reg &= ~MII_AUTO_MDIX_EN; 163 val = reg; 164 switch (polarity) { 165 case ETH_TP_MDI: 166 val &= ~MII_MDIX_EN; 167 break; 168 case ETH_TP_MDI_X: 169 val |= MII_MDIX_EN; 170 break; 171 case ETH_TP_MDI_AUTO: 172 case ETH_TP_MDI_INVALID: 173 default: 174 return 0; 175 } 176 177 if (val != reg) { 178 /* Set the new polarity value in the register */ 179 err = phy_write(phydev, MII_INTERNAL_CTRL_STATUS, val); 180 if (err) 181 return err; 182 } 183 184 return 0; 185 } 186 187 static int rockchip_config_aneg(struct phy_device *phydev) 188 { 189 int err; 190 191 err = rockchip_set_polarity(phydev, phydev->mdix); 192 if (err < 0) 193 return err; 194 195 return genphy_config_aneg(phydev); 196 } 197 198 static int rockchip_phy_resume(struct phy_device *phydev) 199 { 200 genphy_resume(phydev); 201 202 return rockchip_integrated_phy_config_init(phydev); 203 } 204 205 static struct phy_driver rockchip_phy_driver[] = { 206 { 207 .phy_id = INTERNAL_EPHY_ID, 208 .phy_id_mask = 0xfffffff0, 209 .name = "Rockchip integrated EPHY", 210 .features = PHY_BASIC_FEATURES, 211 .flags = 0, 212 .link_change_notify = rockchip_link_change_notify, 213 .soft_reset = genphy_soft_reset, 214 .config_init = rockchip_integrated_phy_config_init, 215 .config_aneg = rockchip_config_aneg, 216 .read_status = genphy_read_status, 217 .suspend = genphy_suspend, 218 .resume = rockchip_phy_resume, 219 }, 220 }; 221 222 module_phy_driver(rockchip_phy_driver); 223 224 static struct mdio_device_id __maybe_unused rockchip_phy_tbl[] = { 225 { INTERNAL_EPHY_ID, 0xfffffff0 }, 226 { } 227 }; 228 229 MODULE_DEVICE_TABLE(mdio, rockchip_phy_tbl); 230 231 MODULE_AUTHOR("David Wu <david.wu@rock-chips.com>"); 232 MODULE_DESCRIPTION("Rockchip Ethernet PHY driver"); 233 MODULE_LICENSE("GPL v2"); 234