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