diff mbox

[RFC,2/6] qpnp: add support for Qualcomm QPNP PMICs

Message ID 1403266885-911-3-git-send-email-svarbanov@mm-sol.com (mailing list archive)
State Superseded, archived
Headers show

Commit Message

Stanimir Varbanov June 20, 2014, 12:21 p.m. UTC
The Qualcomm plug-and-play (QPNP) PMIC chips are components
used with the Snapdragon series SoC family. QPNP chips make
use of Qualcomm's SPMI register convention.

This driver exists largely as a glue component, it exists to
be an owner of an SPMI regmap for children devices described
in device tree.

Suggested-by: Josh Cartwright <joshc@codeaurora.org>
Signed-off-by: Ivan T. Ivanov <iivanov@mm-sol.com>
Signed-off-by: Stanimir Varbanov <svarbanov@mm-sol.com>
---
 drivers/Kconfig          |    2 +
 drivers/Makefile         |    1 +
 drivers/qpnp/Kconfig     |   12 ++
 drivers/qpnp/Makefile    |    1 +
 drivers/qpnp/qpnp-bus.c  |  475 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/qpnp/qpnp-spmi.c |   63 ++++++
 include/linux/qpnp.h     |   83 ++++++++
 7 files changed, 637 insertions(+), 0 deletions(-)
 create mode 100644 drivers/qpnp/Kconfig
 create mode 100644 drivers/qpnp/Makefile
 create mode 100644 drivers/qpnp/qpnp-bus.c
 create mode 100644 drivers/qpnp/qpnp-spmi.c
 create mode 100644 include/linux/qpnp.h
diff mbox

Patch

diff --git a/drivers/Kconfig b/drivers/Kconfig
index 0e87a34..412e6ae 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -176,4 +176,6 @@  source "drivers/powercap/Kconfig"
 
 source "drivers/mcb/Kconfig"
 
+source "drivers/qpnp/Kconfig"
+
 endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index f98b50d..bf2f3f3 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -158,3 +158,4 @@  obj-$(CONFIG_NTB)		+= ntb/
 obj-$(CONFIG_FMC)		+= fmc/
 obj-$(CONFIG_POWERCAP)		+= powercap/
 obj-$(CONFIG_MCB)		+= mcb/
+obj-y				+= qpnp/
diff --git a/drivers/qpnp/Kconfig b/drivers/qpnp/Kconfig
new file mode 100644
index 0000000..c86ab52
--- /dev/null
+++ b/drivers/qpnp/Kconfig
@@ -0,0 +1,12 @@ 
+config QPNP_SPMI
+	tristate "Qualcomm PMIC QPNP driver"
+	depends on ARCH_QCOM
+	select REGMAP_SPMI
+	help
+	  This enables basic support for the Qualcomm QPNP PMICs.
+	  These PMICs are currently used with the Snapdragon 800 series of
+	  SoCs.  Note, that this will only be useful paired with descriptions
+	  of the independent functions as children nodes in the device tree.
+
+	  Say M here if you want to include support for the PM8x41 series as a
+	  module.  The module will be called "qpnp-spmi".
diff --git a/drivers/qpnp/Makefile b/drivers/qpnp/Makefile
new file mode 100644
index 0000000..753bef8
--- /dev/null
+++ b/drivers/qpnp/Makefile
@@ -0,0 +1 @@ 
+obj-$(CONFIG_QPNP_SPMI)	+= qpnp-bus.o qpnp-spmi.o
diff --git a/drivers/qpnp/qpnp-bus.c b/drivers/qpnp/qpnp-bus.c
new file mode 100644
index 0000000..70f72ad
--- /dev/null
+++ b/drivers/qpnp/qpnp-bus.c
@@ -0,0 +1,475 @@ 
+/* Copyright (c) 2014, The Linux Foundation. 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/errno.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/qpnp.h>
+#include <linux/slab.h>
+
+#define QPNP_RESOURCE_SIZE	256
+
+static inline struct qpnp_driver *to_qpnp_driver(struct device_driver *dd)
+{
+	return container_of(dd, struct qpnp_driver, driver);
+}
+
+static void qpnp_device_release(struct device *dev)
+{
+	struct qpnp_device *qdev = to_qpnp_device(dev);
+
+	of_device_node_put(dev);
+	kfree(qdev->resource);
+	kfree(qdev);
+}
+
+static int qpnp_device_match(struct device *dev, struct device_driver *dd)
+{
+	if (of_driver_match_device(dev, dd))
+		return 1;
+
+	return 0;
+}
+
+static int qpnp_driver_probe(struct device *dev)
+{
+	const struct qpnp_driver *qdrv = to_qpnp_driver(dev->driver);
+	struct qpnp_device *qdev = to_qpnp_device(dev);
+
+	return qdrv->probe(qdev);
+}
+
+static int qpnp_driver_remove(struct device *dev)
+{
+	const struct qpnp_driver *qdrv = to_qpnp_driver(dev->driver);
+	struct qpnp_device *qdev = to_qpnp_device(dev);
+
+	qdrv->remove(qdev);
+	return 0;
+}
+
+static struct bus_type qpnp_bus_type = {
+	.name = "qpnp",
+	.match = qpnp_device_match,
+	.probe = qpnp_driver_probe,
+	.remove = qpnp_driver_remove,
+};
+
+/**
+ * qpnp_set_device_name - Use the device node data to assign a unique name
+ * @dev: pointer to device structure that is linked to a device tree node
+ *
+ * This routine will first try using the reg property value to derive a
+ * unique name.  As a last resort it will use the node name followed by
+ * a unique number.
+ */
+static void qpnp_set_device_name(struct device *dev, u8 sid)
+{
+	static atomic_t bus_no_reg_magic;
+	struct device_node *np = dev->of_node;
+	const __be32 *reg;
+	const __be32 *addrp = NULL;
+	u64 addr;
+
+	reg = of_get_property(np, "reg", NULL);
+	if (reg) {
+		addrp = of_get_address(np, 0, NULL, NULL);
+		if (addrp)
+			addr = of_read_number(addrp, 1);
+	}
+
+	if (!addrp)
+		/*
+		 * No BusID, use the np name and add a globally
+		 * incremented counter (and pray...)
+		 */
+		addr = atomic_add_return(1, &bus_no_reg_magic);
+
+	dev_set_name(dev, "%x.%02x.%s", sid, (u8) addr, np->name);
+}
+
+/**
+ * qpnp_device_alloc() - Allocate a new QPNP device
+ *
+ * Allocate a new QPNP device, fills QPNP device members and set device name.
+ */
+static struct qpnp_device *qpnp_device_alloc(struct device_node *np,
+					     struct device *parent, u8 sid,
+					     struct regmap *map)
+{
+	struct qpnp_device *qdev;
+
+	qdev = kzalloc(sizeof(*qdev), GFP_KERNEL);
+	if (!qdev)
+		return NULL;
+
+	qdev->sid = sid;
+	qdev->map = map;
+	qdev->dev.of_node = of_node_get(np);
+	qdev->dev.parent = parent;
+	qdev->dev.release = qpnp_device_release;
+	qdev->dev.bus = &qpnp_bus_type;
+
+	qpnp_set_device_name(&qdev->dev, sid);
+
+	return qdev;
+}
+
+/**
+ * qpnp_index_to_resource - Parse device tree address and return as resource
+ */
+static int
+qpnp_index_to_resource(struct device *dev, int index, struct resource *res)
+{
+	struct device_node *np = dev->of_node;
+	const __be32 *addrp;
+	u64 addr;
+	const char *name = NULL;
+
+	addrp = of_get_address(np, index, NULL, NULL);
+	if (addrp == NULL)
+		return -EINVAL;
+
+	addr = of_read_number(addrp, 1);
+
+	if (addr == OF_BAD_ADDR)
+		return -EINVAL;
+
+	/* Get optional "reg-names" property to add a name to a resource */
+	of_property_read_string_index(np, "reg-names", index, &name);
+
+	/* peripheral id is 8bits */
+	addr &= 0xff;
+	addr <<= 8;
+
+	res->start = addr;
+	res->end = addr + QPNP_RESOURCE_SIZE - 1;
+	res->flags = IORESOURCE_REG;
+	res->name = name ? name : np->full_name;
+
+	return 0;
+}
+
+/**
+ * qpnp_device_add_resources() - Allocate and add resources for QPNP device
+ * @qdev: qpnp device pointer
+ */
+static int qpnp_device_add_resources(struct qpnp_device *qdev)
+{
+	struct device_node *np = qdev->dev.of_node;
+	struct resource *res;
+	int ret, idx, num_resources = 0;
+
+	while (of_get_address(np, num_resources, NULL, NULL) != NULL)
+		num_resources++;
+
+	if (!num_resources)
+		return 0;
+
+	res = kcalloc(num_resources, sizeof(*res), GFP_KERNEL);
+	if (!res)
+		return -ENOMEM;
+
+	qdev->num_resources = num_resources;
+	qdev->resource = res;
+
+	/* Populate the resource table */
+	for (idx = 0; idx < num_resources; idx++, res++) {
+		ret = qpnp_index_to_resource(&qdev->dev, idx, res);
+		if (ret)
+			goto err;
+	}
+
+	return 0;
+err:
+	kfree(qdev->resource);
+	return ret;
+}
+
+/**
+ * qpnp_device_add - Alloc, initialize and register an device
+ * @np: pointer to node to create device for
+ * @parent: Linux device model parent device.
+ * @sid: Slave ID
+ *
+ * Returns pointer to created qpnp device, or NULL if a device was not
+ * registered.
+ */
+static struct qpnp_device *qpnp_device_add(struct device_node *np,
+					   struct device *parent, u8 sid,
+					   struct regmap *map)
+{
+	struct qpnp_device *qdev;
+	int ret;
+
+	qdev = qpnp_device_alloc(np, parent, sid, map);
+	if (!qdev)
+		return NULL;
+
+	ret = qpnp_device_add_resources(qdev);
+	if (ret)
+		goto fail;
+
+	ret = device_register(&qdev->dev);
+	if (ret)
+		goto fail;
+
+	return qdev;
+
+fail:
+	put_device(&qdev->dev);
+	return NULL;
+}
+
+static int qpnp_device_remove(struct device *dev, void *unused)
+{
+	device_unregister(dev);
+
+	return 0;
+}
+
+/**
+ * qpnp_node_create() - Create a device for a node and its children.
+ * @np: device np of the node to instantiate
+ * @parent: parent for new device
+ * @sid: Slave ID
+ *
+ * Creates a qpnp_device for the provided device_node, and
+ * recursively create devices for all the child nodes.
+ */
+static int qpnp_node_create(struct device_node *np, struct device *parent,
+			    u8 sid, struct regmap *map)
+{
+	struct device_node *child;
+	struct qpnp_device *qdev;
+	int ret = 0;
+
+	/* Make sure it has a compatible property */
+	if (!of_get_property(np, "compatible", NULL)) {
+		pr_debug("skipping %s, no compatible prop\n", np->full_name);
+		return 0;
+	}
+
+	qdev = qpnp_device_add(np, parent, sid, map);
+	if (!qdev)
+		return -ENOMEM;
+
+	for_each_available_child_of_node(np, child) {
+		pr_debug("   create child: %s\n", child->full_name);
+		ret = qpnp_node_create(child, &qdev->dev, sid, map);
+		if (ret) {
+			of_node_put(child);
+			break;
+		}
+	}
+
+	return ret;
+}
+
+/**
+ * qpnp_populate_devices() - Populate qpnp_devices from device tree data
+ * @root: parent of the first level to probe
+ * @parent: parent to hook devices from
+ * @sid: Slave ID
+ *
+ * This function walks the device tree and creates devices from nodes.
+ * It follows convention of requiring all device nodes to have a 'compatible'
+ * property, and it is suitable for creating devices which are children
+ * of the root.
+ *
+ * Returns 0 on success, < 0 on failure.
+ */
+int qpnp_populate_devices(struct device_node *root, struct device *parent,
+			  u8 sid, struct regmap *map)
+{
+	struct device_node *child;
+	int ret = 0;
+
+	root = of_node_get(root);
+	if (!root)
+		return -EINVAL;
+
+	for_each_available_child_of_node(root, child) {
+		ret = qpnp_node_create(child, parent, sid, map);
+		if (ret)
+			break;
+	}
+
+	of_node_put(root);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(qpnp_populate_devices);
+
+/**
+ * qpnp_remove_devices(): remove a QPNP devices
+ * @root: root to devices to be removed
+ */
+void qpnp_remove_devices(struct device *root)
+{
+	device_for_each_child(root, NULL, qpnp_device_remove);
+}
+EXPORT_SYMBOL_GPL(qpnp_remove_devices);
+
+/**
+ * __of_irq_get - Decode a node's IRQ and return it as a Linux irq number
+ * @dev: pointer to device tree node
+ * @index: zero-based index of the irq
+ *
+ * Returns Linux irq number on success, -EPROBE_DEFER if the irq domain
+ * is not yet created.
+ */
+static int __of_irq_get(struct device_node *dev, int index)
+{
+	struct of_phandle_args oirq;
+	struct irq_domain *domain;
+	int ret;
+
+	ret = of_irq_parse_one(dev, index, &oirq);
+	if (ret)
+		return ret;
+
+	domain = irq_find_host(oirq.np);
+	if (!domain)
+		return -EPROBE_DEFER;
+
+	return irq_create_of_mapping(&oirq);
+}
+
+/**
+ * qpnp_get_irq - get an IRQ for a device
+ */
+int qpnp_get_irq(struct qpnp_device *qdev, unsigned int num)
+{
+	return __of_irq_get(qdev->dev.of_node, num);
+}
+EXPORT_SYMBOL_GPL(qpnp_get_irq);
+
+/**
+ * qpnp_get_irq_byname - get an IRQ for a device by name
+ */
+int qpnp_get_irq_byname(struct qpnp_device *qdev, const char *name)
+{
+	struct device_node *np = qdev->dev.of_node;
+	int index;
+
+	if (!name)
+		return -EINVAL;
+
+	index = of_property_match_string(np, "interrupt-names", name);
+	if (index < 0)
+		return index;
+
+	return __of_irq_get(np, index);
+}
+EXPORT_SYMBOL_GPL(qpnp_get_irq_byname);
+
+/**
+ * qpnp_get_resource - get a resource for a device
+ * @dev: qpnp device
+ * @type: resource type
+ * @num: resource index
+ */
+struct resource *qpnp_get_resource(struct qpnp_device *dev,
+				   unsigned int type, unsigned int num)
+{
+	struct resource *res;
+	int idx;
+
+	if (type != IORESOURCE_REG)
+		return NULL;
+
+	for (idx = 0; idx < dev->num_resources; idx++) {
+		res = &dev->resource[idx];
+
+		if (type == resource_type(res) && num-- == 0)
+			return res;
+	}
+
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(qpnp_get_resource);
+
+/**
+ * qqpnp_get_region_byname - get a resource for a device by name
+ * @dev: qpnp device
+ * @type: resource type
+ * @name: resource name
+ */
+struct resource *qpnp_get_resource_byname(struct qpnp_device *dev,
+					  unsigned int type, const char *name)
+{
+	struct resource *res;
+	int idx;
+
+	if (type != IORESOURCE_REG)
+		return NULL;
+
+	for (idx = 0; idx < dev->num_resources; idx++) {
+		res = &dev->resource[idx];
+
+		if (!res->name)
+			continue;
+
+		if (type == resource_type(res) && !strcmp(res->name, name))
+			return res;
+	}
+
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(qpnp_get_resource_byname);
+
+/**
+ * qpnp_driver_register() - Register client driver with QPNP bus
+ * @qdrv: client driver to be associated with client-device.
+ *
+ * This API will register the client driver with the QPNP framework.
+ * It is typically called from the driver's module-init function.
+ */
+int qpnp_driver_register(struct qpnp_driver *qdrv)
+{
+	qdrv->driver.bus = &qpnp_bus_type;
+
+	return driver_register(&qdrv->driver);
+}
+EXPORT_SYMBOL_GPL(qpnp_driver_register);
+
+/**
+ * qpnp_driver_unregister() - unregister a QPNP client driver
+ * @qdrv: the driver to unregister
+ */
+void qpnp_driver_unregister(struct qpnp_driver *qdrv)
+{
+	if (qdrv)
+		driver_unregister(&qdrv->driver);
+}
+EXPORT_SYMBOL_GPL(qpnp_driver_unregister);
+
+static void __exit qpnp_exit(void)
+{
+	bus_unregister(&qpnp_bus_type);
+}
+
+module_exit(qpnp_exit);
+
+static int __init qpnp_init(void)
+{
+	return bus_register(&qpnp_bus_type);
+}
+
+postcore_initcall(qpnp_init);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("QPNP bus module");
+MODULE_ALIAS("qpnp");
diff --git a/drivers/qpnp/qpnp-spmi.c b/drivers/qpnp/qpnp-spmi.c
new file mode 100644
index 0000000..c3cb841
--- /dev/null
+++ b/drivers/qpnp/qpnp-spmi.c
@@ -0,0 +1,63 @@ 
+/* Copyright (c) 2014, The Linux Foundation. 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/kernel.h>
+#include <linux/module.h>
+#include <linux/qpnp.h>
+#include <linux/regmap.h>
+#include <linux/spmi.h>
+
+static const struct regmap_config qpnp_spmi_regmap_config = {
+	.reg_bits = 16,
+	.val_bits = 8,
+	.max_register = 0xffff,
+};
+
+static int qpnp_spmi_probe(struct spmi_device *sdev)
+{
+	struct regmap *regmap;
+
+	regmap = devm_regmap_init_spmi_ext(sdev, &qpnp_spmi_regmap_config);
+	if (IS_ERR(regmap)) {
+		dev_err(&sdev->dev, "regmap creation failed.\n");
+		return PTR_ERR(regmap);
+	}
+
+	return qpnp_populate_devices(sdev->dev.of_node, &sdev->dev,
+				     sdev->usid, regmap);
+}
+
+static void qpnp_spmi_remove(struct spmi_device *sdev)
+{
+	qpnp_remove_devices(&sdev->dev);
+}
+
+static const struct of_device_id qpnp_spmi_id_table[] = {
+	{ .compatible = "qcom,qpnp-spmi" },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(of, qpnp_spmi_id_table);
+
+static struct spmi_driver qpnp_spmi_driver = {
+	.probe = qpnp_spmi_probe,
+	.remove = qpnp_spmi_remove,
+	.driver = {
+		   .name = "qpnp-spmi",
+		   .of_match_table = qpnp_spmi_id_table,
+	},
+};
+
+module_spmi_driver(qpnp_spmi_driver);
+
+MODULE_DESCRIPTION("Qualcomm PMIC QPNP driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("spmi:qpnp-spmi");
diff --git a/include/linux/qpnp.h b/include/linux/qpnp.h
new file mode 100644
index 0000000..788f886
--- /dev/null
+++ b/include/linux/qpnp.h
@@ -0,0 +1,83 @@ 
+/* Copyright (c) 2012-2013, The Linux Foundation. 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.
+ */
+#ifndef _LINUX_QPNP_H
+#define _LINUX_QPNP_H
+
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+
+/**
+ * struct qpnp_device - Basic representation of an QPNP device
+ * @map: regmap to be used by driver to access device registers
+ * @dev: driver model representation of the device
+ * @sid: this devices' Unique Slave IDentifier
+ * @num_resources: number of memory resources associated to device
+ * @resource: memory resources asocuated to device
+ */
+struct qpnp_device {
+	struct device		dev;
+	struct regmap		*map;
+	u8			sid;
+	u32			num_resources;
+	struct resource		*resource;
+};
+
+
+static inline struct qpnp_device *to_qpnp_device(struct device *d)
+{
+	return container_of(d, struct qpnp_device, dev);
+}
+
+static inline void *qpnp_get_drvdata(const struct qpnp_device *qdev)
+{
+	return dev_get_drvdata(&qdev->dev);
+}
+
+static inline void qpnp_set_drvdata(struct qpnp_device *qdev, void *data)
+{
+	dev_set_drvdata(&qdev->dev, data);
+}
+
+int qpnp_populate_devices(struct device_node *root, struct device *parent,
+			  u8 sid, struct regmap *map);
+void qpnp_remove_devices(struct device *root);
+
+struct resource *qpnp_get_resource(struct qpnp_device *dev, unsigned int type,
+				   unsigned int num);
+struct resource *qpnp_get_resource_byname(struct qpnp_device *dev,
+					  unsigned int type, const char *name);
+
+int qpnp_get_irq(struct qpnp_device *qdev, unsigned int num);
+int qpnp_get_irq_byname(struct qpnp_device *qdev, const char *name);
+
+/**
+ * struct qpnp_driver - QPNP slave device driver
+ * @driver: QPNP device drivers should initialize name and owner field of
+ *          this structure
+ * @probe: binds this driver to a QPNP device
+ * @remove: unbinds this driver from the QPNP device
+ */
+struct qpnp_driver {
+	struct device_driver driver;
+	int (*probe)(struct qpnp_device *qdev);
+	void (*remove)(struct qpnp_device *qdev);
+};
+
+int qpnp_driver_register(struct qpnp_driver *qdrv);
+void qpnp_driver_unregister(struct qpnp_driver *qdrv);
+
+#define module_qpnp_driver(__qpnp_driver) \
+	module_driver(__qpnp_driver, qpnp_driver_register, \
+			qpnp_driver_unregister)
+
+#endif