Linux Audio

Check our new training course

Buildroot integration, development and maintenance

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