This driver is for the Greybus Bridged Phy class.  It implements a bus
where all bridged phy devices hang off of, and provides the framework
for the Greybus Bridged phy drivers to plug into.

Signed-off-by: Greg Kroah-Hartman <gre...@linuxfoundation.org>
---
 drivers/greybus/gbphy.c |  360 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/greybus/gbphy.h |  110 ++++++++++++++
 2 files changed, 470 insertions(+)

--- /dev/null
+++ b/drivers/greybus/gbphy.c
@@ -0,0 +1,360 @@
+/*
+ * Greybus Bridged-Phy Bus driver
+ *
+ * Copyright 2014 Google Inc.
+ * Copyright 2014 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+
+#include "greybus.h"
+#include "gbphy.h"
+
+#define GB_GBPHY_AUTOSUSPEND_MS        3000
+
+struct gbphy_host {
+       struct gb_bundle *bundle;
+       struct list_head devices;
+};
+
+static DEFINE_IDA(gbphy_id);
+
+static ssize_t protocol_id_show(struct device *dev,
+                                struct device_attribute *attr, char *buf)
+{
+       struct gbphy_device *gbphy_dev = to_gbphy_dev(dev);
+
+       return sprintf(buf, "0x%02x\n", gbphy_dev->cport_desc->protocol_id);
+}
+static DEVICE_ATTR_RO(protocol_id);
+
+static struct attribute *gbphy_dev_attrs[] = {
+       &dev_attr_protocol_id.attr,
+       NULL,
+};
+
+ATTRIBUTE_GROUPS(gbphy_dev);
+
+static void gbphy_dev_release(struct device *dev)
+{
+       struct gbphy_device *gbphy_dev = to_gbphy_dev(dev);
+
+       ida_simple_remove(&gbphy_id, gbphy_dev->id);
+       kfree(gbphy_dev);
+}
+
+#ifdef CONFIG_PM
+static int gb_gbphy_idle(struct device *dev)
+{
+       pm_runtime_mark_last_busy(dev);
+       pm_request_autosuspend(dev);
+       return 0;
+}
+#endif
+
+static const struct dev_pm_ops gb_gbphy_pm_ops = {
+       SET_RUNTIME_PM_OPS(pm_generic_runtime_suspend,
+                          pm_generic_runtime_resume,
+                          gb_gbphy_idle)
+};
+
+static struct device_type greybus_gbphy_dev_type = {
+       .name    =      "gbphy_device",
+       .release =      gbphy_dev_release,
+       .pm     =       &gb_gbphy_pm_ops,
+};
+
+static int gbphy_dev_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+       struct gbphy_device *gbphy_dev = to_gbphy_dev(dev);
+       struct greybus_descriptor_cport *cport_desc = gbphy_dev->cport_desc;
+       struct gb_bundle *bundle = gbphy_dev->bundle;
+       struct gb_interface *intf = bundle->intf;
+       struct gb_module *module = intf->module;
+       struct gb_host_device *hd = intf->hd;
+
+       if (add_uevent_var(env, "BUS=%u", hd->bus_id))
+               return -ENOMEM;
+       if (add_uevent_var(env, "MODULE=%u", module->module_id))
+               return -ENOMEM;
+       if (add_uevent_var(env, "INTERFACE=%u", intf->interface_id))
+               return -ENOMEM;
+       if (add_uevent_var(env, "GREYBUS_ID=%08x/%08x",
+                          intf->vendor_id, intf->product_id))
+               return -ENOMEM;
+       if (add_uevent_var(env, "BUNDLE=%u", gbphy_dev->bundle->id))
+               return -ENOMEM;
+       if (add_uevent_var(env, "BUNDLE_CLASS=%02x", bundle->class))
+               return -ENOMEM;
+       if (add_uevent_var(env, "GBPHY=%u", gbphy_dev->id))
+               return -ENOMEM;
+       if (add_uevent_var(env, "PROTOCOL_ID=%02x", cport_desc->protocol_id))
+               return -ENOMEM;
+
+       return 0;
+}
+
+static const struct gbphy_device_id *
+gbphy_dev_match_id(struct gbphy_device *gbphy_dev, struct gbphy_driver 
*gbphy_drv)
+{
+       const struct gbphy_device_id *id = gbphy_drv->id_table;
+
+       if (!id)
+               return NULL;
+
+       for (; id->protocol_id; id++)
+               if (id->protocol_id == gbphy_dev->cport_desc->protocol_id)
+                       return id;
+
+       return NULL;
+}
+
+static int gbphy_dev_match(struct device *dev, struct device_driver *drv)
+{
+       struct gbphy_driver *gbphy_drv = to_gbphy_driver(drv);
+       struct gbphy_device *gbphy_dev = to_gbphy_dev(dev);
+       const struct gbphy_device_id *id;
+
+       id = gbphy_dev_match_id(gbphy_dev, gbphy_drv);
+       if (id)
+               return 1;
+
+       return 0;
+}
+
+static int gbphy_dev_probe(struct device *dev)
+{
+       struct gbphy_driver *gbphy_drv = to_gbphy_driver(dev->driver);
+       struct gbphy_device *gbphy_dev = to_gbphy_dev(dev);
+       const struct gbphy_device_id *id;
+       int ret;
+
+       id = gbphy_dev_match_id(gbphy_dev, gbphy_drv);
+       if (!id)
+               return -ENODEV;
+
+       /* for old kernels we need get_sync to resume parent devices */
+       ret = gb_pm_runtime_get_sync(gbphy_dev->bundle);
+       if (ret < 0)
+               return ret;
+
+       pm_runtime_set_autosuspend_delay(dev, GB_GBPHY_AUTOSUSPEND_MS);
+       pm_runtime_use_autosuspend(dev);
+       pm_runtime_get_noresume(dev);
+       pm_runtime_set_active(dev);
+       pm_runtime_enable(dev);
+
+       /*
+        * Drivers should call put on the gbphy dev before returning
+        * from probe if they support runtime pm.
+        */
+       ret = gbphy_drv->probe(gbphy_dev, id);
+       if (ret) {
+               pm_runtime_disable(dev);
+               pm_runtime_set_suspended(dev);
+               pm_runtime_put_noidle(dev);
+               pm_runtime_dont_use_autosuspend(dev);
+       }
+
+       gb_pm_runtime_put_autosuspend(gbphy_dev->bundle);
+
+       return ret;
+}
+
+static int gbphy_dev_remove(struct device *dev)
+{
+       struct gbphy_driver *gbphy_drv = to_gbphy_driver(dev->driver);
+       struct gbphy_device *gbphy_dev = to_gbphy_dev(dev);
+
+       gbphy_drv->remove(gbphy_dev);
+
+       pm_runtime_disable(dev);
+       pm_runtime_set_suspended(dev);
+       pm_runtime_put_noidle(dev);
+       pm_runtime_dont_use_autosuspend(dev);
+
+       return 0;
+}
+
+static struct bus_type gbphy_bus_type = {
+       .name =         "gbphy",
+       .match =        gbphy_dev_match,
+       .probe =        gbphy_dev_probe,
+       .remove =       gbphy_dev_remove,
+       .uevent =       gbphy_dev_uevent,
+};
+
+int gb_gbphy_register_driver(struct gbphy_driver *driver,
+                            struct module *owner, const char *mod_name)
+{
+       int retval;
+
+       if (greybus_disabled())
+               return -ENODEV;
+
+       driver->driver.bus = &gbphy_bus_type;
+       driver->driver.name = driver->name;
+       driver->driver.owner = owner;
+       driver->driver.mod_name = mod_name;
+
+       retval = driver_register(&driver->driver);
+       if (retval)
+               return retval;
+
+       pr_info("registered new driver %s\n", driver->name);
+       return 0;
+}
+EXPORT_SYMBOL_GPL(gb_gbphy_register_driver);
+
+void gb_gbphy_deregister_driver(struct gbphy_driver *driver)
+{
+       driver_unregister(&driver->driver);
+}
+EXPORT_SYMBOL_GPL(gb_gbphy_deregister_driver);
+
+static struct gbphy_device *gb_gbphy_create_dev(struct gb_bundle *bundle,
+                               struct greybus_descriptor_cport *cport_desc)
+{
+       struct gbphy_device *gbphy_dev;
+       int retval;
+       int id;
+
+       id = ida_simple_get(&gbphy_id, 1, 0, GFP_KERNEL);
+       if (id < 0)
+               return ERR_PTR(id);
+
+       gbphy_dev = kzalloc(sizeof(*gbphy_dev), GFP_KERNEL);
+       if (!gbphy_dev) {
+               ida_simple_remove(&gbphy_id, id);
+               return ERR_PTR(-ENOMEM);
+       }
+
+       gbphy_dev->id = id;
+       gbphy_dev->bundle = bundle;
+       gbphy_dev->cport_desc = cport_desc;
+       gbphy_dev->dev.parent = &bundle->dev;
+       gbphy_dev->dev.bus = &gbphy_bus_type;
+       gbphy_dev->dev.type = &greybus_gbphy_dev_type;
+       gbphy_dev->dev.groups = gbphy_dev_groups;
+       gbphy_dev->dev.dma_mask = bundle->dev.dma_mask;
+       dev_set_name(&gbphy_dev->dev, "gbphy%d", id);
+
+       retval = device_register(&gbphy_dev->dev);
+       if (retval) {
+               put_device(&gbphy_dev->dev);
+               return ERR_PTR(retval);
+       }
+
+       return gbphy_dev;
+}
+
+static void gb_gbphy_disconnect(struct gb_bundle *bundle)
+{
+       struct gbphy_host *gbphy_host = greybus_get_drvdata(bundle);
+       struct gbphy_device *gbphy_dev, *temp;
+       int ret;
+
+       ret = gb_pm_runtime_get_sync(bundle);
+       if (ret < 0)
+               gb_pm_runtime_get_noresume(bundle);
+
+       list_for_each_entry_safe(gbphy_dev, temp, &gbphy_host->devices, list) {
+               list_del(&gbphy_dev->list);
+               device_unregister(&gbphy_dev->dev);
+       }
+
+       kfree(gbphy_host);
+}
+
+static int gb_gbphy_probe(struct gb_bundle *bundle,
+                         const struct greybus_bundle_id *id)
+{
+       struct gbphy_host *gbphy_host;
+       struct gbphy_device *gbphy_dev;
+       int i;
+
+       if (bundle->num_cports == 0)
+               return -ENODEV;
+
+       gbphy_host = kzalloc(sizeof(*gbphy_host), GFP_KERNEL);
+       if (!gbphy_host)
+               return -ENOMEM;
+
+       gbphy_host->bundle = bundle;
+       INIT_LIST_HEAD(&gbphy_host->devices);
+       greybus_set_drvdata(bundle, gbphy_host);
+
+       /*
+        * Create a bunch of children devices, one per cport, and bind the
+        * bridged phy drivers to them.
+        */
+       for (i = 0; i < bundle->num_cports; ++i) {
+               gbphy_dev = gb_gbphy_create_dev(bundle, &bundle->cport_desc[i]);
+               if (IS_ERR(gbphy_dev)) {
+                       gb_gbphy_disconnect(bundle);
+                       return PTR_ERR(gbphy_dev);
+               }
+               list_add(&gbphy_dev->list, &gbphy_host->devices);
+       }
+
+       gb_pm_runtime_put_autosuspend(bundle);
+
+       return 0;
+}
+
+static const struct greybus_bundle_id gb_gbphy_id_table[] = {
+       { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_BRIDGED_PHY) },
+       { },
+};
+MODULE_DEVICE_TABLE(greybus, gb_gbphy_id_table);
+
+static struct greybus_driver gb_gbphy_driver = {
+       .name           = "gbphy",
+       .probe          = gb_gbphy_probe,
+       .disconnect     = gb_gbphy_disconnect,
+       .id_table       = gb_gbphy_id_table,
+};
+
+static int __init gbphy_init(void)
+{
+       int retval;
+
+       retval = bus_register(&gbphy_bus_type);
+       if (retval) {
+               pr_err("gbphy bus register failed (%d)\n", retval);
+               return retval;
+       }
+
+       retval = greybus_register(&gb_gbphy_driver);
+       if (retval) {
+               pr_err("error registering greybus driver\n");
+               goto error_gbphy;
+       }
+
+       return 0;
+
+error_gbphy:
+       bus_unregister(&gbphy_bus_type);
+       ida_destroy(&gbphy_id);
+       return retval;
+}
+module_init(gbphy_init);
+
+static void __exit gbphy_exit(void)
+{
+       greybus_deregister(&gb_gbphy_driver);
+       bus_unregister(&gbphy_bus_type);
+       ida_destroy(&gbphy_id);
+}
+module_exit(gbphy_exit);
+
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/drivers/greybus/gbphy.h
@@ -0,0 +1,110 @@
+/*
+ * Greybus Bridged-Phy Bus driver
+ *
+ * Copyright 2016 Google Inc.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#ifndef __GBPHY_H
+#define __GBPHY_H
+
+struct gbphy_device {
+       u32 id;
+       struct greybus_descriptor_cport *cport_desc;
+       struct gb_bundle *bundle;
+       struct list_head list;
+       struct device dev;
+};
+#define to_gbphy_dev(d) container_of(d, struct gbphy_device, dev)
+
+static inline void *gb_gbphy_get_data(struct gbphy_device *gdev)
+{
+       return dev_get_drvdata(&gdev->dev);
+}
+
+static inline void gb_gbphy_set_data(struct gbphy_device *gdev, void *data)
+{
+       dev_set_drvdata(&gdev->dev, data);
+}
+
+struct gbphy_device_id {
+       __u8 protocol_id;
+};
+
+#define GBPHY_PROTOCOL(p)              \
+       .protocol_id    = (p),
+
+struct gbphy_driver {
+       const char *name;
+       int (*probe)(struct gbphy_device *,
+                    const struct gbphy_device_id *id);
+       void (*remove)(struct gbphy_device *);
+       const struct gbphy_device_id *id_table;
+
+       struct device_driver driver;
+};
+#define to_gbphy_driver(d) container_of(d, struct gbphy_driver, driver)
+
+int gb_gbphy_register_driver(struct gbphy_driver *driver,
+                            struct module *owner, const char *mod_name);
+void gb_gbphy_deregister_driver(struct gbphy_driver *driver);
+
+#define gb_gbphy_register(driver) \
+       gb_gbphy_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)
+#define gb_gbphy_deregister(driver) \
+       gb_gbphy_deregister_driver(driver)
+
+/**
+ * module_gbphy_driver() - Helper macro for registering a gbphy driver
+ * @__gbphy_driver: gbphy_driver structure
+ *
+ * Helper macro for gbphy drivers to set up proper module init / exit
+ * functions.  Replaces module_init() and module_exit() and keeps people from
+ * printing pointless things to the kernel log when their driver is loaded.
+ */
+#define module_gbphy_driver(__gbphy_driver)    \
+       module_driver(__gbphy_driver, gb_gbphy_register, gb_gbphy_deregister)
+
+#ifdef CONFIG_PM
+static inline int gbphy_runtime_get_sync(struct gbphy_device *gbphy_dev)
+{
+       struct device *dev = &gbphy_dev->dev;
+       int ret;
+
+       ret = pm_runtime_get_sync(dev);
+       if (ret < 0) {
+               dev_err(dev, "pm_runtime_get_sync failed: %d\n", ret);
+               pm_runtime_put_noidle(dev);
+               return ret;
+       }
+
+       return 0;
+}
+
+static inline void gbphy_runtime_put_autosuspend(struct gbphy_device 
*gbphy_dev)
+{
+       struct device *dev = &gbphy_dev->dev;
+
+       pm_runtime_mark_last_busy(dev);
+       pm_runtime_put_autosuspend(dev);
+}
+
+static inline void gbphy_runtime_get_noresume(struct gbphy_device *gbphy_dev)
+{
+       pm_runtime_get_noresume(&gbphy_dev->dev);
+}
+
+static inline void gbphy_runtime_put_noidle(struct gbphy_device *gbphy_dev)
+{
+       pm_runtime_put_noidle(&gbphy_dev->dev);
+}
+#else
+static inline int gbphy_runtime_get_sync(struct gbphy_device *gbphy_dev) { 
return 0; }
+static inline void gbphy_runtime_put_autosuspend(struct gbphy_device 
*gbphy_dev) {}
+static inline void gbphy_runtime_get_noresume(struct gbphy_device *gbphy_dev) 
{}
+static inline void gbphy_runtime_put_noidle(struct gbphy_device *gbphy_dev) {}
+#endif
+
+#endif /* __GBPHY_H */
+


Reply via email to