From 86622b3ef7ed42713f82dcfc7338b15202097024 Mon Sep 17 00:00:00 2001 From: Michael Bohan Date: Wed, 8 Feb 2012 16:59:00 -0800 Subject: [PATCH 1/2] of: of_spmi: Add support for spmi-dev-container binding The spmi-dev-container binding is intended for SPMI configurations that have multiple device nodes associated with only one spmi_device. By default, if this flag is not specified, each device node will create a new spmi_device. Sometimes having multiple spmi_devices for SPMI device nodes is superfluous. One example of this is gpios. In some architectures, a single gpio is treated as a unique device. But from a gpio_chip perspective, the chip is comprised of many gpios. Beyond wasting memory allocating a unique spmi_device per gpio, the implication of not coalescing spmi_devices is that the clients probe() routine would be called N number of times. But this sort of behavior makes it difficult to realize when a gpio_chip starts and stops. If we assume that one gpio_chip represents one call to probe(), then this problem is solved, since all gpios in that chip will be passed as resources. In order to support multiple device nodes per spmi_device, we also need to extend the data structures for spmi_resources. This change also makes an effort to cleanup some of the error handling for illegal combinations of device bindings, as well as adding some additional documentation. Change-Id: If3ce2aaaa07bdf79e0d9fdedf16419e74a00fbec Signed-off-by: Michael Bohan --- .../devicetree/bindings/spmi/msm-spmi.txt | 144 +++++++-- drivers/of/of_spmi.c | 278 +++++++++++++++--- drivers/spmi/spmi.c | 4 +- include/linux/of_spmi.h | 11 + include/linux/spmi.h | 42 ++- 5 files changed, 401 insertions(+), 78 deletions(-) diff --git a/Documentation/devicetree/bindings/spmi/msm-spmi.txt b/Documentation/devicetree/bindings/spmi/msm-spmi.txt index 8fe415bd958..d50037feceb 100644 --- a/Documentation/devicetree/bindings/spmi/msm-spmi.txt +++ b/Documentation/devicetree/bindings/spmi/msm-spmi.txt @@ -1,13 +1,28 @@ * SPMI -The spmi Device Tree support interprets up to two levels of Device Tree +The SPMI Device Tree support interprets up to three 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. +device nodes 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. An additional +flag allows for the possiblity to specify that all device nodes should +have their resources dedicated to only one spmi_device. This flag can +be specified at the second level, or an optional third level. By default +without this flag, one spmi_device is created for each device_node. -First level +[Root Node] + +Recommended properties : + - interrupt-controller : Used to specify the root node as the + interrupt controller for SPMI devices. + - #interrupt-cells : The number of cells used to express one interrupt. + +Notes : + - It is considered an error to include either spmi-container-dev or + spmi-slave-dev in the Root Node. + +[First Level Nodes] Required properites : @@ -22,51 +37,124 @@ Recommended properties : - interrupt-parent : the phandle for the interrupt controller that services interrupts for this device. -Second level +[Second Level Nodes] Required properties : - spmi-slave-container: Used by the parser to understand that this is the - second level of the tree. + second level of the tree that includes device nodes associated with the + same slave_id. - 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, + - 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. -Example: +Optional properties : + + - spmi-dev-container: This specifies that all the device nodes specified for + this slave_id should have their resources coalesced into only one + spmi_device. + +[Third Level Nodes] + +Required properties : + + - spmi-dev-container: This specifies that all the device nodes specified for + this slave_id should have their resources coalesced into only one + spmi_device. + - 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 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. + +Notes : + - It is considered an error to include spmi-slave-dev at this level. + +[Example] / { - qcom,spmi@fc4c0000 { + qpnp: qcom,spmi@fc4c0000 { #address-cells = <1>; #size-cells = <0>; - interrupt-parent = <&qpnpint>; - pmic8941@d { + interrupt-controller; + #interrupt-cells = <3>; + + testint@f { + interrupt-parent = <&qpnp>; + compatible = "qcom,qpnp-testint"; + reg = <0xf>; + interrupts = <0x3 0x15 0x0 0x3 0x15 0x02 0x1 0x47 0x0>; + + }; + + pm8941@0 { + spmi-slave-container; + reg = <0x0>; #address-cells = <1>; #size-cells = <1>; - reg = <0xd>; + + pm8941_gpios: gpios { + spmi-dev-container; + compatible = "qcom,qpnp-gpio"; + gpio-controller; + #gpio-cells = <1>; + #address-cells = <1>; + #size-cells = <1>; + + pm8941_gpio1@0xc000 { + compatible = "qcom,qpnp-gpio"; + reg = <0xc000 0x100>; + qcom,qpnp_gpio = <1>; + interrupt-parent = <&qpnp>; + interrupts = <0x3 0x15 0x02 0x1 0x47 0x0>; + }; + + pm8941_gpio2@0xc100 { + compatible = "qcom,qpnp-gpio"; + reg = <0xc100 0x100>; + qcom,qpnp_gpio = <2>; + interrupt-parent = <&qpnp>; + interrupts = <0x3 0x15 0x0>; + }; + }; + + testgpio@0x1000 { + compatible = "qcom,qpnp-testgpio"; + reg = <0x1000 0x1000>; + qpnp-gpios = <&pm8941_gpios 0x0>; + }; + }; + pm8841@2 { spmi-slave-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>; + #address-cells = <1>; + #size-cells = <1>; + spmi-dev-container; + compatible = "qcom,qpnp-gpio"; + + pm8841_gpio1@0xc000 { + reg = <0xc000 0x100>; + qcom,qpnp_gpio = <1>; + }; + + pm8841_gpio2@0xc100 { + reg = <0xc100 0x100>; + qcom,qpnp_gpio = <2>; + }; }; + }; }; diff --git a/drivers/of/of_spmi.c b/drivers/of/of_spmi.c index 2032b462708..112497ccb74 100644 --- a/drivers/of/of_spmi.c +++ b/drivers/of/of_spmi.c @@ -18,6 +18,7 @@ #include #include #include +#include struct of_spmi_dev_info { struct spmi_controller *ctrl; @@ -30,14 +31,49 @@ struct of_spmi_res_info { uint32_t num_irq; }; -static void of_spmi_sum_resources(struct of_spmi_res_info *r_info, bool has_reg) +/* + * Initialize r_info structure for safe usage + */ +static inline void of_spmi_init_resource(struct of_spmi_res_info *r_info, + struct device_node *node) +{ + r_info->node = node; + r_info->num_reg = 0; + r_info->num_irq = 0; +} + +/* + * Allocate dev_node array for spmi_device + */ +static inline int of_spmi_alloc_device_store(struct of_spmi_dev_info *d_info, + uint32_t num_dev_node) +{ + d_info->b_info.num_dev_node = num_dev_node; + d_info->b_info.dev_node = kzalloc(sizeof(struct spmi_resource) * + num_dev_node, GFP_KERNEL); + if (!d_info->b_info.dev_node) + return -ENOMEM; + + return 0; +} + +/* + * Calculate the number of resources to allocate + * + * The caller is responsible for initializing the of_spmi_res_info structure. + */ +static void of_spmi_sum_node_resources(struct of_spmi_res_info *r_info, + bool has_reg) { struct of_irq oirq; uint64_t size; uint32_t flags; + int i = 0; - while (of_irq_map_one(r_info->node, r_info->num_irq, &oirq) == 0) - r_info->num_irq++; + while (of_irq_map_one(r_info->node, i, &oirq) == 0) + i++; + + r_info->num_irq += i; if (!has_reg) return; @@ -48,16 +84,32 @@ static void of_spmi_sum_resources(struct of_spmi_res_info *r_info, bool has_reg) * parent buses have a size-cell of 0. But SPMI does have a * size-cell of 0. */ - while (of_get_address(r_info->node, r_info->num_reg, - &size, &flags) != NULL) - r_info->num_reg++; + i = 0; + while (of_get_address(r_info->node, i, &size, &flags) != NULL) + i++; + + r_info->num_reg += i; } -/** - * Allocate resources for a child of a spmi-slave node. +/* + * free spmi_resource for the spmi_device */ -static int of_spmi_allocate_resources(struct of_spmi_dev_info *d_info, - struct of_spmi_res_info *r_info) +static void of_spmi_free_device_resources(struct of_spmi_dev_info *d_info) +{ + int i; + + for (i = 0; i < d_info->b_info.num_dev_node; i++) + kfree(d_info->b_info.dev_node[i].resource); + + kfree(d_info->b_info.dev_node); +} + +/* + * Gather node resources and populate + */ +static void of_spmi_populate_node_resources(struct of_spmi_dev_info *d_info, + struct of_spmi_res_info *r_info, + int idx) { uint32_t num_irq = r_info->num_irq, num_reg = r_info->num_reg; @@ -67,13 +119,10 @@ static int of_spmi_allocate_resources(struct of_spmi_dev_info *d_info, uint64_t size; uint32_t flags; - if (num_irq || num_reg) { - res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL); - if (!res) - return -ENOMEM; + res = d_info->b_info.dev_node[idx].resource; + d_info->b_info.dev_node[idx].of_node = r_info->node; - d_info->b_info.num_resources = num_reg + num_irq; - d_info->b_info.resource = res; + if ((num_irq || num_reg) && (res != NULL)) { for (i = 0; i < num_reg; i++, res++) { /* Addresses are always 16 bits */ addrp = of_get_address(r_info->node, i, &size, &flags); @@ -85,10 +134,34 @@ static int of_spmi_allocate_resources(struct of_spmi_dev_info *d_info, WARN_ON(of_irq_to_resource_table(r_info->node, res, num_irq) != num_irq); } +} + +/* + * Allocate enough memory to handle the resources associated with the + * device_node. The number of device nodes included in this allocation + * depends on whether the spmi-dev-container flag is specified or not. + */ +static int of_spmi_allocate_node_resources(struct of_spmi_dev_info *d_info, + struct of_spmi_res_info *r_info, + uint32_t idx) +{ + uint32_t num_irq = r_info->num_irq, num_reg = r_info->num_reg; + struct resource *res = NULL; + + if (num_irq || num_reg) { + res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL); + if (!res) + return -ENOMEM; + } + d_info->b_info.dev_node[idx].num_resources = num_reg + num_irq; + d_info->b_info.dev_node[idx].resource = res; return 0; } +/* + * create a single spmi_device + */ static int of_spmi_create_device(struct of_spmi_dev_info *d_info, struct device_node *node) { @@ -117,6 +190,71 @@ static int of_spmi_create_device(struct of_spmi_dev_info *d_info, return 0; } +/* + * Walks all children of a node containing the spmi-dev-container + * binding. This special type of spmi_device can include resources + * from more than one device node. + */ +static void of_spmi_walk_dev_container(struct of_spmi_dev_info *d_info, + struct device_node *container) +{ + struct of_spmi_res_info r_info = {}; + struct spmi_controller *ctrl = d_info->ctrl; + struct device_node *node; + int rc, i, num_dev_node = 0; + + /* first count the total number of device_nodes */ + for_each_child_of_node(container, node) + num_dev_node++; + + rc = of_spmi_alloc_device_store(d_info, num_dev_node); + if (rc) { + dev_err(&ctrl->dev, "%s: unable to allocate" + " device resources\n", __func__); + return; + } + + /* allocate resources per device_node */ + i = 0; + for_each_child_of_node(container, node) { + of_spmi_init_resource(&r_info, node); + of_spmi_sum_node_resources(&r_info, 1); + rc = of_spmi_allocate_node_resources(d_info, &r_info, i); + if (rc) { + dev_err(&ctrl->dev, "%s: unable to allocate" + " resources\n", __func__); + of_spmi_free_device_resources(d_info); + return; + } + i++; + } + + /** + * Now we need to cycle through again and actually populate + * the resources for each node. + */ + i = 0; + for_each_child_of_node(container, node) { + of_spmi_init_resource(&r_info, node); + of_spmi_sum_node_resources(&r_info, 1); + of_spmi_populate_node_resources(d_info, &r_info, i); + i++; + } + + rc = of_spmi_create_device(d_info, container); + if (rc) { + dev_err(&ctrl->dev, "%s: unable to create device for" + " node %s\n", __func__, container->full_name); + of_spmi_free_device_resources(d_info); + return; + } +} + +/* + * Walks all children of a node containing the spmi-slave-container + * binding. This indicates that all spmi_devices created from this + * point all share the same slave_id. + */ static void of_spmi_walk_slave_container(struct of_spmi_dev_info *d_info, struct device_node *container) { @@ -127,59 +265,133 @@ static void of_spmi_walk_slave_container(struct of_spmi_dev_info *d_info, for_each_child_of_node(container, node) { struct of_spmi_res_info r_info; - r_info.node = node; - of_spmi_sum_resources(&r_info, 1); + /** + * Check to see if this node contains children which + * should be all created as the same spmi_device. + */ + if (of_get_property(node, "spmi-dev-container", NULL)) { + of_spmi_walk_dev_container(d_info, node); + continue; + } - rc = of_spmi_allocate_resources(d_info, &r_info); + rc = of_spmi_alloc_device_store(d_info, 1); + if (rc) { + dev_err(&ctrl->dev, "%s: unable to allocate" + " device resources\n", __func__); + goto slave_err; + } + + of_spmi_init_resource(&r_info, node); + of_spmi_sum_node_resources(&r_info, 1); + + rc = of_spmi_allocate_node_resources(d_info, &r_info, 0); if (rc) { dev_err(&ctrl->dev, "%s: unable to allocate" " resources\n", __func__); - return; + goto slave_err; } + + of_spmi_populate_node_resources(d_info, &r_info, 0); + rc = of_spmi_create_device(d_info, node); if (rc) { dev_err(&ctrl->dev, "%s: unable to create device for" " node %s\n", __func__, node->full_name); - return; + goto slave_err; } } + return; + +slave_err: + of_spmi_free_device_resources(d_info); } int of_spmi_register_devices(struct spmi_controller *ctrl) { - struct device_node *node; + struct device_node *node = ctrl->dev.of_node; /* Only register child devices if the ctrl has a node pointer set */ - if (!ctrl->dev.of_node) + if (!node) return -ENODEV; + if (of_get_property(node, "spmi-slave-container", NULL)) { + dev_err(&ctrl->dev, "%s: structural error: spmi-slave-container" + " is prohibited at the root level\n", __func__); + return -EINVAL; + } else if (of_get_property(node, "spmi-dev-container", NULL)) { + dev_err(&ctrl->dev, "%s: structural error: spmi-dev-container" + " is prohibited at the root level\n", __func__); + return -EINVAL; + } + + /** + * Make best effort to launch as many nodes as possible. If there are + * syntax errors, we will simply ignore that subtree and keep going. + */ for_each_child_of_node(ctrl->dev.of_node, node) { struct of_spmi_dev_info d_info = {}; const __be32 *slave_id; - int len, rc; + int len, rc, have_dev_container = 0; 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); + dev_err(&ctrl->dev, "%s: invalid sid " + "on %s\n", __func__, node->full_name); continue; } d_info.b_info.slave_id = be32_to_cpup(slave_id); d_info.ctrl = ctrl; + if (of_get_property(node, "spmi-dev-container", NULL)) + have_dev_container = 1; if (of_get_property(node, "spmi-slave-container", NULL)) { - of_spmi_walk_slave_container(&d_info, node); - continue; + if (have_dev_container) + of_spmi_walk_dev_container(&d_info, node); + else + of_spmi_walk_slave_container(&d_info, node); } else { struct of_spmi_res_info r_info; - r_info.node = node; - of_spmi_sum_resources(&r_info, 0); - rc = of_spmi_allocate_resources(&d_info, &r_info); - if (rc) + /** + * A dev container at the second level without a slave + * container is considered an error. + */ + if (have_dev_container) { + dev_err(&ctrl->dev, "%s: structural error," + " node %s has spmi-dev-container without" + " specifying spmi-slave-container\n", + __func__, node->full_name); continue; - of_spmi_create_device(&d_info, node); + } + + rc = of_spmi_alloc_device_store(&d_info, 1); + if (rc) { + dev_err(&ctrl->dev, "%s: unable to allocate" + " device resources\n", __func__); + continue; + } + + of_spmi_init_resource(&r_info, node); + of_spmi_sum_node_resources(&r_info, 0); + rc = of_spmi_allocate_node_resources(&d_info, + &r_info, 0); + if (rc) { + dev_err(&ctrl->dev, "%s: unable to allocate" + " resources\n", __func__); + of_spmi_free_device_resources(&d_info); + continue; + } + + of_spmi_populate_node_resources(&d_info, &r_info, 0); + + rc = of_spmi_create_device(&d_info, node); + if (rc) { + dev_err(&ctrl->dev, "%s: unable to create" + " device\n", __func__); + of_spmi_free_device_resources(&d_info); + continue; + } } } diff --git a/drivers/spmi/spmi.c b/drivers/spmi/spmi.c index 2842cd80881..20a290a304e 100644 --- a/drivers/spmi/spmi.c +++ b/drivers/spmi/spmi.c @@ -233,9 +233,9 @@ struct spmi_device *spmi_new_device(struct spmi_controller *ctrl, spmidev->name = info->name; spmidev->sid = info->slave_id; spmidev->dev.of_node = info->of_node; - spmidev->resource = info->resource; - spmidev->num_resources = info->num_resources; spmidev->dev.platform_data = (void *)info->platform_data; + spmidev->num_dev_node = info->num_dev_node; + spmidev->dev_node = info->dev_node; rc = spmi_add_device(spmidev); if (rc < 0) { diff --git a/include/linux/of_spmi.h b/include/linux/of_spmi.h index d07ce635adc..fe09dec4538 100644 --- a/include/linux/of_spmi.h +++ b/include/linux/of_spmi.h @@ -14,6 +14,17 @@ #include #ifdef CONFIG_OF_SPMI +/** + * of_spmi_register_devices() - Register devices in the SPMI Device Tree + * @ctrl: spmi_controller which devices should be registered to. + * + * This routine scans the SPMI Device Tree, allocating resources and + * creating spmi_devices according to the SPMI bus Device Tree + * hierarchy. Details of this hierarchy can be found in + * Documentation/devicetree/bindings/spmi. This routine is normally + * called from the probe routine of the driver registering as a + * spmi_controller. + */ int of_spmi_register_devices(struct spmi_controller *ctrl); #else static int of_spmi_register_devices(struct spmi_controller *ctrl) diff --git a/include/linux/spmi.h b/include/linux/spmi.h index ffcb24eb2db..927978af32b 100644 --- a/include/linux/spmi.h +++ b/include/linux/spmi.h @@ -87,25 +87,37 @@ struct spmi_driver { }; #define to_spmi_driver(d) container_of(d, struct spmi_driver, driver) +/** + * struct spmi_resource: spmi_resource for one device_node + * @num_resources: number of resources for this device node + * @resources: array of resources for this device_node + * @of_node: device_node of the resource in question + */ +struct spmi_resource { + struct resource *resource; + u32 num_resources; + struct device_node *of_node; +}; + /** * Client/device handle (struct spmi_device): * ------------------------------------------ - * This is the client/device handle returned when a SPMI device - * is registered with a controller. - * Pointer to this structure is used by client-driver as a handle. - * @dev: driver model representation of the device. - * @name: name of driver to use with this device. - * @ctrl: SPMI controller managing the bus hosting this device. - * @resource: array of resources for this device_node - * @num_resources: number of resources for this device node - * @sid: slave identifier. + * This is the client/device handle returned when a SPMI device + * is registered with a controller. + * Pointer to this structure is used by client-driver as a handle. + * @dev: Driver model representation of the device. + * @name: Name of driver to use with this device. + * @ctrl: SPMI controller managing the bus hosting this device. + * @dev_node: array of SPMI resources - one entry per device_node. + * @num_dev_node: number of device_node structures. + * @sid: Slave Identifier. */ struct spmi_device { struct device dev; const char *name; struct spmi_controller *ctrl; - struct resource *resource; - u32 num_resources; + struct spmi_resource *dev_node; + u32 num_dev_node; u8 sid; }; #define to_spmi_device(d) container_of(d, struct spmi_device, dev) @@ -115,16 +127,16 @@ struct spmi_device { * @slave_id: slave identifier. * @spmi_device: device to be registered with the SPMI framework. * @of_node: pointer to the OpenFirmware device node. - * @num_resources: number of resources for this device node - * @resource: array of resources for this device_node + * @dev_node: one spmi_resource for each device_node. + * @num_dev_node: number of device_node structures. * @platform_data: goes to spmi_device.dev.platform_data */ struct spmi_boardinfo { char name[SPMI_NAME_SIZE]; uint8_t slave_id; struct device_node *of_node; - u32 num_resources; - struct resource *resource; + struct spmi_resource *dev_node; + u32 num_dev_node; const void *platform_data; }; From 0ba63b86baedb5ef3a9fa87152ff45e33c2d2289 Mon Sep 17 00:00:00 2001 From: Michael Bohan Date: Mon, 6 Feb 2012 13:42:34 -0800 Subject: [PATCH 2/2] msm: qpnp: Add gpiolib support for PMIC GPIOs Add a gpio_chip driver to support the Qualcomm SPMI PMIC architecture called QPNP. The driver supports Device Tree and allows a device_node to be registered as a gpio-controller. The driver also specifies APIs to allow a non-Device Tree user the ability to configure the PMIC GPIOs. This driver does not handle interrupts for GPIOs directly. Instead, that work is handled by the existing qpnp-int driver. This is feasible since the interrupt register map for all QPNP peripherals is the same. Change-Id: I04eb39d9855b0957f0647010fcb203ec2fc83c7c Signed-off-by: Michael Bohan --- .../bindings/spmi/msm-qpnp-gpio.txt | 142 ++++ drivers/gpio/Kconfig | 8 + drivers/gpio/Makefile | 1 + drivers/gpio/qpnp-gpio.c | 706 ++++++++++++++++++ include/linux/qpnp/gpio.h | 114 +++ 5 files changed, 971 insertions(+) create mode 100644 Documentation/devicetree/bindings/spmi/msm-qpnp-gpio.txt create mode 100644 drivers/gpio/qpnp-gpio.c create mode 100644 include/linux/qpnp/gpio.h diff --git a/Documentation/devicetree/bindings/spmi/msm-qpnp-gpio.txt b/Documentation/devicetree/bindings/spmi/msm-qpnp-gpio.txt new file mode 100644 index 00000000000..f07d3c2a10c --- /dev/null +++ b/Documentation/devicetree/bindings/spmi/msm-qpnp-gpio.txt @@ -0,0 +1,142 @@ +* msm-qpnp-gpio + +msm-qpnp-gpio is a GPIO chip driver for the MSM SPMI implementation. +It creates a spmi_device for every spmi-dev-container block of device_nodes. +These device_nodes contained within specify the PMIC GPIO number associated +with each GPIO chip. The driver will map these to Linux GPIO numbers. + +[PMIC GPIO Device Declarations] + +-Root Node- + +Required properties : + - spmi-dev-container : Used to specify the following child nodes as part of the + same SPMI device. + - gpio-controller : Specify as gpio-contoller. All child nodes will belong to this + gpio_chip. + - #gpio-cells: We encode a PMIC GPIO number and a 32-bit flag field to + specify the gpio configuration. This must be set to '2'. + - #address-cells: Specify one address field. This must be set to '1'. + - #size-cells: Specify one size-cell. This must be set to '1'. + - compatible = "qcom,qpnp-gpio" : Specify driver matching for this driver. + +-Child Nodes- + +Required properties : + - reg : Specify the spmi offset and size for this gpio device. + - qcom,qpnp-gpio-num : Specify the PMIC GPIO number for this gpio device. + +Optional properties : + - qcom,qpnp-gpio-cfg : Specify the PMIC gpio configuration. + The format of this configuration is specified in a tuple of 9 entries. + These entries should be specified in the same order as the entries listed + in this following discription. + + @direction: indicates whether the gpio should be input, output, or + both. + QPNP_GPIO_DIR_OUT = 1, + QPNP_GPIO_DIR_IN = 2, + QPNP_GPIO_DIR_BOTH = 3 + + @output_type: indicates gpio should be configured as CMOS or open + drain. + QPNP_GPIO_OUT_BUF_OPEN_DRAIN = 1, + QPNP_GPIO_OUT_BUF_CMOS = 0 + + @output_value: The gpio output value of the gpio line - 0 or 1 + @pull: Indicates whether a pull up or pull down should be + applied. If a pullup is required the current strength + needs to be specified. Current values of 30uA, 1.5uA, + 31.5uA, 1.5uA with 30uA boost are supported. + QPNP_GPIO_PULL_UP_30 = 0, + QPNP_GPIO_PULL_UP_1P5 = 1, + QPNP_GPIO_PULL_UP_31P5 = 2, + QPNP_GPIO_PULL_UP_1P5_30 = 3, + QPNP_GPIO_PULL_DN = 4, + QPNP_GPIO_PULL_NO = 5 + + @vin_sel: specifies the voltage level when the output is set to 1. + For an input gpio specifies the voltage level at which + the input is interpreted as a logical 1. + QPNP_GPIO_VIN0 = 0, + QPNP_GPIO_VIN1 = 1, + QPNP_GPIO_VIN2 = 2, + QPNP_GPIO_VIN3 = 3, + QPNP_GPIO_VIN4 = 4, + QPNP_GPIO_VIN5 = 5, + QPNP_GPIO_VIN6 = 6, + QPNP_GPIO_VIN7 = 7 + + @out_strength: the amount of current supplied for an output gpio. + QPNP_GPIO_OUT_STRENGTH_HIGH = 1, + QPNP_GPIO_OUT_STRENGTH_MED = 2, + QPNP_GPIO_OUT_STRENGTH_LOW = 3 + + @source_sel: choose alternate function for the gpio. Certain gpios + can be paired (shorted) with each other. Some gpio pin + can act as alternate functions. + QPNP_GPIO_FUNC_NORMAL = 0, + QPNP_GPIO_FUNC_PAIRED = 1 + QPNP_GPIO_FUNC_1 = 2, + QPNP_GPIO_FUNC_3 = 3, + QPNP_GPIO_DTEST1 = 4, + QPNP_GPIO_DTEST2 = 5, + QPNP_GPIO_DTEST3 = 6, + QPNP_GPIO_DTEST4 = 7 + + @inv_int_pol: Invert polarity before feeding the line to the interrupt + module in pmic. This feature will almost be never used + since the pm8xxx interrupt block can detect both edges + and both levels. + @master_en: 1 = Enable features within the GPIO block based on + configurations. + 0 = Completely disable the GPIO block and let the pin + float with high impedance regardless of other settings. + +[PMIC GPIO clients] + +Required properties : + - gpios : Contains 3 fields of the form <&gpio_controller pmic_gpio_num flags> + +[Example] + +qpnp: qcom,spmi@fc4c0000 { + #address-cells = <1>; + #size-cells = <0>; + interrupt-controller; + #interrupt-cells = <3>; + + qcom,pm8941@0 { + spmi-slave-container; + reg = <0x0>; + #address-cells = <1>; + #size-cells = <1>; + + pm8941_gpios: gpios { + spmi-dev-container; + compatible = "qcom,qpnp-gpio"; + gpio-controller; + #gpio-cells = <2>; + #address-cells = <1>; + #size-cells = <1>; + + qcom,pm8941_gpio1@0xc000 { + reg = <0xc000 0x100>; + qcom,qpnp-gpio-num = <62>; + }; + + qcom,pm8941_gpio2@0xc100 { + reg = <0xc100 0x100>; + qcom,qpnp-gpio-num = <20>; + qcom,qpnp-gpio-cfg = <0x1 0x1 0x1 0x2 0x3 0x2 0x0 0x0 0x1>; + }; + }; + + qcom,testgpio@1000 { + compatible = "qcom,qpnp-testgpio"; + reg = <0x1000 0x1000>; + gpios = <&pm8941_gpios 62 0x0 &pm8941_gpios 20 0x1>; + }; + }; + }; +}; diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 8f3263e6033..8328ee2a3e7 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -477,4 +477,12 @@ config GPIO_PM8XXX_RPC This option enables support for on-chip GPIO found on Qualcomm PM8xxx PMICs through RPC. +config GPIO_QPNP + depends on ARCH_MSMCOPPER + depends on OF_SPMI + depends on MSM_QPNP_INT + tristate "Qualcomm QPNP GPIO support" + help + Say 'y' here to include support for the Qualcomm QPNP gpio + support. QPNP is a SPMI based PMIC implementation. endif diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 83972f1192e..db19ac89e6d 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -51,3 +51,4 @@ obj-$(CONFIG_GPIO_TPS65910) += tps65910-gpio.o obj-$(CONFIG_GPIO_PM8XXX) += pm8xxx-gpio.o obj-$(CONFIG_GPIO_PM8XXX_MPP) += pm8xxx-mpp.o obj-$(CONFIG_GPIO_PM8XXX_RPC) += gpio-pm8xxx-rpc.o +obj-$(CONFIG_GPIO_QPNP) += qpnp-gpio.o diff --git a/drivers/gpio/qpnp-gpio.c b/drivers/gpio/qpnp-gpio.c new file mode 100644 index 00000000000..b09b040c756 --- /dev/null +++ b/drivers/gpio/qpnp-gpio.c @@ -0,0 +1,706 @@ +/* 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 +#include +#include + +#include + +#define Q_REG_ADDR(q_spec, reg_index) \ + ((q_spec)->offset + reg_index) + +#define Q_REG_STATUS1 0x8 +#define Q_NUM_CTL_REGS 5 + +/* control register base address offsets */ +#define Q_REG_IO_CTL1 0x42 +#define Q_REG_INPUT_CTL1 0x43 +#define Q_REG_OUTPUT_CTL1 0x44 +#define Q_REG_OUTPUT_CTL2 0x45 +#define Q_REG_EN_CTL1 0x46 + +/* control register regs array indices */ +#define Q_REG_I_IO_CTL1 0 +#define Q_REG_I_INPUT_CTL1 1 +#define Q_REG_I_OUTPUT_CTL1 2 +#define Q_REG_I_OUTPUT_CTL2 3 +#define Q_REG_I_EN_CTL1 4 + +/* control register configuration */ +#define Q_REG_VIN_SHIFT 0 +#define Q_REG_VIN_MASK 0x7 +#define Q_REG_PULL_SHIFT 4 +#define Q_REG_PULL_MASK 0x70 +#define Q_REG_INPUT_EN_SHIFT 7 +#define Q_REG_INPUT_EN_MASK 0x80 +#define Q_REG_OUT_STRENGTH_SHIFT 0 +#define Q_REG_OUT_STRENGTH_MASK 0x3 +#define Q_REG_OUT_TYPE_SHIFT 6 +#define Q_REG_OUT_TYPE_MASK 0x40 +#define Q_REG_OUT_INVERT_SHIFT 0 +#define Q_REG_OUT_INVERT_MASK 0x1 +#define Q_REG_SRC_SEL_SHIFT 1 +#define Q_REG_SRC_SEL_MASK 0xE +#define Q_REG_OUTPUT_EN_SHIFT 7 +#define Q_REG_OUTPUT_EN_MASK 0x80 +#define Q_REG_MASTER_EN_SHIFT 7 +#define Q_REG_MASTER_EN_MASK 0x80 + + +struct qpnp_gpio_spec { + uint8_t slave; /* 0-15 */ + uint16_t offset; /* 0-255 */ + uint32_t gpio_chip_idx; /* offset from gpio_chip base */ + int irq; /* logical IRQ number */ + u8 regs[Q_NUM_CTL_REGS]; /* Control regs */ +}; + +struct qpnp_gpio_chip { + struct gpio_chip gpio_chip; + struct spmi_device *spmi; + struct qpnp_gpio_spec **pmic_gpios; + struct qpnp_gpio_spec **chip_gpios; + uint32_t pmic_gpio_lowest; + uint32_t pmic_gpio_highest; + struct device_node *int_ctrl; + struct list_head chip_list; +}; + +static LIST_HEAD(qpnp_gpio_chips); +static DEFINE_MUTEX(qpnp_gpio_chips_lock); + +static inline void qpnp_pmic_gpio_set_spec(struct qpnp_gpio_chip *q_chip, + uint32_t pmic_gpio, + struct qpnp_gpio_spec *spec) +{ + q_chip->pmic_gpios[pmic_gpio - q_chip->pmic_gpio_lowest] = spec; +} + +static inline struct qpnp_gpio_spec *qpnp_pmic_gpio_get_spec( + struct qpnp_gpio_chip *q_chip, + uint32_t pmic_gpio) +{ + if (pmic_gpio < q_chip->pmic_gpio_lowest || + pmic_gpio > q_chip->pmic_gpio_highest) + return NULL; + + return q_chip->pmic_gpios[pmic_gpio - q_chip->pmic_gpio_lowest]; +} + +static inline struct qpnp_gpio_spec *qpnp_chip_gpio_get_spec( + struct qpnp_gpio_chip *q_chip, + uint32_t chip_gpio) +{ + if (chip_gpio > q_chip->gpio_chip.ngpio) + return NULL; + + return q_chip->chip_gpios[chip_gpio]; +} + +static inline void qpnp_chip_gpio_set_spec(struct qpnp_gpio_chip *q_chip, + uint32_t chip_gpio, + struct qpnp_gpio_spec *spec) +{ + q_chip->chip_gpios[chip_gpio] = spec; +} + +int qpnp_gpio_config(int gpio, struct qpnp_gpio_cfg *param) +{ + int rc, chip_offset; + struct qpnp_gpio_chip *q_chip; + struct qpnp_gpio_spec *q_spec = NULL; + struct gpio_chip *gpio_chip; + + if (param == NULL) + return -EINVAL; + + mutex_lock(&qpnp_gpio_chips_lock); + list_for_each_entry(q_chip, &qpnp_gpio_chips, chip_list) { + gpio_chip = &q_chip->gpio_chip; + if (gpio >= gpio_chip->base + && gpio < gpio_chip->base + gpio_chip->ngpio) { + chip_offset = gpio - gpio_chip->base; + q_spec = qpnp_chip_gpio_get_spec(q_chip, chip_offset); + if (WARN_ON(!q_spec)) { + mutex_unlock(&qpnp_gpio_chips_lock); + return -ENODEV; + } + break; + } + } + mutex_unlock(&qpnp_gpio_chips_lock); + if (!q_spec) { + pr_err("gpio %d not handled by any pmic\n", gpio); + return -EINVAL; + } + + q_spec->regs[Q_REG_I_IO_CTL1] = (param->vin_sel << + Q_REG_VIN_SHIFT) & Q_REG_VIN_MASK; + q_spec->regs[Q_REG_I_IO_CTL1] |= (param->pull << + Q_REG_PULL_SHIFT) & Q_REG_PULL_MASK; + q_spec->regs[Q_REG_I_INPUT_CTL1] = ((param->direction & + QPNP_GPIO_DIR_IN) ? ((1 << Q_REG_INPUT_EN_SHIFT)) : 0); + + if (param->direction & QPNP_GPIO_DIR_OUT) { + q_spec->regs[Q_REG_I_OUTPUT_CTL1] = (param->out_strength + << Q_REG_OUT_STRENGTH_SHIFT) & Q_REG_OUT_STRENGTH_MASK; + q_spec->regs[Q_REG_I_OUTPUT_CTL1] |= (param->output_type + << Q_REG_OUT_TYPE_SHIFT) & Q_REG_OUT_TYPE_MASK; + } else { + q_spec->regs[Q_REG_I_OUTPUT_CTL1] = 0; + } + + if (param->direction & QPNP_GPIO_DIR_OUT) { + q_spec->regs[Q_REG_I_OUTPUT_CTL2] = (param->inv_int_pol + << Q_REG_OUT_INVERT_SHIFT) & Q_REG_OUT_INVERT_MASK; + q_spec->regs[Q_REG_I_OUTPUT_CTL2] |= (param->src_select + << Q_REG_SRC_SEL_SHIFT) & Q_REG_SRC_SEL_MASK; + q_spec->regs[Q_REG_I_OUTPUT_CTL2] |= (1 << + Q_REG_OUTPUT_EN_SHIFT) & Q_REG_OUTPUT_EN_MASK; + } else { + q_spec->regs[Q_REG_I_OUTPUT_CTL2] = 0; + } + + q_spec->regs[Q_REG_I_EN_CTL1] = (param->master_en << + Q_REG_MASTER_EN_SHIFT) & Q_REG_MASTER_EN_MASK; + + rc = spmi_ext_register_writel(q_chip->spmi->ctrl, q_spec->slave, + Q_REG_ADDR(q_spec, Q_REG_IO_CTL1), + &q_spec->regs[Q_REG_I_IO_CTL1], Q_NUM_CTL_REGS); + if (rc) + dev_err(&q_chip->spmi->dev, "%s: unable to write master" + " enable\n", __func__); + + return rc; +} +EXPORT_SYMBOL(qpnp_gpio_config); + +int qpnp_gpio_map_gpio(uint16_t slave_id, uint32_t pmic_gpio) +{ + struct qpnp_gpio_chip *q_chip; + struct qpnp_gpio_spec *q_spec = NULL; + + mutex_lock(&qpnp_gpio_chips_lock); + list_for_each_entry(q_chip, &qpnp_gpio_chips, chip_list) { + if (q_chip->spmi->sid != slave_id) + continue; + if (q_chip->pmic_gpio_lowest <= pmic_gpio && + q_chip->pmic_gpio_highest >= pmic_gpio) { + q_spec = qpnp_pmic_gpio_get_spec(q_chip, pmic_gpio); + mutex_unlock(&qpnp_gpio_chips_lock); + if (WARN_ON(!q_spec)) + return -ENODEV; + return q_chip->gpio_chip.base + q_spec->gpio_chip_idx; + } + } + mutex_unlock(&qpnp_gpio_chips_lock); + return -EINVAL; +} +EXPORT_SYMBOL(qpnp_gpio_map_gpio); + +static int qpnp_gpio_to_irq(struct gpio_chip *gpio_chip, unsigned offset) +{ + struct qpnp_gpio_chip *q_chip = dev_get_drvdata(gpio_chip->dev); + struct qpnp_gpio_spec *q_spec; + + q_spec = qpnp_chip_gpio_get_spec(q_chip, offset); + if (!q_spec) + return -EINVAL; + + return q_spec->irq; +} + +static int qpnp_gpio_get(struct gpio_chip *gpio_chip, unsigned offset) +{ + int rc, ret_val; + struct qpnp_gpio_chip *q_chip = dev_get_drvdata(gpio_chip->dev); + struct qpnp_gpio_spec *q_spec = NULL; + u8 buf[1]; + + if (WARN_ON(!q_chip)) + return -ENODEV; + + q_spec = qpnp_chip_gpio_get_spec(q_chip, offset); + if (WARN_ON(!q_spec)) + return -ENODEV; + + /* gpio val is from RT status iff input is enabled */ + if (q_spec->regs[Q_REG_I_INPUT_CTL1] & Q_REG_INPUT_EN_MASK) { + /* INT_RT_STS */ + rc = spmi_ext_register_readl(q_chip->spmi->ctrl, q_spec->slave, + Q_REG_ADDR(q_spec, Q_REG_STATUS1), + &buf[0], 1); + return buf[0]; + + } else { + ret_val = (q_spec->regs[Q_REG_I_OUTPUT_CTL2] & + Q_REG_OUT_INVERT_MASK) >> Q_REG_OUT_INVERT_SHIFT; + return ret_val; + } + + return 0; +} + +static int __qpnp_gpio_set(struct qpnp_gpio_chip *q_chip, + struct qpnp_gpio_spec *q_spec, int value) +{ + int rc; + + if (!q_chip || !q_spec) + return -EINVAL; + + q_spec->regs[Q_REG_I_OUTPUT_CTL2] &= ~(1 << Q_REG_OUT_INVERT_SHIFT); + + if (value) + q_spec->regs[Q_REG_I_OUTPUT_CTL2] |= + (1 << Q_REG_OUT_INVERT_SHIFT); + + rc = spmi_ext_register_writel(q_chip->spmi->ctrl, q_spec->slave, + Q_REG_ADDR(q_spec, Q_REG_OUTPUT_CTL2), + &q_spec->regs[Q_REG_I_OUTPUT_CTL2], 1); + if (rc) + dev_err(&q_chip->spmi->dev, "%s: spmi write failed\n", + __func__); + return rc; +} + + +static void qpnp_gpio_set(struct gpio_chip *gpio_chip, + unsigned offset, int value) +{ + struct qpnp_gpio_chip *q_chip = dev_get_drvdata(gpio_chip->dev); + struct qpnp_gpio_spec *q_spec; + + if (WARN_ON(!q_chip)) + return; + + q_spec = qpnp_chip_gpio_get_spec(q_chip, offset); + if (WARN_ON(!q_spec)) + return; + + __qpnp_gpio_set(q_chip, q_spec, value); +} + +static int qpnp_gpio_set_direction(struct qpnp_gpio_chip *q_chip, + struct qpnp_gpio_spec *q_spec, int direction) +{ + int rc; + + if (!q_chip || !q_spec) + return -EINVAL; + + if (direction & QPNP_GPIO_DIR_IN) { + q_spec->regs[Q_REG_I_INPUT_CTL1] |= + (1 << Q_REG_INPUT_EN_SHIFT); + q_spec->regs[Q_REG_I_OUTPUT_CTL2] &= + ~(1 << Q_REG_OUTPUT_EN_SHIFT); + } else { + q_spec->regs[Q_REG_I_INPUT_CTL1] &= + ~(1 << Q_REG_INPUT_EN_SHIFT); + q_spec->regs[Q_REG_I_OUTPUT_CTL2] |= + (1 << Q_REG_OUTPUT_EN_SHIFT); + } + + rc = spmi_ext_register_writel(q_chip->spmi->ctrl, q_spec->slave, + Q_REG_ADDR(q_spec, Q_REG_INPUT_CTL1), + &q_spec->regs[Q_REG_I_INPUT_CTL1], 3); + return rc; +} + +static int qpnp_gpio_direction_input(struct gpio_chip *gpio_chip, + unsigned offset) +{ + struct qpnp_gpio_chip *q_chip = dev_get_drvdata(gpio_chip->dev); + struct qpnp_gpio_spec *q_spec; + + if (WARN_ON(!q_chip)) + return -ENODEV; + + q_spec = qpnp_chip_gpio_get_spec(q_chip, offset); + if (WARN_ON(!q_spec)) + return -ENODEV; + + return qpnp_gpio_set_direction(q_chip, q_spec, QPNP_GPIO_DIR_IN); +} + +static int qpnp_gpio_direction_output(struct gpio_chip *gpio_chip, + unsigned offset, + int val) +{ + int rc; + struct qpnp_gpio_chip *q_chip = dev_get_drvdata(gpio_chip->dev); + struct qpnp_gpio_spec *q_spec; + + if (WARN_ON(!q_chip)) + return -ENODEV; + + q_spec = qpnp_chip_gpio_get_spec(q_chip, offset); + if (WARN_ON(!q_spec)) + return -ENODEV; + + rc = __qpnp_gpio_set(q_chip, q_spec, val); + if (rc) + return rc; + + rc = qpnp_gpio_set_direction(q_chip, q_spec, QPNP_GPIO_DIR_OUT); + + return rc; +} + +static int qpnp_gpio_of_gpio_xlate(struct gpio_chip *gpio_chip, + struct device_node *np, + const void *gpio_spec, u32 *flags) +{ + struct qpnp_gpio_chip *q_chip = dev_get_drvdata(gpio_chip->dev); + struct qpnp_gpio_spec *q_spec; + const __be32 *gpio = gpio_spec; + u32 n = be32_to_cpup(gpio); + + if (WARN_ON(gpio_chip->of_gpio_n_cells < 2)) { + pr_err("%s: of_gpio_n_cells < 2\n", __func__); + return -EINVAL; + } + + q_spec = qpnp_pmic_gpio_get_spec(q_chip, n); + if (!q_spec) { + pr_err("%s: no such PMIC gpio %u in device topology\n", + __func__, n); + return -EINVAL; + } + + if (flags) + *flags = be32_to_cpu(gpio[1]); + + return q_spec->gpio_chip_idx; +} + +static int qpnp_gpio_config_default(struct spmi_device *spmi, + const __be32 *prop, int gpio) +{ + struct qpnp_gpio_cfg param; + int rc; + + dev_dbg(&spmi->dev, "%s: p[0]: 0x%x p[1]: 0x%x p[2]: 0x%x p[3]:" + " 0x%x p[4]: 0x%x p[5]: 0x%x p[6]: 0x%x p[7]: 0x%x" + " p[8]: 0x%x\n", __func__, + be32_to_cpup(&prop[0]), be32_to_cpup(&prop[1]), + be32_to_cpup(&prop[2]), be32_to_cpup(&prop[3]), + be32_to_cpup(&prop[4]), be32_to_cpup(&prop[5]), + be32_to_cpup(&prop[6]), be32_to_cpup(&prop[7]), + be32_to_cpup(&prop[8])); + + param.direction = be32_to_cpup(&prop[0]); + param.output_type = be32_to_cpup(&prop[1]); + param.output_value = be32_to_cpup(&prop[2]); + param.pull = be32_to_cpup(&prop[3]); + param.vin_sel = be32_to_cpup(&prop[4]); + param.out_strength = be32_to_cpup(&prop[5]); + param.src_select = be32_to_cpup(&prop[6]); + param.inv_int_pol = be32_to_cpup(&prop[7]); + param.master_en = be32_to_cpup(&prop[8]); + + rc = qpnp_gpio_config(gpio, ¶m); + if (rc) + dev_err(&spmi->dev, "%s: unable to set default config for" + " gpio %d\n", __func__, gpio); + return rc; +} + +static int qpnp_gpio_free_chip(struct qpnp_gpio_chip *q_chip) +{ + struct spmi_device *spmi = q_chip->spmi; + int rc, i; + + if (q_chip->chip_gpios) + for (i = 0; i < spmi->num_dev_node; i++) + kfree(q_chip->chip_gpios[i]); + + mutex_lock(&qpnp_gpio_chips_lock); + list_del(&q_chip->chip_list); + mutex_unlock(&qpnp_gpio_chips_lock); + rc = gpiochip_remove(&q_chip->gpio_chip); + if (rc) + dev_err(&q_chip->spmi->dev, "%s: unable to remove gpio\n", + __func__); + kfree(q_chip->chip_gpios); + kfree(q_chip->pmic_gpios); + kfree(q_chip); + return rc; +} + +static int qpnp_gpio_probe(struct spmi_device *spmi) +{ + struct qpnp_gpio_chip *q_chip; + struct resource *res; + struct qpnp_gpio_spec *q_spec; + const __be32 *prop; + int i, rc, ret, gpio, len; + int lowest_gpio = INT_MAX, highest_gpio = INT_MIN; + u32 intspec[3]; + + q_chip = kzalloc(sizeof(*q_chip), GFP_KERNEL); + if (!q_chip) { + dev_err(&spmi->dev, "%s: Can't allocate gpio_chip\n", + __func__); + return -ENOMEM; + } + q_chip->spmi = spmi; + dev_set_drvdata(&spmi->dev, q_chip); + + mutex_lock(&qpnp_gpio_chips_lock); + list_add(&q_chip->chip_list, &qpnp_gpio_chips); + mutex_unlock(&qpnp_gpio_chips_lock); + + /* first scan through nodes to find the range required for allocation */ + for (i = 0; i < spmi->num_dev_node; i++) { + prop = of_get_property(spmi->dev_node[i].of_node, + "qcom,qpnp-gpio-num", &len); + if (!prop) { + dev_err(&spmi->dev, "%s: unable to get" + " qcom,qpnp-gpio-num property\n", __func__); + ret = -EINVAL; + goto err_probe; + } else if (len != sizeof(__be32)) { + dev_err(&spmi->dev, "%s: Invalid qcom,qpnp-gpio-num" + " property\n", __func__); + ret = -EINVAL; + goto err_probe; + } + + gpio = be32_to_cpup(prop); + if (gpio < lowest_gpio) + lowest_gpio = gpio; + if (gpio > highest_gpio) + highest_gpio = gpio; + } + + if (highest_gpio < lowest_gpio) { + dev_err(&spmi->dev, "%s: no device nodes specified in" + " topology\n", __func__); + ret = -EINVAL; + goto err_probe; + } else if (lowest_gpio == 0) { + dev_err(&spmi->dev, "%s: 0 is not a valid PMIC GPIO\n", + __func__); + ret = -EINVAL; + goto err_probe; + } + + q_chip->pmic_gpio_lowest = lowest_gpio; + q_chip->pmic_gpio_highest = highest_gpio; + + /* allocate gpio lookup tables */ + q_chip->pmic_gpios = kzalloc(sizeof(struct qpnp_gpio_spec *) * + highest_gpio - lowest_gpio + 1, + GFP_KERNEL); + q_chip->chip_gpios = kzalloc(sizeof(struct qpnp_gpio_spec *) * + spmi->num_dev_node, GFP_KERNEL); + if (!q_chip->pmic_gpios || !q_chip->chip_gpios) { + dev_err(&spmi->dev, "%s: unable to allocate memory\n", + __func__); + ret = -ENOMEM; + goto err_probe; + } + + /* get interrupt controller device_node */ + q_chip->int_ctrl = of_irq_find_parent(spmi->dev.of_node); + if (!q_chip->int_ctrl) { + dev_err(&spmi->dev, "%s: Can't find interrupt parent\n", + __func__); + ret = -EINVAL; + goto err_probe; + } + + /* now scan through again and populate the lookup table */ + for (i = 0; i < spmi->num_dev_node; i++) { + res = qpnp_get_resource(spmi, i, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&spmi->dev, "%s: node %s is missing has no" + " base address definition\n", + __func__, spmi->dev_node[i].of_node->full_name); + } + + prop = of_get_property(spmi->dev_node[i].of_node, + "qcom,qpnp-gpio-num", &len); + if (!prop) { + dev_err(&spmi->dev, "%s: unable to get" + " qcom,qpnp-gpio-num property\n", __func__); + ret = -EINVAL; + goto err_probe; + } else if (len != sizeof(__be32)) { + dev_err(&spmi->dev, "%s: Invalid qcom,qpnp-gpio-num" + " property\n", __func__); + ret = -EINVAL; + goto err_probe; + } + gpio = be32_to_cpup(prop); + + q_spec = kzalloc(sizeof(struct qpnp_gpio_spec), + GFP_KERNEL); + if (!q_spec) { + dev_err(&spmi->dev, "%s: unable to allocate" + " memory\n", + __func__); + ret = -ENOMEM; + goto err_probe; + } + + q_spec->slave = spmi->sid; + q_spec->offset = res->start; + q_spec->gpio_chip_idx = i; + + /* call into irq_domain to get irq mapping */ + intspec[0] = q_chip->spmi->sid; + intspec[1] = (q_spec->offset >> 8) & 0xFF; + intspec[2] = 0; + q_spec->irq = irq_create_of_mapping(q_chip->int_ctrl, + intspec, 3); + if (!q_spec->irq) { + dev_err(&spmi->dev, "%s: invalid irq for gpio" + " %u\n", __func__, gpio); + ret = -EINVAL; + goto err_probe; + } + /* initialize lookup table entries */ + qpnp_pmic_gpio_set_spec(q_chip, gpio, q_spec); + qpnp_chip_gpio_set_spec(q_chip, i, q_spec); + } + + q_chip->gpio_chip.base = -1; + q_chip->gpio_chip.ngpio = spmi->num_dev_node; + q_chip->gpio_chip.label = "qpnp-gpio"; + q_chip->gpio_chip.direction_input = qpnp_gpio_direction_input; + q_chip->gpio_chip.direction_output = qpnp_gpio_direction_output; + q_chip->gpio_chip.to_irq = qpnp_gpio_to_irq; + q_chip->gpio_chip.get = qpnp_gpio_get; + q_chip->gpio_chip.set = qpnp_gpio_set; + q_chip->gpio_chip.dev = &spmi->dev; + q_chip->gpio_chip.of_xlate = qpnp_gpio_of_gpio_xlate; + q_chip->gpio_chip.of_gpio_n_cells = 2; + q_chip->gpio_chip.can_sleep = 0; + + rc = gpiochip_add(&q_chip->gpio_chip); + if (rc) { + dev_err(&spmi->dev, "%s: Can't add gpio chip, rc = %d\n", + __func__, rc); + ret = rc; + goto err_probe; + } + + /* now configure gpio defaults if they exist */ + for (i = 0; i < spmi->num_dev_node; i++) { + q_spec = qpnp_chip_gpio_get_spec(q_chip, i); + if (WARN_ON(!q_spec)) + return -ENODEV; + + /* It's not an error to not config a default */ + prop = of_get_property(spmi->dev_node[i].of_node, + "qcom,qpnp-gpio-cfg", &len); + /* 9 data values constitute one tuple */ + if (prop && (len != (9 * sizeof(__be32)))) { + dev_err(&spmi->dev, "%s: invalid format for" + " qcom,qpnp-gpio-cfg property\n", + __func__); + ret = -EINVAL; + goto err_probe; + } else if (prop) { + rc = qpnp_gpio_config_default(spmi, prop, + q_chip->gpio_chip.base + i); + if (rc) { + ret = rc; + goto err_probe; + } + } else { + /* initialize with hardware defaults */ + rc = spmi_ext_register_readl( + q_chip->spmi->ctrl, q_spec->slave, + Q_REG_ADDR(q_spec, Q_REG_IO_CTL1), + &q_spec->regs[Q_REG_I_IO_CTL1], + Q_NUM_CTL_REGS); + q_spec->regs[Q_REG_I_EN_CTL1] |= + (1 << Q_REG_MASTER_EN_SHIFT); + rc = spmi_ext_register_writel( + q_chip->spmi->ctrl, q_spec->slave, + Q_REG_ADDR(q_spec, Q_REG_EN_CTL1), + &q_spec->regs[Q_REG_EN_CTL1], 1); + if (rc) { + dev_err(&spmi->dev, "%s: spmi write" + " failed\n", __func__); + ret = rc; + goto err_probe; + } + } + } + + dev_dbg(&spmi->dev, "%s: gpio_chip registered between %d-%u\n", + __func__, q_chip->gpio_chip.base, + (q_chip->gpio_chip.base + q_chip->gpio_chip.ngpio) - 1); + return 0; + +err_probe: + qpnp_gpio_free_chip(q_chip); + return ret; +} + +static int qpnp_gpio_remove(struct spmi_device *spmi) +{ + struct qpnp_gpio_chip *q_chip = dev_get_drvdata(&spmi->dev); + + return qpnp_gpio_free_chip(q_chip); +} + +static struct of_device_id spmi_match_table[] = { + { .compatible = "qcom,qpnp-gpio", + }, + {} +}; + +static const struct spmi_device_id qpnp_gpio_id[] = { + { "qcom,qpnp-gpio", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spmi, qpnp_gpio_id); + +static struct spmi_driver qpnp_gpio_driver = { + .driver = { + .name = "qcom,qpnp-gpio", + .of_match_table = spmi_match_table, + }, + .probe = qpnp_gpio_probe, + .remove = qpnp_gpio_remove, + .id_table = qpnp_gpio_id, +}; + +static int __init qpnp_gpio_init(void) +{ + return spmi_driver_register(&qpnp_gpio_driver); +} + +static void __exit qpnp_gpio_exit(void) +{ +} + +MODULE_AUTHOR( + "Michael Bohan "); +MODULE_DESCRIPTION("QPNP PMIC gpio driver"); +MODULE_LICENSE("GPLv2"); + +module_init(qpnp_gpio_init); +module_exit(qpnp_gpio_exit); diff --git a/include/linux/qpnp/gpio.h b/include/linux/qpnp/gpio.h new file mode 100644 index 00000000000..0447a084b1e --- /dev/null +++ b/include/linux/qpnp/gpio.h @@ -0,0 +1,114 @@ +/* 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 + +#define QPNP_GPIO_DIR_OUT 1 +#define QPNP_GPIO_DIR_IN 2 +#define QPNP_GPIO_DIR_BOTH (QPNP_GPIO_DIR_OUT | QPNP_GPIO_DIR_IN) + +#define QPNP_GPIO_OUT_BUF_OPEN_DRAIN 1 +#define QPNP_GPIO_OUT_BUF_CMOS 0 + +#define QPNP_GPIO_VIN0 0 +#define QPNP_GPIO_VIN1 1 +#define QPNP_GPIO_VIN2 2 +#define QPNP_GPIO_VIN3 3 +#define QPNP_GPIO_VIN4 4 +#define QPNP_GPIO_VIN5 5 +#define QPNP_GPIO_VIN6 6 +#define QPNP_GPIO_VIN7 7 + +#define QPNP_GPIO_PULL_UP_30 0 +#define QPNP_GPIO_PULL_UP_1P5 1 +#define QPNP_GPIO_PULL_UP_31P5 2 +#define QPNP_GPIO_PULL_UP_1P5_30 3 +#define QPNP_GPIO_PULL_DN 4 +#define QPNP_GPIO_PULL_NO 5 + +#define QPNP_GPIO_OUT_STRENGTH_LOW 1 +#define QPNP_GPIO_OUT_STRENGTH_MED 2 +#define QPNP_GPIO_OUT_STRENGTH_HIGH 3 + +#define QPNP_GPIO_FUNC_NORMAL 0 +#define QPNP_GPIO_FUNC_PAIRED 1 +#define QPNP_GPIO_FUNC_1 2 +#define QPNP_GPIO_FUNC_2 3 +#define QPNP_GPIO_DTEST1 4 +#define QPNP_GPIO_DTEST2 5 +#define QPNP_GPIO_DTEST3 6 +#define QPNP_GPIO_DTEST4 7 + +/** + * struct qpnp_gpio_cfg - structure to specify gpio configurtion values + * @direction: indicates whether the gpio should be input, output, or + * both. Should be of the type QPNP_GPIO_DIR_* + * @output_type: indicates gpio should be configured as CMOS or open + * drain. Should be of the type QPNP_GPIO_OUT_BUF_* + * @output_value: The gpio output value of the gpio line - 0 or 1 + * @pull: Indicates whether a pull up or pull down should be + * applied. If a pullup is required the current strength + * needs to be specified. Current values of 30uA, 1.5uA, + * 31.5uA, 1.5uA with 30uA boost are supported. This value + * should be one of the QPNP_GPIO_PULL_* + * @vin_sel: specifies the voltage level when the output is set to 1. + * For an input gpio specifies the voltage level at which + * the input is interpreted as a logical 1. + * @out_strength: the amount of current supplied for an output gpio, + * should be of the type QPNP_GPIO_STRENGTH_* + * @source_sel: choose alternate function for the gpio. Certain gpios + * can be paired (shorted) with each other. Some gpio pin + * can act as alternate functions. This parameter should + * be of type QPNP_GPIO_FUNC_* + * @inv_int_pol: Invert polarity before feeding the line to the interrupt + * module in pmic. This feature will almost be never used + * since the pm8xxx interrupt block can detect both edges + * and both levels. + * @master_en: 1 = Enable features within the GPIO block based on + * configurations. + * 0 = Completely disable the GPIO block and let the pin + * float with high impedance regardless of other settings. + */ +struct qpnp_gpio_cfg { + int direction; + int output_type; + int output_value; + int pull; + int vin_sel; + int out_strength; + int src_select; + int inv_int_pol; + int master_en; +}; + +/** + * qpnp_gpio_config - Apply gpio configuration for Linux gpio + * @gpio: Linux gpio number to configure. + * @param: parameters to configure. + * + * This routine takes a Linux gpio number that corresponds with a + * PMIC gpio and applies the configuration specified in 'param'. + * This gpio number can be ascertained by of_get_gpio_flags() or + * the qpnp_gpio_map_gpio() API. + */ +int qpnp_gpio_config(int gpio, struct qpnp_gpio_cfg *param); + +/** + * qpnp_gpio_map_gpio - Obtain Linux GPIO number from device spec + * @slave_id: slave_id of the spmi_device for the gpio in question. + * @pmic_gpio: PMIC gpio number to lookup. + * + * This routine is used in legacy configurations that do not support + * Device Tree. If you are using Device Tree, you should not use this. + * For such cases, use of_get_gpio() instead. + */ +int qpnp_gpio_map_gpio(uint16_t slave_id, uint32_t pmic_gpio);