blob: 608be8111121c0b2b95de5c6b99abb3b465a22e0 [file] [log] [blame]
David Keitel8927ee42013-03-19 17:25:19 -07001/* Copyright (c) 2013, The Linux Foundation. All rights reserved.
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 */
12#define pr_fmt(fmt) "%s: " fmt, __func__
13
14#include <linux/module.h>
15#include <linux/err.h>
16#include <linux/slab.h>
17#include <linux/spmi.h>
18#include <linux/of.h>
19#include <linux/of_device.h>
20#include <linux/qpnp-misc.h>
21
22#define QPNP_MISC_DEV_NAME "qcom,qpnp-misc"
23
24#define REVID_REVISION2 0x1
25
26static DEFINE_MUTEX(qpnp_misc_dev_list_mutex);
27static LIST_HEAD(qpnp_misc_dev_list);
28
29/**
30 * struct qpnp_misc_dev - holds controller device specific information
31 * @list: Doubly-linked list parameter linking to other
32 * qpnp_misc devices.
33 * @mutex: Mutex lock that is used to ensure mutual
34 * exclusion between probing and accessing misc
35 * driver information
36 * @dev: Device pointer to the misc device
37 * @resource: Resource pointer that holds base address
38 * @spmi: Spmi pointer which holds spmi information
39 */
40struct qpnp_misc_dev {
41 struct list_head list;
42 struct mutex mutex;
43 struct device *dev;
44 struct resource *resource;
45 struct spmi_device *spmi;
46};
47
48static struct of_device_id qpnp_misc_match_table[] = {
49 { .compatible = QPNP_MISC_DEV_NAME },
50 {}
51};
52
53static u8 qpnp_read_byte(struct spmi_device *spmi, u16 addr)
54{
55 int rc;
56 u8 val;
57
58 rc = spmi_ext_register_readl(spmi->ctrl, spmi->sid, addr, &val, 1);
59 if (rc) {
60 pr_err("SPMI read failed rc=%d\n", rc);
61 return 0;
62 }
63 return val;
64}
65
66#define REV2_IRQ_AVAILABLE_VERSION 2
67static bool __misc_irqs_available(struct qpnp_misc_dev *dev)
68{
69 u8 rev2;
70
71 rev2 = qpnp_read_byte(dev->spmi,
72 dev->resource->start + REVID_REVISION2);
73 pr_debug("rev2 0x%x\n", rev2);
74
75 if (rev2 >= REV2_IRQ_AVAILABLE_VERSION)
76 return 1;
77
78 return 0;
79}
80
81int qpnp_misc_irqs_available(struct device *consumer_dev)
82{
83 struct device_node *misc_node = NULL;
84 struct qpnp_misc_dev *mdev = NULL;
85 struct qpnp_misc_dev *mdev_found = NULL;
86
87 misc_node = of_parse_phandle(consumer_dev->of_node, "qcom,misc-ref", 0);
88 if (!misc_node) {
89 pr_debug("Could not find qcom,misc-ref property in %s\n",
90 consumer_dev->of_node->full_name);
91 return 0;
92 }
93
94 mutex_lock(&qpnp_misc_dev_list_mutex);
95 list_for_each_entry(mdev, &qpnp_misc_dev_list, list) {
96 if (mdev->dev->of_node == misc_node) {
97 mdev_found = mdev;
98 break;
99 }
100 }
101 mutex_unlock(&qpnp_misc_dev_list_mutex);
102
103 if (!mdev_found) {
104 /* No MISC device was found. This API should only
105 * be called by drivers which have specified the
106 * misc phandle in their device tree node */
107 pr_err("no probed misc device found\n");
108 return -EPROBE_DEFER;
109 }
110
111 return __misc_irqs_available(mdev_found);
112}
113
114static int __devinit qpnp_misc_probe(struct spmi_device *spmi)
115{
116 struct resource *resource;
117 struct qpnp_misc_dev *mdev = ERR_PTR(-EINVAL);
118
119 resource = spmi_get_resource(spmi, NULL, IORESOURCE_MEM, 0);
120 if (!resource) {
121 pr_err("Unable to get spmi resource for MISC\n");
122 return -EINVAL;
123 }
124
125 mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);
126 if (!mdev) {
127 pr_err("allocation failed\n");
128 return -ENOMEM;
129 }
130
131 mdev->spmi = spmi;
132 mdev->dev = &(spmi->dev);
133 mdev->resource = resource;
134
135 mutex_lock(&qpnp_misc_dev_list_mutex);
136 list_add_tail(&mdev->list, &qpnp_misc_dev_list);
137 mutex_unlock(&qpnp_misc_dev_list_mutex);
138
139 pr_debug("probed successfully\n");
140 return 0;
141}
142
143static struct spmi_driver qpnp_misc_driver = {
144 .probe = qpnp_misc_probe,
145 .driver = {
146 .name = QPNP_MISC_DEV_NAME,
147 .owner = THIS_MODULE,
148 .of_match_table = qpnp_misc_match_table,
149 },
150};
151
152static int __init qpnp_misc_init(void)
153{
154 return spmi_driver_register(&qpnp_misc_driver);
155}
156
157static void __exit qpnp_misc_exit(void)
158{
159 return spmi_driver_unregister(&qpnp_misc_driver);
160}
161
162module_init(qpnp_misc_init);
163module_exit(qpnp_misc_exit);
164
165MODULE_DESCRIPTION(QPNP_MISC_DEV_NAME);
166MODULE_LICENSE("GPL v2");
167MODULE_ALIAS("platform:" QPNP_MISC_DEV_NAME);