From patchwork Fri Jun 20 12:21:21 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stanimir Varbanov X-Patchwork-Id: 4388971 Return-Path: X-Original-To: patchwork-linux-arm-msm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 27F0FBEEAA for ; Fri, 20 Jun 2014 12:23:10 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 4B0EC203DA for ; Fri, 20 Jun 2014 12:23:06 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id B80BD20351 for ; Fri, 20 Jun 2014 12:23:04 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S967259AbaFTMXE (ORCPT ); Fri, 20 Jun 2014 08:23:04 -0400 Received: from ns.mm-sol.com ([37.157.136.199]:45483 "EHLO extserv.mm-sol.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S966074AbaFTMXC (ORCPT ); Fri, 20 Jun 2014 08:23:02 -0400 Received: from localhost.localdomain (unknown [37.157.136.206]) by extserv.mm-sol.com (Postfix) with ESMTPSA id D0B8CC7CD; Fri, 20 Jun 2014 15:23:00 +0300 (EEST) From: Stanimir Varbanov To: linux-arm-msm@vger.kernel.org Cc: Stanimir Varbanov , "Ivan T. Ivanov" Subject: [RFC PATCH 2/6] qpnp: add support for Qualcomm QPNP PMICs Date: Fri, 20 Jun 2014 15:21:21 +0300 Message-Id: <1403266885-911-3-git-send-email-svarbanov@mm-sol.com> X-Mailer: git-send-email 1.7.0.4 In-Reply-To: <1403266885-911-1-git-send-email-svarbanov@mm-sol.com> References: <1403266885-911-1-git-send-email-svarbanov@mm-sol.com> Sender: linux-arm-msm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-arm-msm@vger.kernel.org X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP 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 Signed-off-by: Ivan T. Ivanov Signed-off-by: Stanimir Varbanov --- 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 --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 +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include + +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 +#include +#include + +/** + * 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