1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Texas Instruments Ethernet Switch media-access-controller (MAC) submodule/ |
4 | * Ethernet MAC Sliver (CPGMAC_SL) |
5 | * |
6 | * Copyright (C) 2019 Texas Instruments |
7 | * |
8 | */ |
9 | |
10 | #include <linux/delay.h> |
11 | #include <linux/io.h> |
12 | #include <linux/kernel.h> |
13 | |
14 | #include "cpsw_sl.h" |
15 | |
16 | #define CPSW_SL_REG_NOTUSED U16_MAX |
17 | |
18 | static const u16 cpsw_sl_reg_map_cpsw[] = { |
19 | [CPSW_SL_IDVER] = 0x00, |
20 | [CPSW_SL_MACCONTROL] = 0x04, |
21 | [CPSW_SL_MACSTATUS] = 0x08, |
22 | [CPSW_SL_SOFT_RESET] = 0x0c, |
23 | [CPSW_SL_RX_MAXLEN] = 0x10, |
24 | [CPSW_SL_BOFFTEST] = 0x14, |
25 | [CPSW_SL_RX_PAUSE] = 0x18, |
26 | [CPSW_SL_TX_PAUSE] = 0x1c, |
27 | [CPSW_SL_EMCONTROL] = 0x20, |
28 | [CPSW_SL_RX_PRI_MAP] = 0x24, |
29 | [CPSW_SL_TX_GAP] = 0x28, |
30 | }; |
31 | |
32 | static const u16 cpsw_sl_reg_map_66ak2hk[] = { |
33 | [CPSW_SL_IDVER] = 0x00, |
34 | [CPSW_SL_MACCONTROL] = 0x04, |
35 | [CPSW_SL_MACSTATUS] = 0x08, |
36 | [CPSW_SL_SOFT_RESET] = 0x0c, |
37 | [CPSW_SL_RX_MAXLEN] = 0x10, |
38 | [CPSW_SL_BOFFTEST] = CPSW_SL_REG_NOTUSED, |
39 | [CPSW_SL_RX_PAUSE] = 0x18, |
40 | [CPSW_SL_TX_PAUSE] = 0x1c, |
41 | [CPSW_SL_EMCONTROL] = 0x20, |
42 | [CPSW_SL_RX_PRI_MAP] = 0x24, |
43 | [CPSW_SL_TX_GAP] = CPSW_SL_REG_NOTUSED, |
44 | }; |
45 | |
46 | static const u16 cpsw_sl_reg_map_66ak2x_xgbe[] = { |
47 | [CPSW_SL_IDVER] = 0x00, |
48 | [CPSW_SL_MACCONTROL] = 0x04, |
49 | [CPSW_SL_MACSTATUS] = 0x08, |
50 | [CPSW_SL_SOFT_RESET] = 0x0c, |
51 | [CPSW_SL_RX_MAXLEN] = 0x10, |
52 | [CPSW_SL_BOFFTEST] = CPSW_SL_REG_NOTUSED, |
53 | [CPSW_SL_RX_PAUSE] = 0x18, |
54 | [CPSW_SL_TX_PAUSE] = 0x1c, |
55 | [CPSW_SL_EMCONTROL] = 0x20, |
56 | [CPSW_SL_RX_PRI_MAP] = CPSW_SL_REG_NOTUSED, |
57 | [CPSW_SL_TX_GAP] = 0x28, |
58 | }; |
59 | |
60 | static const u16 cpsw_sl_reg_map_66ak2elg_am65[] = { |
61 | [CPSW_SL_IDVER] = CPSW_SL_REG_NOTUSED, |
62 | [CPSW_SL_MACCONTROL] = 0x00, |
63 | [CPSW_SL_MACSTATUS] = 0x04, |
64 | [CPSW_SL_SOFT_RESET] = 0x08, |
65 | [CPSW_SL_RX_MAXLEN] = CPSW_SL_REG_NOTUSED, |
66 | [CPSW_SL_BOFFTEST] = 0x0c, |
67 | [CPSW_SL_RX_PAUSE] = 0x10, |
68 | [CPSW_SL_TX_PAUSE] = 0x40, |
69 | [CPSW_SL_EMCONTROL] = 0x70, |
70 | [CPSW_SL_RX_PRI_MAP] = CPSW_SL_REG_NOTUSED, |
71 | [CPSW_SL_TX_GAP] = 0x74, |
72 | }; |
73 | |
74 | #define CPSW_SL_SOFT_RESET_BIT BIT(0) |
75 | |
76 | #define CPSW_SL_STATUS_PN_IDLE BIT(31) |
77 | #define CPSW_SL_AM65_STATUS_PN_E_IDLE BIT(30) |
78 | #define CPSW_SL_AM65_STATUS_PN_P_IDLE BIT(29) |
79 | #define CPSW_SL_AM65_STATUS_PN_TX_IDLE BIT(28) |
80 | |
81 | #define CPSW_SL_STATUS_IDLE_MASK_BASE (CPSW_SL_STATUS_PN_IDLE) |
82 | |
83 | #define CPSW_SL_STATUS_IDLE_MASK_K3 \ |
84 | (CPSW_SL_STATUS_IDLE_MASK_BASE | CPSW_SL_AM65_STATUS_PN_E_IDLE | \ |
85 | CPSW_SL_AM65_STATUS_PN_P_IDLE | CPSW_SL_AM65_STATUS_PN_TX_IDLE) |
86 | |
87 | #define CPSW_SL_CTL_FUNC_BASE \ |
88 | (CPSW_SL_CTL_FULLDUPLEX |\ |
89 | CPSW_SL_CTL_LOOPBACK |\ |
90 | CPSW_SL_CTL_RX_FLOW_EN |\ |
91 | CPSW_SL_CTL_TX_FLOW_EN |\ |
92 | CPSW_SL_CTL_GMII_EN |\ |
93 | CPSW_SL_CTL_TX_PACE |\ |
94 | CPSW_SL_CTL_GIG |\ |
95 | CPSW_SL_CTL_CMD_IDLE |\ |
96 | CPSW_SL_CTL_IFCTL_A |\ |
97 | CPSW_SL_CTL_IFCTL_B |\ |
98 | CPSW_SL_CTL_GIG_FORCE |\ |
99 | CPSW_SL_CTL_EXT_EN |\ |
100 | CPSW_SL_CTL_RX_CEF_EN |\ |
101 | CPSW_SL_CTL_RX_CSF_EN |\ |
102 | CPSW_SL_CTL_RX_CMF_EN) |
103 | |
104 | struct cpsw_sl { |
105 | struct device *dev; |
106 | void __iomem *sl_base; |
107 | const u16 *regs; |
108 | u32 control_features; |
109 | u32 idle_mask; |
110 | }; |
111 | |
112 | struct cpsw_sl_dev_id { |
113 | const char *device_id; |
114 | const u16 *regs; |
115 | const u32 control_features; |
116 | const u32 regs_offset; |
117 | const u32 idle_mask; |
118 | }; |
119 | |
120 | static const struct cpsw_sl_dev_id cpsw_sl_id_match[] = { |
121 | { |
122 | .device_id = "cpsw" , |
123 | .regs = cpsw_sl_reg_map_cpsw, |
124 | .control_features = CPSW_SL_CTL_FUNC_BASE | |
125 | CPSW_SL_CTL_MTEST | |
126 | CPSW_SL_CTL_TX_SHORT_GAP_EN | |
127 | CPSW_SL_CTL_TX_SG_LIM_EN, |
128 | .idle_mask = CPSW_SL_STATUS_IDLE_MASK_BASE, |
129 | }, |
130 | { |
131 | .device_id = "66ak2hk" , |
132 | .regs = cpsw_sl_reg_map_66ak2hk, |
133 | .control_features = CPSW_SL_CTL_FUNC_BASE | |
134 | CPSW_SL_CTL_TX_SHORT_GAP_EN, |
135 | .idle_mask = CPSW_SL_STATUS_IDLE_MASK_BASE, |
136 | }, |
137 | { |
138 | .device_id = "66ak2x_xgbe" , |
139 | .regs = cpsw_sl_reg_map_66ak2x_xgbe, |
140 | .control_features = CPSW_SL_CTL_FUNC_BASE | |
141 | CPSW_SL_CTL_XGIG | |
142 | CPSW_SL_CTL_TX_SHORT_GAP_EN | |
143 | CPSW_SL_CTL_CRC_TYPE | |
144 | CPSW_SL_CTL_XGMII_EN, |
145 | .idle_mask = CPSW_SL_STATUS_IDLE_MASK_BASE, |
146 | }, |
147 | { |
148 | .device_id = "66ak2el" , |
149 | .regs = cpsw_sl_reg_map_66ak2elg_am65, |
150 | .regs_offset = 0x330, |
151 | .control_features = CPSW_SL_CTL_FUNC_BASE | |
152 | CPSW_SL_CTL_MTEST | |
153 | CPSW_SL_CTL_TX_SHORT_GAP_EN | |
154 | CPSW_SL_CTL_CRC_TYPE | |
155 | CPSW_SL_CTL_EXT_EN_RX_FLO | |
156 | CPSW_SL_CTL_EXT_EN_TX_FLO | |
157 | CPSW_SL_CTL_TX_SG_LIM_EN, |
158 | .idle_mask = CPSW_SL_STATUS_IDLE_MASK_BASE, |
159 | }, |
160 | { |
161 | .device_id = "66ak2g" , |
162 | .regs = cpsw_sl_reg_map_66ak2elg_am65, |
163 | .regs_offset = 0x330, |
164 | .control_features = CPSW_SL_CTL_FUNC_BASE | |
165 | CPSW_SL_CTL_MTEST | |
166 | CPSW_SL_CTL_CRC_TYPE | |
167 | CPSW_SL_CTL_EXT_EN_RX_FLO | |
168 | CPSW_SL_CTL_EXT_EN_TX_FLO, |
169 | }, |
170 | { |
171 | .device_id = "am65" , |
172 | .regs = cpsw_sl_reg_map_66ak2elg_am65, |
173 | .regs_offset = 0x330, |
174 | .control_features = CPSW_SL_CTL_FUNC_BASE | |
175 | CPSW_SL_CTL_MTEST | |
176 | CPSW_SL_CTL_XGIG | |
177 | CPSW_SL_CTL_TX_SHORT_GAP_EN | |
178 | CPSW_SL_CTL_CRC_TYPE | |
179 | CPSW_SL_CTL_XGMII_EN | |
180 | CPSW_SL_CTL_EXT_EN_RX_FLO | |
181 | CPSW_SL_CTL_EXT_EN_TX_FLO | |
182 | CPSW_SL_CTL_TX_SG_LIM_EN | |
183 | CPSW_SL_CTL_EXT_EN_XGIG, |
184 | .idle_mask = CPSW_SL_STATUS_IDLE_MASK_K3, |
185 | }, |
186 | { }, |
187 | }; |
188 | |
189 | u32 cpsw_sl_reg_read(struct cpsw_sl *sl, enum cpsw_sl_regs reg) |
190 | { |
191 | int val; |
192 | |
193 | if (sl->regs[reg] == CPSW_SL_REG_NOTUSED) { |
194 | dev_err(sl->dev, "cpsw_sl: not sup r reg: %04X\n" , |
195 | sl->regs[reg]); |
196 | return 0; |
197 | } |
198 | |
199 | val = readl(addr: sl->sl_base + sl->regs[reg]); |
200 | dev_dbg(sl->dev, "cpsw_sl: reg: %04X r 0x%08X\n" , sl->regs[reg], val); |
201 | return val; |
202 | } |
203 | |
204 | void cpsw_sl_reg_write(struct cpsw_sl *sl, enum cpsw_sl_regs reg, u32 val) |
205 | { |
206 | if (sl->regs[reg] == CPSW_SL_REG_NOTUSED) { |
207 | dev_err(sl->dev, "cpsw_sl: not sup w reg: %04X\n" , |
208 | sl->regs[reg]); |
209 | return; |
210 | } |
211 | |
212 | dev_dbg(sl->dev, "cpsw_sl: reg: %04X w 0x%08X\n" , sl->regs[reg], val); |
213 | writel(val, addr: sl->sl_base + sl->regs[reg]); |
214 | } |
215 | |
216 | static const struct cpsw_sl_dev_id *cpsw_sl_match_id( |
217 | const struct cpsw_sl_dev_id *id, |
218 | const char *device_id) |
219 | { |
220 | if (!id || !device_id) |
221 | return NULL; |
222 | |
223 | while (id->device_id) { |
224 | if (strcmp(device_id, id->device_id) == 0) |
225 | return id; |
226 | id++; |
227 | } |
228 | return NULL; |
229 | } |
230 | |
231 | struct cpsw_sl *cpsw_sl_get(const char *device_id, struct device *dev, |
232 | void __iomem *sl_base) |
233 | { |
234 | const struct cpsw_sl_dev_id *sl_dev_id; |
235 | struct cpsw_sl *sl; |
236 | |
237 | sl = devm_kzalloc(dev, size: sizeof(struct cpsw_sl), GFP_KERNEL); |
238 | if (!sl) |
239 | return ERR_PTR(error: -ENOMEM); |
240 | sl->dev = dev; |
241 | sl->sl_base = sl_base; |
242 | |
243 | sl_dev_id = cpsw_sl_match_id(id: cpsw_sl_id_match, device_id); |
244 | if (!sl_dev_id) { |
245 | dev_err(sl->dev, "cpsw_sl: dev_id %s not found.\n" , device_id); |
246 | return ERR_PTR(error: -EINVAL); |
247 | } |
248 | sl->regs = sl_dev_id->regs; |
249 | sl->control_features = sl_dev_id->control_features; |
250 | sl->idle_mask = sl_dev_id->idle_mask; |
251 | sl->sl_base += sl_dev_id->regs_offset; |
252 | |
253 | return sl; |
254 | } |
255 | |
256 | void cpsw_sl_reset(struct cpsw_sl *sl, unsigned long tmo) |
257 | { |
258 | unsigned long timeout = jiffies + msecs_to_jiffies(m: tmo); |
259 | |
260 | /* Set the soft reset bit */ |
261 | cpsw_sl_reg_write(sl, reg: CPSW_SL_SOFT_RESET, CPSW_SL_SOFT_RESET_BIT); |
262 | |
263 | /* Wait for the bit to clear */ |
264 | do { |
265 | usleep_range(min: 100, max: 200); |
266 | } while ((cpsw_sl_reg_read(sl, reg: CPSW_SL_SOFT_RESET) & |
267 | CPSW_SL_SOFT_RESET_BIT) && |
268 | time_after(timeout, jiffies)); |
269 | |
270 | if (cpsw_sl_reg_read(sl, reg: CPSW_SL_SOFT_RESET) & CPSW_SL_SOFT_RESET_BIT) |
271 | dev_err(sl->dev, "cpsw_sl failed to soft-reset.\n" ); |
272 | } |
273 | |
274 | u32 cpsw_sl_ctl_set(struct cpsw_sl *sl, u32 ctl_funcs) |
275 | { |
276 | u32 val; |
277 | |
278 | if (ctl_funcs & ~sl->control_features) { |
279 | dev_err(sl->dev, "cpsw_sl: unsupported func 0x%08X\n" , |
280 | ctl_funcs & (~sl->control_features)); |
281 | return -EINVAL; |
282 | } |
283 | |
284 | val = cpsw_sl_reg_read(sl, reg: CPSW_SL_MACCONTROL); |
285 | val |= ctl_funcs; |
286 | cpsw_sl_reg_write(sl, reg: CPSW_SL_MACCONTROL, val); |
287 | |
288 | return 0; |
289 | } |
290 | |
291 | u32 cpsw_sl_ctl_clr(struct cpsw_sl *sl, u32 ctl_funcs) |
292 | { |
293 | u32 val; |
294 | |
295 | if (ctl_funcs & ~sl->control_features) { |
296 | dev_err(sl->dev, "cpsw_sl: unsupported func 0x%08X\n" , |
297 | ctl_funcs & (~sl->control_features)); |
298 | return -EINVAL; |
299 | } |
300 | |
301 | val = cpsw_sl_reg_read(sl, reg: CPSW_SL_MACCONTROL); |
302 | val &= ~ctl_funcs; |
303 | cpsw_sl_reg_write(sl, reg: CPSW_SL_MACCONTROL, val); |
304 | |
305 | return 0; |
306 | } |
307 | |
308 | void cpsw_sl_ctl_reset(struct cpsw_sl *sl) |
309 | { |
310 | cpsw_sl_reg_write(sl, reg: CPSW_SL_MACCONTROL, val: 0); |
311 | } |
312 | |
313 | int cpsw_sl_wait_for_idle(struct cpsw_sl *sl, unsigned long tmo) |
314 | { |
315 | unsigned long timeout = jiffies + msecs_to_jiffies(m: tmo); |
316 | |
317 | do { |
318 | usleep_range(min: 100, max: 200); |
319 | } while (!(cpsw_sl_reg_read(sl, reg: CPSW_SL_MACSTATUS) & |
320 | sl->idle_mask) && time_after(timeout, jiffies)); |
321 | |
322 | if (!(cpsw_sl_reg_read(sl, reg: CPSW_SL_MACSTATUS) & sl->idle_mask)) { |
323 | dev_err(sl->dev, "cpsw_sl failed to soft-reset.\n" ); |
324 | return -ETIMEDOUT; |
325 | } |
326 | |
327 | return 0; |
328 | } |
329 | |