@@ -197,6 +197,17 @@
#define AR7240_PHY_ID1 0x004d
#define AR7240_PHY_ID2 0xd041
+#define MII_ATH_REG_FUNCTION_CTRL 0x10
+#define MII_ATH_FUNCTION_CTRL_MDIX BITS(5, 2)
+#define MII_ATH_FUNCTION_CTRL_MDIX_OFFSET 5
+#define MII_ATH_FUNCTION_CTRL_MDIX_MDI 0x0
+#define MII_ATH_FUNCTION_CTRL_MDIX_MDIX 0x1
+#define MII_ATH_FUNCTION_CTRL_MDIX_AUTO 0x3
+#define MII_ATH_REG_PHY_SPECIFIC_STATUS 0x11
+#define MII_ATH_PHY_SPECIFIC_STATUS_MDIX BIT(6)
+#define MII_ATH_PHY_SPECIFIC_STATUS_MDIX_MDI 0x0
+#define MII_ATH_PHY_SPECIFIC_STATUS_MDIX_MDIX 0x1
+
#define AR934X_PHY_ID1 0x004d
#define AR934X_PHY_ID2 0xd042
@@ -304,6 +315,8 @@ struct ar7240sw {
rwlock_t stats_lock;
struct ar7240sw_port_stat port_stats[AR7240_NUM_PORTS];
+ bool export_netdev;
+ struct net_device *port_netdev[AR7240_NUM_PORTS];
};
struct ar7240sw_hw_stat {
@@ -836,6 +849,260 @@ ar7240_set_ports(struct switch_dev *dev, struct switch_val *val)
return 0;
}
+struct priv_netdev_port {
+ uint8_t port;
+ struct net_device *netdev;
+};
+
+int port_init(struct net_device *dev) {
+ return 0;
+}
+
+void port_dummy_cb(struct net_device *dev) {
+}
+
+int port_open(struct net_device *dev) {
+ phy_start(dev->phydev);
+ netif_start_queue(dev);
+ phy_start_aneg(dev->phydev);
+ return 0;
+}
+
+int port_stop(struct net_device *dev) {
+ netif_stop_queue(dev);
+ phy_stop(dev->phydev);
+ return 0;
+}
+
+static int port_start_xmit(struct sk_buff *skb, struct net_device *dev) {
+ dev_kfree_skb(skb);
+ return NETDEV_TX_OK;
+}
+
+static int port_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+ struct mii_bus *bus;
+ struct phy_device *phydev;
+ int reg;
+
+ if (!dev->phydev || !dev->phydev->bus)
+ return -EINVAL;
+
+ phydev = dev->phydev;
+ bus = dev->phydev->bus;
+
+ /* get mdix ctrl */
+ mutex_lock(&bus->mdio_lock);
+ reg = bus->read(bus, phydev->addr, MII_ATH_REG_FUNCTION_CTRL);
+ mutex_unlock(&bus->mdio_lock);
+ reg &= MII_ATH_FUNCTION_CTRL_MDIX;
+ reg = reg >> MII_ATH_FUNCTION_CTRL_MDIX_OFFSET;
+ switch (reg) {
+ case MII_ATH_FUNCTION_CTRL_MDIX_MDI:
+ cmd->eth_tp_mdix_ctrl = ETH_TP_MDI;
+ break;
+ case MII_ATH_FUNCTION_CTRL_MDIX_MDIX:
+ cmd->eth_tp_mdix_ctrl = ETH_TP_MDI_X;
+ break;
+ case MII_ATH_FUNCTION_CTRL_MDIX_AUTO:
+ cmd->eth_tp_mdix_ctrl = ETH_TP_MDI_AUTO;
+ break;
+ default:
+ printk(KERN_ERR "%s:%d Unknown state %x\n", __FILE__, __LINE__, reg);
+ }
+
+ /* get mdix status */
+ mutex_lock(&bus->mdio_lock);
+ reg = bus->read(bus, phydev->addr, MII_ATH_REG_PHY_SPECIFIC_STATUS);
+ mutex_unlock(&bus->mdio_lock);
+
+ cmd->eth_tp_mdix = reg & MII_ATH_PHY_SPECIFIC_STATUS_MDIX ? ETH_TP_MDI_X : ETH_TP_MDI;
+
+ /* phy_ethtool_gset sets everything correct beside the port */
+ phy_ethtool_gset(dev->phydev, cmd);
+ cmd->port = PORT_TP;
+
+ return 0;
+}
+
+static int port_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+ int reg = 0;
+ int ret = 0;
+ struct mii_bus *bus;
+ struct phy_device *phydev;
+
+ if (!dev->phydev || !dev->phydev->bus)
+ return -EINVAL;
+
+ phydev = dev->phydev;
+ bus = dev->phydev->bus;
+
+ /* mdix is vendor specific */
+ if (cmd->eth_tp_mdix_ctrl) {
+ int mdi;
+
+ switch (cmd->eth_tp_mdix_ctrl) {
+ case ETH_TP_MDI:
+ mdi = MII_ATH_FUNCTION_CTRL_MDIX_MDI;
+ break;
+ case ETH_TP_MDI_X:
+ mdi = MII_ATH_FUNCTION_CTRL_MDIX_MDIX;
+ break;
+ case ETH_TP_MDI_AUTO:
+ mdi = MII_ATH_FUNCTION_CTRL_MDIX_AUTO;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mutex_lock(&bus->mdio_lock);
+ reg = bus->read(bus, phydev->addr, MII_ATH_REG_FUNCTION_CTRL);
+ reg &= ~MII_ATH_FUNCTION_CTRL_MDIX;
+ reg |= mdi << MII_ATH_FUNCTION_CTRL_MDIX_OFFSET;
+ bus->write(bus, phydev->addr, MII_ATH_REG_FUNCTION_CTRL, reg);
+ mutex_unlock(&bus->mdio_lock);
+ ret = genphy_soft_reset(phydev);
+ if (ret)
+ return ret;
+ }
+
+ return phy_ethtool_sset(dev->phydev, cmd);
+}
+
+static int port_nway_reset(struct net_device *dev)
+{
+ if (dev->phydev)
+ return phy_start_aneg(dev->phydev);
+
+ return -EINVAL;
+}
+
+int port_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
+{
+ if (!dev->phydev)
+ return -EINVAL;
+
+ return phy_mii_ioctl(dev->phydev, rq, cmd);
+}
+
+static const struct net_device_ops port_netdev_ops = {
+ .ndo_init = port_init,
+ .ndo_uninit = port_dummy_cb,
+ .ndo_open = port_open,
+ .ndo_stop = port_stop,
+ .ndo_start_xmit = port_start_xmit,
+ .ndo_tx_timeout = port_dummy_cb,
+ .ndo_set_mac_address = eth_mac_addr,
+ .ndo_validate_addr = eth_validate_addr,
+ .ndo_do_ioctl = port_ioctl,
+};
+
+static const struct ethtool_ops port_ethtool_ops = {
+ .get_settings = port_get_settings,
+ .set_settings = port_set_settings,
+ .get_link = ethtool_op_get_link,
+ .nway_reset = port_nway_reset,
+};
+
+void port_dev_setup(struct net_device *dev) {
+}
+
+static int
+ar7240_set_export_netdevs(struct switch_dev *dev, const struct switch_attr *attr,
+ struct switch_val *val)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ bool create = !!val->value.i;
+ int i;
+ int err = 0;
+
+ if (as->export_netdev == create)
+ return 0;
+
+ if (create == as->export_netdev)
+ return 0;
+
+ if (create) {
+ for (i = 0; i < AR7240_NUM_PORTS; i++) {
+ struct net_device *netdev;
+ struct phy_device *phy;
+ struct priv_netdev_port *priv;
+
+ phy = as->mii_bus->phy_map[i];
+ if (!phy)
+ continue;
+
+ netdev = alloc_netdev_mqs(sizeof(struct priv_netdev_port), "phy%d", ether_setup, 1, 1);
+ if (!netdev) {
+ printk(KERN_ERR "can not allow netdev\n");
+ /* TODO: free and set export = 0 */
+ return -1;
+ }
+
+ SET_NETDEV_DEV(netdev, &dev->netdev->dev);
+ netdev->netdev_ops = &port_netdev_ops;
+ SET_ETHTOOL_OPS(netdev, &port_ethtool_ops);
+ eth_hw_addr_random(netdev);
+
+ priv = netdev_priv(netdev);
+ priv->netdev = netdev;
+ priv->port = i;
+
+ err = phy_connect_direct(netdev, phy, &port_dummy_cb, PHY_INTERFACE_MODE_MII);
+ if (err) {
+ printk(KERN_ERR "phy attach failed %d with err %d\n", i, err);
+ continue;
+ }
+
+ phy->supported = (PHY_BASIC_FEATURES & ~SUPPORTED_MII);
+ phy->advertising = phy->supported;
+
+
+ err = register_netdev(netdev);
+ if (err) {
+ dev_err(&dev->netdev->dev, "register netdevice failed %d\n", err);
+ /* TODO: exit */
+ return -1;
+ }
+
+ as->port_netdev[i] = netdev;
+
+ /* after creating the port phys we put them up */
+ rtnl_lock();
+ dev_open(netdev);
+ rtnl_unlock();
+ }
+
+ as->export_netdev = true;
+ } else {
+ /* disable ports */
+ struct net_device *netdev;
+ for (i = 0; i < AR7240_NUM_PORTS; i++) {
+ netdev = as->port_netdev[i];
+
+ if (!netdev)
+ continue;
+
+ unregister_netdev(netdev);
+ as->port_netdev[i] = NULL;
+ }
+ as->export_netdev = false;
+ }
+
+ return 0;
+}
+
+ static int
+ar7240_get_export_netdevs(struct switch_dev *dev, const struct switch_attr *attr,
+ struct switch_val *val)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ val->value.i = as->export_netdev;
+
+ return 0;
+}
+
static int
ar7240_set_vlan(struct switch_dev *dev, const struct switch_attr *attr,
struct switch_val *val)
@@ -996,6 +1263,14 @@ static struct switch_attr ar7240_globals[] = {
.get = ar7240_get_vlan,
.max = 1
},
+ {
+ .type = SWITCH_TYPE_INT,
+ .name = "export_netdevs",
+ .description = "Export ports a netdev (no traffic!)",
+ .set = ar7240_set_export_netdevs,
+ .get = ar7240_get_export_netdevs,
+ .max = 1
+ }
};
static struct switch_attr ar7240_port[] = {