+ dev_dbg (&priv->phydev->dev, "get_ports port_vlan %d\n",
+ val->port_vlan);
+
+ val->len = 0;
+
+ for (i = 0; i < ADM_NUM_PORTS; i++) {
+ struct switch_port *p;
+
+ if (!(ports & (1 << i)))
+ continue;
+
+ p = &val->value.ports[val->len++];
+ p->id = i;
+ if (tagged & (1 << i))
+ p->flags = (1 << SWITCH_PORT_FLAG_TAGGED);
+ else
+ p->flags = 0;
+ }
+
+ return 0;
+};
+
+static int
+adm6996_set_ports(struct switch_dev *dev, struct switch_val *val)
+{
+ struct adm6996_priv *priv = to_adm(dev);
+ u8 *ports = &priv->vlan_table[val->port_vlan];
+ u8 *tagged = &priv->vlan_tagged[val->port_vlan];
+ int i;
+
+ dev_dbg (&priv->phydev->dev, "set_ports port_vlan %d ports",
+ val->port_vlan);
+
+ *ports = 0;
+ *tagged = 0;
+
+ for (i = 0; i < val->len; i++) {
+ struct switch_port *p = &val->value.ports[i];
+
+#ifdef DEBUG
+ pr_cont(" %d%s", p->id,
+ ((p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) ? "T" :
+ ""));
+#endif
+
+ if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED))
+ *tagged |= (1 << p->id);
+
+ *ports |= (1 << p->id);
+ }
+
+#ifdef DEBUG
+ pr_cont("\n");
+#endif
+
+ return 0;
+};
+
+/*
+ * Precondition: reg_mutex must be held
+ */
+static void
+adm6996_enable_vlan(struct adm6996_priv *priv)
+{
+ u16 reg;
+
+ reg = r16(priv->phydev, ADM_OTBE_P2_PVID);
+ reg &= ~(ADM_OTBE_MASK);
+ w16(priv->phydev, ADM_OTBE_P2_PVID, reg);
+ reg = r16(priv->phydev, ADM_IFNTE);
+ reg &= ~(ADM_IFNTE_MASK);
+ w16(priv->phydev, ADM_IFNTE, reg);
+ reg = r16(priv->phydev, ADM_VID_CHECK);
+ reg |= ADM_VID_CHECK_MASK;
+ w16(priv->phydev, ADM_VID_CHECK, reg);
+ reg = r16(priv->phydev, ADM_SYSC0);
+ reg |= ADM_NTTE;
+ reg &= ~(ADM_RVID1);
+ w16(priv->phydev, ADM_SYSC0, reg);
+ reg = r16(priv->phydev, ADM_SYSC3);
+ reg |= ADM_TBV;
+ w16(priv->phydev, ADM_SYSC3, reg);
+
+};
+
+/*
+ * Disable VLANs
+ *
+ * Sets VLAN mapping for port-based VLAN with all ports connected to
+ * eachother (this is also the power-on default).
+ *
+ * Precondition: reg_mutex must be held
+ */
+static void
+adm6996_disable_vlan(struct adm6996_priv *priv)
+{
+ u16 reg;
+ int i;
+
+ for (i = 0; i < ADM_NUM_PORTS; i++) {
+ reg = ADM_VLAN_FILT_MEMBER_MASK;
+ w16(priv->phydev, ADM_VLAN_FILT_L(i), reg);
+ reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(1);
+ w16(priv->phydev, ADM_VLAN_FILT_H(i), reg);
+ }
+
+ reg = r16(priv->phydev, ADM_OTBE_P2_PVID);
+ reg |= ADM_OTBE_MASK;
+ w16(priv->phydev, ADM_OTBE_P2_PVID, reg);
+ reg = r16(priv->phydev, ADM_IFNTE);
+ reg |= ADM_IFNTE_MASK;
+ w16(priv->phydev, ADM_IFNTE, reg);
+ reg = r16(priv->phydev, ADM_VID_CHECK);
+ reg &= ~(ADM_VID_CHECK_MASK);
+ w16(priv->phydev, ADM_VID_CHECK, reg);
+ reg = r16(priv->phydev, ADM_SYSC0);
+ reg &= ~(ADM_NTTE);
+ reg |= ADM_RVID1;
+ w16(priv->phydev, ADM_SYSC0, reg);
+ reg = r16(priv->phydev, ADM_SYSC3);
+ reg &= ~(ADM_TBV);
+ w16(priv->phydev, ADM_SYSC3, reg);
+}
+
+/*
+ * Precondition: reg_mutex must be held
+ */
+static void
+adm6996_apply_port_pvids(struct adm6996_priv *priv)
+{
+ u16 reg;
+ int i;
+
+ for (i = 0; i < ADM_NUM_PORTS; i++) {
+ reg = r16(priv->phydev, adm_portcfg[i]);
+ reg &= ~(ADM_PORTCFG_PVID_MASK);
+ reg |= ADM_PORTCFG_PVID(priv->pvid[i]);
+ w16(priv->phydev, adm_portcfg[i], reg);
+ }
+
+ w16(priv->phydev, ADM_P0_PVID, ADM_P0_PVID_VAL(priv->pvid[0]));
+ w16(priv->phydev, ADM_P1_PVID, ADM_P1_PVID_VAL(priv->pvid[1]));
+ reg = r16(priv->phydev, ADM_OTBE_P2_PVID);
+ reg &= ~(ADM_P2_PVID_MASK);
+ reg |= ADM_P2_PVID_VAL(priv->pvid[2]);
+ w16(priv->phydev, ADM_OTBE_P2_PVID, reg);
+ reg = ADM_P3_PVID_VAL(priv->pvid[3]);
+ reg |= ADM_P4_PVID_VAL(priv->pvid[4]);
+ w16(priv->phydev, ADM_P3_P4_PVID, reg);
+ w16(priv->phydev, ADM_P5_PVID, ADM_P5_PVID_VAL(priv->pvid[5]));
+}
+
+/*
+ * Precondition: reg_mutex must be held
+ */
+static void
+adm6996_apply_vlan_filters(struct adm6996_priv *priv)
+{
+ u8 ports, tagged;
+ u16 vid, reg;
+ int i;
+
+ for (i = 0; i < ADM_NUM_VLANS; i++) {
+ vid = priv->vlan_id[i];
+ ports = priv->vlan_table[i];
+ tagged = priv->vlan_tagged[i];
+
+ if (ports == 0) {
+ /* Disable VLAN entry */
+ w16(priv->phydev, ADM_VLAN_FILT_H(i), 0);
+ w16(priv->phydev, ADM_VLAN_FILT_L(i), 0);
+ continue;
+ }
+
+ reg = ADM_VLAN_FILT_MEMBER(ports);
+ reg |= ADM_VLAN_FILT_TAGGED(tagged);
+ w16(priv->phydev, ADM_VLAN_FILT_L(i), reg);
+ reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(vid);
+ w16(priv->phydev, ADM_VLAN_FILT_H(i), reg);
+ }
+}
+
+static int
+adm6996_hw_apply(struct switch_dev *dev)
+{
+ struct adm6996_priv *priv = to_adm(dev);
+
+ dev_dbg(&priv->phydev->dev, "hw_apply\n");
+
+ mutex_lock(&priv->reg_mutex);
+
+ if (!priv->enable_vlan) {
+ if (priv->vlan_enabled) {
+ adm6996_disable_vlan(priv);
+ priv->vlan_enabled = 0;
+ }
+ goto out;
+ }
+
+ if (!priv->vlan_enabled) {
+ adm6996_enable_vlan(priv);
+ priv->vlan_enabled = 1;
+ }
+
+ adm6996_apply_port_pvids(priv);
+ adm6996_apply_vlan_filters(priv);
+
+out:
+ mutex_unlock(&priv->reg_mutex);
+
+ return 0;
+}
+
+/*
+ * Reset the switch
+ *
+ * The ADM6996 can't do a software-initiated reset, so we just initialise the
+ * registers we support in this driver.
+ *
+ * Precondition: reg_mutex must be held
+ */
+static void
+adm6996_perform_reset (struct adm6996_priv *priv)
+{
+ int i;