1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (C) 2015 Broadcom Corporation
4 */
5
6/* Broadcom Cygnus SoC internal transceivers support. */
7#include "bcm-phy-lib.h"
8#include <linux/brcmphy.h>
9#include <linux/module.h>
10#include <linux/netdevice.h>
11#include <linux/phy.h>
12
13struct bcm_omega_phy_priv {
14 u64 *stats;
15};
16
17/* Broadcom Cygnus Phy specific registers */
18#define MII_BCM_CYGNUS_AFE_VDAC_ICTRL_0 0x91E5 /* VDAL Control register */
19
20static int bcm_cygnus_afe_config(struct phy_device *phydev)
21{
22 int rc;
23
24 /* ensure smdspclk is enabled */
25 rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, val: 0x0c30);
26 if (rc < 0)
27 return rc;
28
29 /* AFE_VDAC_ICTRL_0 bit 7:4 Iq=1100 for 1g 10bt, normal modes */
30 rc = bcm_phy_write_misc(phydev, reg: 0x39, chl: 0x01, value: 0xA7C8);
31 if (rc < 0)
32 return rc;
33
34 /* AFE_HPF_TRIM_OTHERS bit11=1, short cascode enable for all modes*/
35 rc = bcm_phy_write_misc(phydev, reg: 0x3A, chl: 0x00, value: 0x0803);
36 if (rc < 0)
37 return rc;
38
39 /* AFE_TX_CONFIG_1 bit 7:4 Iq=1100 for test modes */
40 rc = bcm_phy_write_misc(phydev, reg: 0x3A, chl: 0x01, value: 0xA740);
41 if (rc < 0)
42 return rc;
43
44 /* AFE TEMPSEN_OTHERS rcal_HT, rcal_LT 10000 */
45 rc = bcm_phy_write_misc(phydev, reg: 0x3A, chl: 0x03, value: 0x8400);
46 if (rc < 0)
47 return rc;
48
49 /* AFE_FUTURE_RSV bit 2:0 rccal <2:0>=100 */
50 rc = bcm_phy_write_misc(phydev, reg: 0x3B, chl: 0x00, value: 0x0004);
51 if (rc < 0)
52 return rc;
53
54 /* Adjust bias current trim to overcome digital offSet */
55 rc = phy_write(phydev, MII_BRCM_CORE_BASE1E, val: 0x02);
56 if (rc < 0)
57 return rc;
58
59 /* make rcal=100, since rdb default is 000 */
60 rc = bcm_phy_write_exp_sel(phydev, MII_BRCM_CORE_EXPB1, val: 0x10);
61 if (rc < 0)
62 return rc;
63
64 /* CORE_EXPB0, Reset R_CAL/RC_CAL Engine */
65 rc = bcm_phy_write_exp_sel(phydev, MII_BRCM_CORE_EXPB0, val: 0x10);
66 if (rc < 0)
67 return rc;
68
69 /* CORE_EXPB0, Disable Reset R_CAL/RC_CAL Engine */
70 rc = bcm_phy_write_exp_sel(phydev, MII_BRCM_CORE_EXPB0, val: 0x00);
71
72 return 0;
73}
74
75static int bcm_cygnus_config_init(struct phy_device *phydev)
76{
77 int reg, rc;
78
79 reg = phy_read(phydev, MII_BCM54XX_ECR);
80 if (reg < 0)
81 return reg;
82
83 /* Mask interrupts globally. */
84 reg |= MII_BCM54XX_ECR_IM;
85 rc = phy_write(phydev, MII_BCM54XX_ECR, val: reg);
86 if (rc)
87 return rc;
88
89 /* Unmask events of interest */
90 reg = ~(MII_BCM54XX_INT_DUPLEX |
91 MII_BCM54XX_INT_SPEED |
92 MII_BCM54XX_INT_LINK);
93 rc = phy_write(phydev, MII_BCM54XX_IMR, val: reg);
94 if (rc)
95 return rc;
96
97 /* Apply AFE settings for the PHY */
98 rc = bcm_cygnus_afe_config(phydev);
99 if (rc)
100 return rc;
101
102 /* Advertise EEE */
103 rc = bcm_phy_set_eee(phydev, enable: true);
104 if (rc)
105 return rc;
106
107 /* Enable APD */
108 return bcm_phy_enable_apd(phydev, dll_pwr_down: false);
109}
110
111static int bcm_cygnus_resume(struct phy_device *phydev)
112{
113 int rc;
114
115 genphy_resume(phydev);
116
117 /* Re-initialize the PHY to apply AFE work-arounds and
118 * configurations when coming out of suspend.
119 */
120 rc = bcm_cygnus_config_init(phydev);
121 if (rc)
122 return rc;
123
124 /* restart auto negotiation with the new settings */
125 return genphy_config_aneg(phydev);
126}
127
128static int bcm_omega_config_init(struct phy_device *phydev)
129{
130 u8 count, rev;
131 int ret = 0;
132
133 rev = phydev->phy_id & ~phydev->drv->phy_id_mask;
134
135 pr_info_once("%s: %s PHY revision: 0x%02x\n",
136 phydev_name(phydev), phydev->drv->name, rev);
137
138 /* Dummy read to a register to workaround an issue upon reset where the
139 * internal inverter may not allow the first MDIO transaction to pass
140 * the MDIO management controller and make us return 0xffff for such
141 * reads.
142 */
143 phy_read(phydev, MII_BMSR);
144
145 switch (rev) {
146 case 0x00:
147 ret = bcm_phy_28nm_a0b0_afe_config_init(phydev);
148 break;
149 default:
150 break;
151 }
152
153 if (ret)
154 return ret;
155
156 ret = bcm_phy_downshift_get(phydev, count: &count);
157 if (ret)
158 return ret;
159
160 /* Only enable EEE if Wirespeed/downshift is disabled */
161 ret = bcm_phy_set_eee(phydev, enable: count == DOWNSHIFT_DEV_DISABLE);
162 if (ret)
163 return ret;
164
165 return bcm_phy_enable_apd(phydev, dll_pwr_down: true);
166}
167
168static int bcm_omega_resume(struct phy_device *phydev)
169{
170 int ret;
171
172 /* Re-apply workarounds coming out suspend/resume */
173 ret = bcm_omega_config_init(phydev);
174 if (ret)
175 return ret;
176
177 /* 28nm Gigabit PHYs come out of reset without any half-duplex
178 * or "hub" compliant advertised mode, fix that. This does not
179 * cause any problems with the PHY library since genphy_config_aneg()
180 * gracefully handles auto-negotiated and forced modes.
181 */
182 return genphy_config_aneg(phydev);
183}
184
185static int bcm_omega_get_tunable(struct phy_device *phydev,
186 struct ethtool_tunable *tuna, void *data)
187{
188 switch (tuna->id) {
189 case ETHTOOL_PHY_DOWNSHIFT:
190 return bcm_phy_downshift_get(phydev, count: (u8 *)data);
191 default:
192 return -EOPNOTSUPP;
193 }
194}
195
196static int bcm_omega_set_tunable(struct phy_device *phydev,
197 struct ethtool_tunable *tuna,
198 const void *data)
199{
200 u8 count = *(u8 *)data;
201 int ret;
202
203 switch (tuna->id) {
204 case ETHTOOL_PHY_DOWNSHIFT:
205 ret = bcm_phy_downshift_set(phydev, count);
206 break;
207 default:
208 return -EOPNOTSUPP;
209 }
210
211 if (ret)
212 return ret;
213
214 /* Disable EEE advertisement since this prevents the PHY
215 * from successfully linking up, trigger auto-negotiation restart
216 * to let the MAC decide what to do.
217 */
218 ret = bcm_phy_set_eee(phydev, enable: count == DOWNSHIFT_DEV_DISABLE);
219 if (ret)
220 return ret;
221
222 return genphy_restart_aneg(phydev);
223}
224
225static void bcm_omega_get_phy_stats(struct phy_device *phydev,
226 struct ethtool_stats *stats, u64 *data)
227{
228 struct bcm_omega_phy_priv *priv = phydev->priv;
229
230 bcm_phy_get_stats(phydev, shadow: priv->stats, stats, data);
231}
232
233static int bcm_omega_probe(struct phy_device *phydev)
234{
235 struct bcm_omega_phy_priv *priv;
236
237 priv = devm_kzalloc(dev: &phydev->mdio.dev, size: sizeof(*priv), GFP_KERNEL);
238 if (!priv)
239 return -ENOMEM;
240
241 phydev->priv = priv;
242
243 priv->stats = devm_kcalloc(dev: &phydev->mdio.dev,
244 n: bcm_phy_get_sset_count(phydev), size: sizeof(u64),
245 GFP_KERNEL);
246 if (!priv->stats)
247 return -ENOMEM;
248
249 return 0;
250}
251
252static struct phy_driver bcm_cygnus_phy_driver[] = {
253{
254 .phy_id = PHY_ID_BCM_CYGNUS,
255 .phy_id_mask = 0xfffffff0,
256 .name = "Broadcom Cygnus PHY",
257 /* PHY_GBIT_FEATURES */
258 .config_init = bcm_cygnus_config_init,
259 .config_intr = bcm_phy_config_intr,
260 .handle_interrupt = bcm_phy_handle_interrupt,
261 .suspend = genphy_suspend,
262 .resume = bcm_cygnus_resume,
263}, {
264 .phy_id = PHY_ID_BCM_OMEGA,
265 .phy_id_mask = 0xfffffff0,
266 .name = "Broadcom Omega Combo GPHY",
267 /* PHY_GBIT_FEATURES */
268 .flags = PHY_IS_INTERNAL,
269 .config_init = bcm_omega_config_init,
270 .suspend = genphy_suspend,
271 .resume = bcm_omega_resume,
272 .get_tunable = bcm_omega_get_tunable,
273 .set_tunable = bcm_omega_set_tunable,
274 .get_sset_count = bcm_phy_get_sset_count,
275 .get_strings = bcm_phy_get_strings,
276 .get_stats = bcm_omega_get_phy_stats,
277 .probe = bcm_omega_probe,
278}
279};
280
281static struct mdio_device_id __maybe_unused bcm_cygnus_phy_tbl[] = {
282 { PHY_ID_BCM_CYGNUS, 0xfffffff0, },
283 { PHY_ID_BCM_OMEGA, 0xfffffff0, },
284 { }
285};
286MODULE_DEVICE_TABLE(mdio, bcm_cygnus_phy_tbl);
287
288module_phy_driver(bcm_cygnus_phy_driver);
289
290MODULE_DESCRIPTION("Broadcom Cygnus internal PHY driver");
291MODULE_LICENSE("GPL v2");
292MODULE_AUTHOR("Broadcom Corporation");
293

source code of linux/drivers/net/phy/bcm-cygnus.c