Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.15.
  1// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
  2/*
  3 *  Driver for Analog Devices Industrial Ethernet T1L PHYs
  4 *
  5 * Copyright 2020 Analog Devices Inc.
  6 */
  7#include <linux/kernel.h>
  8#include <linux/bitfield.h>
  9#include <linux/delay.h>
 10#include <linux/errno.h>
 11#include <linux/init.h>
 12#include <linux/module.h>
 13#include <linux/mii.h>
 14#include <linux/phy.h>
 15#include <linux/property.h>
 16
 17#define PHY_ID_ADIN1100				0x0283bc81
 18#define PHY_ID_ADIN1110				0x0283bc91
 19#define PHY_ID_ADIN2111				0x0283bca1
 20
 21#define ADIN_FORCED_MODE			0x8000
 22#define   ADIN_FORCED_MODE_EN			BIT(0)
 23
 24#define ADIN_CRSM_SFT_RST			0x8810
 25#define   ADIN_CRSM_SFT_RST_EN			BIT(0)
 26
 27#define ADIN_CRSM_SFT_PD_CNTRL			0x8812
 28#define   ADIN_CRSM_SFT_PD_CNTRL_EN		BIT(0)
 29
 30#define ADIN_AN_PHY_INST_STATUS			0x8030
 31#define   ADIN_IS_CFG_SLV			BIT(2)
 32#define   ADIN_IS_CFG_MST			BIT(3)
 33
 34#define ADIN_CRSM_STAT				0x8818
 35#define   ADIN_CRSM_SFT_PD_RDY			BIT(1)
 36#define   ADIN_CRSM_SYS_RDY			BIT(0)
 37
 38#define ADIN_MSE_VAL				0x830B
 39
 40#define ADIN_SQI_MAX	7
 41
 42struct adin_mse_sqi_range {
 43	u16 start;
 44	u16 end;
 45};
 46
 47static const struct adin_mse_sqi_range adin_mse_sqi_map[] = {
 48	{ 0x0A74, 0xFFFF },
 49	{ 0x084E, 0x0A74 },
 50	{ 0x0698, 0x084E },
 51	{ 0x053D, 0x0698 },
 52	{ 0x0429, 0x053D },
 53	{ 0x034E, 0x0429 },
 54	{ 0x02A0, 0x034E },
 55	{ 0x0000, 0x02A0 },
 56};
 57
 58/**
 59 * struct adin_priv - ADIN PHY driver private data
 60 * @tx_level_2v4_able:		set if the PHY supports 2.4V TX levels (10BASE-T1L)
 61 * @tx_level_2v4:		set if the PHY requests 2.4V TX levels (10BASE-T1L)
 62 * @tx_level_prop_present:	set if the TX level is specified in DT
 63 */
 64struct adin_priv {
 65	unsigned int		tx_level_2v4_able:1;
 66	unsigned int		tx_level_2v4:1;
 67	unsigned int		tx_level_prop_present:1;
 68};
 69
 70static int adin_read_status(struct phy_device *phydev)
 71{
 72	int ret;
 73
 74	ret = genphy_c45_read_status(phydev);
 75	if (ret)
 76		return ret;
 77
 78	ret = phy_read_mmd(phydev, MDIO_MMD_AN, ADIN_AN_PHY_INST_STATUS);
 79	if (ret < 0)
 80		return ret;
 81
 82	if (ret & ADIN_IS_CFG_SLV)
 83		phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE;
 84
 85	if (ret & ADIN_IS_CFG_MST)
 86		phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER;
 87
 88	return 0;
 89}
 90
 91static int adin_config_aneg(struct phy_device *phydev)
 92{
 93	struct adin_priv *priv = phydev->priv;
 94	int ret;
 95
 96	if (phydev->autoneg == AUTONEG_DISABLE) {
 97		ret = genphy_c45_pma_setup_forced(phydev);
 98		if (ret < 0)
 99			return ret;
100
101		if (priv->tx_level_prop_present && priv->tx_level_2v4)
102			ret = phy_set_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_B10L_PMA_CTRL,
103					       MDIO_PMA_10T1L_CTRL_2V4_EN);
104		else
105			ret = phy_clear_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_B10L_PMA_CTRL,
106						 MDIO_PMA_10T1L_CTRL_2V4_EN);
107		if (ret < 0)
108			return ret;
109
110		/* Force PHY to use above configurations */
111		return phy_set_bits_mmd(phydev, MDIO_MMD_AN, ADIN_FORCED_MODE, ADIN_FORCED_MODE_EN);
112	}
113
114	ret = phy_clear_bits_mmd(phydev, MDIO_MMD_AN, ADIN_FORCED_MODE, ADIN_FORCED_MODE_EN);
115	if (ret < 0)
116		return ret;
117
118	/* Request increased transmit level from LP. */
119	if (priv->tx_level_prop_present && priv->tx_level_2v4) {
120		ret = phy_set_bits_mmd(phydev, MDIO_MMD_AN, MDIO_AN_T1_ADV_H,
121				       MDIO_AN_T1_ADV_H_10L_TX_HI |
122				       MDIO_AN_T1_ADV_H_10L_TX_HI_REQ);
123		if (ret < 0)
124			return ret;
125	}
126
127	/* Disable 2.4 Vpp transmit level. */
128	if ((priv->tx_level_prop_present && !priv->tx_level_2v4) || !priv->tx_level_2v4_able) {
129		ret = phy_clear_bits_mmd(phydev, MDIO_MMD_AN, MDIO_AN_T1_ADV_H,
130					 MDIO_AN_T1_ADV_H_10L_TX_HI |
131					 MDIO_AN_T1_ADV_H_10L_TX_HI_REQ);
132		if (ret < 0)
133			return ret;
134	}
135
136	return genphy_c45_config_aneg(phydev);
137}
138
139static int adin_set_powerdown_mode(struct phy_device *phydev, bool en)
140{
141	int ret;
142	int val;
143
144	val = en ? ADIN_CRSM_SFT_PD_CNTRL_EN : 0;
145	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1,
146			    ADIN_CRSM_SFT_PD_CNTRL, val);
147	if (ret < 0)
148		return ret;
149
150	return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, ADIN_CRSM_STAT, ret,
151					 (ret & ADIN_CRSM_SFT_PD_RDY) == val,
152					 1000, 30000, true);
153}
154
155static int adin_suspend(struct phy_device *phydev)
156{
157	return adin_set_powerdown_mode(phydev, true);
158}
159
160static int adin_resume(struct phy_device *phydev)
161{
162	return adin_set_powerdown_mode(phydev, false);
163}
164
165static int adin_set_loopback(struct phy_device *phydev, bool enable)
166{
167	if (enable)
168		return phy_set_bits_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_10T1L_CTRL,
169					BMCR_LOOPBACK);
170
171	/* PCS loopback (according to 10BASE-T1L spec) */
172	return phy_clear_bits_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_10T1L_CTRL,
173				 BMCR_LOOPBACK);
174}
175
176static int adin_soft_reset(struct phy_device *phydev)
177{
178	int ret;
179
180	ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, ADIN_CRSM_SFT_RST, ADIN_CRSM_SFT_RST_EN);
181	if (ret < 0)
182		return ret;
183
184	return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, ADIN_CRSM_STAT, ret,
185					 (ret & ADIN_CRSM_SYS_RDY),
186					 10000, 30000, true);
187}
188
189static int adin_get_features(struct phy_device *phydev)
190{
191	struct adin_priv *priv = phydev->priv;
192	struct device *dev = &phydev->mdio.dev;
193	int ret;
194	u8 val;
195
196	ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_10T1L_STAT);
197	if (ret < 0)
198		return ret;
199
200	/* This depends on the voltage level from the power source */
201	priv->tx_level_2v4_able = !!(ret & MDIO_PMA_10T1L_STAT_2V4_ABLE);
202
203	phydev_dbg(phydev, "PHY supports 2.4V TX level: %s\n",
204		   priv->tx_level_2v4_able ? "yes" : "no");
205
206	priv->tx_level_prop_present = device_property_present(dev, "phy-10base-t1l-2.4vpp");
207	if (priv->tx_level_prop_present) {
208		ret = device_property_read_u8(dev, "phy-10base-t1l-2.4vpp", &val);
209		if (ret < 0)
210			return ret;
211
212		priv->tx_level_2v4 = val;
213		if (!priv->tx_level_2v4 && priv->tx_level_2v4_able)
214			phydev_info(phydev,
215				    "PHY supports 2.4V TX level, but disabled via config\n");
216	}
217
218	linkmode_set_bit_array(phy_basic_ports_array, ARRAY_SIZE(phy_basic_ports_array),
219			       phydev->supported);
220
221	return genphy_c45_pma_read_abilities(phydev);
222}
223
224static int adin_get_sqi(struct phy_device *phydev)
225{
226	u16 mse_val;
227	int sqi;
228	int ret;
229
230	ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_STAT1);
231	if (ret < 0)
232		return ret;
233	else if (!(ret & MDIO_STAT1_LSTATUS))
234		return 0;
235
236	ret = phy_read_mmd(phydev, MDIO_STAT1, ADIN_MSE_VAL);
237	if (ret < 0)
238		return ret;
239
240	mse_val = 0xFFFF & ret;
241	for (sqi = 0; sqi < ARRAY_SIZE(adin_mse_sqi_map); sqi++) {
242		if (mse_val >= adin_mse_sqi_map[sqi].start && mse_val <= adin_mse_sqi_map[sqi].end)
243			return sqi;
244	}
245
246	return -EINVAL;
247}
248
249static int adin_get_sqi_max(struct phy_device *phydev)
250{
251	return ADIN_SQI_MAX;
252}
253
254static int adin_probe(struct phy_device *phydev)
255{
256	struct device *dev = &phydev->mdio.dev;
257	struct adin_priv *priv;
258
259	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
260	if (!priv)
261		return -ENOMEM;
262
263	phydev->priv = priv;
264
265	return 0;
266}
267
268static struct phy_driver adin_driver[] = {
269	{
270		.phy_id			= PHY_ID_ADIN1100,
271		.phy_id_mask		= 0xffffffcf,
272		.name			= "ADIN1100",
273		.get_features		= adin_get_features,
274		.soft_reset		= adin_soft_reset,
275		.probe			= adin_probe,
276		.config_aneg		= adin_config_aneg,
277		.read_status		= adin_read_status,
278		.set_loopback		= adin_set_loopback,
279		.suspend		= adin_suspend,
280		.resume			= adin_resume,
281		.get_sqi		= adin_get_sqi,
282		.get_sqi_max		= adin_get_sqi_max,
283	},
284};
285
286module_phy_driver(adin_driver);
287
288static struct mdio_device_id __maybe_unused adin_tbl[] = {
289	{ PHY_ID_MATCH_MODEL(PHY_ID_ADIN1100) },
290	{ PHY_ID_MATCH_MODEL(PHY_ID_ADIN1110) },
291	{ PHY_ID_MATCH_MODEL(PHY_ID_ADIN2111) },
292	{ }
293};
294
295MODULE_DEVICE_TABLE(mdio, adin_tbl);
296MODULE_DESCRIPTION("Analog Devices Industrial Ethernet T1L PHY driver");
297MODULE_LICENSE("Dual BSD/GPL");