blob: 6ad481678f3bcbe2fcab6dcc9a0d972c8d8714b7 [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
David Collins91d3a522013-07-24 10:58:52 -070024#define REG_DIG_MAJOR_REV 0x01
25#define REG_SUBTYPE 0x05
David Keitel8927ee42013-03-19 17:25:19 -070026
27static DEFINE_MUTEX(qpnp_misc_dev_list_mutex);
28static LIST_HEAD(qpnp_misc_dev_list);
29
30/**
31 * struct qpnp_misc_dev - holds controller device specific information
32 * @list: Doubly-linked list parameter linking to other
33 * qpnp_misc devices.
34 * @mutex: Mutex lock that is used to ensure mutual
35 * exclusion between probing and accessing misc
36 * driver information
37 * @dev: Device pointer to the misc device
38 * @resource: Resource pointer that holds base address
39 * @spmi: Spmi pointer which holds spmi information
40 */
41struct qpnp_misc_dev {
42 struct list_head list;
43 struct mutex mutex;
44 struct device *dev;
45 struct resource *resource;
46 struct spmi_device *spmi;
47};
48
David Collins91d3a522013-07-24 10:58:52 -070049struct qpnp_misc_version {
50 u8 subtype;
51 u8 dig_major_rev;
52};
53
David Keitel8927ee42013-03-19 17:25:19 -070054static struct of_device_id qpnp_misc_match_table[] = {
55 { .compatible = QPNP_MISC_DEV_NAME },
56 {}
57};
58
59static u8 qpnp_read_byte(struct spmi_device *spmi, u16 addr)
60{
61 int rc;
62 u8 val;
63
64 rc = spmi_ext_register_readl(spmi->ctrl, spmi->sid, addr, &val, 1);
65 if (rc) {
66 pr_err("SPMI read failed rc=%d\n", rc);
67 return 0;
68 }
69 return val;
70}
71
David Collins91d3a522013-07-24 10:58:52 -070072static struct qpnp_misc_version irq_support_version[] = {
73 {0x01, 0x02}, /* PM8941 */
74 {0x07, 0x00}, /* PM8226 */
75 {0x09, 0x00}, /* PMA8084 */
76};
77
David Keitel8927ee42013-03-19 17:25:19 -070078static bool __misc_irqs_available(struct qpnp_misc_dev *dev)
79{
David Collins91d3a522013-07-24 10:58:52 -070080 int i;
81 u8 subtype, dig_major_rev;
David Keitel8927ee42013-03-19 17:25:19 -070082
David Collins91d3a522013-07-24 10:58:52 -070083 subtype = qpnp_read_byte(dev->spmi, dev->resource->start + REG_SUBTYPE);
84 pr_debug("subtype = 0x%02X\n", subtype);
David Keitel8927ee42013-03-19 17:25:19 -070085
David Collins91d3a522013-07-24 10:58:52 -070086 dig_major_rev = qpnp_read_byte(dev->spmi,
87 dev->resource->start + REG_DIG_MAJOR_REV);
88 pr_debug("dig_major rev = 0x%02X\n", dig_major_rev);
89
90 for (i = 0; i < ARRAY_SIZE(irq_support_version); i++)
91 if (subtype == irq_support_version[i].subtype
92 && dig_major_rev >= irq_support_version[i].dig_major_rev)
93 return 1;
David Keitel8927ee42013-03-19 17:25:19 -070094
95 return 0;
96}
97
98int qpnp_misc_irqs_available(struct device *consumer_dev)
99{
100 struct device_node *misc_node = NULL;
101 struct qpnp_misc_dev *mdev = NULL;
102 struct qpnp_misc_dev *mdev_found = NULL;
103
David Collins91d3a522013-07-24 10:58:52 -0700104 if (IS_ERR_OR_NULL(consumer_dev)) {
105 pr_err("Invalid consumer device pointer\n");
106 return -EINVAL;
107 }
108
David Keitel8927ee42013-03-19 17:25:19 -0700109 misc_node = of_parse_phandle(consumer_dev->of_node, "qcom,misc-ref", 0);
110 if (!misc_node) {
111 pr_debug("Could not find qcom,misc-ref property in %s\n",
112 consumer_dev->of_node->full_name);
113 return 0;
114 }
115
116 mutex_lock(&qpnp_misc_dev_list_mutex);
117 list_for_each_entry(mdev, &qpnp_misc_dev_list, list) {
118 if (mdev->dev->of_node == misc_node) {
119 mdev_found = mdev;
120 break;
121 }
122 }
123 mutex_unlock(&qpnp_misc_dev_list_mutex);
124
125 if (!mdev_found) {
126 /* No MISC device was found. This API should only
127 * be called by drivers which have specified the
128 * misc phandle in their device tree node */
129 pr_err("no probed misc device found\n");
130 return -EPROBE_DEFER;
131 }
132
133 return __misc_irqs_available(mdev_found);
134}
135
136static int __devinit qpnp_misc_probe(struct spmi_device *spmi)
137{
138 struct resource *resource;
139 struct qpnp_misc_dev *mdev = ERR_PTR(-EINVAL);
140
141 resource = spmi_get_resource(spmi, NULL, IORESOURCE_MEM, 0);
142 if (!resource) {
143 pr_err("Unable to get spmi resource for MISC\n");
144 return -EINVAL;
145 }
146
147 mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);
148 if (!mdev) {
149 pr_err("allocation failed\n");
150 return -ENOMEM;
151 }
152
153 mdev->spmi = spmi;
154 mdev->dev = &(spmi->dev);
155 mdev->resource = resource;
156
157 mutex_lock(&qpnp_misc_dev_list_mutex);
158 list_add_tail(&mdev->list, &qpnp_misc_dev_list);
159 mutex_unlock(&qpnp_misc_dev_list_mutex);
160
161 pr_debug("probed successfully\n");
162 return 0;
163}
164
165static struct spmi_driver qpnp_misc_driver = {
166 .probe = qpnp_misc_probe,
167 .driver = {
168 .name = QPNP_MISC_DEV_NAME,
169 .owner = THIS_MODULE,
170 .of_match_table = qpnp_misc_match_table,
171 },
172};
173
174static int __init qpnp_misc_init(void)
175{
176 return spmi_driver_register(&qpnp_misc_driver);
177}
178
179static void __exit qpnp_misc_exit(void)
180{
181 return spmi_driver_unregister(&qpnp_misc_driver);
182}
183
184module_init(qpnp_misc_init);
185module_exit(qpnp_misc_exit);
186
187MODULE_DESCRIPTION(QPNP_MISC_DEV_NAME);
188MODULE_LICENSE("GPL v2");
189MODULE_ALIAS("platform:" QPNP_MISC_DEV_NAME);