1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* ICSSG Ethernet driver |
3 | * |
4 | * Copyright (C) 2022 Texas Instruments Incorporated - https://www.ti.com |
5 | */ |
6 | |
7 | #include <linux/iopoll.h> |
8 | #include <linux/regmap.h> |
9 | #include <uapi/linux/if_ether.h> |
10 | #include "icssg_config.h" |
11 | #include "icssg_prueth.h" |
12 | #include "icssg_switch_map.h" |
13 | #include "icssg_mii_rt.h" |
14 | |
15 | /* TX IPG Values to be set for 100M link speed. These values are |
16 | * in ocp_clk cycles. So need change if ocp_clk is changed for a specific |
17 | * h/w design. |
18 | */ |
19 | |
20 | /* IPG is in core_clk cycles */ |
21 | #define MII_RT_TX_IPG_100M 0x17 |
22 | #define MII_RT_TX_IPG_1G 0xb |
23 | |
24 | #define ICSSG_QUEUES_MAX 64 |
25 | #define ICSSG_QUEUE_OFFSET 0xd00 |
26 | #define ICSSG_QUEUE_PEEK_OFFSET 0xe00 |
27 | #define ICSSG_QUEUE_CNT_OFFSET 0xe40 |
28 | #define ICSSG_QUEUE_RESET_OFFSET 0xf40 |
29 | |
30 | #define ICSSG_NUM_TX_QUEUES 8 |
31 | |
32 | #define RECYCLE_Q_SLICE0 16 |
33 | #define RECYCLE_Q_SLICE1 17 |
34 | |
35 | #define ICSSG_NUM_OTHER_QUEUES 5 /* port, host and special queues */ |
36 | |
37 | #define PORT_HI_Q_SLICE0 32 |
38 | #define PORT_LO_Q_SLICE0 33 |
39 | #define HOST_HI_Q_SLICE0 34 |
40 | #define HOST_LO_Q_SLICE0 35 |
41 | #define HOST_SPL_Q_SLICE0 40 /* Special Queue */ |
42 | |
43 | #define PORT_HI_Q_SLICE1 36 |
44 | #define PORT_LO_Q_SLICE1 37 |
45 | #define HOST_HI_Q_SLICE1 38 |
46 | #define HOST_LO_Q_SLICE1 39 |
47 | #define HOST_SPL_Q_SLICE1 41 /* Special Queue */ |
48 | |
49 | #define MII_RXCFG_DEFAULT (PRUSS_MII_RT_RXCFG_RX_ENABLE | \ |
50 | PRUSS_MII_RT_RXCFG_RX_DATA_RDY_MODE_DIS | \ |
51 | PRUSS_MII_RT_RXCFG_RX_L2_EN | \ |
52 | PRUSS_MII_RT_RXCFG_RX_L2_EOF_SCLR_DIS) |
53 | |
54 | #define MII_TXCFG_DEFAULT (PRUSS_MII_RT_TXCFG_TX_ENABLE | \ |
55 | PRUSS_MII_RT_TXCFG_TX_AUTO_PREAMBLE | \ |
56 | PRUSS_MII_RT_TXCFG_TX_32_MODE_EN | \ |
57 | PRUSS_MII_RT_TXCFG_TX_IPG_WIRE_CLK_EN) |
58 | |
59 | #define ICSSG_CFG_DEFAULT (ICSSG_CFG_TX_L1_EN | \ |
60 | ICSSG_CFG_TX_L2_EN | ICSSG_CFG_RX_L2_G_EN | \ |
61 | ICSSG_CFG_TX_PRU_EN | \ |
62 | ICSSG_CFG_SGMII_MODE) |
63 | |
64 | #define FDB_GEN_CFG1 0x60 |
65 | #define SMEM_VLAN_OFFSET 8 |
66 | #define SMEM_VLAN_OFFSET_MASK GENMASK(25, 8) |
67 | |
68 | #define FDB_GEN_CFG2 0x64 |
69 | #define FDB_VLAN_EN BIT(6) |
70 | #define FDB_HOST_EN BIT(2) |
71 | #define FDB_PRU1_EN BIT(1) |
72 | #define FDB_PRU0_EN BIT(0) |
73 | #define FDB_EN_ALL (FDB_PRU0_EN | FDB_PRU1_EN | \ |
74 | FDB_HOST_EN | FDB_VLAN_EN) |
75 | |
76 | /** |
77 | * struct map - ICSSG Queue Map |
78 | * @queue: Queue number |
79 | * @pd_addr_start: Packet descriptor queue reserved memory |
80 | * @flags: Flags |
81 | * @special: Indicates whether this queue is a special queue or not |
82 | */ |
83 | struct map { |
84 | int queue; |
85 | u32 pd_addr_start; |
86 | u32 flags; |
87 | bool special; |
88 | }; |
89 | |
90 | /* Hardware queue map for ICSSG */ |
91 | static const struct map hwq_map[2][ICSSG_NUM_OTHER_QUEUES] = { |
92 | { |
93 | { PORT_HI_Q_SLICE0, PORT_DESC0_HI, 0x200000, 0 }, |
94 | { PORT_LO_Q_SLICE0, PORT_DESC0_LO, 0, 0 }, |
95 | { HOST_HI_Q_SLICE0, HOST_DESC0_HI, 0x200000, 0 }, |
96 | { HOST_LO_Q_SLICE0, HOST_DESC0_LO, 0, 0 }, |
97 | { HOST_SPL_Q_SLICE0, HOST_SPPD0, 0x400000, 1 }, |
98 | }, |
99 | { |
100 | { PORT_HI_Q_SLICE1, PORT_DESC1_HI, 0xa00000, 0 }, |
101 | { PORT_LO_Q_SLICE1, PORT_DESC1_LO, 0x800000, 0 }, |
102 | { HOST_HI_Q_SLICE1, HOST_DESC1_HI, 0xa00000, 0 }, |
103 | { HOST_LO_Q_SLICE1, HOST_DESC1_LO, 0x800000, 0 }, |
104 | { HOST_SPL_Q_SLICE1, HOST_SPPD1, 0xc00000, 1 }, |
105 | }, |
106 | }; |
107 | |
108 | static void icssg_config_mii_init(struct prueth_emac *emac) |
109 | { |
110 | u32 rxcfg, txcfg, rxcfg_reg, txcfg_reg, pcnt_reg; |
111 | struct prueth *prueth = emac->prueth; |
112 | int slice = prueth_emac_slice(emac); |
113 | struct regmap *mii_rt; |
114 | |
115 | mii_rt = prueth->mii_rt; |
116 | |
117 | rxcfg_reg = (slice == ICSS_MII0) ? PRUSS_MII_RT_RXCFG0 : |
118 | PRUSS_MII_RT_RXCFG1; |
119 | txcfg_reg = (slice == ICSS_MII0) ? PRUSS_MII_RT_TXCFG0 : |
120 | PRUSS_MII_RT_TXCFG1; |
121 | pcnt_reg = (slice == ICSS_MII0) ? PRUSS_MII_RT_RX_PCNT0 : |
122 | PRUSS_MII_RT_RX_PCNT1; |
123 | |
124 | rxcfg = MII_RXCFG_DEFAULT; |
125 | txcfg = MII_TXCFG_DEFAULT; |
126 | |
127 | if (slice == ICSS_MII1) |
128 | rxcfg |= PRUSS_MII_RT_RXCFG_RX_MUX_SEL; |
129 | |
130 | /* In MII mode TX lines swapped inside ICSSG, so TX_MUX_SEL cfg need |
131 | * to be swapped also comparing to RGMII mode. |
132 | */ |
133 | if (emac->phy_if == PHY_INTERFACE_MODE_MII && slice == ICSS_MII0) |
134 | txcfg |= PRUSS_MII_RT_TXCFG_TX_MUX_SEL; |
135 | else if (emac->phy_if != PHY_INTERFACE_MODE_MII && slice == ICSS_MII1) |
136 | txcfg |= PRUSS_MII_RT_TXCFG_TX_MUX_SEL; |
137 | |
138 | regmap_write(map: mii_rt, reg: rxcfg_reg, val: rxcfg); |
139 | regmap_write(map: mii_rt, reg: txcfg_reg, val: txcfg); |
140 | regmap_write(map: mii_rt, reg: pcnt_reg, val: 0x1); |
141 | } |
142 | |
143 | static void icssg_miig_queues_init(struct prueth *prueth, int slice) |
144 | { |
145 | struct regmap *miig_rt = prueth->miig_rt; |
146 | void __iomem *smem = prueth->shram.va; |
147 | u8 pd[ICSSG_SPECIAL_PD_SIZE]; |
148 | int queue = 0, i, j; |
149 | u32 *pdword; |
150 | |
151 | /* reset hwqueues */ |
152 | if (slice) |
153 | queue = ICSSG_NUM_TX_QUEUES; |
154 | |
155 | for (i = 0; i < ICSSG_NUM_TX_QUEUES; i++) { |
156 | regmap_write(map: miig_rt, ICSSG_QUEUE_RESET_OFFSET, val: queue); |
157 | queue++; |
158 | } |
159 | |
160 | queue = slice ? RECYCLE_Q_SLICE1 : RECYCLE_Q_SLICE0; |
161 | regmap_write(map: miig_rt, ICSSG_QUEUE_RESET_OFFSET, val: queue); |
162 | |
163 | for (i = 0; i < ICSSG_NUM_OTHER_QUEUES; i++) { |
164 | regmap_write(map: miig_rt, ICSSG_QUEUE_RESET_OFFSET, |
165 | val: hwq_map[slice][i].queue); |
166 | } |
167 | |
168 | /* initialize packet descriptors in SMEM */ |
169 | /* push pakcet descriptors to hwqueues */ |
170 | |
171 | pdword = (u32 *)pd; |
172 | for (j = 0; j < ICSSG_NUM_OTHER_QUEUES; j++) { |
173 | const struct map *mp; |
174 | int pd_size, num_pds; |
175 | u32 pdaddr; |
176 | |
177 | mp = &hwq_map[slice][j]; |
178 | if (mp->special) { |
179 | pd_size = ICSSG_SPECIAL_PD_SIZE; |
180 | num_pds = ICSSG_NUM_SPECIAL_PDS; |
181 | } else { |
182 | pd_size = ICSSG_NORMAL_PD_SIZE; |
183 | num_pds = ICSSG_NUM_NORMAL_PDS; |
184 | } |
185 | |
186 | for (i = 0; i < num_pds; i++) { |
187 | memset(pd, 0, pd_size); |
188 | |
189 | pdword[0] &= ICSSG_FLAG_MASK; |
190 | pdword[0] |= mp->flags; |
191 | pdaddr = mp->pd_addr_start + i * pd_size; |
192 | |
193 | memcpy_toio(smem + pdaddr, pd, pd_size); |
194 | queue = mp->queue; |
195 | regmap_write(map: miig_rt, ICSSG_QUEUE_OFFSET + 4 * queue, |
196 | val: pdaddr); |
197 | } |
198 | } |
199 | } |
200 | |
201 | void icssg_config_ipg(struct prueth_emac *emac) |
202 | { |
203 | struct prueth *prueth = emac->prueth; |
204 | int slice = prueth_emac_slice(emac); |
205 | |
206 | switch (emac->speed) { |
207 | case SPEED_1000: |
208 | icssg_mii_update_ipg(mii_rt: prueth->mii_rt, mii: slice, MII_RT_TX_IPG_1G); |
209 | break; |
210 | case SPEED_100: |
211 | icssg_mii_update_ipg(mii_rt: prueth->mii_rt, mii: slice, MII_RT_TX_IPG_100M); |
212 | break; |
213 | case SPEED_10: |
214 | /* IPG for 10M is same as 100M */ |
215 | icssg_mii_update_ipg(mii_rt: prueth->mii_rt, mii: slice, MII_RT_TX_IPG_100M); |
216 | break; |
217 | default: |
218 | /* Other links speeds not supported */ |
219 | netdev_err(dev: emac->ndev, format: "Unsupported link speed\n" ); |
220 | return; |
221 | } |
222 | } |
223 | |
224 | static void emac_r30_cmd_init(struct prueth_emac *emac) |
225 | { |
226 | struct icssg_r30_cmd __iomem *p; |
227 | int i; |
228 | |
229 | p = emac->dram.va + MGR_R30_CMD_OFFSET; |
230 | |
231 | for (i = 0; i < 4; i++) |
232 | writel(EMAC_NONE, addr: &p->cmd[i]); |
233 | } |
234 | |
235 | static int emac_r30_is_done(struct prueth_emac *emac) |
236 | { |
237 | const struct icssg_r30_cmd __iomem *p; |
238 | u32 cmd; |
239 | int i; |
240 | |
241 | p = emac->dram.va + MGR_R30_CMD_OFFSET; |
242 | |
243 | for (i = 0; i < 4; i++) { |
244 | cmd = readl(addr: &p->cmd[i]); |
245 | if (cmd != EMAC_NONE) |
246 | return 0; |
247 | } |
248 | |
249 | return 1; |
250 | } |
251 | |
252 | static int prueth_emac_buffer_setup(struct prueth_emac *emac) |
253 | { |
254 | struct icssg_buffer_pool_cfg __iomem *bpool_cfg; |
255 | struct icssg_rxq_ctx __iomem *rxq_ctx; |
256 | struct prueth *prueth = emac->prueth; |
257 | int slice = prueth_emac_slice(emac); |
258 | u32 addr; |
259 | int i; |
260 | |
261 | /* Layout to have 64KB aligned buffer pool |
262 | * |BPOOL0|BPOOL1|RX_CTX0|RX_CTX1| |
263 | */ |
264 | |
265 | addr = lower_32_bits(prueth->msmcram.pa); |
266 | if (slice) |
267 | addr += PRUETH_NUM_BUF_POOLS * PRUETH_EMAC_BUF_POOL_SIZE; |
268 | |
269 | if (addr % SZ_64K) { |
270 | dev_warn(prueth->dev, "buffer pool needs to be 64KB aligned\n" ); |
271 | return -EINVAL; |
272 | } |
273 | |
274 | bpool_cfg = emac->dram.va + BUFFER_POOL_0_ADDR_OFFSET; |
275 | /* workaround for f/w bug. bpool 0 needs to be initilalized */ |
276 | writel(val: addr, addr: &bpool_cfg[0].addr); |
277 | writel(val: 0, addr: &bpool_cfg[0].len); |
278 | |
279 | for (i = PRUETH_EMAC_BUF_POOL_START; |
280 | i < PRUETH_EMAC_BUF_POOL_START + PRUETH_NUM_BUF_POOLS; |
281 | i++) { |
282 | writel(val: addr, addr: &bpool_cfg[i].addr); |
283 | writel(PRUETH_EMAC_BUF_POOL_SIZE, addr: &bpool_cfg[i].len); |
284 | addr += PRUETH_EMAC_BUF_POOL_SIZE; |
285 | } |
286 | |
287 | if (!slice) |
288 | addr += PRUETH_NUM_BUF_POOLS * PRUETH_EMAC_BUF_POOL_SIZE; |
289 | else |
290 | addr += PRUETH_EMAC_RX_CTX_BUF_SIZE * 2; |
291 | |
292 | /* Pre-emptible RX buffer queue */ |
293 | rxq_ctx = emac->dram.va + HOST_RX_Q_PRE_CONTEXT_OFFSET; |
294 | for (i = 0; i < 3; i++) |
295 | writel(val: addr, addr: &rxq_ctx->start[i]); |
296 | |
297 | addr += PRUETH_EMAC_RX_CTX_BUF_SIZE; |
298 | writel(val: addr, addr: &rxq_ctx->end); |
299 | |
300 | /* Express RX buffer queue */ |
301 | rxq_ctx = emac->dram.va + HOST_RX_Q_EXP_CONTEXT_OFFSET; |
302 | for (i = 0; i < 3; i++) |
303 | writel(val: addr, addr: &rxq_ctx->start[i]); |
304 | |
305 | addr += PRUETH_EMAC_RX_CTX_BUF_SIZE; |
306 | writel(val: addr, addr: &rxq_ctx->end); |
307 | |
308 | return 0; |
309 | } |
310 | |
311 | static void icssg_init_emac_mode(struct prueth *prueth) |
312 | { |
313 | /* When the device is configured as a bridge and it is being brought |
314 | * back to the emac mode, the host mac address has to be set as 0. |
315 | */ |
316 | u8 mac[ETH_ALEN] = { 0 }; |
317 | |
318 | if (prueth->emacs_initialized) |
319 | return; |
320 | |
321 | regmap_update_bits(map: prueth->miig_rt, FDB_GEN_CFG1, |
322 | SMEM_VLAN_OFFSET_MASK, val: 0); |
323 | regmap_write(map: prueth->miig_rt, FDB_GEN_CFG2, val: 0); |
324 | /* Clear host MAC address */ |
325 | icssg_class_set_host_mac_addr(miig_rt: prueth->miig_rt, mac); |
326 | } |
327 | |
328 | int icssg_config(struct prueth *prueth, struct prueth_emac *emac, int slice) |
329 | { |
330 | void __iomem *config = emac->dram.va + ICSSG_CONFIG_OFFSET; |
331 | struct icssg_flow_cfg __iomem *flow_cfg; |
332 | int ret; |
333 | |
334 | icssg_init_emac_mode(prueth); |
335 | |
336 | memset_io(config, 0, TAS_GATE_MASK_LIST0); |
337 | icssg_miig_queues_init(prueth, slice); |
338 | |
339 | emac->speed = SPEED_1000; |
340 | emac->duplex = DUPLEX_FULL; |
341 | if (!phy_interface_mode_is_rgmii(mode: emac->phy_if)) { |
342 | emac->speed = SPEED_100; |
343 | emac->duplex = DUPLEX_FULL; |
344 | } |
345 | regmap_update_bits(map: prueth->miig_rt, ICSSG_CFG_OFFSET, |
346 | ICSSG_CFG_DEFAULT, ICSSG_CFG_DEFAULT); |
347 | icssg_miig_set_interface_mode(miig_rt: prueth->miig_rt, mii: slice, phy_if: emac->phy_if); |
348 | icssg_config_mii_init(emac); |
349 | icssg_config_ipg(emac); |
350 | icssg_update_rgmii_cfg(miig_rt: prueth->miig_rt, emac); |
351 | |
352 | /* set GPI mode */ |
353 | pruss_cfg_gpimode(pruss: prueth->pruss, pru_id: prueth->pru_id[slice], |
354 | mode: PRUSS_GPI_MODE_MII); |
355 | |
356 | /* enable XFR shift for PRU and RTU */ |
357 | pruss_cfg_xfr_enable(pruss: prueth->pruss, pru_type: PRU_TYPE_PRU, enable: true); |
358 | pruss_cfg_xfr_enable(pruss: prueth->pruss, pru_type: PRU_TYPE_RTU, enable: true); |
359 | |
360 | /* set C28 to 0x100 */ |
361 | pru_rproc_set_ctable(rproc: prueth->pru[slice], c: PRU_C28, addr: 0x100 << 8); |
362 | pru_rproc_set_ctable(rproc: prueth->rtu[slice], c: PRU_C28, addr: 0x100 << 8); |
363 | pru_rproc_set_ctable(rproc: prueth->txpru[slice], c: PRU_C28, addr: 0x100 << 8); |
364 | |
365 | flow_cfg = config + PSI_L_REGULAR_FLOW_ID_BASE_OFFSET; |
366 | writew(val: emac->rx_flow_id_base, addr: &flow_cfg->rx_base_flow); |
367 | writew(val: 0, addr: &flow_cfg->mgm_base_flow); |
368 | writeb(val: 0, addr: config + SPL_PKT_DEFAULT_PRIORITY); |
369 | writeb(val: 0, addr: config + QUEUE_NUM_UNTAGGED); |
370 | |
371 | ret = prueth_emac_buffer_setup(emac); |
372 | if (ret) |
373 | return ret; |
374 | |
375 | emac_r30_cmd_init(emac); |
376 | |
377 | return 0; |
378 | } |
379 | |
380 | /* Bitmask for ICSSG r30 commands */ |
381 | static const struct icssg_r30_cmd emac_r32_bitmask[] = { |
382 | {{0xffff0004, 0xffff0100, 0xffff0004, EMAC_NONE}}, /* EMAC_PORT_DISABLE */ |
383 | {{0xfffb0040, 0xfeff0200, 0xfeff0200, EMAC_NONE}}, /* EMAC_PORT_BLOCK */ |
384 | {{0xffbb0000, 0xfcff0000, 0xdcfb0000, EMAC_NONE}}, /* EMAC_PORT_FORWARD */ |
385 | {{0xffbb0000, 0xfcff0000, 0xfcff2000, EMAC_NONE}}, /* EMAC_PORT_FORWARD_WO_LEARNING */ |
386 | {{0xffff0001, EMAC_NONE, EMAC_NONE, EMAC_NONE}}, /* ACCEPT ALL */ |
387 | {{0xfffe0002, EMAC_NONE, EMAC_NONE, EMAC_NONE}}, /* ACCEPT TAGGED */ |
388 | {{0xfffc0000, EMAC_NONE, EMAC_NONE, EMAC_NONE}}, /* ACCEPT UNTAGGED and PRIO */ |
389 | {{EMAC_NONE, 0xffff0020, EMAC_NONE, EMAC_NONE}}, /* TAS Trigger List change */ |
390 | {{EMAC_NONE, 0xdfff1000, EMAC_NONE, EMAC_NONE}}, /* TAS set state ENABLE*/ |
391 | {{EMAC_NONE, 0xefff2000, EMAC_NONE, EMAC_NONE}}, /* TAS set state RESET*/ |
392 | {{EMAC_NONE, 0xcfff0000, EMAC_NONE, EMAC_NONE}}, /* TAS set state DISABLE*/ |
393 | {{EMAC_NONE, EMAC_NONE, 0xffff0400, EMAC_NONE}}, /* UC flooding ENABLE*/ |
394 | {{EMAC_NONE, EMAC_NONE, 0xfbff0000, EMAC_NONE}}, /* UC flooding DISABLE*/ |
395 | {{EMAC_NONE, EMAC_NONE, 0xffff0800, EMAC_NONE}}, /* MC flooding ENABLE*/ |
396 | {{EMAC_NONE, EMAC_NONE, 0xf7ff0000, EMAC_NONE}}, /* MC flooding DISABLE*/ |
397 | {{EMAC_NONE, 0xffff4000, EMAC_NONE, EMAC_NONE}}, /* Preemption on Tx ENABLE*/ |
398 | {{EMAC_NONE, 0xbfff0000, EMAC_NONE, EMAC_NONE}}, /* Preemption on Tx DISABLE*/ |
399 | {{0xffff0010, EMAC_NONE, 0xffff0010, EMAC_NONE}}, /* VLAN AWARE*/ |
400 | {{0xffef0000, EMAC_NONE, 0xffef0000, EMAC_NONE}} /* VLAN UNWARE*/ |
401 | }; |
402 | |
403 | int emac_set_port_state(struct prueth_emac *emac, |
404 | enum icssg_port_state_cmd cmd) |
405 | { |
406 | struct icssg_r30_cmd __iomem *p; |
407 | int ret = -ETIMEDOUT; |
408 | int done = 0; |
409 | int i; |
410 | |
411 | p = emac->dram.va + MGR_R30_CMD_OFFSET; |
412 | |
413 | if (cmd >= ICSSG_EMAC_PORT_MAX_COMMANDS) { |
414 | netdev_err(dev: emac->ndev, format: "invalid port command\n" ); |
415 | return -EINVAL; |
416 | } |
417 | |
418 | /* only one command at a time allowed to firmware */ |
419 | mutex_lock(&emac->cmd_lock); |
420 | |
421 | for (i = 0; i < 4; i++) |
422 | writel(val: emac_r32_bitmask[cmd].cmd[i], addr: &p->cmd[i]); |
423 | |
424 | /* wait for done */ |
425 | ret = read_poll_timeout(emac_r30_is_done, done, done == 1, |
426 | 1000, 10000, false, emac); |
427 | |
428 | if (ret == -ETIMEDOUT) |
429 | netdev_err(dev: emac->ndev, format: "timeout waiting for command done\n" ); |
430 | |
431 | mutex_unlock(lock: &emac->cmd_lock); |
432 | |
433 | return ret; |
434 | } |
435 | |
436 | void icssg_config_half_duplex(struct prueth_emac *emac) |
437 | { |
438 | u32 val; |
439 | |
440 | if (!emac->half_duplex) |
441 | return; |
442 | |
443 | val = get_random_u32(); |
444 | writel(val, addr: emac->dram.va + HD_RAND_SEED_OFFSET); |
445 | } |
446 | |
447 | void icssg_config_set_speed(struct prueth_emac *emac) |
448 | { |
449 | u8 fw_speed; |
450 | |
451 | switch (emac->speed) { |
452 | case SPEED_1000: |
453 | fw_speed = FW_LINK_SPEED_1G; |
454 | break; |
455 | case SPEED_100: |
456 | fw_speed = FW_LINK_SPEED_100M; |
457 | break; |
458 | case SPEED_10: |
459 | fw_speed = FW_LINK_SPEED_10M; |
460 | break; |
461 | default: |
462 | /* Other links speeds not supported */ |
463 | netdev_err(dev: emac->ndev, format: "Unsupported link speed\n" ); |
464 | return; |
465 | } |
466 | |
467 | if (emac->duplex == DUPLEX_HALF) |
468 | fw_speed |= FW_LINK_SPEED_HD; |
469 | |
470 | writeb(val: fw_speed, addr: emac->dram.va + PORT_LINK_SPEED_OFFSET); |
471 | } |
472 | |