#include <linux/switch.h>
#include <linux/delay.h>
#include <linux/phy.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
#include "ar8216.h"
-#define AR8216_REG_PORT_RATE(_i) (AR8216_PORT_OFFSET(_i) + 0x000c)
-#define AR8216_REG_PORT_PRIO(_i) (AR8216_PORT_OFFSET(_i) + 0x0010)
struct ar8216_priv {
+ int (*hardstart)(struct sk_buff *skb, struct net_device *dev);
+
struct switch_dev dev;
struct phy_device *phy;
u32 (*read)(struct ar8216_priv *priv, int reg);
void (*write)(struct ar8216_priv *priv, int reg, u32 val);
+
/* all fields below are cleared on reset */
bool vlan;
u8 vlan_id[AR8216_NUM_VLANS];
split_addr((u32) reg, &r1, &r2, &page);
phy->bus->write(phy->bus, 0x18, 0, page);
+ msleep(1); /* wait for the page switch to propagate */
lo = phy->bus->read(phy->bus, 0x10 | r2, r1);
hi = phy->bus->read(phy->bus, 0x10 | r2, r1 + 1);
split_addr((u32) reg, &r1, &r2, &r3);
phy->bus->write(phy->bus, 0x18, 0, r3);
+ msleep(1); /* wait for the page switch to propagate */
lo = val & 0xffff;
hi = (u16) (val >> 16);
}
+static int
+ar8216_mangle_tx(struct sk_buff *skb, struct net_device *dev)
+{
+ struct ar8216_priv *priv = dev->phy_ptr;
+ unsigned char *buf;
+
+ if (unlikely(!priv))
+ goto error;
+
+ if (!priv->vlan)
+ goto send;
+
+ if (unlikely(skb_headroom(skb) < 2)) {
+ if (pskb_expand_head(skb, 2, 0, GFP_ATOMIC) < 0)
+ goto error;
+ }
+
+ buf = skb_push(skb, 2);
+ buf[0] = 0x10;
+ buf[1] = 0x80;
+
+send:
+ return priv->hardstart(skb, dev);
+
+error:
+ dev_kfree_skb_any(skb);
+ return 0;
+}
+
+static int
+ar8216_mangle_rx(struct sk_buff *skb, int napi)
+{
+ struct ar8216_priv *priv;
+ struct net_device *dev;
+ unsigned char *buf;
+ int port, vlan;
+
+ dev = skb->dev;
+ if (!dev)
+ goto error;
+
+ priv = dev->phy_ptr;
+ if (!priv)
+ goto error;
+
+ /* don't strip the header if vlan mode is disabled */
+ if (!priv->vlan)
+ goto recv;
+
+ /* strip header, get vlan id */
+ buf = skb->data;
+ skb_pull(skb, 2);
+
+ /* check for vlan header presence */
+ if ((buf[12 + 2] != 0x81) || (buf[13 + 2] != 0x00))
+ goto recv;
+
+ port = buf[0] & 0xf;
+
+ /* no need to fix up packets coming from a tagged source */
+ if (priv->vlan_tagged & (1 << port))
+ goto recv;
+
+ /* lookup port vid from local table, the switch passes an invalid vlan id */
+ vlan = priv->pvid[port];
+
+ buf[14 + 2] &= 0xf0;
+ buf[14 + 2] |= vlan >> 8;
+ buf[15 + 2] = vlan & 0xff;
+
+recv:
+ skb->protocol = eth_type_trans(skb, skb->dev);
+
+ if (napi)
+ return netif_receive_skb(skb);
+ else
+ return netif_rx(skb);
+
+error:
+ /* no vlan? eat the packet! */
+ dev_kfree_skb_any(skb);
+ return 0;
+}
+
+static int
+ar8216_netif_rx(struct sk_buff *skb)
+{
+ return ar8216_mangle_rx(skb, 0);
+}
+
+static int
+ar8216_netif_receive_skb(struct sk_buff *skb)
+{
+ return ar8216_mangle_rx(skb, 1);
+}
+
+
static struct switch_attr ar8216_globals[] = {
{
.type = SWITCH_TYPE_INT,
if (priv->vlan && (priv->vlan_tagged & (1 << i))) {
egress = AR8216_OUT_ADD_VLAN;
- ingress = AR8216_IN_PORT_FALLBACK;
} else {
egress = AR8216_OUT_STRIP_VLAN;
- ingress = AR8216_IN_SECURE;
}
+ ingress = AR8216_IN_SECURE;
ar8216_rmw(priv, AR8216_REG_PORT_CTRL(i),
AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE |
AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE |
AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK,
AR8216_PORT_CTRL_LEARN |
+ (i == AR8216_PORT_CPU ? AR8216_PORT_CTRL_HEADER : 0) |
(egress << AR8216_PORT_CTRL_VLAN_MODE_S) |
- (priv->vlan ? AR8216_PORT_CTRL_SINGLE_VLAN : 0) |
(AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S));
ar8216_rmw(priv, AR8216_REG_PORT_VLAN(i),
ar8216_config_init(struct phy_device *pdev)
{
struct ar8216_priv *priv;
+ struct net_device *dev = pdev->attached_dev;
int ret;
printk("%s: AR8216 PHY driver attached.\n", pdev->attached_dev->name);
}
ret = ar8216_reset_switch(&priv->dev);
+ if (ret)
+ goto done;
+
+ dev->phy_ptr = priv;
+ pdev->pkt_align = 2;
+ priv->hardstart = dev->hard_start_xmit;
+ pdev->netif_receive_skb = ar8216_netif_receive_skb;
+ pdev->netif_rx = ar8216_netif_rx;
+ dev->hard_start_xmit = ar8216_mangle_tx;
+
done:
return ret;
}
ar8216_remove(struct phy_device *pdev)
{
struct ar8216_priv *priv = pdev->priv;
+ struct net_device *dev = pdev->attached_dev;
if (!priv)
return;
+ if (priv->hardstart && dev)
+ dev->hard_start_xmit = priv->hardstart;
unregister_switch(&priv->dev);
kfree(priv);
}