Skip to content

Commit fd38824

Browse files
AnsuelNipaLocal
authored andcommitted
net: phy: Add Airoha AN8855 Internal Switch Gigabit PHY
Add support for Airoha AN8855 Internal Switch Gigabit PHY. This is a simple PHY driver to configure and calibrate the PHY for the AN8855 Switch with the use of NVMEM cells. Signed-off-by: Christian Marangi <[email protected]> Signed-off-by: NipaLocal <nipa@local>
1 parent b3e6aa8 commit fd38824

File tree

4 files changed

+275
-0
lines changed

4 files changed

+275
-0
lines changed

MAINTAINERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,7 @@ S: Maintained
720720
F: Documentation/devicetree/bindings/net/dsa/airoha,an8855.yaml
721721
F: drivers/net/dsa/an8855.c
722722
F: drivers/net/dsa/an8855.h
723+
F: drivers/net/phy/air_an8855.c
723724

724725
AIROHA ETHERNET DRIVER
725726
M: Lorenzo Bianconi <[email protected]>

drivers/net/phy/Kconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ config SFP
7979

8080
comment "MII PHY device drivers"
8181

82+
config AIR_AN8855_PHY
83+
tristate "Airoha AN8855 Internal Gigabit PHY"
84+
help
85+
Currently supports the internal Airoha AN8855 Switch PHY.
86+
8287
config AIR_EN8811H_PHY
8388
tristate "Airoha EN8811H 2.5 Gigabit PHY"
8489
help

drivers/net/phy/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ obj-y += $(sfp-obj-y) $(sfp-obj-m)
3535

3636
obj-$(CONFIG_ADIN_PHY) += adin.o
3737
obj-$(CONFIG_ADIN1100_PHY) += adin1100.o
38+
obj-$(CONFIG_AIR_AN8855_PHY) += air_an8855.o
3839
obj-$(CONFIG_AIR_EN8811H_PHY) += air_en8811h.o
3940
obj-$(CONFIG_AMD_PHY) += amd.o
4041
obj-$(CONFIG_AMCC_QT2025_PHY) += qt2025.o

drivers/net/phy/air_an8855.c

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
// SPDX-License-Identifier: GPL-2.0+
2+
/*
3+
* Copyright (C) 2024 Christian Marangi <[email protected]>
4+
*/
5+
6+
#include <linux/phy.h>
7+
#include <linux/module.h>
8+
#include <linux/bitfield.h>
9+
#include <linux/nvmem-consumer.h>
10+
11+
#define AN8855_PHY_SELECT_PAGE 0x1f
12+
/* Mask speculation based on page up to 0x4 */
13+
#define AN8855_PHY_PAGE GENMASK(2, 0)
14+
#define AN8855_PHY_PAGE_STANDARD FIELD_PREP_CONST(AN8855_PHY_PAGE, 0x0)
15+
#define AN8855_PHY_PAGE_EXTENDED_1 FIELD_PREP_CONST(AN8855_PHY_PAGE, 0x1)
16+
17+
/* MII Registers Page 1 */
18+
#define AN8855_PHY_EXT_REG_14 0x14
19+
#define AN8855_PHY_EN_DOWN_SHIFT BIT(4)
20+
21+
/* R50 Calibration regs in MDIO_MMD_VEND1 */
22+
#define AN8855_PHY_R500HM_RSEL_TX_AB 0x174
23+
#define AN8855_PHY_R50OHM_RSEL_TX_A_EN BIT(15)
24+
#define AN8855_PHY_R50OHM_RSEL_TX_A GENMASK(14, 8)
25+
#define AN8855_PHY_R50OHM_RSEL_TX_B_EN BIT(7)
26+
#define AN8855_PHY_R50OHM_RSEL_TX_B GENMASK(6, 0)
27+
#define AN8855_PHY_R500HM_RSEL_TX_CD 0x175
28+
#define AN8855_PHY_R50OHM_RSEL_TX_C_EN BIT(15)
29+
#define AN8855_PHY_R50OHM_RSEL_TX_C GENMASK(14, 8)
30+
#define AN8855_PHY_R50OHM_RSEL_TX_D_EN BIT(7)
31+
#define AN8855_PHY_R50OHM_RSEL_TX_D GENMASK(6, 0)
32+
33+
#define AN8855_SWITCH_EFUSE_R50O GENMASK(30, 24)
34+
35+
/* PHY TX PAIR DELAY SELECT Register */
36+
#define AN8855_PHY_TX_PAIR_DLY_SEL_GBE 0x013
37+
#define AN8855_PHY_CR_DA_TX_PAIR_DELKAY_SEL_A_GBE GENMASK(14, 12)
38+
#define AN8855_PHY_CR_DA_TX_PAIR_DELKAY_SEL_B_GBE GENMASK(10, 8)
39+
#define AN8855_PHY_CR_DA_TX_PAIR_DELKAY_SEL_C_GBE GENMASK(6, 4)
40+
#define AN8855_PHY_CR_DA_TX_PAIR_DELKAY_SEL_D_GBE GENMASK(2, 0)
41+
/* PHY ADC Register */
42+
#define AN8855_PHY_RXADC_CTRL 0x0d8
43+
#define AN8855_PHY_RG_AD_SAMNPLE_PHSEL_A BIT(12)
44+
#define AN8855_PHY_RG_AD_SAMNPLE_PHSEL_B BIT(8)
45+
#define AN8855_PHY_RG_AD_SAMNPLE_PHSEL_C BIT(4)
46+
#define AN8855_PHY_RG_AD_SAMNPLE_PHSEL_D BIT(0)
47+
#define AN8855_PHY_RXADC_REV_0 0x0d9
48+
#define AN8855_PHY_RG_AD_RESERVE0_A GENMASK(15, 8)
49+
#define AN8855_PHY_RG_AD_RESERVE0_B GENMASK(7, 0)
50+
#define AN8855_PHY_RXADC_REV_1 0x0da
51+
#define AN8855_PHY_RG_AD_RESERVE0_C GENMASK(15, 8)
52+
#define AN8855_PHY_RG_AD_RESERVE0_D GENMASK(7, 0)
53+
54+
#define AN8855_PHY_ID 0xc0ff0410
55+
56+
#define AN8855_PHY_FLAGS_EN_CALIBRATION BIT(0)
57+
58+
struct air_an8855_priv {
59+
u8 calibration_data[4];
60+
};
61+
62+
static const u8 dsa_r50ohm_table[] = {
63+
127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
64+
127, 127, 127, 127, 127, 127, 127, 126, 122, 117,
65+
112, 109, 104, 101, 97, 94, 90, 88, 84, 80,
66+
78, 74, 72, 68, 66, 64, 61, 58, 56, 53,
67+
51, 48, 47, 44, 42, 40, 38, 36, 34, 32,
68+
31, 28, 27, 24, 24, 22, 20, 18, 16, 16,
69+
14, 12, 11, 9
70+
};
71+
72+
static int en8855_get_r50ohm_val(struct device *dev, const char *calib_name,
73+
u8 *dest)
74+
{
75+
u32 shift_sel, val;
76+
int ret;
77+
int i;
78+
79+
ret = nvmem_cell_read_u32(dev, calib_name, &val);
80+
if (ret)
81+
return ret;
82+
83+
shift_sel = FIELD_GET(AN8855_SWITCH_EFUSE_R50O, val);
84+
for (i = 0; i < ARRAY_SIZE(dsa_r50ohm_table); i++)
85+
if (dsa_r50ohm_table[i] == shift_sel)
86+
break;
87+
88+
if (i < 8 || i >= ARRAY_SIZE(dsa_r50ohm_table))
89+
*dest = dsa_r50ohm_table[25];
90+
else
91+
*dest = dsa_r50ohm_table[i - 8];
92+
93+
return 0;
94+
}
95+
96+
static int an8855_probe(struct phy_device *phydev)
97+
{
98+
struct device *dev = &phydev->mdio.dev;
99+
struct device_node *node = dev->of_node;
100+
struct air_an8855_priv *priv;
101+
int ret;
102+
103+
/* If we don't have a node, skip get calib */
104+
if (!node)
105+
return 0;
106+
107+
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
108+
if (!priv)
109+
return -ENOMEM;
110+
111+
ret = en8855_get_r50ohm_val(dev, "tx_a", &priv->calibration_data[0]);
112+
if (ret)
113+
return ret;
114+
115+
ret = en8855_get_r50ohm_val(dev, "tx_b", &priv->calibration_data[1]);
116+
if (ret)
117+
return ret;
118+
119+
ret = en8855_get_r50ohm_val(dev, "tx_c", &priv->calibration_data[2]);
120+
if (ret)
121+
return ret;
122+
123+
ret = en8855_get_r50ohm_val(dev, "tx_d", &priv->calibration_data[3]);
124+
if (ret)
125+
return ret;
126+
127+
phydev->priv = priv;
128+
129+
return 0;
130+
}
131+
132+
static int an8855_get_downshift(struct phy_device *phydev, u8 *data)
133+
{
134+
int val;
135+
136+
val = phy_read_paged(phydev, AN8855_PHY_PAGE_EXTENDED_1, AN8855_PHY_EXT_REG_14);
137+
if (val < 0)
138+
return val;
139+
140+
*data = val & AN8855_PHY_EN_DOWN_SHIFT ? DOWNSHIFT_DEV_DEFAULT_COUNT :
141+
DOWNSHIFT_DEV_DISABLE;
142+
143+
return 0;
144+
}
145+
146+
static int an8855_set_downshift(struct phy_device *phydev, u8 cnt)
147+
{
148+
u16 ds = cnt != DOWNSHIFT_DEV_DISABLE ? AN8855_PHY_EN_DOWN_SHIFT : 0;
149+
150+
return phy_modify_paged(phydev, AN8855_PHY_PAGE_EXTENDED_1,
151+
AN8855_PHY_EXT_REG_14, AN8855_PHY_EN_DOWN_SHIFT,
152+
ds);
153+
}
154+
155+
static int an8855_config_init(struct phy_device *phydev)
156+
{
157+
struct air_an8855_priv *priv = phydev->priv;
158+
int ret;
159+
160+
/* Enable HW auto downshift */
161+
ret = an8855_set_downshift(phydev, DOWNSHIFT_DEV_DEFAULT_COUNT);
162+
if (ret)
163+
return ret;
164+
165+
/* Apply calibration values, if needed.
166+
* AN8855_PHY_FLAGS_EN_CALIBRATION signal this.
167+
*/
168+
if (priv && phydev->dev_flags & AN8855_PHY_FLAGS_EN_CALIBRATION) {
169+
u8 *calibration_data = priv->calibration_data;
170+
171+
ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, AN8855_PHY_R500HM_RSEL_TX_AB,
172+
AN8855_PHY_R50OHM_RSEL_TX_A | AN8855_PHY_R50OHM_RSEL_TX_B,
173+
FIELD_PREP(AN8855_PHY_R50OHM_RSEL_TX_A, calibration_data[0]) |
174+
FIELD_PREP(AN8855_PHY_R50OHM_RSEL_TX_B, calibration_data[1]));
175+
if (ret)
176+
return ret;
177+
ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, AN8855_PHY_R500HM_RSEL_TX_CD,
178+
AN8855_PHY_R50OHM_RSEL_TX_C | AN8855_PHY_R50OHM_RSEL_TX_D,
179+
FIELD_PREP(AN8855_PHY_R50OHM_RSEL_TX_C, calibration_data[2]) |
180+
FIELD_PREP(AN8855_PHY_R50OHM_RSEL_TX_D, calibration_data[3]));
181+
if (ret)
182+
return ret;
183+
}
184+
185+
/* Apply values to reduce signal noise */
186+
ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AN8855_PHY_TX_PAIR_DLY_SEL_GBE,
187+
FIELD_PREP(AN8855_PHY_CR_DA_TX_PAIR_DELKAY_SEL_A_GBE, 0x4) |
188+
FIELD_PREP(AN8855_PHY_CR_DA_TX_PAIR_DELKAY_SEL_C_GBE, 0x4));
189+
if (ret)
190+
return ret;
191+
ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AN8855_PHY_RXADC_CTRL,
192+
AN8855_PHY_RG_AD_SAMNPLE_PHSEL_A |
193+
AN8855_PHY_RG_AD_SAMNPLE_PHSEL_C);
194+
if (ret)
195+
return ret;
196+
ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AN8855_PHY_RXADC_REV_0,
197+
FIELD_PREP(AN8855_PHY_RG_AD_RESERVE0_A, 0x1));
198+
if (ret)
199+
return ret;
200+
ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AN8855_PHY_RXADC_REV_1,
201+
FIELD_PREP(AN8855_PHY_RG_AD_RESERVE0_C, 0x1));
202+
if (ret)
203+
return ret;
204+
205+
return 0;
206+
}
207+
208+
static int an8855_get_tunable(struct phy_device *phydev,
209+
struct ethtool_tunable *tuna, void *data)
210+
{
211+
switch (tuna->id) {
212+
case ETHTOOL_PHY_DOWNSHIFT:
213+
return an8855_get_downshift(phydev, data);
214+
default:
215+
return -EOPNOTSUPP;
216+
}
217+
}
218+
219+
static int an8855_set_tunable(struct phy_device *phydev,
220+
struct ethtool_tunable *tuna, const void *data)
221+
{
222+
switch (tuna->id) {
223+
case ETHTOOL_PHY_DOWNSHIFT:
224+
return an8855_set_downshift(phydev, *(const u8 *)data);
225+
default:
226+
return -EOPNOTSUPP;
227+
}
228+
}
229+
230+
static int an8855_read_page(struct phy_device *phydev)
231+
{
232+
return __phy_read(phydev, AN8855_PHY_SELECT_PAGE);
233+
}
234+
235+
static int an8855_write_page(struct phy_device *phydev, int page)
236+
{
237+
return __phy_write(phydev, AN8855_PHY_SELECT_PAGE, page);
238+
}
239+
240+
static struct phy_driver an8855_driver[] = {
241+
{
242+
PHY_ID_MATCH_EXACT(AN8855_PHY_ID),
243+
.name = "Airoha AN8855 internal PHY",
244+
/* PHY_GBIT_FEATURES */
245+
.flags = PHY_IS_INTERNAL,
246+
.probe = an8855_probe,
247+
.config_init = an8855_config_init,
248+
.soft_reset = genphy_soft_reset,
249+
.get_tunable = an8855_get_tunable,
250+
.set_tunable = an8855_set_tunable,
251+
.suspend = genphy_suspend,
252+
.resume = genphy_resume,
253+
.read_page = an8855_read_page,
254+
.write_page = an8855_write_page,
255+
}, };
256+
257+
module_phy_driver(an8855_driver);
258+
259+
static struct mdio_device_id __maybe_unused an8855_tbl[] = {
260+
{ PHY_ID_MATCH_EXACT(AN8855_PHY_ID) },
261+
{ }
262+
};
263+
264+
MODULE_DEVICE_TABLE(mdio, an8855_tbl);
265+
266+
MODULE_DESCRIPTION("Airoha AN8855 PHY driver");
267+
MODULE_AUTHOR("Christian Marangi <[email protected]>");
268+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)