1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
|
// SPDX-License-Identifier: GPL-2.0+
#include <linux/phy.h>
#include <linux/module.h>
#include "qcom.h"
#define AT803X_DEBUG_REG_3C 0x3C
#define AT803X_DEBUG_REG_GREEN 0x3D
#define AT803X_DEBUG_GATE_CLK_IN1000 BIT(6)
#define MDIO_AZ_DEBUG 0x800D
#define QCA8327_A_PHY_ID 0x004dd033
#define QCA8327_B_PHY_ID 0x004dd034
#define QCA8337_PHY_ID 0x004dd036
#define QCA8K_DEVFLAGS_REVISION_MASK GENMASK(2, 0)
static struct at803x_hw_stat qca83xx_hw_stats[] = {
{ "phy_idle_errors", 0xa, GENMASK(7, 0), PHY},
{ "phy_receive_errors", 0x15, GENMASK(15, 0), PHY},
{ "eee_wake_errors", 0x16, GENMASK(15, 0), MMD},
};
struct qca83xx_priv {
u64 stats[ARRAY_SIZE(qca83xx_hw_stats)];
};
MODULE_DESCRIPTION("Qualcomm Atheros QCA83XX PHY driver");
MODULE_AUTHOR("Matus Ujhelyi");
MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
MODULE_LICENSE("GPL");
static int qca83xx_get_sset_count(struct phy_device *phydev)
{
return ARRAY_SIZE(qca83xx_hw_stats);
}
static void qca83xx_get_strings(struct phy_device *phydev, u8 *data)
{
int i;
for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++)
ethtool_puts(&data, qca83xx_hw_stats[i].string);
}
static u64 qca83xx_get_stat(struct phy_device *phydev, int i)
{
struct at803x_hw_stat stat = qca83xx_hw_stats[i];
struct qca83xx_priv *priv = phydev->priv;
int val;
u64 ret;
if (stat.access_type == MMD)
val = phy_read_mmd(phydev, MDIO_MMD_PCS, stat.reg);
else
val = phy_read(phydev, stat.reg);
if (val < 0) {
ret = U64_MAX;
} else {
val = val & stat.mask;
priv->stats[i] += val;
ret = priv->stats[i];
}
return ret;
}
static void qca83xx_get_stats(struct phy_device *phydev,
struct ethtool_stats *stats, u64 *data)
{
int i;
for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++)
data[i] = qca83xx_get_stat(phydev, i);
}
static int qca83xx_probe(struct phy_device *phydev)
{
struct device *dev = &phydev->mdio.dev;
struct qca83xx_priv *priv;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
phydev->priv = priv;
return 0;
}
static int qca83xx_config_init(struct phy_device *phydev)
{
u8 switch_revision;
switch_revision = phydev->dev_flags & QCA8K_DEVFLAGS_REVISION_MASK;
switch (switch_revision) {
case 1:
/* For 100M waveform */
at803x_debug_reg_write(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, 0x02ea);
/* Turn on Gigabit clock */
at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x68a0);
break;
case 2:
phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0x0);
fallthrough;
case 4:
phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_AZ_DEBUG, 0x803f);
at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x6860);
at803x_debug_reg_write(phydev, AT803X_DEBUG_SYSTEM_CTRL_MODE, 0x2c46);
at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_3C, 0x6000);
break;
}
/* Following original QCA sourcecode set port to prefer master */
phy_set_bits(phydev, MII_CTRL1000, CTL1000_PREFER_MASTER);
return 0;
}
static int qca8327_config_init(struct phy_device *phydev)
{
/* QCA8327 require DAC amplitude adjustment for 100m set to +6%.
* Disable on init and enable only with 100m speed following
* qca original source code.
*/
at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
QCA8327_DEBUG_MANU_CTRL_EN, 0);
return qca83xx_config_init(phydev);
}
static void qca83xx_link_change_notify(struct phy_device *phydev)
{
/* Set DAC Amplitude adjustment to +6% for 100m on link running */
if (phydev->state == PHY_RUNNING) {
if (phydev->speed == SPEED_100)
at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
QCA8327_DEBUG_MANU_CTRL_EN,
QCA8327_DEBUG_MANU_CTRL_EN);
} else {
/* Reset DAC Amplitude adjustment */
at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
QCA8327_DEBUG_MANU_CTRL_EN, 0);
}
}
static int qca83xx_resume(struct phy_device *phydev)
{
int ret, val;
/* Skip reset if not suspended */
if (!phydev->suspended)
return 0;
/* Reinit the port, reset values set by suspend */
qca83xx_config_init(phydev);
/* Reset the port on port resume */
phy_set_bits(phydev, MII_BMCR, BMCR_RESET | BMCR_ANENABLE);
/* On resume from suspend the switch execute a reset and
* restart auto-negotiation. Wait for reset to complete.
*/
ret = phy_read_poll_timeout(phydev, MII_BMCR, val, !(val & BMCR_RESET),
50000, 600000, true);
if (ret)
return ret;
usleep_range(1000, 2000);
return 0;
}
static int qca83xx_suspend(struct phy_device *phydev)
{
at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_GREEN,
AT803X_DEBUG_GATE_CLK_IN1000, 0);
at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_HIB_CTRL,
AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE |
AT803X_DEBUG_HIB_CTRL_SEL_RST_80U, 0);
return 0;
}
static int qca8337_suspend(struct phy_device *phydev)
{
/* Only QCA8337 support actual suspend. */
genphy_suspend(phydev);
return qca83xx_suspend(phydev);
}
static int qca8327_suspend(struct phy_device *phydev)
{
u16 mask = 0;
/* QCA8327 cause port unreliability when phy suspend
* is set.
*/
mask |= ~(BMCR_SPEED1000 | BMCR_FULLDPLX);
phy_modify(phydev, MII_BMCR, mask, 0);
return qca83xx_suspend(phydev);
}
static struct phy_driver qca83xx_driver[] = {
{
/* QCA8337 */
PHY_ID_MATCH_EXACT(QCA8337_PHY_ID),
.name = "Qualcomm Atheros 8337 internal PHY",
/* PHY_GBIT_FEATURES */
.probe = qca83xx_probe,
.flags = PHY_IS_INTERNAL,
.config_init = qca83xx_config_init,
.soft_reset = genphy_soft_reset,
.get_sset_count = qca83xx_get_sset_count,
.get_strings = qca83xx_get_strings,
.get_stats = qca83xx_get_stats,
.suspend = qca8337_suspend,
.resume = qca83xx_resume,
}, {
/* QCA8327-A from switch QCA8327-AL1A */
PHY_ID_MATCH_EXACT(QCA8327_A_PHY_ID),
.name = "Qualcomm Atheros 8327-A internal PHY",
/* PHY_GBIT_FEATURES */
.link_change_notify = qca83xx_link_change_notify,
.probe = qca83xx_probe,
.flags = PHY_IS_INTERNAL,
.config_init = qca8327_config_init,
.soft_reset = genphy_soft_reset,
.get_sset_count = qca83xx_get_sset_count,
.get_strings = qca83xx_get_strings,
.get_stats = qca83xx_get_stats,
.suspend = qca8327_suspend,
.resume = qca83xx_resume,
}, {
/* QCA8327-B from switch QCA8327-BL1A */
PHY_ID_MATCH_EXACT(QCA8327_B_PHY_ID),
.name = "Qualcomm Atheros 8327-B internal PHY",
/* PHY_GBIT_FEATURES */
.link_change_notify = qca83xx_link_change_notify,
.probe = qca83xx_probe,
.flags = PHY_IS_INTERNAL,
.config_init = qca8327_config_init,
.soft_reset = genphy_soft_reset,
.get_sset_count = qca83xx_get_sset_count,
.get_strings = qca83xx_get_strings,
.get_stats = qca83xx_get_stats,
.suspend = qca8327_suspend,
.resume = qca83xx_resume,
}, };
module_phy_driver(qca83xx_driver);
static const struct mdio_device_id __maybe_unused qca83xx_tbl[] = {
{ PHY_ID_MATCH_EXACT(QCA8337_PHY_ID) },
{ PHY_ID_MATCH_EXACT(QCA8327_A_PHY_ID) },
{ PHY_ID_MATCH_EXACT(QCA8327_B_PHY_ID) },
{ }
};
MODULE_DEVICE_TABLE(mdio, qca83xx_tbl);
|