xref: /linux/drivers/net/phy/bcm87xx.c (revision 2da68a77)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (C) 2011 - 2012 Cavium, Inc.
4  */
5 
6 #include <linux/module.h>
7 #include <linux/phy.h>
8 #include <linux/of.h>
9 
10 #define PHY_ID_BCM8706	0x0143bdc1
11 #define PHY_ID_BCM8727	0x0143bff0
12 
13 #define BCM87XX_PMD_RX_SIGNAL_DETECT	0x000a
14 #define BCM87XX_10GBASER_PCS_STATUS	0x0020
15 #define BCM87XX_XGXS_LANE_STATUS	0x0018
16 
17 #define BCM87XX_LASI_CONTROL		0x9002
18 #define BCM87XX_LASI_STATUS		0x9005
19 
20 #if IS_ENABLED(CONFIG_OF_MDIO)
21 /* Set and/or override some configuration registers based on the
22  * broadcom,c45-reg-init property stored in the of_node for the phydev.
23  *
24  * broadcom,c45-reg-init = <devid reg mask value>,...;
25  *
26  * There may be one or more sets of <devid reg mask value>:
27  *
28  * devid: which sub-device to use.
29  * reg: the register.
30  * mask: if non-zero, ANDed with existing register value.
31  * value: ORed with the masked value and written to the regiser.
32  *
33  */
34 static int bcm87xx_of_reg_init(struct phy_device *phydev)
35 {
36 	const __be32 *paddr;
37 	const __be32 *paddr_end;
38 	int len, ret;
39 
40 	if (!phydev->mdio.dev.of_node)
41 		return 0;
42 
43 	paddr = of_get_property(phydev->mdio.dev.of_node,
44 				"broadcom,c45-reg-init", &len);
45 	if (!paddr)
46 		return 0;
47 
48 	paddr_end = paddr + (len /= sizeof(*paddr));
49 
50 	ret = 0;
51 
52 	while (paddr + 3 < paddr_end) {
53 		u16 devid	= be32_to_cpup(paddr++);
54 		u16 reg		= be32_to_cpup(paddr++);
55 		u16 mask	= be32_to_cpup(paddr++);
56 		u16 val_bits	= be32_to_cpup(paddr++);
57 		int val = 0;
58 
59 		if (mask) {
60 			val = phy_read_mmd(phydev, devid, reg);
61 			if (val < 0) {
62 				ret = val;
63 				goto err;
64 			}
65 			val &= mask;
66 		}
67 		val |= val_bits;
68 
69 		ret = phy_write_mmd(phydev, devid, reg, val);
70 		if (ret < 0)
71 			goto err;
72 	}
73 err:
74 	return ret;
75 }
76 #else
77 static int bcm87xx_of_reg_init(struct phy_device *phydev)
78 {
79 	return 0;
80 }
81 #endif /* CONFIG_OF_MDIO */
82 
83 static int bcm87xx_get_features(struct phy_device *phydev)
84 {
85 	linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseR_FEC_BIT,
86 			 phydev->supported);
87 	return 0;
88 }
89 
90 static int bcm87xx_config_init(struct phy_device *phydev)
91 {
92 	return bcm87xx_of_reg_init(phydev);
93 }
94 
95 static int bcm87xx_config_aneg(struct phy_device *phydev)
96 {
97 	return -EINVAL;
98 }
99 
100 static int bcm87xx_read_status(struct phy_device *phydev)
101 {
102 	int rx_signal_detect;
103 	int pcs_status;
104 	int xgxs_lane_status;
105 
106 	rx_signal_detect = phy_read_mmd(phydev, MDIO_MMD_PMAPMD,
107 					BCM87XX_PMD_RX_SIGNAL_DETECT);
108 	if (rx_signal_detect < 0)
109 		return rx_signal_detect;
110 
111 	if ((rx_signal_detect & 1) == 0)
112 		goto no_link;
113 
114 	pcs_status = phy_read_mmd(phydev, MDIO_MMD_PCS,
115 				  BCM87XX_10GBASER_PCS_STATUS);
116 	if (pcs_status < 0)
117 		return pcs_status;
118 
119 	if ((pcs_status & 1) == 0)
120 		goto no_link;
121 
122 	xgxs_lane_status = phy_read_mmd(phydev, MDIO_MMD_PHYXS,
123 					BCM87XX_XGXS_LANE_STATUS);
124 	if (xgxs_lane_status < 0)
125 		return xgxs_lane_status;
126 
127 	if ((xgxs_lane_status & 0x1000) == 0)
128 		goto no_link;
129 
130 	phydev->speed = 10000;
131 	phydev->link = 1;
132 	phydev->duplex = 1;
133 	return 0;
134 
135 no_link:
136 	phydev->link = 0;
137 	return 0;
138 }
139 
140 static int bcm87xx_config_intr(struct phy_device *phydev)
141 {
142 	int reg, err;
143 
144 	reg = phy_read_mmd(phydev, MDIO_MMD_PCS, BCM87XX_LASI_CONTROL);
145 
146 	if (reg < 0)
147 		return reg;
148 
149 	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
150 		err = phy_read_mmd(phydev, MDIO_MMD_PCS, BCM87XX_LASI_STATUS);
151 		if (err)
152 			return err;
153 
154 		reg |= 1;
155 		err = phy_write_mmd(phydev, MDIO_MMD_PCS,
156 				    BCM87XX_LASI_CONTROL, reg);
157 	} else {
158 		reg &= ~1;
159 		err = phy_write_mmd(phydev, MDIO_MMD_PCS,
160 				    BCM87XX_LASI_CONTROL, reg);
161 		if (err)
162 			return err;
163 
164 		err = phy_read_mmd(phydev, MDIO_MMD_PCS, BCM87XX_LASI_STATUS);
165 	}
166 
167 	return err;
168 }
169 
170 static irqreturn_t bcm87xx_handle_interrupt(struct phy_device *phydev)
171 {
172 	int irq_status;
173 
174 	irq_status = phy_read(phydev, BCM87XX_LASI_STATUS);
175 	if (irq_status < 0) {
176 		phy_error(phydev);
177 		return IRQ_NONE;
178 	}
179 
180 	if (irq_status == 0)
181 		return IRQ_NONE;
182 
183 	phy_trigger_machine(phydev);
184 
185 	return IRQ_HANDLED;
186 }
187 
188 static int bcm8706_match_phy_device(struct phy_device *phydev)
189 {
190 	return phydev->c45_ids.device_ids[4] == PHY_ID_BCM8706;
191 }
192 
193 static int bcm8727_match_phy_device(struct phy_device *phydev)
194 {
195 	return phydev->c45_ids.device_ids[4] == PHY_ID_BCM8727;
196 }
197 
198 static struct phy_driver bcm87xx_driver[] = {
199 {
200 	.phy_id		= PHY_ID_BCM8706,
201 	.phy_id_mask	= 0xffffffff,
202 	.name		= "Broadcom BCM8706",
203 	.get_features	= bcm87xx_get_features,
204 	.config_init	= bcm87xx_config_init,
205 	.config_aneg	= bcm87xx_config_aneg,
206 	.read_status	= bcm87xx_read_status,
207 	.config_intr	= bcm87xx_config_intr,
208 	.handle_interrupt = bcm87xx_handle_interrupt,
209 	.match_phy_device = bcm8706_match_phy_device,
210 }, {
211 	.phy_id		= PHY_ID_BCM8727,
212 	.phy_id_mask	= 0xffffffff,
213 	.name		= "Broadcom BCM8727",
214 	.get_features	= bcm87xx_get_features,
215 	.config_init	= bcm87xx_config_init,
216 	.config_aneg	= bcm87xx_config_aneg,
217 	.read_status	= bcm87xx_read_status,
218 	.config_intr	= bcm87xx_config_intr,
219 	.handle_interrupt = bcm87xx_handle_interrupt,
220 	.match_phy_device = bcm8727_match_phy_device,
221 } };
222 
223 module_phy_driver(bcm87xx_driver);
224 
225 MODULE_LICENSE("GPL v2");
226