diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2019-09-18 11:05:34 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2019-09-18 11:05:34 -0700 |
commit | e6874fc29410fabfdbc8c12b467f41a16cbcfd2b (patch) | |
tree | bb869a9b481a6c05bf334a0a4db0b1397ce4ca90 /drivers/greybus/core.c | |
parent | e444d51b14c4795074f485c79debd234931f0e49 (diff) | |
parent | 3fb73eddba106ad2a265a5c5c29d14b0ed6aaee1 (diff) |
Merge tag 'staging-5.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging
Pull staging and IIO driver updates from Greg KH:
"Here is the big staging/iio driver update for 5.4-rc1.
Lots of churn here, with a few driver/filesystems moving out of
staging finally:
- erofs moved out of staging
- greybus core code moved out of staging
Along with that, a new filesytem has been added:
- extfat
to provide support for those devices requiring that filesystem (i.e.
transfer devices to/from windows systems or printers)
Other than that, there a number of new IIO drivers, and lots and lots
and lots of staging driver cleanups and minor fixes as people continue
to dig into those for easy changes.
All of these have been in linux-next for a while with no reported
issues"
* tag 'staging-5.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging: (453 commits)
Staging: gasket: Use temporaries to reduce line length.
Staging: octeon: Avoid several usecases of strcpy
staging: vhciq_core: replace snprintf with scnprintf
staging: wilc1000: avoid twice IRQ handler execution for each single interrupt
staging: wilc1000: remove unused interrupt status handling code
staging: fbtft: make several arrays static const, makes object smaller
staging: rtl8188eu: make two arrays static const, makes object smaller
staging: rtl8723bs: core: Remove Macro "IS_MAC_ADDRESS_BROADCAST"
dt-bindings: anybus-controller: move to staging/ tree
staging: emxx_udc: remove local TRUE/FALSE definition
staging: wilc1000: look for rtc_clk clock
staging: dt-bindings: wilc1000: add optional rtc_clk property
staging: nvec: make use of devm_platform_ioremap_resource
staging: exfat: drop unused function parameter
Staging: exfat: Avoid use of strcpy
staging: exfat: use integer constants
staging: exfat: cleanup spacing for casts
staging: exfat: cleanup spacing for operators
staging: rtl8723bs: hal: remove redundant variable n
staging: pi433: Fix typo in documentation
...
Diffstat (limited to 'drivers/greybus/core.c')
-rw-r--r-- | drivers/greybus/core.c | 349 |
1 files changed, 349 insertions, 0 deletions
diff --git a/drivers/greybus/core.c b/drivers/greybus/core.c new file mode 100644 index 000000000000..e546c6431877 --- /dev/null +++ b/drivers/greybus/core.c @@ -0,0 +1,349 @@ +// 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>"); |