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