root/drivers/staging/greybus/gbphy.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Greybus Bridged-Phy Bus driver
 *
 * Copyright 2014 Google Inc.
 * Copyright 2014 Linaro Ltd.
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/types.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/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_free(&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 const struct device_type greybus_gbphy_dev_type = {
        .name    =      "gbphy_device",
        .release =      gbphy_dev_release,
        .pm     =       &gb_gbphy_pm_ops,
};

static int gbphy_dev_uevent(const struct device *dev, struct kobj_uevent_env *env)
{
        const struct gbphy_device *gbphy_dev = to_gbphy_dev(dev);
        const struct greybus_descriptor_cport *cport_desc = gbphy_dev->cport_desc;
        const struct gb_bundle *bundle = gbphy_dev->bundle;
        const struct gb_interface *intf = bundle->intf;
        const struct gb_module *module = intf->module;
        const 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(const struct gbphy_device *gbphy_dev,
                   const 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, const struct device_driver *drv)
{
        const 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 void 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);
}

static const 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_alloc_min(&gbphy_id, 1, GFP_KERNEL);
        if (id < 0)
                return ERR_PTR(id);

        gbphy_dev = kzalloc_obj(*gbphy_dev);
        if (!gbphy_dev) {
                ida_free(&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_obj(*gbphy_host);
        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_DESCRIPTION("Greybus Bridged-Phy Bus driver");
MODULE_LICENSE("GPL v2");