RFC: ag71xx_ar7240 add new switch func export_netdevs

export_netdevs will export a net device for every port. These netdev represent a port
with out traffic.
When such a device is broght down via ifconfig the port is shutdown and
vice versa. Carrier sense is working too and ethtool can be used to
control advertise, autoneg, ...
This commit is contained in:
Alexander Couzens 2014-12-06 08:33:57 +01:00
parent af54c0148b
commit 21f5cd2f8a
1 changed files with 275 additions and 0 deletions

View File

@ -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
@ -303,6 +314,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 {
@ -835,6 +848,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)
@ -995,6 +1262,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[] = {