Commit 8c3f3362 authored by David S. Miller's avatar David S. Miller

Merge branch 'ax88772-phylib'

Oleksij Rempel says:

====================
port asix ax88772 to the PHYlib

changes v2:
- add Reviewed-by: Andrew Lunn <andrew@lunn.ch> to some patches
- refactor asix_read_phy_addr() and add error handling for all callers
- refactor asix_mdio_bus_read()

Port ax88772 part of asix driver to the phylib to be able to use more
advanced external PHY attached to this controller.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents ef91f798 2c9d6c2b
......@@ -10,6 +10,8 @@
#include <linux/mii.h>
#include <linux/phy.h>
#define PHY_ID_ASIX_AX88772A 0x003b1861
#define PHY_ID_ASIX_AX88772C 0x003b1881
#define PHY_ID_ASIX_AX88796B 0x003b1841
MODULE_DESCRIPTION("Asix PHY driver");
......@@ -39,7 +41,75 @@ static int asix_soft_reset(struct phy_device *phydev)
return genphy_soft_reset(phydev);
}
static struct phy_driver asix_driver[] = { {
/* AX88772A is not working properly with some old switches (NETGEAR EN 108TP):
* after autoneg is done and the link status is reported as active, the MII_LPA
* register is 0. This issue is not reproducible on AX88772C.
*/
static int asix_ax88772a_read_status(struct phy_device *phydev)
{
int ret, val;
ret = genphy_update_link(phydev);
if (ret)
return ret;
if (!phydev->link)
return 0;
/* If MII_LPA is 0, phy_resolve_aneg_linkmode() will fail to resolve
* linkmode so use MII_BMCR as default values.
*/
val = phy_read(phydev, MII_BMCR);
if (val < 0)
return val;
if (val & BMCR_SPEED100)
phydev->speed = SPEED_100;
else
phydev->speed = SPEED_10;
if (val & BMCR_FULLDPLX)
phydev->duplex = DUPLEX_FULL;
else
phydev->duplex = DUPLEX_HALF;
ret = genphy_read_lpa(phydev);
if (ret < 0)
return ret;
if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete)
phy_resolve_aneg_linkmode(phydev);
return 0;
}
static void asix_ax88772a_link_change_notify(struct phy_device *phydev)
{
/* Reset PHY, otherwise MII_LPA will provide outdated information.
* This issue is reproducible only with some link partner PHYs
*/
if (phydev->state == PHY_NOLINK && phydev->drv->soft_reset)
phydev->drv->soft_reset(phydev);
}
static struct phy_driver asix_driver[] = {
{
PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772A),
.name = "Asix Electronics AX88772A",
.flags = PHY_IS_INTERNAL,
.read_status = asix_ax88772a_read_status,
.suspend = genphy_suspend,
.resume = genphy_resume,
.soft_reset = asix_soft_reset,
.link_change_notify = asix_ax88772a_link_change_notify,
}, {
PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772C),
.name = "Asix Electronics AX88772C",
.flags = PHY_IS_INTERNAL,
.suspend = genphy_suspend,
.resume = genphy_resume,
.soft_reset = asix_soft_reset,
}, {
.phy_id = PHY_ID_ASIX_AX88796B,
.name = "Asix Electronics AX88796B",
.phy_id_mask = 0xfffffff0,
......@@ -50,6 +120,8 @@ static struct phy_driver asix_driver[] = { {
module_phy_driver(asix_driver);
static struct mdio_device_id __maybe_unused asix_tbl[] = {
{ PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772A) },
{ PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772C) },
{ PHY_ID_ASIX_AX88796B, 0xfffffff0 },
{ }
};
......
......@@ -1136,6 +1136,9 @@ void phy_state_machine(struct work_struct *work)
else if (do_suspend)
phy_suspend(phydev);
if (err == -ENODEV)
return;
if (err < 0)
phy_error(phydev);
......
......@@ -164,6 +164,8 @@ config USB_NET_AX8817X
depends on USB_USBNET
select CRC32
select PHYLIB
select AX88796B_PHY
imply NET_SELFTESTS
default y
help
This option adds support for ASIX AX88xxx based USB 2.0
......
......@@ -25,6 +25,8 @@
#include <linux/usb/usbnet.h>
#include <linux/slab.h>
#include <linux/if_vlan.h>
#include <linux/phy.h>
#include <net/selftests.h>
#define DRIVER_VERSION "22-Dec-2011"
#define DRIVER_NAME "asix"
......@@ -178,6 +180,10 @@ struct asix_common_private {
u16 presvd_phy_advertise;
u16 presvd_phy_bmcr;
struct asix_rx_fixup_info rx_fixup_info;
struct mii_bus *mdio;
struct phy_device *phydev;
u16 phy_addr;
char phy_name[20];
};
extern const struct driver_info ax88172a_info;
......@@ -205,8 +211,7 @@ struct sk_buff *asix_tx_fixup(struct usbnet *dev, struct sk_buff *skb,
int asix_set_sw_mii(struct usbnet *dev, int in_pm);
int asix_set_hw_mii(struct usbnet *dev, int in_pm);
int asix_read_phy_addr(struct usbnet *dev, int internal);
int asix_get_phy_addr(struct usbnet *dev);
int asix_read_phy_addr(struct usbnet *dev, bool internal);
int asix_sw_reset(struct usbnet *dev, u8 flags, int in_pm);
......@@ -215,6 +220,7 @@ int asix_write_rx_ctl(struct usbnet *dev, u16 mode, int in_pm);
u16 asix_read_medium_status(struct usbnet *dev, int in_pm);
int asix_write_medium_mode(struct usbnet *dev, u16 mode, int in_pm);
void asix_adjust_link(struct net_device *netdev);
int asix_write_gpio(struct usbnet *dev, u16 value, int sleep, int in_pm);
......@@ -223,6 +229,9 @@ void asix_set_multicast(struct net_device *net);
int asix_mdio_read(struct net_device *netdev, int phy_id, int loc);
void asix_mdio_write(struct net_device *netdev, int phy_id, int loc, int val);
int asix_mdio_bus_read(struct mii_bus *bus, int phy_id, int regnum);
int asix_mdio_bus_write(struct mii_bus *bus, int phy_id, int regnum, u16 val);
int asix_mdio_read_nopm(struct net_device *netdev, int phy_id, int loc);
void asix_mdio_write_nopm(struct net_device *netdev, int phy_id, int loc,
int val);
......
......@@ -288,32 +288,33 @@ int asix_set_hw_mii(struct usbnet *dev, int in_pm)
return ret;
}
int asix_read_phy_addr(struct usbnet *dev, int internal)
int asix_read_phy_addr(struct usbnet *dev, bool internal)
{
int offset = (internal ? 1 : 0);
int ret, offset;
u8 buf[2];
int ret = asix_read_cmd(dev, AX_CMD_READ_PHY_ID, 0, 0, 2, buf, 0);
netdev_dbg(dev->net, "asix_get_phy_addr()\n");
ret = asix_read_cmd(dev, AX_CMD_READ_PHY_ID, 0, 0, 2, buf, 0);
if (ret < 0)
goto error;
if (ret < 2) {
netdev_err(dev->net, "Error reading PHYID register: %02x\n", ret);
goto out;
ret = -EIO;
goto error;
}
netdev_dbg(dev->net, "asix_get_phy_addr() returning 0x%04x\n",
*((__le16 *)buf));
offset = (internal ? 1 : 0);
ret = buf[offset];
out:
netdev_dbg(dev->net, "%s PHY address 0x%x\n",
internal ? "internal" : "external", ret);
return ret;
}
int asix_get_phy_addr(struct usbnet *dev)
{
/* return the address of the internal phy */
return asix_read_phy_addr(dev, 1);
}
error:
netdev_err(dev->net, "Error reading PHY_ID register: %02x\n", ret);
return ret;
}
int asix_sw_reset(struct usbnet *dev, u8 flags, int in_pm)
{
......@@ -383,6 +384,27 @@ int asix_write_medium_mode(struct usbnet *dev, u16 mode, int in_pm)
return ret;
}
/* set MAC link settings according to information from phylib */
void asix_adjust_link(struct net_device *netdev)
{
struct phy_device *phydev = netdev->phydev;
struct usbnet *dev = netdev_priv(netdev);
u16 mode = 0;
if (phydev->link) {
mode = AX88772_MEDIUM_DEFAULT;
if (phydev->duplex == DUPLEX_HALF)
mode &= ~AX_MEDIUM_FD;
if (phydev->speed != SPEED_100)
mode &= ~AX_MEDIUM_PS;
}
asix_write_medium_mode(dev, mode, 0);
phy_print_status(phydev);
}
int asix_write_gpio(struct usbnet *dev, u16 value, int sleep, int in_pm)
{
int ret;
......@@ -463,18 +485,23 @@ int asix_mdio_read(struct net_device *netdev, int phy_id, int loc)
return ret;
}
asix_read_cmd(dev, AX_CMD_READ_MII_REG, phy_id,
(__u16)loc, 2, &res, 0);
asix_set_hw_mii(dev, 0);
ret = asix_read_cmd(dev, AX_CMD_READ_MII_REG, phy_id, (__u16)loc, 2,
&res, 0);
if (ret < 0)
goto out;
ret = asix_set_hw_mii(dev, 0);
out:
mutex_unlock(&dev->phy_mutex);
netdev_dbg(dev->net, "asix_mdio_read() phy_id=0x%02x, loc=0x%02x, returns=0x%04x\n",
phy_id, loc, le16_to_cpu(res));
return le16_to_cpu(res);
return ret < 0 ? ret : le16_to_cpu(res);
}
void asix_mdio_write(struct net_device *netdev, int phy_id, int loc, int val)
static int __asix_mdio_write(struct net_device *netdev, int phy_id, int loc,
int val)
{
struct usbnet *dev = netdev_priv(netdev);
__le16 res = cpu_to_le16(val);
......@@ -494,15 +521,40 @@ void asix_mdio_write(struct net_device *netdev, int phy_id, int loc, int val)
ret = asix_read_cmd(dev, AX_CMD_STATMNGSTS_REG,
0, 0, 1, &smsr, 0);
} while (!(smsr & AX_HOST_EN) && (i++ < 30) && (ret != -ENODEV));
if (ret == -ENODEV) {
mutex_unlock(&dev->phy_mutex);
return;
}
asix_write_cmd(dev, AX_CMD_WRITE_MII_REG, phy_id,
(__u16)loc, 2, &res, 0);
asix_set_hw_mii(dev, 0);
if (ret == -ENODEV)
goto out;
ret = asix_write_cmd(dev, AX_CMD_WRITE_MII_REG, phy_id, (__u16)loc, 2,
&res, 0);
if (ret < 0)
goto out;
ret = asix_set_hw_mii(dev, 0);
out:
mutex_unlock(&dev->phy_mutex);
return ret < 0 ? ret : 0;
}
void asix_mdio_write(struct net_device *netdev, int phy_id, int loc, int val)
{
__asix_mdio_write(netdev, phy_id, loc, val);
}
/* MDIO read and write wrappers for phylib */
int asix_mdio_bus_read(struct mii_bus *bus, int phy_id, int regnum)
{
struct usbnet *priv = bus->priv;
return asix_mdio_read(priv->net, phy_id, regnum);
}
int asix_mdio_bus_write(struct mii_bus *bus, int phy_id, int regnum, u16 val)
{
struct usbnet *priv = bus->priv;
return __asix_mdio_write(priv->net, phy_id, regnum, val);
}
int asix_mdio_read_nopm(struct net_device *netdev, int phy_id, int loc)
......
......@@ -262,7 +262,10 @@ static int ax88172_bind(struct usbnet *dev, struct usb_interface *intf)
dev->mii.mdio_write = asix_mdio_write;
dev->mii.phy_id_mask = 0x3f;
dev->mii.reg_num_mask = 0x1f;
dev->mii.phy_id = asix_get_phy_addr(dev);
dev->mii.phy_id = asix_read_phy_addr(dev, true);
if (dev->mii.phy_id < 0)
return dev->mii.phy_id;
dev->net->netdev_ops = &ax88172_netdev_ops;
dev->net->ethtool_ops = &ax88172_ethtool_ops;
......@@ -280,9 +283,29 @@ static int ax88172_bind(struct usbnet *dev, struct usb_interface *intf)
return ret;
}
static void ax88772_ethtool_get_strings(struct net_device *netdev, u32 sset,
u8 *data)
{
switch (sset) {
case ETH_SS_TEST:
net_selftest_get_strings(data);
break;
}
}
static int ax88772_ethtool_get_sset_count(struct net_device *ndev, int sset)
{
switch (sset) {
case ETH_SS_TEST:
return net_selftest_get_count();
default:
return -EOPNOTSUPP;
}
}
static const struct ethtool_ops ax88772_ethtool_ops = {
.get_drvinfo = asix_get_drvinfo,
.get_link = asix_get_link,
.get_link = usbnet_get_link,
.get_msglevel = usbnet_get_msglevel,
.set_msglevel = usbnet_set_msglevel,
.get_wol = asix_get_wol,
......@@ -290,37 +313,18 @@ static const struct ethtool_ops ax88772_ethtool_ops = {
.get_eeprom_len = asix_get_eeprom_len,
.get_eeprom = asix_get_eeprom,
.set_eeprom = asix_set_eeprom,
.nway_reset = usbnet_nway_reset,
.get_link_ksettings = usbnet_get_link_ksettings_mii,
.set_link_ksettings = usbnet_set_link_ksettings_mii,
.nway_reset = phy_ethtool_nway_reset,
.get_link_ksettings = phy_ethtool_get_link_ksettings,
.set_link_ksettings = phy_ethtool_set_link_ksettings,
.self_test = net_selftest,
.get_strings = ax88772_ethtool_get_strings,
.get_sset_count = ax88772_ethtool_get_sset_count,
};
static int ax88772_link_reset(struct usbnet *dev)
{
u16 mode;
struct ethtool_cmd ecmd = { .cmd = ETHTOOL_GSET };
mii_check_media(&dev->mii, 1, 1);
mii_ethtool_gset(&dev->mii, &ecmd);
mode = AX88772_MEDIUM_DEFAULT;
if (ethtool_cmd_speed(&ecmd) != SPEED_100)
mode &= ~AX_MEDIUM_PS;
if (ecmd.duplex != DUPLEX_FULL)
mode &= ~AX_MEDIUM_FD;
netdev_dbg(dev->net, "ax88772_link_reset() speed: %u duplex: %d setting mode to 0x%04x\n",
ethtool_cmd_speed(&ecmd), ecmd.duplex, mode);
asix_write_medium_mode(dev, mode, 0);
return 0;
}
static int ax88772_reset(struct usbnet *dev)
{
struct asix_data *data = (struct asix_data *)&dev->data;
struct asix_common_private *priv = dev->driver_priv;
int ret;
/* Rewrite MAC address */
......@@ -339,6 +343,8 @@ static int ax88772_reset(struct usbnet *dev)
if (ret < 0)
goto out;
phy_start(priv->phydev);
return 0;
out:
......@@ -583,7 +589,7 @@ static const struct net_device_ops ax88772_netdev_ops = {
.ndo_get_stats64 = dev_get_tstats64,
.ndo_set_mac_address = asix_set_mac_address,
.ndo_validate_addr = eth_validate_addr,
.ndo_do_ioctl = asix_ioctl,
.ndo_do_ioctl = phy_do_ioctl_running,
.ndo_set_rx_mode = asix_set_multicast,
};
......@@ -674,12 +680,57 @@ static int asix_resume(struct usb_interface *intf)
return usbnet_resume(intf);
}
static int ax88772_init_mdio(struct usbnet *dev)
{
struct asix_common_private *priv = dev->driver_priv;
priv->mdio = devm_mdiobus_alloc(&dev->udev->dev);
if (!priv->mdio)
return -ENOMEM;
priv->mdio->priv = dev;
priv->mdio->read = &asix_mdio_bus_read;
priv->mdio->write = &asix_mdio_bus_write;
priv->mdio->name = "Asix MDIO Bus";
/* mii bus name is usb-<usb bus number>-<usb device number> */
snprintf(priv->mdio->id, MII_BUS_ID_SIZE, "usb-%03d:%03d",
dev->udev->bus->busnum, dev->udev->devnum);
return devm_mdiobus_register(&dev->udev->dev, priv->mdio);
}
static int ax88772_init_phy(struct usbnet *dev)
{
struct asix_common_private *priv = dev->driver_priv;
int ret;
priv->phy_addr = asix_read_phy_addr(dev, true);
if (priv->phy_addr < 0)
return priv->phy_addr;
snprintf(priv->phy_name, sizeof(priv->phy_name), PHY_ID_FMT,
priv->mdio->id, priv->phy_addr);
priv->phydev = phy_connect(dev->net, priv->phy_name, &asix_adjust_link,
PHY_INTERFACE_MODE_INTERNAL);
if (IS_ERR(priv->phydev)) {
netdev_err(dev->net, "Could not connect to PHY device %s\n",
priv->phy_name);
ret = PTR_ERR(priv->phydev);
return ret;
}
phy_attached_info(priv->phydev);
return 0;
}
static int ax88772_bind(struct usbnet *dev, struct usb_interface *intf)
{
int ret, i;
u8 buf[ETH_ALEN] = {0}, chipcode = 0;
u32 phyid;
struct asix_common_private *priv;
int ret, i;
u32 phyid;
usbnet_get_endpoints(dev, intf);
......@@ -711,14 +762,6 @@ static int ax88772_bind(struct usbnet *dev, struct usb_interface *intf)
asix_set_netdev_dev_addr(dev, buf);
/* Initialize MII structure */
dev->mii.dev = dev->net;
dev->mii.mdio_read = asix_mdio_read;
dev->mii.mdio_write = asix_mdio_write;
dev->mii.phy_id_mask = 0x1f;
dev->mii.reg_num_mask = 0x1f;
dev->mii.phy_id = asix_get_phy_addr(dev);
dev->net->netdev_ops = &ax88772_netdev_ops;
dev->net->ethtool_ops = &ax88772_ethtool_ops;
dev->net->needed_headroom = 4; /* cf asix_tx_fixup() */
......@@ -746,11 +789,11 @@ static int ax88772_bind(struct usbnet *dev, struct usb_interface *intf)
dev->rx_urb_size = 2048;
}
dev->driver_priv = kzalloc(sizeof(struct asix_common_private), GFP_KERNEL);
if (!dev->driver_priv)
priv = devm_kzalloc(&dev->udev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv = dev->driver_priv;
dev->driver_priv = priv;
priv->presvd_phy_bmcr = 0;
priv->presvd_phy_advertise = 0;
......@@ -762,13 +805,32 @@ static int ax88772_bind(struct usbnet *dev, struct usb_interface *intf)
priv->suspend = ax88772_suspend;
}
ret = ax88772_init_mdio(dev);
if (ret)
return ret;
return ax88772_init_phy(dev);
}
static int ax88772_stop(struct usbnet *dev)
{
struct asix_common_private *priv = dev->driver_priv;
/* On unplugged USB, we will get MDIO communication errors and the
* PHY will be set in to PHY_HALTED state.
*/
if (priv->phydev->state != PHY_HALTED)
phy_stop(priv->phydev);
return 0;
}
static void ax88772_unbind(struct usbnet *dev, struct usb_interface *intf)
{
struct asix_common_private *priv = dev->driver_priv;
phy_disconnect(priv->phydev);
asix_rx_fixup_common_free(dev->driver_priv);
kfree(dev->driver_priv);
}
static const struct ethtool_ops ax88178_ethtool_ops = {
......@@ -1081,7 +1143,10 @@ static int ax88178_bind(struct usbnet *dev, struct usb_interface *intf)
dev->mii.phy_id_mask = 0x1f;
dev->mii.reg_num_mask = 0xff;
dev->mii.supports_gmii = 1;
dev->mii.phy_id = asix_get_phy_addr(dev);
dev->mii.phy_id = asix_read_phy_addr(dev, true);
if (dev->mii.phy_id < 0)
return dev->mii.phy_id;
dev->net->netdev_ops = &ax88178_netdev_ops;
dev->net->ethtool_ops = &ax88178_ethtool_ops;
......@@ -1153,8 +1218,8 @@ static const struct driver_info ax88772_info = {
.bind = ax88772_bind,
.unbind = ax88772_unbind,
.status = asix_status,
.link_reset = ax88772_link_reset,
.reset = ax88772_reset,
.stop = ax88772_stop,
.flags = FLAG_ETHER | FLAG_FRAMING_AX | FLAG_LINK_INTR | FLAG_MULTI_PACKET,
.rx_fixup = asix_rx_fixup_common,
.tx_fixup = asix_tx_fixup,
......@@ -1165,7 +1230,6 @@ static const struct driver_info ax88772b_info = {
.bind = ax88772_bind,
.unbind = ax88772_unbind,
.status = asix_status,
.link_reset = ax88772_link_reset,
.reset = ax88772_reset,
.flags = FLAG_ETHER | FLAG_FRAMING_AX | FLAG_LINK_INTR |
FLAG_MULTI_PACKET,
......@@ -1201,7 +1265,6 @@ static const struct driver_info hg20f9_info = {
.bind = ax88772_bind,
.unbind = ax88772_unbind,
.status = asix_status,
.link_reset = ax88772_link_reset,
.reset = ax88772_reset,
.flags = FLAG_ETHER | FLAG_FRAMING_AX | FLAG_LINK_INTR |
FLAG_MULTI_PACKET,
......
......@@ -25,20 +25,6 @@ struct ax88172a_private {
struct asix_rx_fixup_info rx_fixup_info;
};
/* MDIO read and write wrappers for phylib */
static int asix_mdio_bus_read(struct mii_bus *bus, int phy_id, int regnum)
{
return asix_mdio_read(((struct usbnet *)bus->priv)->net, phy_id,
regnum);
}
static int asix_mdio_bus_write(struct mii_bus *bus, int phy_id, int regnum,
u16 val)
{
asix_mdio_write(((struct usbnet *)bus->priv)->net, phy_id, regnum, val);
return 0;
}
/* set MAC link settings according to information from phylib */
static void ax88172a_adjust_link(struct net_device *netdev)
{
......@@ -220,6 +206,11 @@ static int ax88172a_bind(struct usbnet *dev, struct usb_interface *intf)
}
priv->phy_addr = asix_read_phy_addr(dev, priv->use_embdphy);
if (priv->phy_addr < 0) {
ret = priv->phy_addr;
goto free;
}
ax88172a_reset_phy(dev, priv->use_embdphy);
/* Asix framing packs multiple eth frames into a 2K usb bulk transfer */
......
......@@ -1597,6 +1597,9 @@ void usbnet_disconnect (struct usb_interface *intf)
xdev->bus->bus_name, xdev->devpath,
dev->driver_info->description);
if (dev->driver_info->unbind)
dev->driver_info->unbind(dev, intf);
net = dev->net;
unregister_netdev (net);
......@@ -1604,9 +1607,6 @@ void usbnet_disconnect (struct usb_interface *intf)
usb_scuttle_anchored_urbs(&dev->deferred);
if (dev->driver_info->unbind)
dev->driver_info->unbind (dev, intf);
usb_kill_urb(dev->interrupt);
usb_free_urb(dev->interrupt);
kfree(dev->padding_pkt);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment