2014-08-11 01:30:45 -06:00
|
|
|
/*
|
|
|
|
* Greybus "Core"
|
|
|
|
*
|
2015-05-22 11:59:16 -06:00
|
|
|
* Copyright 2014-2015 Google Inc.
|
|
|
|
* Copyright 2014-2015 Linaro Ltd.
|
2014-08-11 01:30:45 -06:00
|
|
|
*
|
|
|
|
* Released under the GPLv2 only.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
|
|
|
|
#include "greybus.h"
|
|
|
|
|
|
|
|
/* Allow greybus to be disabled at boot if needed */
|
|
|
|
static bool nogreybus;
|
|
|
|
#ifdef MODULE
|
|
|
|
module_param(nogreybus, bool, 0444);
|
|
|
|
#else
|
2014-10-20 05:15:50 -06:00
|
|
|
core_param(nogreybus, nogreybus, bool, 0444);
|
2014-08-11 01:30:45 -06:00
|
|
|
#endif
|
|
|
|
int greybus_disabled(void)
|
|
|
|
{
|
|
|
|
return nogreybus;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(greybus_disabled);
|
|
|
|
|
2014-09-22 18:19:03 -06:00
|
|
|
static int greybus_module_match(struct device *dev, struct device_driver *drv)
|
2014-08-11 01:30:45 -06:00
|
|
|
{
|
2014-11-14 04:54:59 -07:00
|
|
|
struct greybus_driver *driver = to_greybus_driver(drv);
|
2015-04-01 09:02:04 -06:00
|
|
|
struct gb_bundle *bundle = to_gb_bundle(dev);
|
|
|
|
const struct greybus_bundle_id *id;
|
2014-08-11 01:30:45 -06:00
|
|
|
|
2015-04-01 09:02:04 -06:00
|
|
|
id = gb_bundle_match_id(bundle, driver->id_table);
|
2014-08-11 01:30:45 -06:00
|
|
|
if (id)
|
|
|
|
return 1;
|
2014-11-20 22:56:30 -07:00
|
|
|
/* FIXME - Dynamic ids? */
|
2014-08-11 01:30:45 -06:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int greybus_uevent(struct device *dev, struct kobj_uevent_env *env)
|
|
|
|
{
|
2014-12-21 15:10:26 -07:00
|
|
|
struct gb_module *module = NULL;
|
2014-12-19 15:56:31 -07:00
|
|
|
struct gb_interface *intf = NULL;
|
2014-12-12 15:10:17 -07:00
|
|
|
struct gb_bundle *bundle = NULL;
|
2014-11-15 13:12:16 -07:00
|
|
|
struct gb_connection *connection = NULL;
|
|
|
|
|
2015-04-07 12:26:53 -06:00
|
|
|
if (is_gb_endo(dev)) {
|
|
|
|
/*
|
|
|
|
* Not much to do for an endo, just fall through, as the
|
|
|
|
* "default" attributes are good enough for us.
|
|
|
|
*/
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-12-21 15:10:26 -07:00
|
|
|
if (is_gb_module(dev)) {
|
|
|
|
module = to_gb_module(dev);
|
|
|
|
} else if (is_gb_interface(dev)) {
|
2014-12-19 15:56:31 -07:00
|
|
|
intf = to_gb_interface(dev);
|
2014-12-12 15:10:17 -07:00
|
|
|
} else if (is_gb_bundle(dev)) {
|
|
|
|
bundle = to_gb_bundle(dev);
|
2014-12-19 15:56:31 -07:00
|
|
|
intf = bundle->intf;
|
2014-11-15 13:12:16 -07:00
|
|
|
} else if (is_gb_connection(dev)) {
|
|
|
|
connection = to_gb_connection(dev);
|
2014-12-12 15:10:17 -07:00
|
|
|
bundle = connection->bundle;
|
2014-12-19 15:56:31 -07:00
|
|
|
intf = bundle->intf;
|
2014-11-15 13:12:16 -07:00
|
|
|
} else {
|
|
|
|
dev_WARN(dev, "uevent for unknown greybus device \"type\"!\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (connection) {
|
|
|
|
// FIXME
|
|
|
|
// add a uevent that can "load" a connection type
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-12-12 15:10:17 -07:00
|
|
|
if (bundle) {
|
2014-11-15 13:12:16 -07:00
|
|
|
// FIXME
|
2014-12-12 15:10:17 -07:00
|
|
|
// add a uevent that can "load" a bundle type
|
2014-11-15 13:12:16 -07:00
|
|
|
// This is what we need to bind a driver to so use the info
|
|
|
|
// in gmod here as well
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME
|
|
|
|
// "just" a module, be vague here, nothing binds to a module except
|
|
|
|
// the greybus core, so there's not much, if anything, we need to
|
|
|
|
// advertise.
|
2014-08-11 01:30:45 -06:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-10-24 03:34:46 -06:00
|
|
|
struct bus_type greybus_bus_type = {
|
2014-08-11 01:30:45 -06:00
|
|
|
.name = "greybus",
|
2014-09-22 18:19:03 -06:00
|
|
|
.match = greybus_module_match,
|
2014-08-11 01:30:45 -06:00
|
|
|
.uevent = greybus_uevent,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int greybus_probe(struct device *dev)
|
|
|
|
{
|
|
|
|
struct greybus_driver *driver = to_greybus_driver(dev->driver);
|
2015-04-01 09:02:04 -06:00
|
|
|
struct gb_bundle *bundle = to_gb_bundle(dev);
|
|
|
|
const struct greybus_bundle_id *id;
|
2014-08-11 01:30:45 -06:00
|
|
|
int retval;
|
|
|
|
|
|
|
|
/* match id */
|
2015-04-01 09:02:04 -06:00
|
|
|
id = gb_bundle_match_id(bundle, driver->id_table);
|
2014-08-11 01:30:45 -06:00
|
|
|
if (!id)
|
|
|
|
return -ENODEV;
|
|
|
|
|
2015-04-01 09:02:04 -06:00
|
|
|
retval = driver->probe(bundle, id);
|
2014-08-11 01:30:45 -06:00
|
|
|
if (retval)
|
|
|
|
return retval;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int greybus_remove(struct device *dev)
|
|
|
|
{
|
|
|
|
struct greybus_driver *driver = to_greybus_driver(dev->driver);
|
2015-04-01 09:02:04 -06:00
|
|
|
struct gb_bundle *bundle = to_gb_bundle(dev);
|
2014-08-11 01:30:45 -06:00
|
|
|
|
2015-04-01 09:02:04 -06:00
|
|
|
driver->disconnect(bundle);
|
2014-08-11 01:30:45 -06:00
|
|
|
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.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);
|
|
|
|
|
2015-06-08 11:05:13 -06:00
|
|
|
void greybus_deregister_driver(struct greybus_driver *driver)
|
2014-08-11 01:30:45 -06:00
|
|
|
{
|
|
|
|
driver_unregister(&driver->driver);
|
|
|
|
}
|
2015-06-08 11:05:13 -06:00
|
|
|
EXPORT_SYMBOL_GPL(greybus_deregister_driver);
|
2014-08-11 01:30:45 -06:00
|
|
|
|
2014-08-30 17:20:22 -06:00
|
|
|
|
2014-09-07 14:12:11 -06:00
|
|
|
static DEFINE_MUTEX(hd_mutex);
|
|
|
|
|
|
|
|
static void free_hd(struct kref *kref)
|
|
|
|
{
|
|
|
|
struct greybus_host_device *hd;
|
|
|
|
|
|
|
|
hd = container_of(kref, struct greybus_host_device, kref);
|
|
|
|
|
|
|
|
kfree(hd);
|
2014-10-16 05:35:26 -06:00
|
|
|
mutex_unlock(&hd_mutex);
|
2014-09-07 14:12:11 -06:00
|
|
|
}
|
|
|
|
|
2014-09-06 17:57:36 -06:00
|
|
|
struct greybus_host_device *greybus_create_hd(struct greybus_host_driver *driver,
|
2015-05-19 03:22:43 -06:00
|
|
|
struct device *parent,
|
|
|
|
size_t buffer_size_max)
|
2014-09-06 17:57:36 -06:00
|
|
|
{
|
|
|
|
struct greybus_host_device *hd;
|
|
|
|
|
2014-10-26 23:32:27 -06:00
|
|
|
/*
|
|
|
|
* Validate that the driver implements all of the callbacks
|
|
|
|
* so that we don't have to every time we make them.
|
|
|
|
*/
|
2015-04-07 03:27:16 -06:00
|
|
|
if ((!driver->message_send) || (!driver->message_cancel) ||
|
2014-11-18 12:26:44 -07:00
|
|
|
(!driver->submit_svc)) {
|
2014-10-26 23:32:27 -06:00
|
|
|
pr_err("Must implement all greybus_host_driver callbacks!\n");
|
2015-05-22 08:52:45 -06:00
|
|
|
return ERR_PTR(-EINVAL);
|
2014-10-26 23:32:27 -06:00
|
|
|
}
|
2015-05-19 03:22:45 -06:00
|
|
|
|
|
|
|
if (buffer_size_max < GB_OPERATION_MESSAGE_SIZE_MIN) {
|
|
|
|
dev_err(parent, "greybus host-device buffers too small\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
2014-10-26 23:32:27 -06:00
|
|
|
|
2015-05-19 03:22:43 -06:00
|
|
|
/*
|
|
|
|
* Make sure to never allocate messages larger than what the Greybus
|
|
|
|
* protocol supports.
|
|
|
|
*/
|
|
|
|
if (buffer_size_max > GB_OPERATION_MESSAGE_SIZE_MAX) {
|
|
|
|
dev_warn(parent, "limiting buffer size to %u\n",
|
|
|
|
GB_OPERATION_MESSAGE_SIZE_MAX);
|
|
|
|
buffer_size_max = GB_OPERATION_MESSAGE_SIZE_MAX;
|
|
|
|
}
|
|
|
|
|
2014-09-06 17:57:36 -06:00
|
|
|
hd = kzalloc(sizeof(*hd) + driver->hd_priv_size, GFP_KERNEL);
|
|
|
|
if (!hd)
|
2015-05-22 08:52:45 -06:00
|
|
|
return ERR_PTR(-ENOMEM);
|
2014-09-06 17:57:36 -06:00
|
|
|
|
|
|
|
kref_init(&hd->kref);
|
2014-09-14 13:27:28 -06:00
|
|
|
hd->parent = parent;
|
|
|
|
hd->driver = driver;
|
2014-12-19 15:56:36 -07:00
|
|
|
INIT_LIST_HEAD(&hd->interfaces);
|
2014-11-17 07:08:44 -07:00
|
|
|
INIT_LIST_HEAD(&hd->connections);
|
2015-06-16 20:43:05 -06:00
|
|
|
ida_init(&hd->cport_id_map);
|
2015-05-19 03:22:43 -06:00
|
|
|
hd->buffer_size_max = buffer_size_max;
|
2014-10-02 11:30:03 -06:00
|
|
|
|
2014-09-06 17:57:36 -06:00
|
|
|
return hd;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(greybus_create_hd);
|
|
|
|
|
2015-05-22 11:56:49 -06:00
|
|
|
int greybus_endo_setup(struct greybus_host_device *hd, u16 endo_id,
|
|
|
|
u8 ap_intf_id)
|
|
|
|
{
|
|
|
|
struct gb_endo *endo;
|
|
|
|
|
2015-05-22 11:56:48 -06:00
|
|
|
endo = gb_endo_create(hd, endo_id, ap_intf_id);
|
2015-05-22 11:56:49 -06:00
|
|
|
if (IS_ERR(endo))
|
|
|
|
return PTR_ERR(endo);
|
2015-05-22 08:52:44 -06:00
|
|
|
hd->endo = endo;
|
2015-04-07 12:27:15 -06:00
|
|
|
|
2015-05-22 11:56:49 -06:00
|
|
|
return 0;
|
2014-09-06 17:57:36 -06:00
|
|
|
}
|
2015-05-22 11:56:49 -06:00
|
|
|
EXPORT_SYMBOL_GPL(greybus_endo_setup);
|
2014-09-06 17:57:36 -06:00
|
|
|
|
2014-09-07 14:12:11 -06:00
|
|
|
void greybus_remove_hd(struct greybus_host_device *hd)
|
|
|
|
{
|
2015-04-07 12:27:15 -06:00
|
|
|
/*
|
|
|
|
* Tear down all interfaces, modules, and the endo that is associated
|
|
|
|
* with this host controller before freeing the memory associated with
|
|
|
|
* the host controller.
|
|
|
|
*/
|
2015-05-20 06:03:51 -06:00
|
|
|
gb_interfaces_remove(hd);
|
2015-04-07 12:27:15 -06:00
|
|
|
gb_endo_remove(hd->endo);
|
2014-09-07 14:12:11 -06:00
|
|
|
kref_put_mutex(&hd->kref, free_hd, &hd_mutex);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(greybus_remove_hd);
|
|
|
|
|
2014-08-30 17:21:03 -06:00
|
|
|
static int __init gb_init(void)
|
2014-08-30 17:20:22 -06:00
|
|
|
{
|
2014-08-30 17:47:26 -06:00
|
|
|
int retval;
|
|
|
|
|
2015-03-20 08:59:13 -06:00
|
|
|
if (greybus_disabled())
|
|
|
|
return -ENODEV;
|
|
|
|
|
2015-06-13 10:02:09 -06:00
|
|
|
BUILD_BUG_ON(CPORT_ID_MAX >= (long)CPORT_ID_BAD);
|
2014-10-02 11:30:03 -06:00
|
|
|
|
2015-03-27 04:38:06 -06:00
|
|
|
gb_debugfs_init();
|
2014-08-30 17:47:26 -06:00
|
|
|
|
2014-08-31 14:54:59 -06:00
|
|
|
retval = bus_register(&greybus_bus_type);
|
2014-09-13 17:15:52 -06:00
|
|
|
if (retval) {
|
2015-06-09 16:42:51 -06:00
|
|
|
pr_err("bus_register failed (%d)\n", retval);
|
2014-08-31 14:54:59 -06:00
|
|
|
goto error_bus;
|
2014-09-13 17:15:52 -06:00
|
|
|
}
|
2014-08-31 14:54:59 -06:00
|
|
|
|
2014-09-14 12:40:35 -06:00
|
|
|
retval = gb_ap_init();
|
2014-09-13 17:15:52 -06:00
|
|
|
if (retval) {
|
2015-06-09 16:42:51 -06:00
|
|
|
pr_err("gb_ap_init failed (%d)\n", retval);
|
2014-09-14 12:40:35 -06:00
|
|
|
goto error_ap;
|
2014-09-13 17:15:52 -06:00
|
|
|
}
|
2014-08-31 17:17:04 -06:00
|
|
|
|
2014-10-16 05:35:34 -06:00
|
|
|
retval = gb_operation_init();
|
|
|
|
if (retval) {
|
2015-06-09 16:42:51 -06:00
|
|
|
pr_err("gb_operation_init failed (%d)\n", retval);
|
2014-10-16 05:35:34 -06:00
|
|
|
goto error_operation;
|
|
|
|
}
|
|
|
|
|
2015-06-09 16:42:51 -06:00
|
|
|
retval = gb_endo_init();
|
|
|
|
if (retval) {
|
|
|
|
pr_err("gb_endo_init failed (%d)\n", retval);
|
|
|
|
goto error_endo;
|
|
|
|
}
|
|
|
|
|
2014-11-05 15:12:53 -07:00
|
|
|
return 0; /* Success */
|
|
|
|
|
2015-06-09 16:42:51 -06:00
|
|
|
error_endo:
|
|
|
|
gb_operation_exit();
|
2014-10-16 05:35:34 -06:00
|
|
|
error_operation:
|
2014-09-14 12:40:35 -06:00
|
|
|
gb_ap_exit();
|
|
|
|
error_ap:
|
2014-08-31 14:54:59 -06:00
|
|
|
bus_unregister(&greybus_bus_type);
|
|
|
|
error_bus:
|
2014-08-31 17:17:04 -06:00
|
|
|
gb_debugfs_cleanup();
|
2014-08-31 14:54:59 -06:00
|
|
|
|
|
|
|
return retval;
|
2014-08-30 17:20:22 -06:00
|
|
|
}
|
2015-03-19 05:32:49 -06:00
|
|
|
module_init(gb_init);
|
2014-08-30 17:20:22 -06:00
|
|
|
|
2014-08-30 17:21:03 -06:00
|
|
|
static void __exit gb_exit(void)
|
2014-08-30 17:20:22 -06:00
|
|
|
{
|
2015-06-09 16:42:51 -06:00
|
|
|
gb_endo_exit();
|
2014-10-16 05:35:34 -06:00
|
|
|
gb_operation_exit();
|
2014-09-14 12:40:35 -06:00
|
|
|
gb_ap_exit();
|
2014-08-31 14:54:59 -06:00
|
|
|
bus_unregister(&greybus_bus_type);
|
2014-08-31 17:17:04 -06:00
|
|
|
gb_debugfs_cleanup();
|
2014-08-30 17:20:22 -06:00
|
|
|
}
|
|
|
|
module_exit(gb_exit);
|
2015-04-13 11:51:33 -06:00
|
|
|
MODULE_LICENSE("GPL v2");
|
2014-08-11 01:30:45 -06:00
|
|
|
MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@linuxfoundation.org>");
|