Commit | Line | Data |
---|---|---|
165cd04f OR |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Driver for the Texas Instruments DP83TD510 PHY | |
3 | * Copyright (c) 2022 Pengutronix, Oleksij Rempel <kernel@pengutronix.de> | |
4 | */ | |
5 | ||
6 | #include <linux/bitfield.h> | |
7 | #include <linux/kernel.h> | |
8 | #include <linux/module.h> | |
9 | #include <linux/phy.h> | |
10 | ||
11 | #define DP83TD510E_PHY_ID 0x20000181 | |
12 | ||
13 | /* MDIO_MMD_VEND2 registers */ | |
14 | #define DP83TD510E_PHY_STS 0x10 | |
15 | #define DP83TD510E_STS_MII_INT BIT(7) | |
16 | #define DP83TD510E_LINK_STATUS BIT(0) | |
17 | ||
18 | #define DP83TD510E_GEN_CFG 0x11 | |
19 | #define DP83TD510E_GENCFG_INT_POLARITY BIT(3) | |
20 | #define DP83TD510E_GENCFG_INT_EN BIT(1) | |
21 | #define DP83TD510E_GENCFG_INT_OE BIT(0) | |
22 | ||
23 | #define DP83TD510E_INTERRUPT_REG_1 0x12 | |
24 | #define DP83TD510E_INT1_LINK BIT(13) | |
25 | #define DP83TD510E_INT1_LINK_EN BIT(5) | |
26 | ||
27 | #define DP83TD510E_AN_STAT_1 0x60c | |
28 | #define DP83TD510E_MASTER_SLAVE_RESOL_FAIL BIT(15) | |
29 | ||
a80d8fb7 OR |
30 | #define DP83TD510E_MSE_DETECT 0xa85 |
31 | ||
32 | #define DP83TD510_SQI_MAX 7 | |
33 | ||
34 | /* Register values are converted to SNR(dB) as suggested by | |
35 | * "Application Report - DP83TD510E Cable Diagnostics Toolkit": | |
36 | * SNR(dB) = -10 * log10 (VAL/2^17) - 1.76 dB. | |
37 | * SQI ranges are implemented according to "OPEN ALLIANCE - Advanced diagnostic | |
38 | * features for 100BASE-T1 automotive Ethernet PHYs" | |
39 | */ | |
40 | static const u16 dp83td510_mse_sqi_map[] = { | |
41 | 0x0569, /* < 18dB */ | |
42 | 0x044c, /* 18dB =< SNR < 19dB */ | |
43 | 0x0369, /* 19dB =< SNR < 20dB */ | |
44 | 0x02b6, /* 20dB =< SNR < 21dB */ | |
45 | 0x0227, /* 21dB =< SNR < 22dB */ | |
46 | 0x01b6, /* 22dB =< SNR < 23dB */ | |
47 | 0x015b, /* 23dB =< SNR < 24dB */ | |
48 | 0x0000 /* 24dB =< SNR */ | |
49 | }; | |
50 | ||
165cd04f OR |
51 | static int dp83td510_config_intr(struct phy_device *phydev) |
52 | { | |
53 | int ret; | |
54 | ||
55 | if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { | |
56 | /* Clear any pending interrupts */ | |
57 | ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_PHY_STS, | |
58 | 0x0); | |
59 | if (ret) | |
60 | return ret; | |
61 | ||
62 | ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, | |
63 | DP83TD510E_INTERRUPT_REG_1, | |
64 | DP83TD510E_INT1_LINK_EN); | |
65 | if (ret) | |
66 | return ret; | |
67 | ||
68 | ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, | |
69 | DP83TD510E_GEN_CFG, | |
70 | DP83TD510E_GENCFG_INT_POLARITY | | |
71 | DP83TD510E_GENCFG_INT_EN | | |
72 | DP83TD510E_GENCFG_INT_OE); | |
73 | } else { | |
74 | ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, | |
75 | DP83TD510E_INTERRUPT_REG_1, 0x0); | |
76 | if (ret) | |
77 | return ret; | |
78 | ||
79 | ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, | |
80 | DP83TD510E_GEN_CFG, | |
81 | DP83TD510E_GENCFG_INT_EN); | |
82 | if (ret) | |
83 | return ret; | |
84 | ||
85 | /* Clear any pending interrupts */ | |
86 | ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_PHY_STS, | |
87 | 0x0); | |
88 | } | |
89 | ||
90 | return ret; | |
91 | } | |
92 | ||
93 | static irqreturn_t dp83td510_handle_interrupt(struct phy_device *phydev) | |
94 | { | |
95 | int ret; | |
96 | ||
97 | ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_PHY_STS); | |
98 | if (ret < 0) { | |
99 | phy_error(phydev); | |
100 | return IRQ_NONE; | |
101 | } else if (!(ret & DP83TD510E_STS_MII_INT)) { | |
102 | return IRQ_NONE; | |
103 | } | |
104 | ||
105 | /* Read the current enabled interrupts */ | |
106 | ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_INTERRUPT_REG_1); | |
107 | if (ret < 0) { | |
108 | phy_error(phydev); | |
109 | return IRQ_NONE; | |
110 | } else if (!(ret & DP83TD510E_INT1_LINK_EN) || | |
111 | !(ret & DP83TD510E_INT1_LINK)) { | |
112 | return IRQ_NONE; | |
113 | } | |
114 | ||
115 | phy_trigger_machine(phydev); | |
116 | ||
117 | return IRQ_HANDLED; | |
118 | } | |
119 | ||
120 | static int dp83td510_read_status(struct phy_device *phydev) | |
121 | { | |
122 | u16 phy_sts; | |
123 | int ret; | |
124 | ||
125 | phydev->speed = SPEED_UNKNOWN; | |
126 | phydev->duplex = DUPLEX_UNKNOWN; | |
127 | phydev->pause = 0; | |
128 | phydev->asym_pause = 0; | |
129 | linkmode_zero(phydev->lp_advertising); | |
130 | ||
131 | phy_sts = phy_read(phydev, DP83TD510E_PHY_STS); | |
132 | ||
133 | phydev->link = !!(phy_sts & DP83TD510E_LINK_STATUS); | |
134 | if (phydev->link) { | |
135 | /* This PHY supports only one link mode: 10BaseT1L_Full */ | |
136 | phydev->duplex = DUPLEX_FULL; | |
137 | phydev->speed = SPEED_10; | |
138 | ||
139 | if (phydev->autoneg == AUTONEG_ENABLE) { | |
140 | ret = genphy_c45_read_lpa(phydev); | |
141 | if (ret) | |
142 | return ret; | |
143 | ||
144 | phy_resolve_aneg_linkmode(phydev); | |
145 | } | |
146 | } | |
147 | ||
148 | if (phydev->autoneg == AUTONEG_ENABLE) { | |
149 | ret = genphy_c45_baset1_read_status(phydev); | |
150 | if (ret < 0) | |
151 | return ret; | |
152 | ||
153 | ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, | |
154 | DP83TD510E_AN_STAT_1); | |
155 | if (ret < 0) | |
156 | return ret; | |
157 | ||
158 | if (ret & DP83TD510E_MASTER_SLAVE_RESOL_FAIL) | |
159 | phydev->master_slave_state = MASTER_SLAVE_STATE_ERR; | |
160 | } else { | |
161 | return genphy_c45_pma_baset1_read_master_slave(phydev); | |
162 | } | |
163 | ||
164 | return 0; | |
165 | } | |
166 | ||
167 | static int dp83td510_config_aneg(struct phy_device *phydev) | |
168 | { | |
169 | bool changed = false; | |
170 | int ret; | |
171 | ||
172 | ret = genphy_c45_pma_baset1_setup_master_slave(phydev); | |
173 | if (ret < 0) | |
174 | return ret; | |
175 | ||
176 | if (phydev->autoneg == AUTONEG_DISABLE) | |
177 | return genphy_c45_an_disable_aneg(phydev); | |
178 | ||
179 | ret = genphy_c45_an_config_aneg(phydev); | |
180 | if (ret < 0) | |
181 | return ret; | |
182 | if (ret > 0) | |
183 | changed = true; | |
184 | ||
185 | return genphy_c45_check_and_restart_aneg(phydev, changed); | |
186 | } | |
187 | ||
a80d8fb7 OR |
188 | static int dp83td510_get_sqi(struct phy_device *phydev) |
189 | { | |
190 | int sqi, ret; | |
191 | u16 mse_val; | |
192 | ||
193 | if (!phydev->link) | |
194 | return 0; | |
195 | ||
196 | ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_MSE_DETECT); | |
197 | if (ret < 0) | |
198 | return ret; | |
199 | ||
200 | mse_val = 0xFFFF & ret; | |
201 | for (sqi = 0; sqi < ARRAY_SIZE(dp83td510_mse_sqi_map); sqi++) { | |
202 | if (mse_val >= dp83td510_mse_sqi_map[sqi]) | |
203 | return sqi; | |
204 | } | |
205 | ||
206 | return -EINVAL; | |
207 | } | |
208 | ||
209 | static int dp83td510_get_sqi_max(struct phy_device *phydev) | |
210 | { | |
211 | return DP83TD510_SQI_MAX; | |
212 | } | |
213 | ||
165cd04f OR |
214 | static int dp83td510_get_features(struct phy_device *phydev) |
215 | { | |
216 | /* This PHY can't respond on MDIO bus if no RMII clock is enabled. | |
217 | * In case RMII mode is used (most meaningful mode for this PHY) and | |
218 | * the PHY do not have own XTAL, and CLK providing MAC is not probed, | |
219 | * we won't be able to read all needed ability registers. | |
220 | * So provide it manually. | |
221 | */ | |
222 | ||
223 | linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported); | |
224 | linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, phydev->supported); | |
225 | linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT, phydev->supported); | |
226 | linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT1L_Full_BIT, | |
227 | phydev->supported); | |
228 | ||
229 | return 0; | |
230 | } | |
231 | ||
232 | static struct phy_driver dp83td510_driver[] = { | |
233 | { | |
234 | PHY_ID_MATCH_MODEL(DP83TD510E_PHY_ID), | |
235 | .name = "TI DP83TD510E", | |
236 | ||
237 | .config_aneg = dp83td510_config_aneg, | |
238 | .read_status = dp83td510_read_status, | |
239 | .get_features = dp83td510_get_features, | |
240 | .config_intr = dp83td510_config_intr, | |
241 | .handle_interrupt = dp83td510_handle_interrupt, | |
a80d8fb7 OR |
242 | .get_sqi = dp83td510_get_sqi, |
243 | .get_sqi_max = dp83td510_get_sqi_max, | |
165cd04f OR |
244 | |
245 | .suspend = genphy_suspend, | |
246 | .resume = genphy_resume, | |
247 | } }; | |
248 | module_phy_driver(dp83td510_driver); | |
249 | ||
250 | static struct mdio_device_id __maybe_unused dp83td510_tbl[] = { | |
251 | { PHY_ID_MATCH_MODEL(DP83TD510E_PHY_ID) }, | |
252 | { } | |
253 | }; | |
254 | MODULE_DEVICE_TABLE(mdio, dp83td510_tbl); | |
255 | ||
256 | MODULE_DESCRIPTION("Texas Instruments DP83TD510E PHY driver"); | |
257 | MODULE_AUTHOR("Oleksij Rempel <kernel@pengutronix.de>"); | |
258 | MODULE_LICENSE("GPL v2"); |