2120 lines · c
1// SPDX-License-Identifier: (GPL-2.0 OR MIT)2/*3 * DSA driver for:4 * Hirschmann Hellcreek TSN switch.5 *6 * Copyright (C) 2019-2021 Linutronix GmbH7 * Author Kurt Kanzenbach <kurt@linutronix.de>8 */9 10#include <linux/kernel.h>11#include <linux/module.h>12#include <linux/device.h>13#include <linux/of.h>14#include <linux/of_mdio.h>15#include <linux/platform_device.h>16#include <linux/bitops.h>17#include <linux/if_bridge.h>18#include <linux/if_vlan.h>19#include <linux/etherdevice.h>20#include <linux/random.h>21#include <linux/iopoll.h>22#include <linux/mutex.h>23#include <linux/delay.h>24#include <net/dsa.h>25 26#include "hellcreek.h"27#include "hellcreek_ptp.h"28#include "hellcreek_hwtstamp.h"29 30static const struct hellcreek_counter hellcreek_counter[] = {31 { 0x00, "RxFiltered", },32 { 0x01, "RxOctets1k", },33 { 0x02, "RxVTAG", },34 { 0x03, "RxL2BAD", },35 { 0x04, "RxOverloadDrop", },36 { 0x05, "RxUC", },37 { 0x06, "RxMC", },38 { 0x07, "RxBC", },39 { 0x08, "RxRS<64", },40 { 0x09, "RxRS64", },41 { 0x0a, "RxRS65_127", },42 { 0x0b, "RxRS128_255", },43 { 0x0c, "RxRS256_511", },44 { 0x0d, "RxRS512_1023", },45 { 0x0e, "RxRS1024_1518", },46 { 0x0f, "RxRS>1518", },47 { 0x10, "TxTailDropQueue0", },48 { 0x11, "TxTailDropQueue1", },49 { 0x12, "TxTailDropQueue2", },50 { 0x13, "TxTailDropQueue3", },51 { 0x14, "TxTailDropQueue4", },52 { 0x15, "TxTailDropQueue5", },53 { 0x16, "TxTailDropQueue6", },54 { 0x17, "TxTailDropQueue7", },55 { 0x18, "RxTrafficClass0", },56 { 0x19, "RxTrafficClass1", },57 { 0x1a, "RxTrafficClass2", },58 { 0x1b, "RxTrafficClass3", },59 { 0x1c, "RxTrafficClass4", },60 { 0x1d, "RxTrafficClass5", },61 { 0x1e, "RxTrafficClass6", },62 { 0x1f, "RxTrafficClass7", },63 { 0x21, "TxOctets1k", },64 { 0x22, "TxVTAG", },65 { 0x23, "TxL2BAD", },66 { 0x25, "TxUC", },67 { 0x26, "TxMC", },68 { 0x27, "TxBC", },69 { 0x28, "TxTS<64", },70 { 0x29, "TxTS64", },71 { 0x2a, "TxTS65_127", },72 { 0x2b, "TxTS128_255", },73 { 0x2c, "TxTS256_511", },74 { 0x2d, "TxTS512_1023", },75 { 0x2e, "TxTS1024_1518", },76 { 0x2f, "TxTS>1518", },77 { 0x30, "TxTrafficClassOverrun0", },78 { 0x31, "TxTrafficClassOverrun1", },79 { 0x32, "TxTrafficClassOverrun2", },80 { 0x33, "TxTrafficClassOverrun3", },81 { 0x34, "TxTrafficClassOverrun4", },82 { 0x35, "TxTrafficClassOverrun5", },83 { 0x36, "TxTrafficClassOverrun6", },84 { 0x37, "TxTrafficClassOverrun7", },85 { 0x38, "TxTrafficClass0", },86 { 0x39, "TxTrafficClass1", },87 { 0x3a, "TxTrafficClass2", },88 { 0x3b, "TxTrafficClass3", },89 { 0x3c, "TxTrafficClass4", },90 { 0x3d, "TxTrafficClass5", },91 { 0x3e, "TxTrafficClass6", },92 { 0x3f, "TxTrafficClass7", },93};94 95static u16 hellcreek_read(struct hellcreek *hellcreek, unsigned int offset)96{97 return readw(hellcreek->base + offset);98}99 100static u16 hellcreek_read_ctrl(struct hellcreek *hellcreek)101{102 return readw(hellcreek->base + HR_CTRL_C);103}104 105static u16 hellcreek_read_stat(struct hellcreek *hellcreek)106{107 return readw(hellcreek->base + HR_SWSTAT);108}109 110static void hellcreek_write(struct hellcreek *hellcreek, u16 data,111 unsigned int offset)112{113 writew(data, hellcreek->base + offset);114}115 116static void hellcreek_select_port(struct hellcreek *hellcreek, int port)117{118 u16 val = port << HR_PSEL_PTWSEL_SHIFT;119 120 hellcreek_write(hellcreek, val, HR_PSEL);121}122 123static void hellcreek_select_prio(struct hellcreek *hellcreek, int prio)124{125 u16 val = prio << HR_PSEL_PRTCWSEL_SHIFT;126 127 hellcreek_write(hellcreek, val, HR_PSEL);128}129 130static void hellcreek_select_port_prio(struct hellcreek *hellcreek, int port,131 int prio)132{133 u16 val = port << HR_PSEL_PTWSEL_SHIFT;134 135 val |= prio << HR_PSEL_PRTCWSEL_SHIFT;136 137 hellcreek_write(hellcreek, val, HR_PSEL);138}139 140static void hellcreek_select_counter(struct hellcreek *hellcreek, int counter)141{142 u16 val = counter << HR_CSEL_SHIFT;143 144 hellcreek_write(hellcreek, val, HR_CSEL);145 146 /* Data sheet states to wait at least 20 internal clock cycles */147 ndelay(200);148}149 150static void hellcreek_select_vlan(struct hellcreek *hellcreek, int vid,151 bool pvid)152{153 u16 val = 0;154 155 /* Set pvid bit first */156 if (pvid)157 val |= HR_VIDCFG_PVID;158 hellcreek_write(hellcreek, val, HR_VIDCFG);159 160 /* Set vlan */161 val |= vid << HR_VIDCFG_VID_SHIFT;162 hellcreek_write(hellcreek, val, HR_VIDCFG);163}164 165static void hellcreek_select_tgd(struct hellcreek *hellcreek, int port)166{167 u16 val = port << TR_TGDSEL_TDGSEL_SHIFT;168 169 hellcreek_write(hellcreek, val, TR_TGDSEL);170}171 172static int hellcreek_wait_until_ready(struct hellcreek *hellcreek)173{174 u16 val;175 176 /* Wait up to 1ms, although 3 us should be enough */177 return readx_poll_timeout(hellcreek_read_ctrl, hellcreek,178 val, val & HR_CTRL_C_READY,179 3, 1000);180}181 182static int hellcreek_wait_until_transitioned(struct hellcreek *hellcreek)183{184 u16 val;185 186 return readx_poll_timeout_atomic(hellcreek_read_ctrl, hellcreek,187 val, !(val & HR_CTRL_C_TRANSITION),188 1, 1000);189}190 191static int hellcreek_wait_fdb_ready(struct hellcreek *hellcreek)192{193 u16 val;194 195 return readx_poll_timeout_atomic(hellcreek_read_stat, hellcreek,196 val, !(val & HR_SWSTAT_BUSY),197 1, 1000);198}199 200static int hellcreek_detect(struct hellcreek *hellcreek)201{202 u16 id, rel_low, rel_high, date_low, date_high, tgd_ver;203 u8 tgd_maj, tgd_min;204 u32 rel, date;205 206 id = hellcreek_read(hellcreek, HR_MODID_C);207 rel_low = hellcreek_read(hellcreek, HR_REL_L_C);208 rel_high = hellcreek_read(hellcreek, HR_REL_H_C);209 date_low = hellcreek_read(hellcreek, HR_BLD_L_C);210 date_high = hellcreek_read(hellcreek, HR_BLD_H_C);211 tgd_ver = hellcreek_read(hellcreek, TR_TGDVER);212 213 if (id != hellcreek->pdata->module_id)214 return -ENODEV;215 216 rel = rel_low | (rel_high << 16);217 date = date_low | (date_high << 16);218 tgd_maj = (tgd_ver & TR_TGDVER_REV_MAJ_MASK) >> TR_TGDVER_REV_MAJ_SHIFT;219 tgd_min = (tgd_ver & TR_TGDVER_REV_MIN_MASK) >> TR_TGDVER_REV_MIN_SHIFT;220 221 dev_info(hellcreek->dev, "Module ID=%02x Release=%04x Date=%04x TGD Version=%02x.%02x\n",222 id, rel, date, tgd_maj, tgd_min);223 224 return 0;225}226 227static void hellcreek_feature_detect(struct hellcreek *hellcreek)228{229 u16 features;230 231 features = hellcreek_read(hellcreek, HR_FEABITS0);232 233 /* Only detect the size of the FDB table. The size and current234 * utilization can be queried via devlink.235 */236 hellcreek->fdb_entries = ((features & HR_FEABITS0_FDBBINS_MASK) >>237 HR_FEABITS0_FDBBINS_SHIFT) * 32;238}239 240static enum dsa_tag_protocol hellcreek_get_tag_protocol(struct dsa_switch *ds,241 int port,242 enum dsa_tag_protocol mp)243{244 return DSA_TAG_PROTO_HELLCREEK;245}246 247static int hellcreek_port_enable(struct dsa_switch *ds, int port,248 struct phy_device *phy)249{250 struct hellcreek *hellcreek = ds->priv;251 struct hellcreek_port *hellcreek_port;252 u16 val;253 254 hellcreek_port = &hellcreek->ports[port];255 256 dev_dbg(hellcreek->dev, "Enable port %d\n", port);257 258 mutex_lock(&hellcreek->reg_lock);259 260 hellcreek_select_port(hellcreek, port);261 val = hellcreek_port->ptcfg;262 val |= HR_PTCFG_ADMIN_EN;263 hellcreek_write(hellcreek, val, HR_PTCFG);264 hellcreek_port->ptcfg = val;265 266 mutex_unlock(&hellcreek->reg_lock);267 268 return 0;269}270 271static void hellcreek_port_disable(struct dsa_switch *ds, int port)272{273 struct hellcreek *hellcreek = ds->priv;274 struct hellcreek_port *hellcreek_port;275 u16 val;276 277 hellcreek_port = &hellcreek->ports[port];278 279 dev_dbg(hellcreek->dev, "Disable port %d\n", port);280 281 mutex_lock(&hellcreek->reg_lock);282 283 hellcreek_select_port(hellcreek, port);284 val = hellcreek_port->ptcfg;285 val &= ~HR_PTCFG_ADMIN_EN;286 hellcreek_write(hellcreek, val, HR_PTCFG);287 hellcreek_port->ptcfg = val;288 289 mutex_unlock(&hellcreek->reg_lock);290}291 292static void hellcreek_get_strings(struct dsa_switch *ds, int port,293 u32 stringset, uint8_t *data)294{295 int i;296 297 for (i = 0; i < ARRAY_SIZE(hellcreek_counter); ++i) {298 const struct hellcreek_counter *counter = &hellcreek_counter[i];299 300 strscpy(data + i * ETH_GSTRING_LEN,301 counter->name, ETH_GSTRING_LEN);302 }303}304 305static int hellcreek_get_sset_count(struct dsa_switch *ds, int port, int sset)306{307 if (sset != ETH_SS_STATS)308 return 0;309 310 return ARRAY_SIZE(hellcreek_counter);311}312 313static void hellcreek_get_ethtool_stats(struct dsa_switch *ds, int port,314 uint64_t *data)315{316 struct hellcreek *hellcreek = ds->priv;317 struct hellcreek_port *hellcreek_port;318 int i;319 320 hellcreek_port = &hellcreek->ports[port];321 322 for (i = 0; i < ARRAY_SIZE(hellcreek_counter); ++i) {323 const struct hellcreek_counter *counter = &hellcreek_counter[i];324 u8 offset = counter->offset + port * 64;325 u16 high, low;326 u64 value;327 328 mutex_lock(&hellcreek->reg_lock);329 330 hellcreek_select_counter(hellcreek, offset);331 332 /* The registers are locked internally by selecting the333 * counter. So low and high can be read without reading high334 * again.335 */336 high = hellcreek_read(hellcreek, HR_CRDH);337 low = hellcreek_read(hellcreek, HR_CRDL);338 value = ((u64)high << 16) | low;339 340 hellcreek_port->counter_values[i] += value;341 data[i] = hellcreek_port->counter_values[i];342 343 mutex_unlock(&hellcreek->reg_lock);344 }345}346 347static u16 hellcreek_private_vid(int port)348{349 return VLAN_N_VID - port + 1;350}351 352static int hellcreek_vlan_prepare(struct dsa_switch *ds, int port,353 const struct switchdev_obj_port_vlan *vlan,354 struct netlink_ext_ack *extack)355{356 struct hellcreek *hellcreek = ds->priv;357 int i;358 359 dev_dbg(hellcreek->dev, "VLAN prepare for port %d\n", port);360 361 /* Restriction: Make sure that nobody uses the "private" VLANs. These362 * VLANs are internally used by the driver to ensure port363 * separation. Thus, they cannot be used by someone else.364 */365 for (i = 0; i < hellcreek->pdata->num_ports; ++i) {366 const u16 restricted_vid = hellcreek_private_vid(i);367 368 if (!dsa_is_user_port(ds, i))369 continue;370 371 if (vlan->vid == restricted_vid) {372 NL_SET_ERR_MSG_MOD(extack, "VID restricted by driver");373 return -EBUSY;374 }375 }376 377 return 0;378}379 380static void hellcreek_select_vlan_params(struct hellcreek *hellcreek, int port,381 int *shift, int *mask)382{383 switch (port) {384 case 0:385 *shift = HR_VIDMBRCFG_P0MBR_SHIFT;386 *mask = HR_VIDMBRCFG_P0MBR_MASK;387 break;388 case 1:389 *shift = HR_VIDMBRCFG_P1MBR_SHIFT;390 *mask = HR_VIDMBRCFG_P1MBR_MASK;391 break;392 case 2:393 *shift = HR_VIDMBRCFG_P2MBR_SHIFT;394 *mask = HR_VIDMBRCFG_P2MBR_MASK;395 break;396 case 3:397 *shift = HR_VIDMBRCFG_P3MBR_SHIFT;398 *mask = HR_VIDMBRCFG_P3MBR_MASK;399 break;400 default:401 *shift = *mask = 0;402 dev_err(hellcreek->dev, "Unknown port %d selected!\n", port);403 }404}405 406static void hellcreek_apply_vlan(struct hellcreek *hellcreek, int port, u16 vid,407 bool pvid, bool untagged)408{409 int shift, mask;410 u16 val;411 412 dev_dbg(hellcreek->dev, "Apply VLAN: port=%d vid=%u pvid=%d untagged=%d",413 port, vid, pvid, untagged);414 415 mutex_lock(&hellcreek->reg_lock);416 417 hellcreek_select_port(hellcreek, port);418 hellcreek_select_vlan(hellcreek, vid, pvid);419 420 /* Setup port vlan membership */421 hellcreek_select_vlan_params(hellcreek, port, &shift, &mask);422 val = hellcreek->vidmbrcfg[vid];423 val &= ~mask;424 if (untagged)425 val |= HELLCREEK_VLAN_UNTAGGED_MEMBER << shift;426 else427 val |= HELLCREEK_VLAN_TAGGED_MEMBER << shift;428 429 hellcreek_write(hellcreek, val, HR_VIDMBRCFG);430 hellcreek->vidmbrcfg[vid] = val;431 432 mutex_unlock(&hellcreek->reg_lock);433}434 435static void hellcreek_unapply_vlan(struct hellcreek *hellcreek, int port,436 u16 vid)437{438 int shift, mask;439 u16 val;440 441 dev_dbg(hellcreek->dev, "Unapply VLAN: port=%d vid=%u\n", port, vid);442 443 mutex_lock(&hellcreek->reg_lock);444 445 hellcreek_select_vlan(hellcreek, vid, false);446 447 /* Setup port vlan membership */448 hellcreek_select_vlan_params(hellcreek, port, &shift, &mask);449 val = hellcreek->vidmbrcfg[vid];450 val &= ~mask;451 val |= HELLCREEK_VLAN_NO_MEMBER << shift;452 453 hellcreek_write(hellcreek, val, HR_VIDMBRCFG);454 hellcreek->vidmbrcfg[vid] = val;455 456 mutex_unlock(&hellcreek->reg_lock);457}458 459static int hellcreek_vlan_add(struct dsa_switch *ds, int port,460 const struct switchdev_obj_port_vlan *vlan,461 struct netlink_ext_ack *extack)462{463 bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;464 bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;465 struct hellcreek *hellcreek = ds->priv;466 int err;467 468 err = hellcreek_vlan_prepare(ds, port, vlan, extack);469 if (err)470 return err;471 472 dev_dbg(hellcreek->dev, "Add VLAN %d on port %d, %s, %s\n",473 vlan->vid, port, untagged ? "untagged" : "tagged",474 pvid ? "PVID" : "no PVID");475 476 hellcreek_apply_vlan(hellcreek, port, vlan->vid, pvid, untagged);477 478 return 0;479}480 481static int hellcreek_vlan_del(struct dsa_switch *ds, int port,482 const struct switchdev_obj_port_vlan *vlan)483{484 struct hellcreek *hellcreek = ds->priv;485 486 dev_dbg(hellcreek->dev, "Remove VLAN %d on port %d\n", vlan->vid, port);487 488 hellcreek_unapply_vlan(hellcreek, port, vlan->vid);489 490 return 0;491}492 493static void hellcreek_port_stp_state_set(struct dsa_switch *ds, int port,494 u8 state)495{496 struct hellcreek *hellcreek = ds->priv;497 struct hellcreek_port *hellcreek_port;498 const char *new_state;499 u16 val;500 501 mutex_lock(&hellcreek->reg_lock);502 503 hellcreek_port = &hellcreek->ports[port];504 val = hellcreek_port->ptcfg;505 506 switch (state) {507 case BR_STATE_DISABLED:508 new_state = "DISABLED";509 val |= HR_PTCFG_BLOCKED;510 val &= ~HR_PTCFG_LEARNING_EN;511 break;512 case BR_STATE_BLOCKING:513 new_state = "BLOCKING";514 val |= HR_PTCFG_BLOCKED;515 val &= ~HR_PTCFG_LEARNING_EN;516 break;517 case BR_STATE_LISTENING:518 new_state = "LISTENING";519 val |= HR_PTCFG_BLOCKED;520 val &= ~HR_PTCFG_LEARNING_EN;521 break;522 case BR_STATE_LEARNING:523 new_state = "LEARNING";524 val |= HR_PTCFG_BLOCKED;525 val |= HR_PTCFG_LEARNING_EN;526 break;527 case BR_STATE_FORWARDING:528 new_state = "FORWARDING";529 val &= ~HR_PTCFG_BLOCKED;530 val |= HR_PTCFG_LEARNING_EN;531 break;532 default:533 new_state = "UNKNOWN";534 }535 536 hellcreek_select_port(hellcreek, port);537 hellcreek_write(hellcreek, val, HR_PTCFG);538 hellcreek_port->ptcfg = val;539 540 mutex_unlock(&hellcreek->reg_lock);541 542 dev_dbg(hellcreek->dev, "Configured STP state for port %d: %s\n",543 port, new_state);544}545 546static void hellcreek_setup_ingressflt(struct hellcreek *hellcreek, int port,547 bool enable)548{549 struct hellcreek_port *hellcreek_port = &hellcreek->ports[port];550 u16 ptcfg;551 552 mutex_lock(&hellcreek->reg_lock);553 554 ptcfg = hellcreek_port->ptcfg;555 556 if (enable)557 ptcfg |= HR_PTCFG_INGRESSFLT;558 else559 ptcfg &= ~HR_PTCFG_INGRESSFLT;560 561 hellcreek_select_port(hellcreek, port);562 hellcreek_write(hellcreek, ptcfg, HR_PTCFG);563 hellcreek_port->ptcfg = ptcfg;564 565 mutex_unlock(&hellcreek->reg_lock);566}567 568static void hellcreek_setup_vlan_awareness(struct hellcreek *hellcreek,569 bool enable)570{571 u16 swcfg;572 573 mutex_lock(&hellcreek->reg_lock);574 575 swcfg = hellcreek->swcfg;576 577 if (enable)578 swcfg |= HR_SWCFG_VLAN_UNAWARE;579 else580 swcfg &= ~HR_SWCFG_VLAN_UNAWARE;581 582 hellcreek_write(hellcreek, swcfg, HR_SWCFG);583 584 mutex_unlock(&hellcreek->reg_lock);585}586 587/* Default setup for DSA: VLAN <X>: CPU and Port <X> egress untagged. */588static void hellcreek_setup_vlan_membership(struct dsa_switch *ds, int port,589 bool enabled)590{591 const u16 vid = hellcreek_private_vid(port);592 int upstream = dsa_upstream_port(ds, port);593 struct hellcreek *hellcreek = ds->priv;594 595 /* Apply vid to port as egress untagged and port vlan id */596 if (enabled)597 hellcreek_apply_vlan(hellcreek, port, vid, true, true);598 else599 hellcreek_unapply_vlan(hellcreek, port, vid);600 601 /* Apply vid to cpu port as well */602 if (enabled)603 hellcreek_apply_vlan(hellcreek, upstream, vid, false, true);604 else605 hellcreek_unapply_vlan(hellcreek, upstream, vid);606}607 608static void hellcreek_port_set_ucast_flood(struct hellcreek *hellcreek,609 int port, bool enable)610{611 struct hellcreek_port *hellcreek_port;612 u16 val;613 614 hellcreek_port = &hellcreek->ports[port];615 616 dev_dbg(hellcreek->dev, "%s unicast flooding on port %d\n",617 enable ? "Enable" : "Disable", port);618 619 mutex_lock(&hellcreek->reg_lock);620 621 hellcreek_select_port(hellcreek, port);622 val = hellcreek_port->ptcfg;623 if (enable)624 val &= ~HR_PTCFG_UUC_FLT;625 else626 val |= HR_PTCFG_UUC_FLT;627 hellcreek_write(hellcreek, val, HR_PTCFG);628 hellcreek_port->ptcfg = val;629 630 mutex_unlock(&hellcreek->reg_lock);631}632 633static void hellcreek_port_set_mcast_flood(struct hellcreek *hellcreek,634 int port, bool enable)635{636 struct hellcreek_port *hellcreek_port;637 u16 val;638 639 hellcreek_port = &hellcreek->ports[port];640 641 dev_dbg(hellcreek->dev, "%s multicast flooding on port %d\n",642 enable ? "Enable" : "Disable", port);643 644 mutex_lock(&hellcreek->reg_lock);645 646 hellcreek_select_port(hellcreek, port);647 val = hellcreek_port->ptcfg;648 if (enable)649 val &= ~HR_PTCFG_UMC_FLT;650 else651 val |= HR_PTCFG_UMC_FLT;652 hellcreek_write(hellcreek, val, HR_PTCFG);653 hellcreek_port->ptcfg = val;654 655 mutex_unlock(&hellcreek->reg_lock);656}657 658static int hellcreek_pre_bridge_flags(struct dsa_switch *ds, int port,659 struct switchdev_brport_flags flags,660 struct netlink_ext_ack *extack)661{662 if (flags.mask & ~(BR_FLOOD | BR_MCAST_FLOOD))663 return -EINVAL;664 665 return 0;666}667 668static int hellcreek_bridge_flags(struct dsa_switch *ds, int port,669 struct switchdev_brport_flags flags,670 struct netlink_ext_ack *extack)671{672 struct hellcreek *hellcreek = ds->priv;673 674 if (flags.mask & BR_FLOOD)675 hellcreek_port_set_ucast_flood(hellcreek, port,676 !!(flags.val & BR_FLOOD));677 678 if (flags.mask & BR_MCAST_FLOOD)679 hellcreek_port_set_mcast_flood(hellcreek, port,680 !!(flags.val & BR_MCAST_FLOOD));681 682 return 0;683}684 685static int hellcreek_port_bridge_join(struct dsa_switch *ds, int port,686 struct dsa_bridge bridge,687 bool *tx_fwd_offload,688 struct netlink_ext_ack *extack)689{690 struct hellcreek *hellcreek = ds->priv;691 692 dev_dbg(hellcreek->dev, "Port %d joins a bridge\n", port);693 694 /* When joining a vlan_filtering bridge, keep the switch VLAN aware */695 if (!ds->vlan_filtering)696 hellcreek_setup_vlan_awareness(hellcreek, false);697 698 /* Drop private vlans */699 hellcreek_setup_vlan_membership(ds, port, false);700 701 return 0;702}703 704static void hellcreek_port_bridge_leave(struct dsa_switch *ds, int port,705 struct dsa_bridge bridge)706{707 struct hellcreek *hellcreek = ds->priv;708 709 dev_dbg(hellcreek->dev, "Port %d leaves a bridge\n", port);710 711 /* Enable VLAN awareness */712 hellcreek_setup_vlan_awareness(hellcreek, true);713 714 /* Enable private vlans */715 hellcreek_setup_vlan_membership(ds, port, true);716}717 718static int __hellcreek_fdb_add(struct hellcreek *hellcreek,719 const struct hellcreek_fdb_entry *entry)720{721 u16 meta = 0;722 723 dev_dbg(hellcreek->dev, "Add static FDB entry: MAC=%pM, MASK=0x%02x, "724 "OBT=%d, PASS_BLOCKED=%d, REPRIO_EN=%d, PRIO=%d\n", entry->mac,725 entry->portmask, entry->is_obt, entry->pass_blocked,726 entry->reprio_en, entry->reprio_tc);727 728 /* Add mac address */729 hellcreek_write(hellcreek, entry->mac[1] | (entry->mac[0] << 8), HR_FDBWDH);730 hellcreek_write(hellcreek, entry->mac[3] | (entry->mac[2] << 8), HR_FDBWDM);731 hellcreek_write(hellcreek, entry->mac[5] | (entry->mac[4] << 8), HR_FDBWDL);732 733 /* Meta data */734 meta |= entry->portmask << HR_FDBWRM0_PORTMASK_SHIFT;735 if (entry->is_obt)736 meta |= HR_FDBWRM0_OBT;737 if (entry->pass_blocked)738 meta |= HR_FDBWRM0_PASS_BLOCKED;739 if (entry->reprio_en) {740 meta |= HR_FDBWRM0_REPRIO_EN;741 meta |= entry->reprio_tc << HR_FDBWRM0_REPRIO_TC_SHIFT;742 }743 hellcreek_write(hellcreek, meta, HR_FDBWRM0);744 745 /* Commit */746 hellcreek_write(hellcreek, 0x00, HR_FDBWRCMD);747 748 /* Wait until done */749 return hellcreek_wait_fdb_ready(hellcreek);750}751 752static int __hellcreek_fdb_del(struct hellcreek *hellcreek,753 const struct hellcreek_fdb_entry *entry)754{755 dev_dbg(hellcreek->dev, "Delete FDB entry: MAC=%pM!\n", entry->mac);756 757 /* Delete by matching idx */758 hellcreek_write(hellcreek, entry->idx | HR_FDBWRCMD_FDBDEL, HR_FDBWRCMD);759 760 /* Wait until done */761 return hellcreek_wait_fdb_ready(hellcreek);762}763 764static void hellcreek_populate_fdb_entry(struct hellcreek *hellcreek,765 struct hellcreek_fdb_entry *entry,766 size_t idx)767{768 unsigned char addr[ETH_ALEN];769 u16 meta, mac;770 771 /* Read values */772 meta = hellcreek_read(hellcreek, HR_FDBMDRD);773 mac = hellcreek_read(hellcreek, HR_FDBRDL);774 addr[5] = mac & 0xff;775 addr[4] = (mac & 0xff00) >> 8;776 mac = hellcreek_read(hellcreek, HR_FDBRDM);777 addr[3] = mac & 0xff;778 addr[2] = (mac & 0xff00) >> 8;779 mac = hellcreek_read(hellcreek, HR_FDBRDH);780 addr[1] = mac & 0xff;781 addr[0] = (mac & 0xff00) >> 8;782 783 /* Populate @entry */784 memcpy(entry->mac, addr, sizeof(addr));785 entry->idx = idx;786 entry->portmask = (meta & HR_FDBMDRD_PORTMASK_MASK) >>787 HR_FDBMDRD_PORTMASK_SHIFT;788 entry->age = (meta & HR_FDBMDRD_AGE_MASK) >>789 HR_FDBMDRD_AGE_SHIFT;790 entry->is_obt = !!(meta & HR_FDBMDRD_OBT);791 entry->pass_blocked = !!(meta & HR_FDBMDRD_PASS_BLOCKED);792 entry->is_static = !!(meta & HR_FDBMDRD_STATIC);793 entry->reprio_tc = (meta & HR_FDBMDRD_REPRIO_TC_MASK) >>794 HR_FDBMDRD_REPRIO_TC_SHIFT;795 entry->reprio_en = !!(meta & HR_FDBMDRD_REPRIO_EN);796}797 798/* Retrieve the index of a FDB entry by mac address. Currently we search through799 * the complete table in hardware. If that's too slow, we might have to cache800 * the complete FDB table in software.801 */802static int hellcreek_fdb_get(struct hellcreek *hellcreek,803 const unsigned char *dest,804 struct hellcreek_fdb_entry *entry)805{806 size_t i;807 808 /* Set read pointer to zero: The read of HR_FDBMAX (read-only register)809 * should reset the internal pointer. But, that doesn't work. The vendor810 * suggested a subsequent write as workaround. Same for HR_FDBRDH below.811 */812 hellcreek_read(hellcreek, HR_FDBMAX);813 hellcreek_write(hellcreek, 0x00, HR_FDBMAX);814 815 /* We have to read the complete table, because the switch/driver might816 * enter new entries anywhere.817 */818 for (i = 0; i < hellcreek->fdb_entries; ++i) {819 struct hellcreek_fdb_entry tmp = { 0 };820 821 /* Read entry */822 hellcreek_populate_fdb_entry(hellcreek, &tmp, i);823 824 /* Force next entry */825 hellcreek_write(hellcreek, 0x00, HR_FDBRDH);826 827 if (memcmp(tmp.mac, dest, ETH_ALEN))828 continue;829 830 /* Match found */831 memcpy(entry, &tmp, sizeof(*entry));832 833 return 0;834 }835 836 return -ENOENT;837}838 839static int hellcreek_fdb_add(struct dsa_switch *ds, int port,840 const unsigned char *addr, u16 vid,841 struct dsa_db db)842{843 struct hellcreek_fdb_entry entry = { 0 };844 struct hellcreek *hellcreek = ds->priv;845 int ret;846 847 dev_dbg(hellcreek->dev, "Add FDB entry for MAC=%pM\n", addr);848 849 mutex_lock(&hellcreek->reg_lock);850 851 ret = hellcreek_fdb_get(hellcreek, addr, &entry);852 if (ret) {853 /* Not found */854 memcpy(entry.mac, addr, sizeof(entry.mac));855 entry.portmask = BIT(port);856 857 ret = __hellcreek_fdb_add(hellcreek, &entry);858 if (ret) {859 dev_err(hellcreek->dev, "Failed to add FDB entry!\n");860 goto out;861 }862 } else {863 /* Found */864 ret = __hellcreek_fdb_del(hellcreek, &entry);865 if (ret) {866 dev_err(hellcreek->dev, "Failed to delete FDB entry!\n");867 goto out;868 }869 870 entry.portmask |= BIT(port);871 872 ret = __hellcreek_fdb_add(hellcreek, &entry);873 if (ret) {874 dev_err(hellcreek->dev, "Failed to add FDB entry!\n");875 goto out;876 }877 }878 879out:880 mutex_unlock(&hellcreek->reg_lock);881 882 return ret;883}884 885static int hellcreek_fdb_del(struct dsa_switch *ds, int port,886 const unsigned char *addr, u16 vid,887 struct dsa_db db)888{889 struct hellcreek_fdb_entry entry = { 0 };890 struct hellcreek *hellcreek = ds->priv;891 int ret;892 893 dev_dbg(hellcreek->dev, "Delete FDB entry for MAC=%pM\n", addr);894 895 mutex_lock(&hellcreek->reg_lock);896 897 ret = hellcreek_fdb_get(hellcreek, addr, &entry);898 if (ret) {899 /* Not found */900 dev_err(hellcreek->dev, "FDB entry for deletion not found!\n");901 } else {902 /* Found */903 ret = __hellcreek_fdb_del(hellcreek, &entry);904 if (ret) {905 dev_err(hellcreek->dev, "Failed to delete FDB entry!\n");906 goto out;907 }908 909 entry.portmask &= ~BIT(port);910 911 if (entry.portmask != 0x00) {912 ret = __hellcreek_fdb_add(hellcreek, &entry);913 if (ret) {914 dev_err(hellcreek->dev, "Failed to add FDB entry!\n");915 goto out;916 }917 }918 }919 920out:921 mutex_unlock(&hellcreek->reg_lock);922 923 return ret;924}925 926static int hellcreek_fdb_dump(struct dsa_switch *ds, int port,927 dsa_fdb_dump_cb_t *cb, void *data)928{929 struct hellcreek *hellcreek = ds->priv;930 u16 entries;931 int ret = 0;932 size_t i;933 934 mutex_lock(&hellcreek->reg_lock);935 936 /* Set read pointer to zero: The read of HR_FDBMAX (read-only register)937 * should reset the internal pointer. But, that doesn't work. The vendor938 * suggested a subsequent write as workaround. Same for HR_FDBRDH below.939 */940 entries = hellcreek_read(hellcreek, HR_FDBMAX);941 hellcreek_write(hellcreek, 0x00, HR_FDBMAX);942 943 dev_dbg(hellcreek->dev, "FDB dump for port %d, entries=%d!\n", port, entries);944 945 /* Read table */946 for (i = 0; i < hellcreek->fdb_entries; ++i) {947 struct hellcreek_fdb_entry entry = { 0 };948 949 /* Read entry */950 hellcreek_populate_fdb_entry(hellcreek, &entry, i);951 952 /* Force next entry */953 hellcreek_write(hellcreek, 0x00, HR_FDBRDH);954 955 /* Check valid */956 if (is_zero_ether_addr(entry.mac))957 continue;958 959 /* Check port mask */960 if (!(entry.portmask & BIT(port)))961 continue;962 963 ret = cb(entry.mac, 0, entry.is_static, data);964 if (ret)965 break;966 }967 968 mutex_unlock(&hellcreek->reg_lock);969 970 return ret;971}972 973static int hellcreek_vlan_filtering(struct dsa_switch *ds, int port,974 bool vlan_filtering,975 struct netlink_ext_ack *extack)976{977 struct hellcreek *hellcreek = ds->priv;978 979 dev_dbg(hellcreek->dev, "%s VLAN filtering on port %d\n",980 vlan_filtering ? "Enable" : "Disable", port);981 982 /* Configure port to drop packages with not known vids */983 hellcreek_setup_ingressflt(hellcreek, port, vlan_filtering);984 985 /* Enable VLAN awareness on the switch. This save due to986 * ds->vlan_filtering_is_global.987 */988 hellcreek_setup_vlan_awareness(hellcreek, vlan_filtering);989 990 return 0;991}992 993static int hellcreek_enable_ip_core(struct hellcreek *hellcreek)994{995 int ret;996 u16 val;997 998 mutex_lock(&hellcreek->reg_lock);999 1000 val = hellcreek_read(hellcreek, HR_CTRL_C);1001 val |= HR_CTRL_C_ENABLE;1002 hellcreek_write(hellcreek, val, HR_CTRL_C);1003 ret = hellcreek_wait_until_transitioned(hellcreek);1004 1005 mutex_unlock(&hellcreek->reg_lock);1006 1007 return ret;1008}1009 1010static void hellcreek_setup_cpu_and_tunnel_port(struct hellcreek *hellcreek)1011{1012 struct hellcreek_port *tunnel_port = &hellcreek->ports[TUNNEL_PORT];1013 struct hellcreek_port *cpu_port = &hellcreek->ports[CPU_PORT];1014 u16 ptcfg = 0;1015 1016 ptcfg |= HR_PTCFG_LEARNING_EN | HR_PTCFG_ADMIN_EN;1017 1018 mutex_lock(&hellcreek->reg_lock);1019 1020 hellcreek_select_port(hellcreek, CPU_PORT);1021 hellcreek_write(hellcreek, ptcfg, HR_PTCFG);1022 1023 hellcreek_select_port(hellcreek, TUNNEL_PORT);1024 hellcreek_write(hellcreek, ptcfg, HR_PTCFG);1025 1026 cpu_port->ptcfg = ptcfg;1027 tunnel_port->ptcfg = ptcfg;1028 1029 mutex_unlock(&hellcreek->reg_lock);1030}1031 1032static void hellcreek_setup_tc_identity_mapping(struct hellcreek *hellcreek)1033{1034 int i;1035 1036 /* The switch has multiple egress queues per port. The queue is selected1037 * via the PCP field in the VLAN header. The switch internally deals1038 * with traffic classes instead of PCP values and this mapping is1039 * configurable.1040 *1041 * The default mapping is (PCP - TC):1042 * 7 - 71043 * 6 - 61044 * 5 - 51045 * 4 - 41046 * 3 - 31047 * 2 - 11048 * 1 - 01049 * 0 - 21050 *1051 * The default should be an identity mapping.1052 */1053 1054 for (i = 0; i < 8; ++i) {1055 mutex_lock(&hellcreek->reg_lock);1056 1057 hellcreek_select_prio(hellcreek, i);1058 hellcreek_write(hellcreek,1059 i << HR_PRTCCFG_PCP_TC_MAP_SHIFT,1060 HR_PRTCCFG);1061 1062 mutex_unlock(&hellcreek->reg_lock);1063 }1064}1065 1066static int hellcreek_setup_fdb(struct hellcreek *hellcreek)1067{1068 static struct hellcreek_fdb_entry l2_ptp = {1069 /* MAC: 01-1B-19-00-00-00 */1070 .mac = { 0x01, 0x1b, 0x19, 0x00, 0x00, 0x00 },1071 .portmask = 0x03, /* Management ports */1072 .age = 0,1073 .is_obt = 0,1074 .pass_blocked = 0,1075 .is_static = 1,1076 .reprio_tc = 6, /* TC: 6 as per IEEE 802.1AS */1077 .reprio_en = 1,1078 };1079 static struct hellcreek_fdb_entry udp4_ptp = {1080 /* MAC: 01-00-5E-00-01-81 */1081 .mac = { 0x01, 0x00, 0x5e, 0x00, 0x01, 0x81 },1082 .portmask = 0x03, /* Management ports */1083 .age = 0,1084 .is_obt = 0,1085 .pass_blocked = 0,1086 .is_static = 1,1087 .reprio_tc = 6,1088 .reprio_en = 1,1089 };1090 static struct hellcreek_fdb_entry udp6_ptp = {1091 /* MAC: 33-33-00-00-01-81 */1092 .mac = { 0x33, 0x33, 0x00, 0x00, 0x01, 0x81 },1093 .portmask = 0x03, /* Management ports */1094 .age = 0,1095 .is_obt = 0,1096 .pass_blocked = 0,1097 .is_static = 1,1098 .reprio_tc = 6,1099 .reprio_en = 1,1100 };1101 static struct hellcreek_fdb_entry l2_p2p = {1102 /* MAC: 01-80-C2-00-00-0E */1103 .mac = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e },1104 .portmask = 0x03, /* Management ports */1105 .age = 0,1106 .is_obt = 0,1107 .pass_blocked = 1,1108 .is_static = 1,1109 .reprio_tc = 6, /* TC: 6 as per IEEE 802.1AS */1110 .reprio_en = 1,1111 };1112 static struct hellcreek_fdb_entry udp4_p2p = {1113 /* MAC: 01-00-5E-00-00-6B */1114 .mac = { 0x01, 0x00, 0x5e, 0x00, 0x00, 0x6b },1115 .portmask = 0x03, /* Management ports */1116 .age = 0,1117 .is_obt = 0,1118 .pass_blocked = 1,1119 .is_static = 1,1120 .reprio_tc = 6,1121 .reprio_en = 1,1122 };1123 static struct hellcreek_fdb_entry udp6_p2p = {1124 /* MAC: 33-33-00-00-00-6B */1125 .mac = { 0x33, 0x33, 0x00, 0x00, 0x00, 0x6b },1126 .portmask = 0x03, /* Management ports */1127 .age = 0,1128 .is_obt = 0,1129 .pass_blocked = 1,1130 .is_static = 1,1131 .reprio_tc = 6,1132 .reprio_en = 1,1133 };1134 static struct hellcreek_fdb_entry stp = {1135 /* MAC: 01-80-C2-00-00-00 */1136 .mac = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 },1137 .portmask = 0x03, /* Management ports */1138 .age = 0,1139 .is_obt = 0,1140 .pass_blocked = 1,1141 .is_static = 1,1142 .reprio_tc = 6,1143 .reprio_en = 1,1144 };1145 int ret;1146 1147 mutex_lock(&hellcreek->reg_lock);1148 ret = __hellcreek_fdb_add(hellcreek, &l2_ptp);1149 if (ret)1150 goto out;1151 ret = __hellcreek_fdb_add(hellcreek, &udp4_ptp);1152 if (ret)1153 goto out;1154 ret = __hellcreek_fdb_add(hellcreek, &udp6_ptp);1155 if (ret)1156 goto out;1157 ret = __hellcreek_fdb_add(hellcreek, &l2_p2p);1158 if (ret)1159 goto out;1160 ret = __hellcreek_fdb_add(hellcreek, &udp4_p2p);1161 if (ret)1162 goto out;1163 ret = __hellcreek_fdb_add(hellcreek, &udp6_p2p);1164 if (ret)1165 goto out;1166 ret = __hellcreek_fdb_add(hellcreek, &stp);1167out:1168 mutex_unlock(&hellcreek->reg_lock);1169 1170 return ret;1171}1172 1173static int hellcreek_devlink_info_get(struct dsa_switch *ds,1174 struct devlink_info_req *req,1175 struct netlink_ext_ack *extack)1176{1177 struct hellcreek *hellcreek = ds->priv;1178 1179 return devlink_info_version_fixed_put(req,1180 DEVLINK_INFO_VERSION_GENERIC_ASIC_ID,1181 hellcreek->pdata->name);1182}1183 1184static u64 hellcreek_devlink_vlan_table_get(void *priv)1185{1186 struct hellcreek *hellcreek = priv;1187 u64 count = 0;1188 int i;1189 1190 mutex_lock(&hellcreek->reg_lock);1191 for (i = 0; i < VLAN_N_VID; ++i)1192 if (hellcreek->vidmbrcfg[i])1193 count++;1194 mutex_unlock(&hellcreek->reg_lock);1195 1196 return count;1197}1198 1199static u64 hellcreek_devlink_fdb_table_get(void *priv)1200{1201 struct hellcreek *hellcreek = priv;1202 u64 count = 0;1203 1204 /* Reading this register has side effects. Synchronize against the other1205 * FDB operations.1206 */1207 mutex_lock(&hellcreek->reg_lock);1208 count = hellcreek_read(hellcreek, HR_FDBMAX);1209 mutex_unlock(&hellcreek->reg_lock);1210 1211 return count;1212}1213 1214static int hellcreek_setup_devlink_resources(struct dsa_switch *ds)1215{1216 struct devlink_resource_size_params size_vlan_params;1217 struct devlink_resource_size_params size_fdb_params;1218 struct hellcreek *hellcreek = ds->priv;1219 int err;1220 1221 devlink_resource_size_params_init(&size_vlan_params, VLAN_N_VID,1222 VLAN_N_VID,1223 1, DEVLINK_RESOURCE_UNIT_ENTRY);1224 1225 devlink_resource_size_params_init(&size_fdb_params,1226 hellcreek->fdb_entries,1227 hellcreek->fdb_entries,1228 1, DEVLINK_RESOURCE_UNIT_ENTRY);1229 1230 err = dsa_devlink_resource_register(ds, "VLAN", VLAN_N_VID,1231 HELLCREEK_DEVLINK_PARAM_ID_VLAN_TABLE,1232 DEVLINK_RESOURCE_ID_PARENT_TOP,1233 &size_vlan_params);1234 if (err)1235 goto out;1236 1237 err = dsa_devlink_resource_register(ds, "FDB", hellcreek->fdb_entries,1238 HELLCREEK_DEVLINK_PARAM_ID_FDB_TABLE,1239 DEVLINK_RESOURCE_ID_PARENT_TOP,1240 &size_fdb_params);1241 if (err)1242 goto out;1243 1244 dsa_devlink_resource_occ_get_register(ds,1245 HELLCREEK_DEVLINK_PARAM_ID_VLAN_TABLE,1246 hellcreek_devlink_vlan_table_get,1247 hellcreek);1248 1249 dsa_devlink_resource_occ_get_register(ds,1250 HELLCREEK_DEVLINK_PARAM_ID_FDB_TABLE,1251 hellcreek_devlink_fdb_table_get,1252 hellcreek);1253 1254 return 0;1255 1256out:1257 dsa_devlink_resources_unregister(ds);1258 1259 return err;1260}1261 1262static int hellcreek_devlink_region_vlan_snapshot(struct devlink *dl,1263 const struct devlink_region_ops *ops,1264 struct netlink_ext_ack *extack,1265 u8 **data)1266{1267 struct hellcreek_devlink_vlan_entry *table, *entry;1268 struct dsa_switch *ds = dsa_devlink_to_ds(dl);1269 struct hellcreek *hellcreek = ds->priv;1270 int i;1271 1272 table = kcalloc(VLAN_N_VID, sizeof(*entry), GFP_KERNEL);1273 if (!table)1274 return -ENOMEM;1275 1276 entry = table;1277 1278 mutex_lock(&hellcreek->reg_lock);1279 for (i = 0; i < VLAN_N_VID; ++i, ++entry) {1280 entry->member = hellcreek->vidmbrcfg[i];1281 entry->vid = i;1282 }1283 mutex_unlock(&hellcreek->reg_lock);1284 1285 *data = (u8 *)table;1286 1287 return 0;1288}1289 1290static int hellcreek_devlink_region_fdb_snapshot(struct devlink *dl,1291 const struct devlink_region_ops *ops,1292 struct netlink_ext_ack *extack,1293 u8 **data)1294{1295 struct dsa_switch *ds = dsa_devlink_to_ds(dl);1296 struct hellcreek_fdb_entry *table, *entry;1297 struct hellcreek *hellcreek = ds->priv;1298 size_t i;1299 1300 table = kcalloc(hellcreek->fdb_entries, sizeof(*entry), GFP_KERNEL);1301 if (!table)1302 return -ENOMEM;1303 1304 entry = table;1305 1306 mutex_lock(&hellcreek->reg_lock);1307 1308 /* Start table read */1309 hellcreek_read(hellcreek, HR_FDBMAX);1310 hellcreek_write(hellcreek, 0x00, HR_FDBMAX);1311 1312 for (i = 0; i < hellcreek->fdb_entries; ++i, ++entry) {1313 /* Read current entry */1314 hellcreek_populate_fdb_entry(hellcreek, entry, i);1315 1316 /* Advance read pointer */1317 hellcreek_write(hellcreek, 0x00, HR_FDBRDH);1318 }1319 1320 mutex_unlock(&hellcreek->reg_lock);1321 1322 *data = (u8 *)table;1323 1324 return 0;1325}1326 1327static struct devlink_region_ops hellcreek_region_vlan_ops = {1328 .name = "vlan",1329 .snapshot = hellcreek_devlink_region_vlan_snapshot,1330 .destructor = kfree,1331};1332 1333static struct devlink_region_ops hellcreek_region_fdb_ops = {1334 .name = "fdb",1335 .snapshot = hellcreek_devlink_region_fdb_snapshot,1336 .destructor = kfree,1337};1338 1339static int hellcreek_setup_devlink_regions(struct dsa_switch *ds)1340{1341 struct hellcreek *hellcreek = ds->priv;1342 struct devlink_region_ops *ops;1343 struct devlink_region *region;1344 u64 size;1345 int ret;1346 1347 /* VLAN table */1348 size = VLAN_N_VID * sizeof(struct hellcreek_devlink_vlan_entry);1349 ops = &hellcreek_region_vlan_ops;1350 1351 region = dsa_devlink_region_create(ds, ops, 1, size);1352 if (IS_ERR(region))1353 return PTR_ERR(region);1354 1355 hellcreek->vlan_region = region;1356 1357 /* FDB table */1358 size = hellcreek->fdb_entries * sizeof(struct hellcreek_fdb_entry);1359 ops = &hellcreek_region_fdb_ops;1360 1361 region = dsa_devlink_region_create(ds, ops, 1, size);1362 if (IS_ERR(region)) {1363 ret = PTR_ERR(region);1364 goto err_fdb;1365 }1366 1367 hellcreek->fdb_region = region;1368 1369 return 0;1370 1371err_fdb:1372 dsa_devlink_region_destroy(hellcreek->vlan_region);1373 1374 return ret;1375}1376 1377static void hellcreek_teardown_devlink_regions(struct dsa_switch *ds)1378{1379 struct hellcreek *hellcreek = ds->priv;1380 1381 dsa_devlink_region_destroy(hellcreek->fdb_region);1382 dsa_devlink_region_destroy(hellcreek->vlan_region);1383}1384 1385static int hellcreek_setup(struct dsa_switch *ds)1386{1387 struct hellcreek *hellcreek = ds->priv;1388 u16 swcfg = 0;1389 int ret, i;1390 1391 dev_dbg(hellcreek->dev, "Set up the switch\n");1392 1393 /* Let's go */1394 ret = hellcreek_enable_ip_core(hellcreek);1395 if (ret) {1396 dev_err(hellcreek->dev, "Failed to enable IP core!\n");1397 return ret;1398 }1399 1400 /* Enable CPU/Tunnel ports */1401 hellcreek_setup_cpu_and_tunnel_port(hellcreek);1402 1403 /* Switch config: Keep defaults, enable FDB aging and learning and tag1404 * each frame from/to cpu port for DSA tagging. Also enable the length1405 * aware shaping mode. This eliminates the need for Qbv guard bands.1406 */1407 swcfg |= HR_SWCFG_FDBAGE_EN |1408 HR_SWCFG_FDBLRN_EN |1409 HR_SWCFG_ALWAYS_OBT |1410 (HR_SWCFG_LAS_ON << HR_SWCFG_LAS_MODE_SHIFT);1411 hellcreek->swcfg = swcfg;1412 hellcreek_write(hellcreek, swcfg, HR_SWCFG);1413 1414 /* Initial vlan membership to reflect port separation */1415 for (i = 0; i < ds->num_ports; ++i) {1416 if (!dsa_is_user_port(ds, i))1417 continue;1418 1419 hellcreek_setup_vlan_membership(ds, i, true);1420 }1421 1422 /* Configure PCP <-> TC mapping */1423 hellcreek_setup_tc_identity_mapping(hellcreek);1424 1425 /* The VLAN awareness is a global switch setting. Therefore, mixed vlan1426 * filtering setups are not supported.1427 */1428 ds->vlan_filtering_is_global = true;1429 ds->needs_standalone_vlan_filtering = true;1430 1431 /* Intercept _all_ PTP multicast traffic */1432 ret = hellcreek_setup_fdb(hellcreek);1433 if (ret) {1434 dev_err(hellcreek->dev,1435 "Failed to insert static PTP FDB entries\n");1436 return ret;1437 }1438 1439 /* Register devlink resources with DSA */1440 ret = hellcreek_setup_devlink_resources(ds);1441 if (ret) {1442 dev_err(hellcreek->dev,1443 "Failed to setup devlink resources!\n");1444 return ret;1445 }1446 1447 ret = hellcreek_setup_devlink_regions(ds);1448 if (ret) {1449 dev_err(hellcreek->dev,1450 "Failed to setup devlink regions!\n");1451 goto err_regions;1452 }1453 1454 return 0;1455 1456err_regions:1457 dsa_devlink_resources_unregister(ds);1458 1459 return ret;1460}1461 1462static void hellcreek_teardown(struct dsa_switch *ds)1463{1464 hellcreek_teardown_devlink_regions(ds);1465 dsa_devlink_resources_unregister(ds);1466}1467 1468static void hellcreek_phylink_get_caps(struct dsa_switch *ds, int port,1469 struct phylink_config *config)1470{1471 struct hellcreek *hellcreek = ds->priv;1472 1473 __set_bit(PHY_INTERFACE_MODE_MII, config->supported_interfaces);1474 __set_bit(PHY_INTERFACE_MODE_RGMII, config->supported_interfaces);1475 1476 /* Include GMII - the hardware does not support this interface1477 * mode, but it's the default interface mode for phylib, so we1478 * need it for compatibility with existing DT.1479 */1480 __set_bit(PHY_INTERFACE_MODE_GMII, config->supported_interfaces);1481 1482 /* The MAC settings are a hardware configuration option and cannot be1483 * changed at run time or by strapping. Therefore the attached PHYs1484 * should be programmed to only advertise settings which are supported1485 * by the hardware.1486 */1487 if (hellcreek->pdata->is_100_mbits)1488 config->mac_capabilities = MAC_100FD;1489 else1490 config->mac_capabilities = MAC_1000FD;1491}1492 1493static int1494hellcreek_port_prechangeupper(struct dsa_switch *ds, int port,1495 struct netdev_notifier_changeupper_info *info)1496{1497 struct hellcreek *hellcreek = ds->priv;1498 bool used = true;1499 int ret = -EBUSY;1500 u16 vid;1501 int i;1502 1503 dev_dbg(hellcreek->dev, "Pre change upper for port %d\n", port);1504 1505 /*1506 * Deny VLAN devices on top of lan ports with the same VLAN ids, because1507 * it breaks the port separation due to the private VLANs. Example:1508 *1509 * lan0.100 *and* lan1.100 cannot be used in parallel. However, lan0.991510 * and lan1.100 works.1511 */1512 1513 if (!is_vlan_dev(info->upper_dev))1514 return 0;1515 1516 vid = vlan_dev_vlan_id(info->upper_dev);1517 1518 /* For all ports, check bitmaps */1519 mutex_lock(&hellcreek->vlan_lock);1520 for (i = 0; i < hellcreek->pdata->num_ports; ++i) {1521 if (!dsa_is_user_port(ds, i))1522 continue;1523 1524 if (port == i)1525 continue;1526 1527 used = used && test_bit(vid, hellcreek->ports[i].vlan_dev_bitmap);1528 }1529 1530 if (used)1531 goto out;1532 1533 /* Update bitmap */1534 set_bit(vid, hellcreek->ports[port].vlan_dev_bitmap);1535 1536 ret = 0;1537 1538out:1539 mutex_unlock(&hellcreek->vlan_lock);1540 1541 return ret;1542}1543 1544static void hellcreek_setup_maxsdu(struct hellcreek *hellcreek, int port,1545 const struct tc_taprio_qopt_offload *schedule)1546{1547 int tc;1548 1549 for (tc = 0; tc < 8; ++tc) {1550 u32 max_sdu = schedule->max_sdu[tc] + VLAN_ETH_HLEN - ETH_FCS_LEN;1551 u16 val;1552 1553 if (!schedule->max_sdu[tc])1554 continue;1555 1556 dev_dbg(hellcreek->dev, "Configure max-sdu %u for tc %d on port %d\n",1557 max_sdu, tc, port);1558 1559 hellcreek_select_port_prio(hellcreek, port, tc);1560 1561 val = (max_sdu & HR_PTPRTCCFG_MAXSDU_MASK) << HR_PTPRTCCFG_MAXSDU_SHIFT;1562 1563 hellcreek_write(hellcreek, val, HR_PTPRTCCFG);1564 }1565}1566 1567static void hellcreek_reset_maxsdu(struct hellcreek *hellcreek, int port)1568{1569 int tc;1570 1571 for (tc = 0; tc < 8; ++tc) {1572 u16 val;1573 1574 hellcreek_select_port_prio(hellcreek, port, tc);1575 1576 val = (HELLCREEK_DEFAULT_MAX_SDU & HR_PTPRTCCFG_MAXSDU_MASK)1577 << HR_PTPRTCCFG_MAXSDU_SHIFT;1578 1579 hellcreek_write(hellcreek, val, HR_PTPRTCCFG);1580 }1581}1582 1583static void hellcreek_setup_gcl(struct hellcreek *hellcreek, int port,1584 const struct tc_taprio_qopt_offload *schedule)1585{1586 const struct tc_taprio_sched_entry *cur, *initial, *next;1587 size_t i;1588 1589 cur = initial = &schedule->entries[0];1590 next = cur + 1;1591 1592 for (i = 1; i <= schedule->num_entries; ++i) {1593 u16 data;1594 u8 gates;1595 1596 if (i == schedule->num_entries)1597 gates = initial->gate_mask ^1598 cur->gate_mask;1599 else1600 gates = next->gate_mask ^1601 cur->gate_mask;1602 1603 data = gates;1604 1605 if (i == schedule->num_entries)1606 data |= TR_GCLDAT_GCLWRLAST;1607 1608 /* Gates states */1609 hellcreek_write(hellcreek, data, TR_GCLDAT);1610 1611 /* Time interval */1612 hellcreek_write(hellcreek,1613 cur->interval & 0x0000ffff,1614 TR_GCLTIL);1615 hellcreek_write(hellcreek,1616 (cur->interval & 0xffff0000) >> 16,1617 TR_GCLTIH);1618 1619 /* Commit entry */1620 data = ((i - 1) << TR_GCLCMD_GCLWRADR_SHIFT) |1621 (initial->gate_mask <<1622 TR_GCLCMD_INIT_GATE_STATES_SHIFT);1623 hellcreek_write(hellcreek, data, TR_GCLCMD);1624 1625 cur++;1626 next++;1627 }1628}1629 1630static void hellcreek_set_cycle_time(struct hellcreek *hellcreek,1631 const struct tc_taprio_qopt_offload *schedule)1632{1633 u32 cycle_time = schedule->cycle_time;1634 1635 hellcreek_write(hellcreek, cycle_time & 0x0000ffff, TR_CTWRL);1636 hellcreek_write(hellcreek, (cycle_time & 0xffff0000) >> 16, TR_CTWRH);1637}1638 1639static void hellcreek_switch_schedule(struct hellcreek *hellcreek,1640 ktime_t start_time)1641{1642 struct timespec64 ts = ktime_to_timespec64(start_time);1643 1644 /* Start schedule at this point of time */1645 hellcreek_write(hellcreek, ts.tv_nsec & 0x0000ffff, TR_ESTWRL);1646 hellcreek_write(hellcreek, (ts.tv_nsec & 0xffff0000) >> 16, TR_ESTWRH);1647 1648 /* Arm timer, set seconds and switch schedule */1649 hellcreek_write(hellcreek, TR_ESTCMD_ESTARM | TR_ESTCMD_ESTSWCFG |1650 ((ts.tv_sec & TR_ESTCMD_ESTSEC_MASK) <<1651 TR_ESTCMD_ESTSEC_SHIFT), TR_ESTCMD);1652}1653 1654static bool hellcreek_schedule_startable(struct hellcreek *hellcreek, int port)1655{1656 struct hellcreek_port *hellcreek_port = &hellcreek->ports[port];1657 s64 base_time_ns, current_ns;1658 1659 /* The switch allows a schedule to be started only eight seconds within1660 * the future. Therefore, check the current PTP time if the schedule is1661 * startable or not.1662 */1663 1664 /* Use the "cached" time. That should be alright, as it's updated quite1665 * frequently in the PTP code.1666 */1667 mutex_lock(&hellcreek->ptp_lock);1668 current_ns = hellcreek->seconds * NSEC_PER_SEC + hellcreek->last_ts;1669 mutex_unlock(&hellcreek->ptp_lock);1670 1671 /* Calculate difference to admin base time */1672 base_time_ns = ktime_to_ns(hellcreek_port->current_schedule->base_time);1673 1674 return base_time_ns - current_ns < (s64)4 * NSEC_PER_SEC;1675}1676 1677static void hellcreek_start_schedule(struct hellcreek *hellcreek, int port)1678{1679 struct hellcreek_port *hellcreek_port = &hellcreek->ports[port];1680 ktime_t base_time, current_time;1681 s64 current_ns;1682 u32 cycle_time;1683 1684 /* First select port */1685 hellcreek_select_tgd(hellcreek, port);1686 1687 /* Forward base time into the future if needed */1688 mutex_lock(&hellcreek->ptp_lock);1689 current_ns = hellcreek->seconds * NSEC_PER_SEC + hellcreek->last_ts;1690 mutex_unlock(&hellcreek->ptp_lock);1691 1692 current_time = ns_to_ktime(current_ns);1693 base_time = hellcreek_port->current_schedule->base_time;1694 cycle_time = hellcreek_port->current_schedule->cycle_time;1695 1696 if (ktime_compare(current_time, base_time) > 0) {1697 s64 n;1698 1699 n = div64_s64(ktime_sub_ns(current_time, base_time),1700 cycle_time);1701 base_time = ktime_add_ns(base_time, (n + 1) * cycle_time);1702 }1703 1704 /* Set admin base time and switch schedule */1705 hellcreek_switch_schedule(hellcreek, base_time);1706 1707 taprio_offload_free(hellcreek_port->current_schedule);1708 hellcreek_port->current_schedule = NULL;1709 1710 dev_dbg(hellcreek->dev, "Armed EST timer for port %d\n",1711 hellcreek_port->port);1712}1713 1714static void hellcreek_check_schedule(struct work_struct *work)1715{1716 struct delayed_work *dw = to_delayed_work(work);1717 struct hellcreek_port *hellcreek_port;1718 struct hellcreek *hellcreek;1719 bool startable;1720 1721 hellcreek_port = dw_to_hellcreek_port(dw);1722 hellcreek = hellcreek_port->hellcreek;1723 1724 mutex_lock(&hellcreek->reg_lock);1725 1726 /* Check starting time */1727 startable = hellcreek_schedule_startable(hellcreek,1728 hellcreek_port->port);1729 if (startable) {1730 hellcreek_start_schedule(hellcreek, hellcreek_port->port);1731 mutex_unlock(&hellcreek->reg_lock);1732 return;1733 }1734 1735 mutex_unlock(&hellcreek->reg_lock);1736 1737 /* Reschedule */1738 schedule_delayed_work(&hellcreek_port->schedule_work,1739 HELLCREEK_SCHEDULE_PERIOD);1740}1741 1742static int hellcreek_port_set_schedule(struct dsa_switch *ds, int port,1743 struct tc_taprio_qopt_offload *taprio)1744{1745 struct hellcreek *hellcreek = ds->priv;1746 struct hellcreek_port *hellcreek_port;1747 bool startable;1748 u16 ctrl;1749 1750 hellcreek_port = &hellcreek->ports[port];1751 1752 dev_dbg(hellcreek->dev, "Configure traffic schedule on port %d\n",1753 port);1754 1755 /* First cancel delayed work */1756 cancel_delayed_work_sync(&hellcreek_port->schedule_work);1757 1758 mutex_lock(&hellcreek->reg_lock);1759 1760 if (hellcreek_port->current_schedule) {1761 taprio_offload_free(hellcreek_port->current_schedule);1762 hellcreek_port->current_schedule = NULL;1763 }1764 hellcreek_port->current_schedule = taprio_offload_get(taprio);1765 1766 /* Configure max sdu */1767 hellcreek_setup_maxsdu(hellcreek, port, hellcreek_port->current_schedule);1768 1769 /* Select tdg */1770 hellcreek_select_tgd(hellcreek, port);1771 1772 /* Enable gating and keep defaults */1773 ctrl = (0xff << TR_TGDCTRL_ADMINGATESTATES_SHIFT) | TR_TGDCTRL_GATE_EN;1774 hellcreek_write(hellcreek, ctrl, TR_TGDCTRL);1775 1776 /* Cancel pending schedule */1777 hellcreek_write(hellcreek, 0x00, TR_ESTCMD);1778 1779 /* Setup a new schedule */1780 hellcreek_setup_gcl(hellcreek, port, hellcreek_port->current_schedule);1781 1782 /* Configure cycle time */1783 hellcreek_set_cycle_time(hellcreek, hellcreek_port->current_schedule);1784 1785 /* Check starting time */1786 startable = hellcreek_schedule_startable(hellcreek, port);1787 if (startable) {1788 hellcreek_start_schedule(hellcreek, port);1789 mutex_unlock(&hellcreek->reg_lock);1790 return 0;1791 }1792 1793 mutex_unlock(&hellcreek->reg_lock);1794 1795 /* Schedule periodic schedule check */1796 schedule_delayed_work(&hellcreek_port->schedule_work,1797 HELLCREEK_SCHEDULE_PERIOD);1798 1799 return 0;1800}1801 1802static int hellcreek_port_del_schedule(struct dsa_switch *ds, int port)1803{1804 struct hellcreek *hellcreek = ds->priv;1805 struct hellcreek_port *hellcreek_port;1806 1807 hellcreek_port = &hellcreek->ports[port];1808 1809 dev_dbg(hellcreek->dev, "Remove traffic schedule on port %d\n", port);1810 1811 /* First cancel delayed work */1812 cancel_delayed_work_sync(&hellcreek_port->schedule_work);1813 1814 mutex_lock(&hellcreek->reg_lock);1815 1816 if (hellcreek_port->current_schedule) {1817 taprio_offload_free(hellcreek_port->current_schedule);1818 hellcreek_port->current_schedule = NULL;1819 }1820 1821 /* Reset max sdu */1822 hellcreek_reset_maxsdu(hellcreek, port);1823 1824 /* Select tgd */1825 hellcreek_select_tgd(hellcreek, port);1826 1827 /* Disable gating and return to regular switching flow */1828 hellcreek_write(hellcreek, 0xff << TR_TGDCTRL_ADMINGATESTATES_SHIFT,1829 TR_TGDCTRL);1830 1831 mutex_unlock(&hellcreek->reg_lock);1832 1833 return 0;1834}1835 1836static bool hellcreek_validate_schedule(struct hellcreek *hellcreek,1837 struct tc_taprio_qopt_offload *schedule)1838{1839 size_t i;1840 1841 /* Does this hellcreek version support Qbv in hardware? */1842 if (!hellcreek->pdata->qbv_support)1843 return false;1844 1845 /* cycle time can only be 32bit */1846 if (schedule->cycle_time > (u32)-1)1847 return false;1848 1849 /* cycle time extension is not supported */1850 if (schedule->cycle_time_extension)1851 return false;1852 1853 /* Only set command is supported */1854 for (i = 0; i < schedule->num_entries; ++i)1855 if (schedule->entries[i].command != TC_TAPRIO_CMD_SET_GATES)1856 return false;1857 1858 return true;1859}1860 1861static int hellcreek_tc_query_caps(struct tc_query_caps_base *base)1862{1863 switch (base->type) {1864 case TC_SETUP_QDISC_TAPRIO: {1865 struct tc_taprio_caps *caps = base->caps;1866 1867 caps->supports_queue_max_sdu = true;1868 1869 return 0;1870 }1871 default:1872 return -EOPNOTSUPP;1873 }1874}1875 1876static int hellcreek_port_setup_tc(struct dsa_switch *ds, int port,1877 enum tc_setup_type type, void *type_data)1878{1879 struct hellcreek *hellcreek = ds->priv;1880 1881 switch (type) {1882 case TC_QUERY_CAPS:1883 return hellcreek_tc_query_caps(type_data);1884 case TC_SETUP_QDISC_TAPRIO: {1885 struct tc_taprio_qopt_offload *taprio = type_data;1886 1887 switch (taprio->cmd) {1888 case TAPRIO_CMD_REPLACE:1889 if (!hellcreek_validate_schedule(hellcreek, taprio))1890 return -EOPNOTSUPP;1891 1892 return hellcreek_port_set_schedule(ds, port, taprio);1893 case TAPRIO_CMD_DESTROY:1894 return hellcreek_port_del_schedule(ds, port);1895 default:1896 return -EOPNOTSUPP;1897 }1898 }1899 default:1900 return -EOPNOTSUPP;1901 }1902}1903 1904static const struct dsa_switch_ops hellcreek_ds_ops = {1905 .devlink_info_get = hellcreek_devlink_info_get,1906 .get_ethtool_stats = hellcreek_get_ethtool_stats,1907 .get_sset_count = hellcreek_get_sset_count,1908 .get_strings = hellcreek_get_strings,1909 .get_tag_protocol = hellcreek_get_tag_protocol,1910 .get_ts_info = hellcreek_get_ts_info,1911 .phylink_get_caps = hellcreek_phylink_get_caps,1912 .port_bridge_flags = hellcreek_bridge_flags,1913 .port_bridge_join = hellcreek_port_bridge_join,1914 .port_bridge_leave = hellcreek_port_bridge_leave,1915 .port_disable = hellcreek_port_disable,1916 .port_enable = hellcreek_port_enable,1917 .port_fdb_add = hellcreek_fdb_add,1918 .port_fdb_del = hellcreek_fdb_del,1919 .port_fdb_dump = hellcreek_fdb_dump,1920 .port_hwtstamp_set = hellcreek_port_hwtstamp_set,1921 .port_hwtstamp_get = hellcreek_port_hwtstamp_get,1922 .port_pre_bridge_flags = hellcreek_pre_bridge_flags,1923 .port_prechangeupper = hellcreek_port_prechangeupper,1924 .port_rxtstamp = hellcreek_port_rxtstamp,1925 .port_setup_tc = hellcreek_port_setup_tc,1926 .port_stp_state_set = hellcreek_port_stp_state_set,1927 .port_txtstamp = hellcreek_port_txtstamp,1928 .port_vlan_add = hellcreek_vlan_add,1929 .port_vlan_del = hellcreek_vlan_del,1930 .port_vlan_filtering = hellcreek_vlan_filtering,1931 .setup = hellcreek_setup,1932 .teardown = hellcreek_teardown,1933};1934 1935static int hellcreek_probe(struct platform_device *pdev)1936{1937 struct device *dev = &pdev->dev;1938 struct hellcreek *hellcreek;1939 struct resource *res;1940 int ret, i;1941 1942 hellcreek = devm_kzalloc(dev, sizeof(*hellcreek), GFP_KERNEL);1943 if (!hellcreek)1944 return -ENOMEM;1945 1946 hellcreek->vidmbrcfg = devm_kcalloc(dev, VLAN_N_VID,1947 sizeof(*hellcreek->vidmbrcfg),1948 GFP_KERNEL);1949 if (!hellcreek->vidmbrcfg)1950 return -ENOMEM;1951 1952 hellcreek->pdata = of_device_get_match_data(dev);1953 1954 hellcreek->ports = devm_kcalloc(dev, hellcreek->pdata->num_ports,1955 sizeof(*hellcreek->ports),1956 GFP_KERNEL);1957 if (!hellcreek->ports)1958 return -ENOMEM;1959 1960 for (i = 0; i < hellcreek->pdata->num_ports; ++i) {1961 struct hellcreek_port *port = &hellcreek->ports[i];1962 1963 port->counter_values =1964 devm_kcalloc(dev,1965 ARRAY_SIZE(hellcreek_counter),1966 sizeof(*port->counter_values),1967 GFP_KERNEL);1968 if (!port->counter_values)1969 return -ENOMEM;1970 1971 port->vlan_dev_bitmap = devm_bitmap_zalloc(dev, VLAN_N_VID,1972 GFP_KERNEL);1973 if (!port->vlan_dev_bitmap)1974 return -ENOMEM;1975 1976 port->hellcreek = hellcreek;1977 port->port = i;1978 1979 INIT_DELAYED_WORK(&port->schedule_work,1980 hellcreek_check_schedule);1981 }1982 1983 mutex_init(&hellcreek->reg_lock);1984 mutex_init(&hellcreek->vlan_lock);1985 mutex_init(&hellcreek->ptp_lock);1986 1987 hellcreek->dev = dev;1988 1989 res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tsn");1990 if (!res) {1991 dev_err(dev, "No memory region provided!\n");1992 return -ENODEV;1993 }1994 1995 hellcreek->base = devm_ioremap_resource(dev, res);1996 if (IS_ERR(hellcreek->base))1997 return PTR_ERR(hellcreek->base);1998 1999 res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ptp");2000 if (!res) {2001 dev_err(dev, "No PTP memory region provided!\n");2002 return -ENODEV;2003 }2004 2005 hellcreek->ptp_base = devm_ioremap_resource(dev, res);2006 if (IS_ERR(hellcreek->ptp_base))2007 return PTR_ERR(hellcreek->ptp_base);2008 2009 ret = hellcreek_detect(hellcreek);2010 if (ret) {2011 dev_err(dev, "No (known) chip found!\n");2012 return ret;2013 }2014 2015 ret = hellcreek_wait_until_ready(hellcreek);2016 if (ret) {2017 dev_err(dev, "Switch didn't become ready!\n");2018 return ret;2019 }2020 2021 hellcreek_feature_detect(hellcreek);2022 2023 hellcreek->ds = devm_kzalloc(dev, sizeof(*hellcreek->ds), GFP_KERNEL);2024 if (!hellcreek->ds)2025 return -ENOMEM;2026 2027 hellcreek->ds->dev = dev;2028 hellcreek->ds->priv = hellcreek;2029 hellcreek->ds->ops = &hellcreek_ds_ops;2030 hellcreek->ds->num_ports = hellcreek->pdata->num_ports;2031 hellcreek->ds->num_tx_queues = HELLCREEK_NUM_EGRESS_QUEUES;2032 2033 ret = dsa_register_switch(hellcreek->ds);2034 if (ret) {2035 dev_err_probe(dev, ret, "Unable to register switch\n");2036 return ret;2037 }2038 2039 ret = hellcreek_ptp_setup(hellcreek);2040 if (ret) {2041 dev_err(dev, "Failed to setup PTP!\n");2042 goto err_ptp_setup;2043 }2044 2045 ret = hellcreek_hwtstamp_setup(hellcreek);2046 if (ret) {2047 dev_err(dev, "Failed to setup hardware timestamping!\n");2048 goto err_tstamp_setup;2049 }2050 2051 platform_set_drvdata(pdev, hellcreek);2052 2053 return 0;2054 2055err_tstamp_setup:2056 hellcreek_ptp_free(hellcreek);2057err_ptp_setup:2058 dsa_unregister_switch(hellcreek->ds);2059 2060 return ret;2061}2062 2063static void hellcreek_remove(struct platform_device *pdev)2064{2065 struct hellcreek *hellcreek = platform_get_drvdata(pdev);2066 2067 if (!hellcreek)2068 return;2069 2070 hellcreek_hwtstamp_free(hellcreek);2071 hellcreek_ptp_free(hellcreek);2072 dsa_unregister_switch(hellcreek->ds);2073}2074 2075static void hellcreek_shutdown(struct platform_device *pdev)2076{2077 struct hellcreek *hellcreek = platform_get_drvdata(pdev);2078 2079 if (!hellcreek)2080 return;2081 2082 dsa_switch_shutdown(hellcreek->ds);2083 2084 platform_set_drvdata(pdev, NULL);2085}2086 2087static const struct hellcreek_platform_data de1soc_r1_pdata = {2088 .name = "r4c30",2089 .num_ports = 4,2090 .is_100_mbits = 1,2091 .qbv_support = 1,2092 .qbv_on_cpu_port = 1,2093 .qbu_support = 0,2094 .module_id = 0x4c30,2095};2096 2097static const struct of_device_id hellcreek_of_match[] = {2098 {2099 .compatible = "hirschmann,hellcreek-de1soc-r1",2100 .data = &de1soc_r1_pdata,2101 },2102 { /* sentinel */ },2103};2104MODULE_DEVICE_TABLE(of, hellcreek_of_match);2105 2106static struct platform_driver hellcreek_driver = {2107 .probe = hellcreek_probe,2108 .remove_new = hellcreek_remove,2109 .shutdown = hellcreek_shutdown,2110 .driver = {2111 .name = "hellcreek",2112 .of_match_table = hellcreek_of_match,2113 },2114};2115module_platform_driver(hellcreek_driver);2116 2117MODULE_AUTHOR("Kurt Kanzenbach <kurt@linutronix.de>");2118MODULE_DESCRIPTION("Hirschmann Hellcreek driver");2119MODULE_LICENSE("Dual MIT/GPL");2120