blob: 632815c10a401f7bd873e077a262528b73ceed7d [file] [log] [blame]
Guenter Roeck3ad50cc2014-10-29 10:44:56 -07001/*
2 * net/dsa/mv88e6352.c - Marvell 88e6352 switch chip support
3 *
4 * Copyright (c) 2014 Guenter Roeck
5 *
6 * Derived from mv88e6123_61_65.c
7 * Copyright (c) 2008-2009 Marvell Semiconductor
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 */
14
15#include <linux/delay.h>
16#include <linux/jiffies.h>
17#include <linux/list.h>
18#include <linux/module.h>
19#include <linux/netdevice.h>
20#include <linux/platform_device.h>
21#include <linux/phy.h>
22#include <net/dsa.h>
23#include "mv88e6xxx.h"
24
Guenter Roeck3ad50cc2014-10-29 10:44:56 -070025static char *mv88e6352_probe(struct device *host_dev, int sw_addr)
26{
27 struct mii_bus *bus = dsa_host_dev_to_mii_bus(host_dev);
28 int ret;
29
30 if (bus == NULL)
31 return NULL;
32
Andrew Lunncca8b132015-04-02 04:06:39 +020033 ret = __mv88e6xxx_reg_read(bus, sw_addr, REG_PORT(0), PORT_SWITCH_ID);
Guenter Roeck3ad50cc2014-10-29 10:44:56 -070034 if (ret >= 0) {
Andrew Lunn1636d882015-05-06 01:09:50 +020035 if ((ret & 0xfff0) == PORT_SWITCH_ID_6172)
36 return "Marvell 88E6172";
Andrew Lunncca8b132015-04-02 04:06:39 +020037 if ((ret & 0xfff0) == PORT_SWITCH_ID_6176)
Guenter Roeck27167772014-10-29 10:44:57 -070038 return "Marvell 88E6176";
Andrew Lunncca8b132015-04-02 04:06:39 +020039 if (ret == PORT_SWITCH_ID_6352_A0)
Guenter Roeck3ad50cc2014-10-29 10:44:56 -070040 return "Marvell 88E6352 (A0)";
Andrew Lunncca8b132015-04-02 04:06:39 +020041 if (ret == PORT_SWITCH_ID_6352_A1)
Guenter Roeck3ad50cc2014-10-29 10:44:56 -070042 return "Marvell 88E6352 (A1)";
Andrew Lunncca8b132015-04-02 04:06:39 +020043 if ((ret & 0xfff0) == PORT_SWITCH_ID_6352)
Guenter Roeck3ad50cc2014-10-29 10:44:56 -070044 return "Marvell 88E6352";
45 }
46
47 return NULL;
48}
49
Guenter Roeck3ad50cc2014-10-29 10:44:56 -070050static int mv88e6352_setup_global(struct dsa_switch *ds)
51{
Andrew Lunn15966a22015-05-06 01:09:49 +020052 u32 upstream_port = dsa_upstream_port(ds);
Guenter Roeck3ad50cc2014-10-29 10:44:56 -070053 int ret;
Andrew Lunn15966a22015-05-06 01:09:49 +020054 u32 reg;
Andrew Lunn54d792f2015-05-06 01:09:47 +020055
56 ret = mv88e6xxx_setup_global(ds);
57 if (ret)
58 return ret;
Guenter Roeck3ad50cc2014-10-29 10:44:56 -070059
60 /* Discard packets with excessive collisions,
61 * mask all interrupt sources, enable PPU (bit 14, undocumented).
62 */
Andrew Lunn15966a22015-05-06 01:09:49 +020063 REG_WRITE(REG_GLOBAL, GLOBAL_CONTROL,
64 GLOBAL_CONTROL_PPU_ENABLE | GLOBAL_CONTROL_DISCARD_EXCESS);
Guenter Roeck3ad50cc2014-10-29 10:44:56 -070065
Guenter Roeck3ad50cc2014-10-29 10:44:56 -070066 /* Configure the upstream port, and configure the upstream
67 * port as the port to which ingress and egress monitor frames
68 * are to be sent.
69 */
Andrew Lunn15966a22015-05-06 01:09:49 +020070 reg = upstream_port << GLOBAL_MONITOR_CONTROL_INGRESS_SHIFT |
71 upstream_port << GLOBAL_MONITOR_CONTROL_EGRESS_SHIFT |
72 upstream_port << GLOBAL_MONITOR_CONTROL_ARP_SHIFT;
73 REG_WRITE(REG_GLOBAL, GLOBAL_MONITOR_CONTROL, reg);
Guenter Roeck3ad50cc2014-10-29 10:44:56 -070074
75 /* Disable remote management for now, and set the switch's
76 * DSA device number.
77 */
78 REG_WRITE(REG_GLOBAL, 0x1c, ds->index & 0x1f);
79
Guenter Roeck3ad50cc2014-10-29 10:44:56 -070080 return 0;
81}
82
Guenter Roeck276db3b2014-10-29 10:44:59 -070083#ifdef CONFIG_NET_DSA_HWMON
84
Guenter Roeck276db3b2014-10-29 10:44:59 -070085static int mv88e6352_get_temp(struct dsa_switch *ds, int *temp)
86{
87 int ret;
88
89 *temp = 0;
90
Andrew Lunn491435852015-04-02 04:06:35 +020091 ret = mv88e6xxx_phy_page_read(ds, 0, 6, 27);
Guenter Roeck276db3b2014-10-29 10:44:59 -070092 if (ret < 0)
93 return ret;
94
95 *temp = (ret & 0xff) - 25;
96
97 return 0;
98}
99
100static int mv88e6352_get_temp_limit(struct dsa_switch *ds, int *temp)
101{
102 int ret;
103
104 *temp = 0;
105
Andrew Lunn491435852015-04-02 04:06:35 +0200106 ret = mv88e6xxx_phy_page_read(ds, 0, 6, 26);
Guenter Roeck276db3b2014-10-29 10:44:59 -0700107 if (ret < 0)
108 return ret;
109
110 *temp = (((ret >> 8) & 0x1f) * 5) - 25;
111
112 return 0;
113}
114
115static int mv88e6352_set_temp_limit(struct dsa_switch *ds, int temp)
116{
117 int ret;
118
Andrew Lunn491435852015-04-02 04:06:35 +0200119 ret = mv88e6xxx_phy_page_read(ds, 0, 6, 26);
Guenter Roeck276db3b2014-10-29 10:44:59 -0700120 if (ret < 0)
121 return ret;
122 temp = clamp_val(DIV_ROUND_CLOSEST(temp, 5) + 5, 0, 0x1f);
Andrew Lunn491435852015-04-02 04:06:35 +0200123 return mv88e6xxx_phy_page_write(ds, 0, 6, 26,
Guenter Roeck276db3b2014-10-29 10:44:59 -0700124 (ret & 0xe0ff) | (temp << 8));
125}
126
127static int mv88e6352_get_temp_alarm(struct dsa_switch *ds, bool *alarm)
128{
129 int ret;
130
131 *alarm = false;
132
Andrew Lunn491435852015-04-02 04:06:35 +0200133 ret = mv88e6xxx_phy_page_read(ds, 0, 6, 26);
Guenter Roeck276db3b2014-10-29 10:44:59 -0700134 if (ret < 0)
135 return ret;
136
137 *alarm = !!(ret & 0x40);
138
139 return 0;
140}
141#endif /* CONFIG_NET_DSA_HWMON */
142
Guenter Roeck3ad50cc2014-10-29 10:44:56 -0700143static int mv88e6352_setup(struct dsa_switch *ds)
144{
145 struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
146 int ret;
Guenter Roeck3ad50cc2014-10-29 10:44:56 -0700147
Guenter Roeckacdaffc2015-03-26 18:36:28 -0700148 ret = mv88e6xxx_setup_common(ds);
149 if (ret < 0)
150 return ret;
151
Andrew Lunn44e50dd2015-04-02 04:06:33 +0200152 ps->num_ports = 7;
153
Guenter Roeck33b43df2014-10-29 10:45:03 -0700154 mutex_init(&ps->eeprom_mutex);
Guenter Roeck3ad50cc2014-10-29 10:44:56 -0700155
Andrew Lunn143a8302015-04-02 04:06:34 +0200156 ret = mv88e6xxx_switch_reset(ds, true);
Guenter Roeck3ad50cc2014-10-29 10:44:56 -0700157 if (ret < 0)
158 return ret;
159
Guenter Roeck3ad50cc2014-10-29 10:44:56 -0700160 ret = mv88e6352_setup_global(ds);
161 if (ret < 0)
162 return ret;
163
Andrew Lunndbde9e62015-05-06 01:09:48 +0200164 return mv88e6xxx_setup_ports(ds);
Guenter Roeck3ad50cc2014-10-29 10:44:56 -0700165}
166
Guenter Roeck33b43df2014-10-29 10:45:03 -0700167static int mv88e6352_read_eeprom_word(struct dsa_switch *ds, int addr)
168{
169 struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
170 int ret;
171
172 mutex_lock(&ps->eeprom_mutex);
173
174 ret = mv88e6xxx_reg_write(ds, REG_GLOBAL2, 0x14,
175 0xc000 | (addr & 0xff));
176 if (ret < 0)
177 goto error;
178
Andrew Lunnf3044682015-02-14 19:17:50 +0100179 ret = mv88e6xxx_eeprom_busy_wait(ds);
Guenter Roeck33b43df2014-10-29 10:45:03 -0700180 if (ret < 0)
181 goto error;
182
183 ret = mv88e6xxx_reg_read(ds, REG_GLOBAL2, 0x15);
184error:
185 mutex_unlock(&ps->eeprom_mutex);
186 return ret;
187}
188
189static int mv88e6352_get_eeprom(struct dsa_switch *ds,
190 struct ethtool_eeprom *eeprom, u8 *data)
191{
192 int offset;
193 int len;
194 int ret;
195
196 offset = eeprom->offset;
197 len = eeprom->len;
198 eeprom->len = 0;
199
200 eeprom->magic = 0xc3ec4951;
201
Andrew Lunnf3044682015-02-14 19:17:50 +0100202 ret = mv88e6xxx_eeprom_load_wait(ds);
Guenter Roeck33b43df2014-10-29 10:45:03 -0700203 if (ret < 0)
204 return ret;
205
206 if (offset & 1) {
207 int word;
208
209 word = mv88e6352_read_eeprom_word(ds, offset >> 1);
210 if (word < 0)
211 return word;
212
213 *data++ = (word >> 8) & 0xff;
214
215 offset++;
216 len--;
217 eeprom->len++;
218 }
219
220 while (len >= 2) {
221 int word;
222
223 word = mv88e6352_read_eeprom_word(ds, offset >> 1);
224 if (word < 0)
225 return word;
226
227 *data++ = word & 0xff;
228 *data++ = (word >> 8) & 0xff;
229
230 offset += 2;
231 len -= 2;
232 eeprom->len += 2;
233 }
234
235 if (len) {
236 int word;
237
238 word = mv88e6352_read_eeprom_word(ds, offset >> 1);
239 if (word < 0)
240 return word;
241
242 *data++ = word & 0xff;
243
244 offset++;
245 len--;
246 eeprom->len++;
247 }
248
249 return 0;
250}
251
252static int mv88e6352_eeprom_is_readonly(struct dsa_switch *ds)
253{
254 int ret;
255
256 ret = mv88e6xxx_reg_read(ds, REG_GLOBAL2, 0x14);
257 if (ret < 0)
258 return ret;
259
260 if (!(ret & 0x0400))
261 return -EROFS;
262
263 return 0;
264}
265
266static int mv88e6352_write_eeprom_word(struct dsa_switch *ds, int addr,
267 u16 data)
268{
269 struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
270 int ret;
271
272 mutex_lock(&ps->eeprom_mutex);
273
274 ret = mv88e6xxx_reg_write(ds, REG_GLOBAL2, 0x15, data);
275 if (ret < 0)
276 goto error;
277
278 ret = mv88e6xxx_reg_write(ds, REG_GLOBAL2, 0x14,
279 0xb000 | (addr & 0xff));
280 if (ret < 0)
281 goto error;
282
Andrew Lunnf3044682015-02-14 19:17:50 +0100283 ret = mv88e6xxx_eeprom_busy_wait(ds);
Guenter Roeck33b43df2014-10-29 10:45:03 -0700284error:
285 mutex_unlock(&ps->eeprom_mutex);
286 return ret;
287}
288
289static int mv88e6352_set_eeprom(struct dsa_switch *ds,
290 struct ethtool_eeprom *eeprom, u8 *data)
291{
292 int offset;
293 int ret;
294 int len;
295
296 if (eeprom->magic != 0xc3ec4951)
297 return -EINVAL;
298
299 ret = mv88e6352_eeprom_is_readonly(ds);
300 if (ret)
301 return ret;
302
303 offset = eeprom->offset;
304 len = eeprom->len;
305 eeprom->len = 0;
306
Andrew Lunnf3044682015-02-14 19:17:50 +0100307 ret = mv88e6xxx_eeprom_load_wait(ds);
Guenter Roeck33b43df2014-10-29 10:45:03 -0700308 if (ret < 0)
309 return ret;
310
311 if (offset & 1) {
312 int word;
313
314 word = mv88e6352_read_eeprom_word(ds, offset >> 1);
315 if (word < 0)
316 return word;
317
318 word = (*data++ << 8) | (word & 0xff);
319
320 ret = mv88e6352_write_eeprom_word(ds, offset >> 1, word);
321 if (ret < 0)
322 return ret;
323
324 offset++;
325 len--;
326 eeprom->len++;
327 }
328
329 while (len >= 2) {
330 int word;
331
332 word = *data++;
333 word |= *data++ << 8;
334
335 ret = mv88e6352_write_eeprom_word(ds, offset >> 1, word);
336 if (ret < 0)
337 return ret;
338
339 offset += 2;
340 len -= 2;
341 eeprom->len += 2;
342 }
343
344 if (len) {
345 int word;
346
347 word = mv88e6352_read_eeprom_word(ds, offset >> 1);
348 if (word < 0)
349 return word;
350
351 word = (word & 0xff00) | *data++;
352
353 ret = mv88e6352_write_eeprom_word(ds, offset >> 1, word);
354 if (ret < 0)
355 return ret;
356
357 offset++;
358 len--;
359 eeprom->len++;
360 }
361
362 return 0;
363}
364
Guenter Roeck3ad50cc2014-10-29 10:44:56 -0700365struct dsa_switch_driver mv88e6352_switch_driver = {
366 .tag_protocol = DSA_TAG_PROTO_EDSA,
367 .priv_size = sizeof(struct mv88e6xxx_priv_state),
368 .probe = mv88e6352_probe,
369 .setup = mv88e6352_setup,
370 .set_addr = mv88e6xxx_set_addr_indirect,
Andrew Lunnfd3a0ee2015-04-02 04:06:36 +0200371 .phy_read = mv88e6xxx_phy_read_indirect,
372 .phy_write = mv88e6xxx_phy_write_indirect,
Guenter Roeck3ad50cc2014-10-29 10:44:56 -0700373 .poll_link = mv88e6xxx_poll_link,
Andrew Lunne413e7e2015-04-02 04:06:38 +0200374 .get_strings = mv88e6xxx_get_strings,
375 .get_ethtool_stats = mv88e6xxx_get_ethtool_stats,
376 .get_sset_count = mv88e6xxx_get_sset_count,
Guenter Roeck04b0a802015-03-06 22:23:52 -0800377 .set_eee = mv88e6xxx_set_eee,
378 .get_eee = mv88e6xxx_get_eee,
Guenter Roeck276db3b2014-10-29 10:44:59 -0700379#ifdef CONFIG_NET_DSA_HWMON
380 .get_temp = mv88e6352_get_temp,
381 .get_temp_limit = mv88e6352_get_temp_limit,
382 .set_temp_limit = mv88e6352_set_temp_limit,
383 .get_temp_alarm = mv88e6352_get_temp_alarm,
384#endif
Guenter Roeck33b43df2014-10-29 10:45:03 -0700385 .get_eeprom = mv88e6352_get_eeprom,
386 .set_eeprom = mv88e6352_set_eeprom,
Guenter Roeck95d08b52014-10-29 10:45:06 -0700387 .get_regs_len = mv88e6xxx_get_regs_len,
388 .get_regs = mv88e6xxx_get_regs,
Guenter Roeck3f244ab2015-03-26 18:36:36 -0700389 .port_join_bridge = mv88e6xxx_join_bridge,
390 .port_leave_bridge = mv88e6xxx_leave_bridge,
391 .port_stp_update = mv88e6xxx_port_stp_update,
Guenter Roeck4f431e52015-03-26 18:36:39 -0700392 .fdb_add = mv88e6xxx_port_fdb_add,
393 .fdb_del = mv88e6xxx_port_fdb_del,
394 .fdb_getnext = mv88e6xxx_port_fdb_getnext,
Guenter Roeck3ad50cc2014-10-29 10:44:56 -0700395};
396
397MODULE_ALIAS("platform:mv88e6352");
Andrew Lunn1636d882015-05-06 01:09:50 +0200398MODULE_ALIAS("platform:mv88e6172");