1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Driver for 100BASE-TX PHY embedded into NXP SJA1110 switch |
3 | * |
4 | * Copyright 2022-2023 NXP |
5 | */ |
6 | |
7 | #include <linux/kernel.h> |
8 | #include <linux/mii.h> |
9 | #include <linux/module.h> |
10 | #include <linux/phy.h> |
11 | |
12 | #define PHY_ID_CBTX_SJA1110 0x001bb020 |
13 | |
14 | /* Registers */ |
15 | #define CBTX_MODE_CTRL_STAT 0x11 |
16 | #define CBTX_PDOWN_CTRL 0x18 |
17 | #define CBTX_RX_ERR_COUNTER 0x1a |
18 | #define CBTX_IRQ_STAT 0x1d |
19 | #define CBTX_IRQ_ENABLE 0x1e |
20 | |
21 | /* Fields */ |
22 | #define CBTX_MODE_CTRL_STAT_AUTO_MDIX_EN BIT(7) |
23 | #define CBTX_MODE_CTRL_STAT_MDIX_MODE BIT(6) |
24 | |
25 | #define CBTX_PDOWN_CTL_TRUE_PDOWN BIT(0) |
26 | |
27 | #define CBTX_IRQ_ENERGYON BIT(7) |
28 | #define CBTX_IRQ_AN_COMPLETE BIT(6) |
29 | #define CBTX_IRQ_REM_FAULT BIT(5) |
30 | #define CBTX_IRQ_LINK_DOWN BIT(4) |
31 | #define CBTX_IRQ_AN_LP_ACK BIT(3) |
32 | #define CBTX_IRQ_PARALLEL_DETECT_FAULT BIT(2) |
33 | #define CBTX_IRQ_AN_PAGE_RECV BIT(1) |
34 | |
35 | static int cbtx_soft_reset(struct phy_device *phydev) |
36 | { |
37 | int ret; |
38 | |
39 | /* Can't soft reset unless we remove PHY from true power down mode */ |
40 | ret = phy_clear_bits(phydev, CBTX_PDOWN_CTRL, |
41 | CBTX_PDOWN_CTL_TRUE_PDOWN); |
42 | if (ret) |
43 | return ret; |
44 | |
45 | return genphy_soft_reset(phydev); |
46 | } |
47 | |
48 | static int cbtx_config_init(struct phy_device *phydev) |
49 | { |
50 | /* Wait for cbtx_config_aneg() to kick in and apply this */ |
51 | phydev->mdix_ctrl = ETH_TP_MDI_AUTO; |
52 | |
53 | return 0; |
54 | } |
55 | |
56 | static int cbtx_mdix_status(struct phy_device *phydev) |
57 | { |
58 | int ret; |
59 | |
60 | ret = phy_read(phydev, CBTX_MODE_CTRL_STAT); |
61 | if (ret < 0) |
62 | return ret; |
63 | |
64 | if (ret & CBTX_MODE_CTRL_STAT_MDIX_MODE) |
65 | phydev->mdix = ETH_TP_MDI_X; |
66 | else |
67 | phydev->mdix = ETH_TP_MDI; |
68 | |
69 | return 0; |
70 | } |
71 | |
72 | static int cbtx_read_status(struct phy_device *phydev) |
73 | { |
74 | int ret; |
75 | |
76 | ret = cbtx_mdix_status(phydev); |
77 | if (ret) |
78 | return ret; |
79 | |
80 | return genphy_read_status(phydev); |
81 | } |
82 | |
83 | static int cbtx_mdix_config(struct phy_device *phydev) |
84 | { |
85 | int ret; |
86 | |
87 | switch (phydev->mdix_ctrl) { |
88 | case ETH_TP_MDI_AUTO: |
89 | return phy_set_bits(phydev, CBTX_MODE_CTRL_STAT, |
90 | CBTX_MODE_CTRL_STAT_AUTO_MDIX_EN); |
91 | case ETH_TP_MDI: |
92 | ret = phy_clear_bits(phydev, CBTX_MODE_CTRL_STAT, |
93 | CBTX_MODE_CTRL_STAT_AUTO_MDIX_EN); |
94 | if (ret) |
95 | return ret; |
96 | |
97 | return phy_clear_bits(phydev, CBTX_MODE_CTRL_STAT, |
98 | CBTX_MODE_CTRL_STAT_MDIX_MODE); |
99 | case ETH_TP_MDI_X: |
100 | ret = phy_clear_bits(phydev, CBTX_MODE_CTRL_STAT, |
101 | CBTX_MODE_CTRL_STAT_AUTO_MDIX_EN); |
102 | if (ret) |
103 | return ret; |
104 | |
105 | return phy_set_bits(phydev, CBTX_MODE_CTRL_STAT, |
106 | CBTX_MODE_CTRL_STAT_MDIX_MODE); |
107 | } |
108 | |
109 | return 0; |
110 | } |
111 | |
112 | static int cbtx_config_aneg(struct phy_device *phydev) |
113 | { |
114 | int ret; |
115 | |
116 | ret = cbtx_mdix_config(phydev); |
117 | if (ret) |
118 | return ret; |
119 | |
120 | return genphy_config_aneg(phydev); |
121 | } |
122 | |
123 | static int cbtx_ack_interrupts(struct phy_device *phydev) |
124 | { |
125 | return phy_read(phydev, CBTX_IRQ_STAT); |
126 | } |
127 | |
128 | static int cbtx_config_intr(struct phy_device *phydev) |
129 | { |
130 | int ret; |
131 | |
132 | if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { |
133 | ret = cbtx_ack_interrupts(phydev); |
134 | if (ret < 0) |
135 | return ret; |
136 | |
137 | ret = phy_write(phydev, CBTX_IRQ_ENABLE, CBTX_IRQ_LINK_DOWN | |
138 | CBTX_IRQ_AN_COMPLETE | CBTX_IRQ_ENERGYON); |
139 | if (ret) |
140 | return ret; |
141 | } else { |
142 | ret = phy_write(phydev, CBTX_IRQ_ENABLE, val: 0); |
143 | if (ret) |
144 | return ret; |
145 | |
146 | ret = cbtx_ack_interrupts(phydev); |
147 | if (ret < 0) |
148 | return ret; |
149 | } |
150 | |
151 | return 0; |
152 | } |
153 | |
154 | static irqreturn_t cbtx_handle_interrupt(struct phy_device *phydev) |
155 | { |
156 | int irq_stat, irq_enabled; |
157 | |
158 | irq_stat = cbtx_ack_interrupts(phydev); |
159 | if (irq_stat < 0) { |
160 | phy_error(phydev); |
161 | return IRQ_NONE; |
162 | } |
163 | |
164 | irq_enabled = phy_read(phydev, CBTX_IRQ_ENABLE); |
165 | if (irq_enabled < 0) { |
166 | phy_error(phydev); |
167 | return IRQ_NONE; |
168 | } |
169 | |
170 | if (!(irq_enabled & irq_stat)) |
171 | return IRQ_NONE; |
172 | |
173 | phy_trigger_machine(phydev); |
174 | |
175 | return IRQ_HANDLED; |
176 | } |
177 | |
178 | static int cbtx_get_sset_count(struct phy_device *phydev) |
179 | { |
180 | return 1; |
181 | } |
182 | |
183 | static void cbtx_get_strings(struct phy_device *phydev, u8 *data) |
184 | { |
185 | strncpy(p: data, q: "100btx_rx_err" , ETH_GSTRING_LEN); |
186 | } |
187 | |
188 | static void cbtx_get_stats(struct phy_device *phydev, |
189 | struct ethtool_stats *stats, u64 *data) |
190 | { |
191 | int ret; |
192 | |
193 | ret = phy_read(phydev, CBTX_RX_ERR_COUNTER); |
194 | data[0] = (ret < 0) ? U64_MAX : ret; |
195 | } |
196 | |
197 | static struct phy_driver cbtx_driver[] = { |
198 | { |
199 | PHY_ID_MATCH_MODEL(PHY_ID_CBTX_SJA1110), |
200 | .name = "NXP CBTX (SJA1110)" , |
201 | /* PHY_BASIC_FEATURES */ |
202 | .soft_reset = cbtx_soft_reset, |
203 | .config_init = cbtx_config_init, |
204 | .suspend = genphy_suspend, |
205 | .resume = genphy_resume, |
206 | .config_intr = cbtx_config_intr, |
207 | .handle_interrupt = cbtx_handle_interrupt, |
208 | .read_status = cbtx_read_status, |
209 | .config_aneg = cbtx_config_aneg, |
210 | .get_sset_count = cbtx_get_sset_count, |
211 | .get_strings = cbtx_get_strings, |
212 | .get_stats = cbtx_get_stats, |
213 | }, |
214 | }; |
215 | |
216 | module_phy_driver(cbtx_driver); |
217 | |
218 | static struct mdio_device_id __maybe_unused cbtx_tbl[] = { |
219 | { PHY_ID_MATCH_MODEL(PHY_ID_CBTX_SJA1110) }, |
220 | { }, |
221 | }; |
222 | |
223 | MODULE_DEVICE_TABLE(mdio, cbtx_tbl); |
224 | |
225 | MODULE_AUTHOR("Vladimir Oltean <vladimir.oltean@nxp.com>" ); |
226 | MODULE_DESCRIPTION("NXP CBTX PHY driver" ); |
227 | MODULE_LICENSE("GPL" ); |
228 | |