of: Add Device Tree support for SPMI
This change adds SPMI Device Tree parsing. The
of_spmi_register_devices() API should be called from the probe()
routine of each SPMI controller to parse the subtree and add the
respective SPMI devices.
The SPMI subtree is nested up to two levels deep. The first level
is the most basic and treats the address as the SPMI slave ID.
This should be used for simple devices that has no notion of
segmented SPMI address spaces.
An optional second level specifies the address as an offset
within the outer layer's slave ID. This is used to specify
multiple devices on the same slave ID that have different address
ranges. In fact, it's reasonable to specify any number of address
ranges at this level.
Devices can also specify any number of interrupts that's decoding
is done by an external interrupt device.
Sections of this code were taken from drivers/of/platform.c.
Change-Id: Ib9f06764a9bd85e3b2aab43b72aa7132885aa044
Signed-off-by: Michael Bohan <mbohan@codeaurora.org>
diff --git a/drivers/of/of_spmi.c b/drivers/of/of_spmi.c
new file mode 100644
index 0000000..9f2a396
--- /dev/null
+++ b/drivers/of/of_spmi.c
@@ -0,0 +1,160 @@
+/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/spmi.h>
+#include <linux/irq.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_spmi.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+
+/**
+ * Allocate resources for a child of a spmi-container node.
+ */
+static int of_spmi_allocate_resources(struct spmi_controller *ctrl,
+ struct spmi_boardinfo *info,
+ struct device_node *node,
+ uint32_t num_reg)
+{
+ int i, num_irq = 0;
+ uint64_t size;
+ uint32_t flags;
+ struct resource *res;
+ const __be32 *addrp;
+ struct of_irq oirq;
+
+ while (of_irq_map_one(node, num_irq, &oirq) == 0)
+ num_irq++;
+
+ if (num_irq || num_reg) {
+ res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
+ if (!res)
+ return -ENOMEM;
+
+ info->num_resources = num_reg + num_irq;
+ info->resource = res;
+ for (i = 0; i < num_reg; i++, res++) {
+ /* Addresses are always 16 bits */
+ addrp = of_get_address(node, i, &size, &flags);
+ BUG_ON(!addrp);
+ res->start = be32_to_cpup(addrp);
+ res->end = res->start + size - 1;
+ res->flags = flags;
+ }
+ WARN_ON(of_irq_to_resource_table(node, res, num_irq) !=
+ num_irq);
+ }
+
+ return 0;
+}
+
+static int of_spmi_create_device(struct spmi_controller *ctrl,
+ struct spmi_boardinfo *info,
+ struct device_node *node)
+{
+ void *result;
+ int rc;
+
+ rc = of_modalias_node(node, info->name, sizeof(info->name));
+ if (rc < 0) {
+ dev_err(&ctrl->dev, "of_spmi modalias failure on %s\n",
+ node->full_name);
+ return rc;
+ }
+
+ info->of_node = of_node_get(node);
+ result = spmi_new_device(ctrl, info);
+
+ if (result == NULL) {
+ dev_err(&ctrl->dev, "of_spmi: Failure registering %s\n",
+ node->full_name);
+ of_node_put(node);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static void of_spmi_walk_container_children(struct spmi_controller *ctrl,
+ struct spmi_boardinfo *info,
+ struct device_node *container)
+{
+ struct device_node *node;
+ uint64_t size;
+ uint32_t flags, num_reg = 0;
+ int rc;
+
+ for_each_child_of_node(container, node) {
+ /*
+ * We can't use of_address_to_resource here since it includes
+ * address translation; and address translation assumes that no
+ * parent buses have a size-cell of 0. But SPMI does have a
+ * size-cell of 0.
+ */
+ while (of_get_address(node, num_reg, &size, &flags) != NULL)
+ num_reg++;
+
+ rc = of_spmi_allocate_resources(ctrl, info, node, num_reg);
+ if (rc) {
+ dev_err(&ctrl->dev, "%s: unable to allocate"
+ " resources\n", __func__);
+ return;
+ }
+ rc = of_spmi_create_device(ctrl, info, node);
+ if (rc) {
+ dev_err(&ctrl->dev, "%s: unable to create device for"
+ " node %s\n", __func__, node->full_name);
+ return;
+ }
+ }
+}
+
+int of_spmi_register_devices(struct spmi_controller *ctrl)
+{
+ struct device_node *node;
+
+ /* Only register child devices if the ctrl has a node pointer set */
+ if (!ctrl->dev.of_node)
+ return -ENODEV;
+
+ for_each_child_of_node(ctrl->dev.of_node, node) {
+ struct spmi_boardinfo info = {};
+ const __be32 *slave_id;
+ int len, rc;
+
+ slave_id = of_get_property(node, "reg", &len);
+ if (!slave_id) {
+ dev_err(&ctrl->dev, "of_spmi: invalid sid "
+ "on %s\n", node->full_name);
+ continue;
+ }
+
+ info.slave_id = be32_to_cpup(slave_id);
+
+ if (of_get_property(node, "spmi-dev-container", NULL)) {
+ of_spmi_walk_container_children(ctrl, &info, node);
+ continue;
+ } else {
+ rc = of_spmi_allocate_resources(ctrl, &info, node, 0);
+ if (rc)
+ continue;
+ of_spmi_create_device(ctrl, &info, node);
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(of_spmi_register_devices);
+
+MODULE_LICENSE("GPL");