123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- /* Copyright (c) 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.
- */
- #define pr_fmt(fmt) "%s: " fmt, __func__
- #include <linux/module.h>
- #include <linux/err.h>
- #include <linux/slab.h>
- #include <linux/spmi.h>
- #include <linux/of.h>
- #include <linux/of_device.h>
- #include <linux/qpnp-misc.h>
- #define QPNP_MISC_DEV_NAME "qcom,qpnp-misc"
- #define REG_DIG_MAJOR_REV 0x01
- #define REG_SUBTYPE 0x05
- static DEFINE_MUTEX(qpnp_misc_dev_list_mutex);
- static LIST_HEAD(qpnp_misc_dev_list);
- /**
- * struct qpnp_misc_dev - holds controller device specific information
- * @list: Doubly-linked list parameter linking to other
- * qpnp_misc devices.
- * @mutex: Mutex lock that is used to ensure mutual
- * exclusion between probing and accessing misc
- * driver information
- * @dev: Device pointer to the misc device
- * @resource: Resource pointer that holds base address
- * @spmi: Spmi pointer which holds spmi information
- */
- struct qpnp_misc_dev {
- struct list_head list;
- struct mutex mutex;
- struct device *dev;
- struct resource *resource;
- struct spmi_device *spmi;
- };
- struct qpnp_misc_version {
- u8 subtype;
- u8 dig_major_rev;
- };
- static struct of_device_id qpnp_misc_match_table[] = {
- { .compatible = QPNP_MISC_DEV_NAME },
- {}
- };
- static u8 qpnp_read_byte(struct spmi_device *spmi, u16 addr)
- {
- int rc;
- u8 val;
- rc = spmi_ext_register_readl(spmi->ctrl, spmi->sid, addr, &val, 1);
- if (rc) {
- pr_err("SPMI read failed rc=%d\n", rc);
- return 0;
- }
- return val;
- }
- static struct qpnp_misc_version irq_support_version[] = {
- {0x01, 0x02}, /* PM8941 */
- {0x07, 0x00}, /* PM8226 */
- {0x09, 0x00}, /* PMA8084 */
- };
- static bool __misc_irqs_available(struct qpnp_misc_dev *dev)
- {
- int i;
- u8 subtype, dig_major_rev;
- subtype = qpnp_read_byte(dev->spmi, dev->resource->start + REG_SUBTYPE);
- pr_debug("subtype = 0x%02X\n", subtype);
- dig_major_rev = qpnp_read_byte(dev->spmi,
- dev->resource->start + REG_DIG_MAJOR_REV);
- pr_debug("dig_major rev = 0x%02X\n", dig_major_rev);
- for (i = 0; i < ARRAY_SIZE(irq_support_version); i++)
- if (subtype == irq_support_version[i].subtype
- && dig_major_rev >= irq_support_version[i].dig_major_rev)
- return 1;
- return 0;
- }
- int qpnp_misc_irqs_available(struct device *consumer_dev)
- {
- struct device_node *misc_node = NULL;
- struct qpnp_misc_dev *mdev = NULL;
- struct qpnp_misc_dev *mdev_found = NULL;
- if (IS_ERR_OR_NULL(consumer_dev)) {
- pr_err("Invalid consumer device pointer\n");
- return -EINVAL;
- }
- misc_node = of_parse_phandle(consumer_dev->of_node, "qcom,misc-ref", 0);
- if (!misc_node) {
- pr_debug("Could not find qcom,misc-ref property in %s\n",
- consumer_dev->of_node->full_name);
- return 0;
- }
- mutex_lock(&qpnp_misc_dev_list_mutex);
- list_for_each_entry(mdev, &qpnp_misc_dev_list, list) {
- if (mdev->dev->of_node == misc_node) {
- mdev_found = mdev;
- break;
- }
- }
- mutex_unlock(&qpnp_misc_dev_list_mutex);
- if (!mdev_found) {
- /* No MISC device was found. This API should only
- * be called by drivers which have specified the
- * misc phandle in their device tree node */
- pr_err("no probed misc device found\n");
- return -EPROBE_DEFER;
- }
- return __misc_irqs_available(mdev_found);
- }
- static int __devinit qpnp_misc_probe(struct spmi_device *spmi)
- {
- struct resource *resource;
- struct qpnp_misc_dev *mdev = ERR_PTR(-EINVAL);
- resource = spmi_get_resource(spmi, NULL, IORESOURCE_MEM, 0);
- if (!resource) {
- pr_err("Unable to get spmi resource for MISC\n");
- return -EINVAL;
- }
- mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);
- if (!mdev) {
- pr_err("allocation failed\n");
- return -ENOMEM;
- }
- mdev->spmi = spmi;
- mdev->dev = &(spmi->dev);
- mdev->resource = resource;
- mutex_lock(&qpnp_misc_dev_list_mutex);
- list_add_tail(&mdev->list, &qpnp_misc_dev_list);
- mutex_unlock(&qpnp_misc_dev_list_mutex);
- pr_debug("probed successfully\n");
- return 0;
- }
- static struct spmi_driver qpnp_misc_driver = {
- .probe = qpnp_misc_probe,
- .driver = {
- .name = QPNP_MISC_DEV_NAME,
- .owner = THIS_MODULE,
- .of_match_table = qpnp_misc_match_table,
- },
- };
- static int __init qpnp_misc_init(void)
- {
- return spmi_driver_register(&qpnp_misc_driver);
- }
- static void __exit qpnp_misc_exit(void)
- {
- return spmi_driver_unregister(&qpnp_misc_driver);
- }
- module_init(qpnp_misc_init);
- module_exit(qpnp_misc_exit);
- MODULE_DESCRIPTION(QPNP_MISC_DEV_NAME);
- MODULE_LICENSE("GPL v2");
- MODULE_ALIAS("platform:" QPNP_MISC_DEV_NAME);
|