summaryrefslogtreecommitdiff
path: root/drivers/staging/greybus/bundle.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/greybus/bundle.c')
-rw-r--r--drivers/staging/greybus/bundle.c186
1 files changed, 186 insertions, 0 deletions
diff --git a/drivers/staging/greybus/bundle.c b/drivers/staging/greybus/bundle.c
new file mode 100644
index 000000000000..742781ceb135
--- /dev/null
+++ b/drivers/staging/greybus/bundle.c
@@ -0,0 +1,186 @@
+/*
+ * Greybus interfaces
+ *
+ * Copyright 2014 Google Inc.
+ * Copyright 2014 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include "greybus.h"
+
+static ssize_t device_id_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct gb_interface *interface = to_gb_interface(dev);
+
+ return sprintf(buf, "%d", interface->device_id);
+}
+static DEVICE_ATTR_RO(device_id);
+
+static struct attribute *interface_attrs[] = {
+ &dev_attr_device_id.attr,
+ NULL,
+};
+
+ATTRIBUTE_GROUPS(interface);
+
+static void gb_interface_release(struct device *dev)
+{
+ struct gb_interface *interface = to_gb_interface(dev);
+
+ kfree(interface);
+}
+
+struct device_type greybus_interface_type = {
+ .name = "greybus_interface",
+ .release = gb_interface_release,
+};
+
+
+/* XXX This could be per-host device or per-module */
+static DEFINE_SPINLOCK(gb_interfaces_lock);
+
+/*
+ * A Greybus interface represents a UniPro device present on a
+ * module. For Project Ara, each active Interface Block on a module
+ * implements a UniPro device, and therefore a Greybus interface. A
+ * Greybus module has at least one interface, but can have two (or
+ * even more).
+ *
+ * Create a gb_interface structure to represent a discovered
+ * interface. Returns a pointer to the new interface or a null
+ * pointer if a failure occurs due to memory exhaustion.
+ */
+struct gb_interface *
+gb_interface_create(struct gb_interface_block *gb_ib, u8 interface_id)
+{
+ struct gb_interface *interface;
+ int retval;
+
+ interface = kzalloc(sizeof(*interface), GFP_KERNEL);
+ if (!interface)
+ return NULL;
+
+ interface->gb_ib = gb_ib;
+ interface->id = interface_id;
+ interface->device_id = 0xff; /* Invalid device id to start with */
+ INIT_LIST_HEAD(&interface->connections);
+
+ /* Build up the interface device structures and register it with the
+ * driver core */
+ interface->dev.parent = &gb_ib->dev;
+ interface->dev.bus = &greybus_bus_type;
+ interface->dev.type = &greybus_interface_type;
+ interface->dev.groups = interface_groups;
+ device_initialize(&interface->dev);
+ dev_set_name(&interface->dev, "%d:%d", gb_ib->module_id, interface_id);
+
+ retval = device_add(&interface->dev);
+ if (retval) {
+ pr_err("failed to add interface device for id 0x%02hhx\n",
+ interface_id);
+ put_device(&interface->dev);
+ kfree(interface);
+ return NULL;
+ }
+
+ spin_lock_irq(&gb_interfaces_lock);
+ list_add_tail(&interface->links, &gb_ib->interfaces);
+ spin_unlock_irq(&gb_interfaces_lock);
+
+ return interface;
+}
+
+/*
+ * Tear down a previously set up interface.
+ */
+void gb_interface_destroy(struct gb_interface_block *gb_ib)
+{
+ struct gb_interface *interface;
+ struct gb_interface *temp;
+
+ if (WARN_ON(!gb_ib))
+ return;
+
+ spin_lock_irq(&gb_interfaces_lock);
+ list_for_each_entry_safe(interface, temp, &gb_ib->interfaces, links) {
+ list_del(&interface->links);
+ gb_interface_connections_exit(interface);
+ device_del(&interface->dev);
+ }
+ spin_unlock_irq(&gb_interfaces_lock);
+}
+
+int gb_interface_init(struct gb_interface_block *gb_ib, u8 interface_id, u8 device_id)
+{
+ struct gb_interface *interface;
+ int ret;
+
+ interface = gb_interface_find(gb_ib, interface_id);
+ if (!interface) {
+ dev_err(gb_ib->hd->parent, "module %hhu not found\n",
+ interface_id);
+ return -ENOENT;
+ }
+ interface->device_id = device_id;
+
+ ret = svc_set_route_send(interface, gb_ib->hd);
+ if (ret) {
+ dev_err(gb_ib->hd->parent, "failed to set route (%d)\n", ret);
+ return ret;
+ }
+
+ ret = gb_interface_connections_init(interface);
+ if (ret) {
+ dev_err(gb_ib->hd->parent, "module interface init error %d\n",
+ ret);
+ /* XXX clear route */
+ return ret;
+ }
+
+ return 0;
+}
+
+struct gb_interface *gb_interface_find(struct gb_interface_block *gb_ib,
+ u8 interface_id)
+{
+ struct gb_interface *interface;
+
+ spin_lock_irq(&gb_interfaces_lock);
+ list_for_each_entry(interface, &gb_ib->interfaces, links)
+ if (interface->id == interface_id) {
+ spin_unlock_irq(&gb_interfaces_lock);
+ return interface;
+ }
+ spin_unlock_irq(&gb_interfaces_lock);
+
+ return NULL;
+}
+
+int gb_interface_connections_init(struct gb_interface *interface)
+{
+ struct gb_connection *connection;
+ int ret = 0;
+
+ list_for_each_entry(connection, &interface->connections,
+ interface_links) {
+ ret = gb_connection_init(connection);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
+void gb_interface_connections_exit(struct gb_interface *interface)
+{
+ struct gb_connection *connection;
+ struct gb_connection *next;
+
+ list_for_each_entry_safe(connection, next, &interface->connections,
+ interface_links) {
+ gb_connection_exit(connection);
+ gb_connection_destroy(connection);
+ }
+}