Skip to content

Commit

Permalink
mdio: Add support for mdio drivers.
Browse files Browse the repository at this point in the history
Not all devices on an MDIO bus are PHYs. Meaning not all MDIO drivers
are PHY drivers. Add support for generic MDIO drivers.

Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Andrew Lunn authored and David S. Miller committed Jan 7, 2016
1 parent f89df3f commit a9049e0
Show file tree
Hide file tree
Showing 7 changed files with 286 additions and 17 deletions.
2 changes: 1 addition & 1 deletion drivers/net/phy/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Makefile for Linux PHY drivers

libphy-objs := phy.o phy_device.o mdio_bus.o
libphy-objs := phy.o phy_device.o mdio_bus.o mdio_device.o

obj-$(CONFIG_PHYLIB) += libphy.o
obj-$(CONFIG_AQUANTIA_PHY) += aquantia.o
Expand Down
13 changes: 11 additions & 2 deletions drivers/net/phy/mdio_bus.c
Original file line number Diff line number Diff line change
Expand Up @@ -357,16 +357,25 @@ EXPORT_SYMBOL(__mdiobus_register);

void mdiobus_unregister(struct mii_bus *bus)
{
struct mdio_device *mdiodev;
struct phy_device *phydev;
int i;

BUG_ON(bus->state != MDIOBUS_REGISTERED);
bus->state = MDIOBUS_UNREGISTERED;

for (i = 0; i < PHY_MAX_ADDR; i++) {
struct phy_device *phydev = mdiobus_get_phy(bus, i);
if (phydev) {
mdiodev = bus->mdio_map[i];
if (!mdiodev)
continue;

if (!(mdiodev->flags & MDIO_DEVICE_FLAG_PHY)) {
phydev = container_of(mdiodev, struct phy_device, mdio);
phy_device_remove(phydev);
phy_device_free(phydev);
} else {
mdio_device_remove(mdiodev);
mdio_device_free(mdiodev);
}
}
device_del(&bus->dev);
Expand Down
169 changes: 169 additions & 0 deletions drivers/net/phy/mdio_device.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/* Framework for MDIO devices, other than PHYs.
*
* Copyright (c) 2016 Andrew Lunn <andrew@lunn.ch>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*/

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/errno.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mdio.h>
#include <linux/mii.h>
#include <linux/module.h>
#include <linux/phy.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/unistd.h>

void mdio_device_free(struct mdio_device *mdiodev)
{
put_device(&mdiodev->dev);
}
EXPORT_SYMBOL(mdio_device_free);

static void mdio_device_release(struct device *dev)
{
kfree(to_mdio_device(dev));
}

struct mdio_device *mdio_device_create(struct mii_bus *bus, int addr)
{
struct mdio_device *mdiodev;

/* We allocate the device, and initialize the default values */
mdiodev = kzalloc(sizeof(*mdiodev), GFP_KERNEL);
if (!mdiodev)
return ERR_PTR(-ENOMEM);

mdiodev->dev.release = mdio_device_release;
mdiodev->dev.parent = &bus->dev;
mdiodev->dev.bus = &mdio_bus_type;
mdiodev->bus = bus;
mdiodev->addr = addr;

dev_set_name(&mdiodev->dev, PHY_ID_FMT, bus->id, addr);

device_initialize(&mdiodev->dev);

return mdiodev;
}
EXPORT_SYMBOL(mdio_device_create);

/**
* mdio_device_register - Register the mdio device on the MDIO bus
* @mdiodev: mdio_device structure to be added to the MDIO bus
*/
int mdio_device_register(struct mdio_device *mdiodev)
{
int err;

dev_info(&mdiodev->dev, "mdio_device_register\n");

err = mdiobus_register_device(mdiodev);
if (err)
return err;

err = device_add(&mdiodev->dev);
if (err) {
pr_err("MDIO %d failed to add\n", mdiodev->addr);
goto out;
}

return 0;

out:
mdiobus_unregister_device(mdiodev);
return err;
}
EXPORT_SYMBOL(mdio_device_register);

/**
* mdio_device_remove - Remove a previously registered mdio device from the
* MDIO bus
* @mdiodev: mdio_device structure to remove
*
* This doesn't free the mdio_device itself, it merely reverses the effects
* of mdio_device_register(). Use mdio_device_free() to free the device
* after calling this function.
*/
void mdio_device_remove(struct mdio_device *mdiodev)
{
device_del(&mdiodev->dev);
mdiobus_unregister_device(mdiodev);
}
EXPORT_SYMBOL(mdio_device_remove);

/**
* mdio_probe - probe an MDIO device
* @dev: device to probe
*
* Description: Take care of setting up the mdio_device structure
* and calling the driver to probe the device.
*/
static int mdio_probe(struct device *dev)
{
struct mdio_device *mdiodev = to_mdio_device(dev);
struct device_driver *drv = mdiodev->dev.driver;
struct mdio_driver *mdiodrv = to_mdio_driver(drv);
int err = 0;

if (mdiodrv->probe)
err = mdiodrv->probe(mdiodev);

return err;
}

static int mdio_remove(struct device *dev)
{
struct mdio_device *mdiodev = to_mdio_device(dev);
struct device_driver *drv = mdiodev->dev.driver;
struct mdio_driver *mdiodrv = to_mdio_driver(drv);

if (mdiodrv->remove)
mdiodrv->remove(mdiodev);

return 0;
}

/**
* mdio_driver_register - register an mdio_driver with the MDIO layer
* @new_driver: new mdio_driver to register
*/
int mdio_driver_register(struct mdio_driver *drv)
{
struct mdio_driver_common *mdiodrv = &drv->mdiodrv;
int retval;

pr_info("mdio_driver_register: %s\n", mdiodrv->driver.name);

mdiodrv->driver.bus = &mdio_bus_type;
mdiodrv->driver.probe = mdio_probe;
mdiodrv->driver.remove = mdio_remove;

retval = driver_register(&mdiodrv->driver);
if (retval) {
pr_err("%s: Error %d in registering driver\n",
mdiodrv->driver.name, retval);

return retval;
}

return 0;
}
EXPORT_SYMBOL(mdio_driver_register);

void mdio_driver_unregister(struct mdio_driver *drv)
{
struct mdio_driver_common *mdiodrv = &drv->mdiodrv;

driver_unregister(&mdiodrv->driver);
}
EXPORT_SYMBOL(mdio_driver_unregister);
27 changes: 17 additions & 10 deletions drivers/net/phy/phy_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,9 @@ static int phy_bus_match(struct device *dev, struct device_driver *drv)
const int num_ids = ARRAY_SIZE(phydev->c45_ids.device_ids);
int i;

if (!(phydrv->mdiodrv.flags & MDIO_DEVICE_IS_PHY))
return 0;

if (phydrv->match_phy_device)
return phydrv->match_phy_device(phydev);

Expand Down Expand Up @@ -851,9 +854,11 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
*/
if (!d->driver) {
if (phydev->is_c45)
d->driver = &genphy_driver[GENPHY_DRV_10G].driver;
d->driver =
&genphy_driver[GENPHY_DRV_10G].mdiodrv.driver;
else
d->driver = &genphy_driver[GENPHY_DRV_1G].driver;
d->driver =
&genphy_driver[GENPHY_DRV_1G].mdiodrv.driver;

err = d->driver->probe(d);
if (err >= 0)
Expand Down Expand Up @@ -954,7 +959,8 @@ void phy_detach(struct phy_device *phydev)
* real driver could be loaded
*/
for (i = 0; i < ARRAY_SIZE(genphy_driver); i++) {
if (phydev->mdio.dev.driver == &genphy_driver[i].driver) {
if (phydev->mdio.dev.driver ==
&genphy_driver[i].mdiodrv.driver) {
device_release_driver(&phydev->mdio.dev);
break;
}
Expand Down Expand Up @@ -1598,13 +1604,14 @@ int phy_driver_register(struct phy_driver *new_driver, struct module *owner)
{
int retval;

new_driver->driver.name = new_driver->name;
new_driver->driver.bus = &mdio_bus_type;
new_driver->driver.probe = phy_probe;
new_driver->driver.remove = phy_remove;
new_driver->driver.owner = owner;
new_driver->mdiodrv.flags |= MDIO_DEVICE_IS_PHY;
new_driver->mdiodrv.driver.name = new_driver->name;
new_driver->mdiodrv.driver.bus = &mdio_bus_type;
new_driver->mdiodrv.driver.probe = phy_probe;
new_driver->mdiodrv.driver.remove = phy_remove;
new_driver->mdiodrv.driver.owner = owner;

retval = driver_register(&new_driver->driver);
retval = driver_register(&new_driver->mdiodrv.driver);
if (retval) {
pr_err("%s: Error %d in registering driver\n",
new_driver->name, retval);
Expand Down Expand Up @@ -1637,7 +1644,7 @@ EXPORT_SYMBOL(phy_drivers_register);

void phy_driver_unregister(struct phy_driver *drv)
{
driver_unregister(&drv->driver);
driver_unregister(&drv->mdiodrv.driver);
}
EXPORT_SYMBOL(phy_driver_unregister);

Expand Down
33 changes: 33 additions & 0 deletions drivers/of/of_mdio.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,37 @@ static int of_mdiobus_register_phy(struct mii_bus *mdio, struct device_node *chi
return 0;
}

static int of_mdiobus_register_device(struct mii_bus *mdio,
struct device_node *child,
u32 addr)
{
struct mdio_device *mdiodev;
int rc;

mdiodev = mdio_device_create(mdio, addr);
if (!mdiodev || IS_ERR(mdiodev))
return 1;

/* Associate the OF node with the device structure so it
* can be looked up later.
*/
of_node_get(child);
mdiodev->dev.of_node = child;

/* All data is now stored in the mdiodev struct; register it. */
rc = mdio_device_register(mdiodev);
if (rc) {
mdio_device_free(mdiodev);
of_node_put(child);
return 1;
}

dev_dbg(&mdio->dev, "registered mdio device %s at address %i\n",
child->name, addr);

return 0;
}

int of_mdio_parse_addr(struct device *dev, const struct device_node *np)
{
u32 addr;
Expand Down Expand Up @@ -179,6 +210,8 @@ int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)

if (of_mdiobus_child_is_phy(child))
of_mdiobus_register_phy(mdio, child, addr);
else
of_mdiobus_register_device(mdio, child, addr);
}

if (!scanphys)
Expand Down
50 changes: 50 additions & 0 deletions include/linux/mdio.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ struct mii_bus;

struct mdio_device {
struct device dev;

const struct dev_pm_ops *pm_ops;
struct mii_bus *bus;
int (*bus_match)(struct device *dev, struct device_driver *drv);
Expand All @@ -24,7 +25,37 @@ struct mdio_device {
};
#define to_mdio_device(d) container_of(d, struct mdio_device, dev)

/* struct mdio_driver_common: Common to all MDIO drivers */
struct mdio_driver_common {
struct device_driver driver;
int flags;
};
#define MDIO_DEVICE_FLAG_PHY 1
#define to_mdio_common_driver(d) \
container_of(d, struct mdio_driver_common, driver)

/* struct mdio_driver: Generic MDIO driver */
struct mdio_driver {
struct mdio_driver_common mdiodrv;

/*
* Called during discovery. Used to set
* up device-specific structures, if any
*/
int (*probe)(struct mdio_device *mdiodev);

/* Clears up any memory if needed */
void (*remove)(struct mdio_device *mdiodev);
};
#define to_mdio_driver(d) \
container_of(to_mdio_common_driver(d), struct mdio_driver, mdiodrv)

void mdio_device_free(struct mdio_device *mdiodev);
struct mdio_device *mdio_device_create(struct mii_bus *bus, int addr);
int mdio_device_register(struct mdio_device *mdiodev);
void mdio_device_remove(struct mdio_device *mdiodev);
int mdio_driver_register(struct mdio_driver *drv);
void mdio_driver_unregister(struct mdio_driver *drv);

static inline bool mdio_phy_id_is_c45(int phy_id)
{
Expand Down Expand Up @@ -197,4 +228,23 @@ int mdiobus_unregister_device(struct mdio_device *mdiodev);
bool mdiobus_is_registered_device(struct mii_bus *bus, int addr);
struct phy_device *mdiobus_get_phy(struct mii_bus *bus, int addr);

/**
* module_mdio_driver() - Helper macro for registering mdio drivers
*
* Helper macro for MDIO drivers which do not do anything special in module
* init/exit. Each module may only use this macro once, and calling it
* replaces module_init() and module_exit().
*/
#define mdio_module_driver(_mdio_driver) \
static int __init mdio_module_init(void) \
{ \
return mdio_driver_register(&_mdio_driver); \
} \
module_init(mdio_module_init); \
static void __exit mdio_module_exit(void) \
{ \
mdio_driver_unregister(&_mdio_driver); \
} \
module_exit(mdio_module_exit)

#endif /* __LINUX_MDIO_H__ */
Loading

0 comments on commit a9049e0

Please sign in to comment.