|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * Greybus "Core" | 
|  | * | 
|  | * Copyright 2014-2015 Google Inc. | 
|  | * Copyright 2014-2015 Linaro Ltd. | 
|  | */ | 
|  |  | 
|  | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | 
|  |  | 
|  | #define CREATE_TRACE_POINTS | 
|  | #include <linux/greybus.h> | 
|  | #include "greybus_trace.h" | 
|  |  | 
|  | #define GB_BUNDLE_AUTOSUSPEND_MS	3000 | 
|  |  | 
|  | /* Allow greybus to be disabled at boot if needed */ | 
|  | static bool nogreybus; | 
|  | #ifdef MODULE | 
|  | module_param(nogreybus, bool, 0444); | 
|  | #else | 
|  | core_param(nogreybus, nogreybus, bool, 0444); | 
|  | #endif | 
|  | int greybus_disabled(void) | 
|  | { | 
|  | return nogreybus; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(greybus_disabled); | 
|  |  | 
|  | static bool greybus_match_one_id(struct gb_bundle *bundle, | 
|  | const struct greybus_bundle_id *id) | 
|  | { | 
|  | if ((id->match_flags & GREYBUS_ID_MATCH_VENDOR) && | 
|  | (id->vendor != bundle->intf->vendor_id)) | 
|  | return false; | 
|  |  | 
|  | if ((id->match_flags & GREYBUS_ID_MATCH_PRODUCT) && | 
|  | (id->product != bundle->intf->product_id)) | 
|  | return false; | 
|  |  | 
|  | if ((id->match_flags & GREYBUS_ID_MATCH_CLASS) && | 
|  | (id->class != bundle->class)) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static const struct greybus_bundle_id * | 
|  | greybus_match_id(struct gb_bundle *bundle, const struct greybus_bundle_id *id) | 
|  | { | 
|  | if (!id) | 
|  | return NULL; | 
|  |  | 
|  | for (; id->vendor || id->product || id->class || id->driver_info; | 
|  | id++) { | 
|  | if (greybus_match_one_id(bundle, id)) | 
|  | return id; | 
|  | } | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static int greybus_match_device(struct device *dev, struct device_driver *drv) | 
|  | { | 
|  | struct greybus_driver *driver = to_greybus_driver(drv); | 
|  | struct gb_bundle *bundle; | 
|  | const struct greybus_bundle_id *id; | 
|  |  | 
|  | if (!is_gb_bundle(dev)) | 
|  | return 0; | 
|  |  | 
|  | bundle = to_gb_bundle(dev); | 
|  |  | 
|  | id = greybus_match_id(bundle, driver->id_table); | 
|  | if (id) | 
|  | return 1; | 
|  | /* FIXME - Dynamic ids? */ | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int greybus_uevent(struct device *dev, struct kobj_uevent_env *env) | 
|  | { | 
|  | struct gb_host_device *hd; | 
|  | struct gb_module *module = NULL; | 
|  | struct gb_interface *intf = NULL; | 
|  | struct gb_control *control = NULL; | 
|  | struct gb_bundle *bundle = NULL; | 
|  | struct gb_svc *svc = NULL; | 
|  |  | 
|  | if (is_gb_host_device(dev)) { | 
|  | hd = to_gb_host_device(dev); | 
|  | } else if (is_gb_module(dev)) { | 
|  | module = to_gb_module(dev); | 
|  | hd = module->hd; | 
|  | } else if (is_gb_interface(dev)) { | 
|  | intf = to_gb_interface(dev); | 
|  | module = intf->module; | 
|  | hd = intf->hd; | 
|  | } else if (is_gb_control(dev)) { | 
|  | control = to_gb_control(dev); | 
|  | intf = control->intf; | 
|  | module = intf->module; | 
|  | hd = intf->hd; | 
|  | } else if (is_gb_bundle(dev)) { | 
|  | bundle = to_gb_bundle(dev); | 
|  | intf = bundle->intf; | 
|  | module = intf->module; | 
|  | hd = intf->hd; | 
|  | } else if (is_gb_svc(dev)) { | 
|  | svc = to_gb_svc(dev); | 
|  | hd = svc->hd; | 
|  | } else { | 
|  | dev_WARN(dev, "uevent for unknown greybus device \"type\"!\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (add_uevent_var(env, "BUS=%u", hd->bus_id)) | 
|  | return -ENOMEM; | 
|  |  | 
|  | if (module) { | 
|  | if (add_uevent_var(env, "MODULE=%u", module->module_id)) | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | if (intf) { | 
|  | 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 (bundle) { | 
|  | // FIXME | 
|  | // add a uevent that can "load" a bundle type | 
|  | // This is what we need to bind a driver to so use the info | 
|  | // in gmod here as well | 
|  |  | 
|  | if (add_uevent_var(env, "BUNDLE=%u", bundle->id)) | 
|  | return -ENOMEM; | 
|  | if (add_uevent_var(env, "BUNDLE_CLASS=%02x", bundle->class)) | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void greybus_shutdown(struct device *dev) | 
|  | { | 
|  | if (is_gb_host_device(dev)) { | 
|  | struct gb_host_device *hd; | 
|  |  | 
|  | hd = to_gb_host_device(dev); | 
|  | gb_hd_shutdown(hd); | 
|  | } | 
|  | } | 
|  |  | 
|  | struct bus_type greybus_bus_type = { | 
|  | .name =		"greybus", | 
|  | .match =	greybus_match_device, | 
|  | .uevent =	greybus_uevent, | 
|  | .shutdown =	greybus_shutdown, | 
|  | }; | 
|  |  | 
|  | static int greybus_probe(struct device *dev) | 
|  | { | 
|  | struct greybus_driver *driver = to_greybus_driver(dev->driver); | 
|  | struct gb_bundle *bundle = to_gb_bundle(dev); | 
|  | const struct greybus_bundle_id *id; | 
|  | int retval; | 
|  |  | 
|  | /* match id */ | 
|  | id = greybus_match_id(bundle, driver->id_table); | 
|  | if (!id) | 
|  | return -ENODEV; | 
|  |  | 
|  | retval = pm_runtime_get_sync(&bundle->intf->dev); | 
|  | if (retval < 0) { | 
|  | pm_runtime_put_noidle(&bundle->intf->dev); | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | retval = gb_control_bundle_activate(bundle->intf->control, bundle->id); | 
|  | if (retval) { | 
|  | pm_runtime_put(&bundle->intf->dev); | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Unbound bundle devices are always deactivated. During probe, the | 
|  | * Runtime PM is set to enabled and active and the usage count is | 
|  | * incremented. If the driver supports runtime PM, it should call | 
|  | * pm_runtime_put() in its probe routine and pm_runtime_get_sync() | 
|  | * in remove routine. | 
|  | */ | 
|  | pm_runtime_set_autosuspend_delay(dev, GB_BUNDLE_AUTOSUSPEND_MS); | 
|  | pm_runtime_use_autosuspend(dev); | 
|  | pm_runtime_get_noresume(dev); | 
|  | pm_runtime_set_active(dev); | 
|  | pm_runtime_enable(dev); | 
|  |  | 
|  | retval = driver->probe(bundle, id); | 
|  | if (retval) { | 
|  | /* | 
|  | * Catch buggy drivers that fail to destroy their connections. | 
|  | */ | 
|  | WARN_ON(!list_empty(&bundle->connections)); | 
|  |  | 
|  | gb_control_bundle_deactivate(bundle->intf->control, bundle->id); | 
|  |  | 
|  | pm_runtime_disable(dev); | 
|  | pm_runtime_set_suspended(dev); | 
|  | pm_runtime_put_noidle(dev); | 
|  | pm_runtime_dont_use_autosuspend(dev); | 
|  | pm_runtime_put(&bundle->intf->dev); | 
|  |  | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | pm_runtime_put(&bundle->intf->dev); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int greybus_remove(struct device *dev) | 
|  | { | 
|  | struct greybus_driver *driver = to_greybus_driver(dev->driver); | 
|  | struct gb_bundle *bundle = to_gb_bundle(dev); | 
|  | struct gb_connection *connection; | 
|  | int retval; | 
|  |  | 
|  | retval = pm_runtime_get_sync(dev); | 
|  | if (retval < 0) | 
|  | dev_err(dev, "failed to resume bundle: %d\n", retval); | 
|  |  | 
|  | /* | 
|  | * Disable (non-offloaded) connections early in case the interface is | 
|  | * already gone to avoid unceccessary operation timeouts during | 
|  | * driver disconnect. Otherwise, only disable incoming requests. | 
|  | */ | 
|  | list_for_each_entry(connection, &bundle->connections, bundle_links) { | 
|  | if (gb_connection_is_offloaded(connection)) | 
|  | continue; | 
|  |  | 
|  | if (bundle->intf->disconnected) | 
|  | gb_connection_disable_forced(connection); | 
|  | else | 
|  | gb_connection_disable_rx(connection); | 
|  | } | 
|  |  | 
|  | driver->disconnect(bundle); | 
|  |  | 
|  | /* Catch buggy drivers that fail to destroy their connections. */ | 
|  | WARN_ON(!list_empty(&bundle->connections)); | 
|  |  | 
|  | if (!bundle->intf->disconnected) | 
|  | gb_control_bundle_deactivate(bundle->intf->control, bundle->id); | 
|  |  | 
|  | pm_runtime_put_noidle(dev); | 
|  | pm_runtime_disable(dev); | 
|  | pm_runtime_set_suspended(dev); | 
|  | pm_runtime_dont_use_autosuspend(dev); | 
|  | pm_runtime_put_noidle(dev); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int greybus_register_driver(struct greybus_driver *driver, struct module *owner, | 
|  | const char *mod_name) | 
|  | { | 
|  | int retval; | 
|  |  | 
|  | if (greybus_disabled()) | 
|  | return -ENODEV; | 
|  |  | 
|  | driver->driver.bus = &greybus_bus_type; | 
|  | driver->driver.name = driver->name; | 
|  | driver->driver.probe = greybus_probe; | 
|  | driver->driver.remove = greybus_remove; | 
|  | 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(greybus_register_driver); | 
|  |  | 
|  | void greybus_deregister_driver(struct greybus_driver *driver) | 
|  | { | 
|  | driver_unregister(&driver->driver); | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(greybus_deregister_driver); | 
|  |  | 
|  | static int __init gb_init(void) | 
|  | { | 
|  | int retval; | 
|  |  | 
|  | if (greybus_disabled()) | 
|  | return -ENODEV; | 
|  |  | 
|  | BUILD_BUG_ON(CPORT_ID_MAX >= (long)CPORT_ID_BAD); | 
|  |  | 
|  | gb_debugfs_init(); | 
|  |  | 
|  | retval = bus_register(&greybus_bus_type); | 
|  | if (retval) { | 
|  | pr_err("bus_register failed (%d)\n", retval); | 
|  | goto error_bus; | 
|  | } | 
|  |  | 
|  | retval = gb_hd_init(); | 
|  | if (retval) { | 
|  | pr_err("gb_hd_init failed (%d)\n", retval); | 
|  | goto error_hd; | 
|  | } | 
|  |  | 
|  | retval = gb_operation_init(); | 
|  | if (retval) { | 
|  | pr_err("gb_operation_init failed (%d)\n", retval); | 
|  | goto error_operation; | 
|  | } | 
|  | return 0;	/* Success */ | 
|  |  | 
|  | error_operation: | 
|  | gb_hd_exit(); | 
|  | error_hd: | 
|  | bus_unregister(&greybus_bus_type); | 
|  | error_bus: | 
|  | gb_debugfs_cleanup(); | 
|  |  | 
|  | return retval; | 
|  | } | 
|  | module_init(gb_init); | 
|  |  | 
|  | static void __exit gb_exit(void) | 
|  | { | 
|  | gb_operation_exit(); | 
|  | gb_hd_exit(); | 
|  | bus_unregister(&greybus_bus_type); | 
|  | gb_debugfs_cleanup(); | 
|  | tracepoint_synchronize_unregister(); | 
|  | } | 
|  | module_exit(gb_exit); | 
|  | MODULE_LICENSE("GPL v2"); | 
|  | MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@linuxfoundation.org>"); |