diff --git a/Documentation/devicetree/bindings/spmi/msm-spmi.txt b/Documentation/devicetree/bindings/spmi/msm-spmi.txt new file mode 100644 index 00000000000..fa9151484ae --- /dev/null +++ b/Documentation/devicetree/bindings/spmi/msm-spmi.txt @@ -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 : 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: 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 : 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>; + }; + }; +}; diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig index d06a6374ed6..a306357c78f 100644 --- a/drivers/of/Kconfig +++ b/drivers/of/Kconfig @@ -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 diff --git a/drivers/of/Makefile b/drivers/of/Makefile index f7861ed2f28..2087c5e25e9 100644 --- a/drivers/of/Makefile +++ b/drivers/of/Makefile @@ -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 diff --git a/drivers/of/of_spmi.c b/drivers/of/of_spmi.c new file mode 100644 index 00000000000..9f2a396cdc4 --- /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 +#include +#include +#include +#include +#include +#include +#include + +/** + * 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"); diff --git a/include/linux/of_spmi.h b/include/linux/of_spmi.h new file mode 100644 index 00000000000..d07ce635adc --- /dev/null +++ b/include/linux/of_spmi.h @@ -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 +#include + +#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 */