xref: /openbmc/linux/drivers/net/phy/bcm87xx.c (revision cad75717)
18bbe833aSAndrew Lunn // SPDX-License-Identifier: GPL-2.0
2e9976d7cSDavid Daney /*
3e9976d7cSDavid Daney  * Copyright (C) 2011 - 2012 Cavium, Inc.
4e9976d7cSDavid Daney  */
5e9976d7cSDavid Daney 
6e9976d7cSDavid Daney #include <linux/module.h>
7e9976d7cSDavid Daney #include <linux/phy.h>
8e9976d7cSDavid Daney #include <linux/of.h>
9e9976d7cSDavid Daney 
10e9976d7cSDavid Daney #define PHY_ID_BCM8706	0x0143bdc1
11e9976d7cSDavid Daney #define PHY_ID_BCM8727	0x0143bff0
12e9976d7cSDavid Daney 
13*cad75717SAndrew Lunn #define BCM87XX_PMD_RX_SIGNAL_DETECT	0x000a
14*cad75717SAndrew Lunn #define BCM87XX_10GBASER_PCS_STATUS	0x0020
15*cad75717SAndrew Lunn #define BCM87XX_XGXS_LANE_STATUS	0x0018
16e9976d7cSDavid Daney 
17*cad75717SAndrew Lunn #define BCM87XX_LASI_CONTROL		0x9002
18*cad75717SAndrew Lunn #define BCM87XX_LASI_STATUS		0x9005
19e9976d7cSDavid Daney 
20e9976d7cSDavid Daney #if IS_ENABLED(CONFIG_OF_MDIO)
21e9976d7cSDavid Daney /* Set and/or override some configuration registers based on the
22806a0fbeSPeter Korsgaard  * broadcom,c45-reg-init property stored in the of_node for the phydev.
23e9976d7cSDavid Daney  *
24e9976d7cSDavid Daney  * broadcom,c45-reg-init = <devid reg mask value>,...;
25e9976d7cSDavid Daney  *
26e9976d7cSDavid Daney  * There may be one or more sets of <devid reg mask value>:
27e9976d7cSDavid Daney  *
28e9976d7cSDavid Daney  * devid: which sub-device to use.
29e9976d7cSDavid Daney  * reg: the register.
30e9976d7cSDavid Daney  * mask: if non-zero, ANDed with existing register value.
31e9976d7cSDavid Daney  * value: ORed with the masked value and written to the regiser.
32e9976d7cSDavid Daney  *
33e9976d7cSDavid Daney  */
bcm87xx_of_reg_init(struct phy_device * phydev)34e9976d7cSDavid Daney static int bcm87xx_of_reg_init(struct phy_device *phydev)
35e9976d7cSDavid Daney {
36e9976d7cSDavid Daney 	const __be32 *paddr;
37e9976d7cSDavid Daney 	const __be32 *paddr_end;
38e9976d7cSDavid Daney 	int len, ret;
39e9976d7cSDavid Daney 
40e5a03bfdSAndrew Lunn 	if (!phydev->mdio.dev.of_node)
41e9976d7cSDavid Daney 		return 0;
42e9976d7cSDavid Daney 
43e5a03bfdSAndrew Lunn 	paddr = of_get_property(phydev->mdio.dev.of_node,
44e9976d7cSDavid Daney 				"broadcom,c45-reg-init", &len);
45e9976d7cSDavid Daney 	if (!paddr)
46e9976d7cSDavid Daney 		return 0;
47e9976d7cSDavid Daney 
48e9976d7cSDavid Daney 	paddr_end = paddr + (len /= sizeof(*paddr));
49e9976d7cSDavid Daney 
50e9976d7cSDavid Daney 	ret = 0;
51e9976d7cSDavid Daney 
52e9976d7cSDavid Daney 	while (paddr + 3 < paddr_end) {
53e9976d7cSDavid Daney 		u16 devid	= be32_to_cpup(paddr++);
54e9976d7cSDavid Daney 		u16 reg		= be32_to_cpup(paddr++);
55e9976d7cSDavid Daney 		u16 mask	= be32_to_cpup(paddr++);
56e9976d7cSDavid Daney 		u16 val_bits	= be32_to_cpup(paddr++);
57775f2547SWenpeng Liang 		int val = 0;
58775f2547SWenpeng Liang 
59e9976d7cSDavid Daney 		if (mask) {
60*cad75717SAndrew Lunn 			val = phy_read_mmd(phydev, devid, reg);
61e9976d7cSDavid Daney 			if (val < 0) {
62e9976d7cSDavid Daney 				ret = val;
63e9976d7cSDavid Daney 				goto err;
64e9976d7cSDavid Daney 			}
65e9976d7cSDavid Daney 			val &= mask;
66e9976d7cSDavid Daney 		}
67e9976d7cSDavid Daney 		val |= val_bits;
68e9976d7cSDavid Daney 
69*cad75717SAndrew Lunn 		ret = phy_write_mmd(phydev, devid, reg, val);
70e9976d7cSDavid Daney 		if (ret < 0)
71e9976d7cSDavid Daney 			goto err;
72e9976d7cSDavid Daney 	}
73e9976d7cSDavid Daney err:
74e9976d7cSDavid Daney 	return ret;
75e9976d7cSDavid Daney }
76e9976d7cSDavid Daney #else
bcm87xx_of_reg_init(struct phy_device * phydev)77e9976d7cSDavid Daney static int bcm87xx_of_reg_init(struct phy_device *phydev)
78e9976d7cSDavid Daney {
79e9976d7cSDavid Daney 	return 0;
80e9976d7cSDavid Daney }
81e9976d7cSDavid Daney #endif /* CONFIG_OF_MDIO */
82e9976d7cSDavid Daney 
bcm87xx_get_features(struct phy_device * phydev)83476cc6c9SHeiner Kallweit static int bcm87xx_get_features(struct phy_device *phydev)
84e9976d7cSDavid Daney {
853c1bcc86SAndrew Lunn 	linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseR_FEC_BIT,
863c1bcc86SAndrew Lunn 			 phydev->supported);
87e9976d7cSDavid Daney 	return 0;
88e9976d7cSDavid Daney }
89e9976d7cSDavid Daney 
bcm87xx_config_init(struct phy_device * phydev)90476cc6c9SHeiner Kallweit static int bcm87xx_config_init(struct phy_device *phydev)
91476cc6c9SHeiner Kallweit {
92476cc6c9SHeiner Kallweit 	return bcm87xx_of_reg_init(phydev);
93476cc6c9SHeiner Kallweit }
94476cc6c9SHeiner Kallweit 
bcm87xx_config_aneg(struct phy_device * phydev)95e9976d7cSDavid Daney static int bcm87xx_config_aneg(struct phy_device *phydev)
96e9976d7cSDavid Daney {
97e9976d7cSDavid Daney 	return -EINVAL;
98e9976d7cSDavid Daney }
99e9976d7cSDavid Daney 
bcm87xx_read_status(struct phy_device * phydev)100e9976d7cSDavid Daney static int bcm87xx_read_status(struct phy_device *phydev)
101e9976d7cSDavid Daney {
102e9976d7cSDavid Daney 	int rx_signal_detect;
103e9976d7cSDavid Daney 	int pcs_status;
104e9976d7cSDavid Daney 	int xgxs_lane_status;
105e9976d7cSDavid Daney 
106*cad75717SAndrew Lunn 	rx_signal_detect = phy_read_mmd(phydev, MDIO_MMD_PMAPMD,
107*cad75717SAndrew Lunn 					BCM87XX_PMD_RX_SIGNAL_DETECT);
108e9976d7cSDavid Daney 	if (rx_signal_detect < 0)
109e9976d7cSDavid Daney 		return rx_signal_detect;
110e9976d7cSDavid Daney 
111e9976d7cSDavid Daney 	if ((rx_signal_detect & 1) == 0)
112e9976d7cSDavid Daney 		goto no_link;
113e9976d7cSDavid Daney 
114*cad75717SAndrew Lunn 	pcs_status = phy_read_mmd(phydev, MDIO_MMD_PCS,
115*cad75717SAndrew Lunn 				  BCM87XX_10GBASER_PCS_STATUS);
116e9976d7cSDavid Daney 	if (pcs_status < 0)
117e9976d7cSDavid Daney 		return pcs_status;
118e9976d7cSDavid Daney 
119e9976d7cSDavid Daney 	if ((pcs_status & 1) == 0)
120e9976d7cSDavid Daney 		goto no_link;
121e9976d7cSDavid Daney 
122*cad75717SAndrew Lunn 	xgxs_lane_status = phy_read_mmd(phydev, MDIO_MMD_PHYXS,
123*cad75717SAndrew Lunn 					BCM87XX_XGXS_LANE_STATUS);
124e9976d7cSDavid Daney 	if (xgxs_lane_status < 0)
125e9976d7cSDavid Daney 		return xgxs_lane_status;
126e9976d7cSDavid Daney 
127e9976d7cSDavid Daney 	if ((xgxs_lane_status & 0x1000) == 0)
128e9976d7cSDavid Daney 		goto no_link;
129e9976d7cSDavid Daney 
130e9976d7cSDavid Daney 	phydev->speed = 10000;
131e9976d7cSDavid Daney 	phydev->link = 1;
132e9976d7cSDavid Daney 	phydev->duplex = 1;
133e9976d7cSDavid Daney 	return 0;
134e9976d7cSDavid Daney 
135e9976d7cSDavid Daney no_link:
136e9976d7cSDavid Daney 	phydev->link = 0;
137e9976d7cSDavid Daney 	return 0;
138e9976d7cSDavid Daney }
139e9976d7cSDavid Daney 
bcm87xx_config_intr(struct phy_device * phydev)140e9976d7cSDavid Daney static int bcm87xx_config_intr(struct phy_device *phydev)
141e9976d7cSDavid Daney {
142e9976d7cSDavid Daney 	int reg, err;
143e9976d7cSDavid Daney 
144*cad75717SAndrew Lunn 	reg = phy_read_mmd(phydev, MDIO_MMD_PCS, BCM87XX_LASI_CONTROL);
145e9976d7cSDavid Daney 
146e9976d7cSDavid Daney 	if (reg < 0)
147e9976d7cSDavid Daney 		return reg;
148e9976d7cSDavid Daney 
14915772e4dSIoana Ciornei 	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
150*cad75717SAndrew Lunn 		err = phy_read_mmd(phydev, MDIO_MMD_PCS, BCM87XX_LASI_STATUS);
15115772e4dSIoana Ciornei 		if (err)
15215772e4dSIoana Ciornei 			return err;
153e9976d7cSDavid Daney 
15415772e4dSIoana Ciornei 		reg |= 1;
155*cad75717SAndrew Lunn 		err = phy_write_mmd(phydev, MDIO_MMD_PCS,
156*cad75717SAndrew Lunn 				    BCM87XX_LASI_CONTROL, reg);
15715772e4dSIoana Ciornei 	} else {
15815772e4dSIoana Ciornei 		reg &= ~1;
159*cad75717SAndrew Lunn 		err = phy_write_mmd(phydev, MDIO_MMD_PCS,
160*cad75717SAndrew Lunn 				    BCM87XX_LASI_CONTROL, reg);
16115772e4dSIoana Ciornei 		if (err)
16215772e4dSIoana Ciornei 			return err;
16315772e4dSIoana Ciornei 
164*cad75717SAndrew Lunn 		err = phy_read_mmd(phydev, MDIO_MMD_PCS, BCM87XX_LASI_STATUS);
16515772e4dSIoana Ciornei 	}
16615772e4dSIoana Ciornei 
167e9976d7cSDavid Daney 	return err;
168e9976d7cSDavid Daney }
169e9976d7cSDavid Daney 
bcm87xx_handle_interrupt(struct phy_device * phydev)1704567d5c3SIoana Ciornei static irqreturn_t bcm87xx_handle_interrupt(struct phy_device *phydev)
1714567d5c3SIoana Ciornei {
1724567d5c3SIoana Ciornei 	int irq_status;
1734567d5c3SIoana Ciornei 
1744567d5c3SIoana Ciornei 	irq_status = phy_read(phydev, BCM87XX_LASI_STATUS);
1754567d5c3SIoana Ciornei 	if (irq_status < 0) {
1764567d5c3SIoana Ciornei 		phy_error(phydev);
1774567d5c3SIoana Ciornei 		return IRQ_NONE;
1784567d5c3SIoana Ciornei 	}
1794567d5c3SIoana Ciornei 
1804567d5c3SIoana Ciornei 	if (irq_status == 0)
1814567d5c3SIoana Ciornei 		return IRQ_NONE;
1824567d5c3SIoana Ciornei 
1834567d5c3SIoana Ciornei 	phy_trigger_machine(phydev);
1844567d5c3SIoana Ciornei 
1854567d5c3SIoana Ciornei 	return IRQ_HANDLED;
1864567d5c3SIoana Ciornei }
1874567d5c3SIoana Ciornei 
bcm8706_match_phy_device(struct phy_device * phydev)188e9976d7cSDavid Daney static int bcm8706_match_phy_device(struct phy_device *phydev)
189e9976d7cSDavid Daney {
190e9976d7cSDavid Daney 	return phydev->c45_ids.device_ids[4] == PHY_ID_BCM8706;
191e9976d7cSDavid Daney }
192e9976d7cSDavid Daney 
bcm8727_match_phy_device(struct phy_device * phydev)193e9976d7cSDavid Daney static int bcm8727_match_phy_device(struct phy_device *phydev)
194e9976d7cSDavid Daney {
195e9976d7cSDavid Daney 	return phydev->c45_ids.device_ids[4] == PHY_ID_BCM8727;
196e9976d7cSDavid Daney }
197e9976d7cSDavid Daney 
198d5bf9071SChristian Hohnstaedt static struct phy_driver bcm87xx_driver[] = {
199d5bf9071SChristian Hohnstaedt {
200e9976d7cSDavid Daney 	.phy_id		= PHY_ID_BCM8706,
201e9976d7cSDavid Daney 	.phy_id_mask	= 0xffffffff,
202e9976d7cSDavid Daney 	.name		= "Broadcom BCM8706",
203476cc6c9SHeiner Kallweit 	.get_features	= bcm87xx_get_features,
204e9976d7cSDavid Daney 	.config_init	= bcm87xx_config_init,
205e9976d7cSDavid Daney 	.config_aneg	= bcm87xx_config_aneg,
206e9976d7cSDavid Daney 	.read_status	= bcm87xx_read_status,
207e9976d7cSDavid Daney 	.config_intr	= bcm87xx_config_intr,
2084567d5c3SIoana Ciornei 	.handle_interrupt = bcm87xx_handle_interrupt,
209e9976d7cSDavid Daney 	.match_phy_device = bcm8706_match_phy_device,
210d5bf9071SChristian Hohnstaedt }, {
211e9976d7cSDavid Daney 	.phy_id		= PHY_ID_BCM8727,
212e9976d7cSDavid Daney 	.phy_id_mask	= 0xffffffff,
213e9976d7cSDavid Daney 	.name		= "Broadcom BCM8727",
214476cc6c9SHeiner Kallweit 	.get_features	= bcm87xx_get_features,
215e9976d7cSDavid Daney 	.config_init	= bcm87xx_config_init,
216e9976d7cSDavid Daney 	.config_aneg	= bcm87xx_config_aneg,
217e9976d7cSDavid Daney 	.read_status	= bcm87xx_read_status,
218e9976d7cSDavid Daney 	.config_intr	= bcm87xx_config_intr,
2194567d5c3SIoana Ciornei 	.handle_interrupt = bcm87xx_handle_interrupt,
220e9976d7cSDavid Daney 	.match_phy_device = bcm8727_match_phy_device,
221d5bf9071SChristian Hohnstaedt } };
222e9976d7cSDavid Daney 
22350fd7150SJohan Hovold module_phy_driver(bcm87xx_driver);
2249913b8c8SPeter Hüwe 
2258bbe833aSAndrew Lunn MODULE_LICENSE("GPL v2");
226