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>
This commit is contained in:
Michael Bohan
2012-01-05 14:16:43 -08:00
parent ac5d15452f
commit 86e30dc371
5 changed files with 262 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
* SPMI
The spmi Device Tree support interprets up to two levels of Device Tree
topology. The first level is required and specifies only a slave address.
The second level is optional and allows for the specification of different
offsets within the same 16-bit address space underneath a particular SPMI
slave ID. Within the second level, any number of address ranges can be
associated with a particular device within that 16-bit range.
First level
Required properites :
- reg: SPMI Slave ID (0-15) with no size cell.
- compatible : "qcom," prefixed string to match against the driver.
Recommended properties :
- interrupts : <a b c> where a is the slave ID, b is the peripheral ID,
c is the device interrupt number (0-7). Each device supports any arbitrary
number of interrupts.
- interrupt-parent : the phandle for the interrupt controller that
services interrupts for this device.
Second level
Required properties :
- spmi-dev-container: Used by the parser to understand that this is the second
level of the tree.
- reg: <a b> where a is < 65536 and b is a size. Each device supports an
arbitrary number of address ranges.
- compatible : "qcom," prefixed string to match against the driver.
Recommended properties :
- interrupts : <a b c> where a is the slave ID, b is is the peripheral ID,
c is the device interrupt number (0-7). Each device supports any arbitrary
number of interrupts.
- interrupt-parent : the phandle for the interrupt controller that
services interrupts for this device.
Example:
/ {
qcom,spmi@fc4c0000 {
#address-cells = <1>;
#size-cells = <0>;
interrupt-parent = <&qpnpint>;
pmic8941@d {
#address-cells = <1>;
#size-cells = <1>;
reg = <0xd>;
spmi-dev-container;
coincell@2800 {
compatible = "qcom,qpnp-coincell";
reg = <0x2800 0x4000>;
interrupts = <0xd 0x28 0x6 0xd 0x28 0x3>;
};
pon@800 {
compatible = "qcom,qpnp-pon";
reg = <0x800 0x4000>;
};
};
customer_dev@2 {
compatible = "qcom,qpnp-pon";
reg = <0x2>;
interrupts = <0x2 0x08 0x1 0x2 0x8 0x3>;
};
};
};

View File

@@ -75,4 +75,10 @@ config OF_PCI
help
OpenFirmware PCI bus accessors
config OF_SPMI
def_tristate SPMI
depends on SPMI
help
OpenFirmware SPMI bus accessors
endmenu # OF

View File

@@ -10,3 +10,4 @@ obj-$(CONFIG_OF_NET) += of_net.o
obj-$(CONFIG_OF_SPI) += of_spi.o
obj-$(CONFIG_OF_MDIO) += of_mdio.o
obj-$(CONFIG_OF_PCI) += of_pci.o
obj-$(CONFIG_OF_SPMI) += of_spmi.o

160
drivers/of/of_spmi.c Normal file
View File

@@ -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");

23
include/linux/of_spmi.h Normal file
View File

@@ -0,0 +1,23 @@
/* 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/of_irq.h>
#ifdef CONFIG_OF_SPMI
int of_spmi_register_devices(struct spmi_controller *ctrl);
#else
static int of_spmi_register_devices(struct spmi_controller *ctrl)
{
return -ENXIO;
}
#endif /* CONFIG_OF_SPMI */