Loading...
Note: File does not exist in v6.13.7.
1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (C) 2022 Bootlin
4 *
5 * Maxime Chevallier <maxime.chevallier@bootlin.com>
6 */
7
8#include <linux/netdevice.h>
9#include <linux/phy.h>
10#include <linux/phylink.h>
11#include <linux/pcs-altera-tse.h>
12
13/* SGMII PCS register addresses
14 */
15#define SGMII_PCS_LINK_TIMER_0 0x12
16#define SGMII_PCS_LINK_TIMER_1 0x13
17#define SGMII_PCS_IF_MODE 0x14
18#define PCS_IF_MODE_SGMII_ENA BIT(0)
19#define PCS_IF_MODE_USE_SGMII_AN BIT(1)
20#define PCS_IF_MODE_SGMI_HALF_DUPLEX BIT(4)
21#define PCS_IF_MODE_SGMI_PHY_AN BIT(5)
22#define SGMII_PCS_SW_RESET_TIMEOUT 100 /* usecs */
23
24struct altera_tse_pcs {
25 struct phylink_pcs pcs;
26 void __iomem *base;
27 int reg_width;
28};
29
30static struct altera_tse_pcs *phylink_pcs_to_tse_pcs(struct phylink_pcs *pcs)
31{
32 return container_of(pcs, struct altera_tse_pcs, pcs);
33}
34
35static u16 tse_pcs_read(struct altera_tse_pcs *tse_pcs, int regnum)
36{
37 if (tse_pcs->reg_width == 4)
38 return readl(tse_pcs->base + regnum * 4);
39 else
40 return readw(tse_pcs->base + regnum * 2);
41}
42
43static void tse_pcs_write(struct altera_tse_pcs *tse_pcs, int regnum,
44 u16 value)
45{
46 if (tse_pcs->reg_width == 4)
47 writel(value, tse_pcs->base + regnum * 4);
48 else
49 writew(value, tse_pcs->base + regnum * 2);
50}
51
52static int tse_pcs_reset(struct altera_tse_pcs *tse_pcs)
53{
54 u16 bmcr;
55
56 /* Reset PCS block */
57 bmcr = tse_pcs_read(tse_pcs, MII_BMCR);
58 bmcr |= BMCR_RESET;
59 tse_pcs_write(tse_pcs, MII_BMCR, bmcr);
60
61 return read_poll_timeout(tse_pcs_read, bmcr, (bmcr & BMCR_RESET),
62 10, SGMII_PCS_SW_RESET_TIMEOUT, 1,
63 tse_pcs, MII_BMCR);
64}
65
66static int alt_tse_pcs_validate(struct phylink_pcs *pcs,
67 unsigned long *supported,
68 const struct phylink_link_state *state)
69{
70 if (state->interface == PHY_INTERFACE_MODE_SGMII ||
71 state->interface == PHY_INTERFACE_MODE_1000BASEX)
72 return 1;
73
74 return -EINVAL;
75}
76
77static int alt_tse_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
78 phy_interface_t interface,
79 const unsigned long *advertising,
80 bool permit_pause_to_mac)
81{
82 struct altera_tse_pcs *tse_pcs = phylink_pcs_to_tse_pcs(pcs);
83 u32 ctrl, if_mode;
84
85 ctrl = tse_pcs_read(tse_pcs, MII_BMCR);
86 if_mode = tse_pcs_read(tse_pcs, SGMII_PCS_IF_MODE);
87
88 /* Set link timer to 1.6ms, as per the MegaCore Function User Guide */
89 tse_pcs_write(tse_pcs, SGMII_PCS_LINK_TIMER_0, 0x0D40);
90 tse_pcs_write(tse_pcs, SGMII_PCS_LINK_TIMER_1, 0x03);
91
92 if (interface == PHY_INTERFACE_MODE_SGMII) {
93 if_mode |= PCS_IF_MODE_USE_SGMII_AN | PCS_IF_MODE_SGMII_ENA;
94 } else if (interface == PHY_INTERFACE_MODE_1000BASEX) {
95 if_mode &= ~(PCS_IF_MODE_USE_SGMII_AN | PCS_IF_MODE_SGMII_ENA);
96 }
97
98 ctrl |= (BMCR_SPEED1000 | BMCR_FULLDPLX | BMCR_ANENABLE);
99
100 tse_pcs_write(tse_pcs, MII_BMCR, ctrl);
101 tse_pcs_write(tse_pcs, SGMII_PCS_IF_MODE, if_mode);
102
103 return tse_pcs_reset(tse_pcs);
104}
105
106static void alt_tse_pcs_get_state(struct phylink_pcs *pcs,
107 struct phylink_link_state *state)
108{
109 struct altera_tse_pcs *tse_pcs = phylink_pcs_to_tse_pcs(pcs);
110 u16 bmsr, lpa;
111
112 bmsr = tse_pcs_read(tse_pcs, MII_BMSR);
113 lpa = tse_pcs_read(tse_pcs, MII_LPA);
114
115 phylink_mii_c22_pcs_decode_state(state, bmsr, lpa);
116}
117
118static void alt_tse_pcs_an_restart(struct phylink_pcs *pcs)
119{
120 struct altera_tse_pcs *tse_pcs = phylink_pcs_to_tse_pcs(pcs);
121 u16 bmcr;
122
123 bmcr = tse_pcs_read(tse_pcs, MII_BMCR);
124 bmcr |= BMCR_ANRESTART;
125 tse_pcs_write(tse_pcs, MII_BMCR, bmcr);
126
127 /* This PCS seems to require a soft reset to re-sync the AN logic */
128 tse_pcs_reset(tse_pcs);
129}
130
131static const struct phylink_pcs_ops alt_tse_pcs_ops = {
132 .pcs_validate = alt_tse_pcs_validate,
133 .pcs_get_state = alt_tse_pcs_get_state,
134 .pcs_config = alt_tse_pcs_config,
135 .pcs_an_restart = alt_tse_pcs_an_restart,
136};
137
138struct phylink_pcs *alt_tse_pcs_create(struct net_device *ndev,
139 void __iomem *pcs_base, int reg_width)
140{
141 struct altera_tse_pcs *tse_pcs;
142
143 if (reg_width != 4 && reg_width != 2)
144 return ERR_PTR(-EINVAL);
145
146 tse_pcs = devm_kzalloc(&ndev->dev, sizeof(*tse_pcs), GFP_KERNEL);
147 if (!tse_pcs)
148 return ERR_PTR(-ENOMEM);
149
150 tse_pcs->pcs.ops = &alt_tse_pcs_ops;
151 tse_pcs->base = pcs_base;
152 tse_pcs->reg_width = reg_width;
153
154 return &tse_pcs->pcs;
155}
156EXPORT_SYMBOL_GPL(alt_tse_pcs_create);
157
158MODULE_LICENSE("GPL");
159MODULE_DESCRIPTION("Altera TSE PCS driver");
160MODULE_AUTHOR("Maxime Chevallier <maxime.chevallier@bootlin.com>");