blob: 690b28bde3e9749bda0115a7ac409e75b267e75f [file] [log] [blame]
Subbaraman Narayanamurthye68d90f2017-04-03 15:05:21 -07001/* Copyright (c) 2013-2014,2016-2017, 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/regmap.h>
18#include <linux/of.h>
19#include <linux/of_device.h>
20#include <linux/qpnp/qpnp-misc.h>
21
22#define QPNP_MISC_DEV_NAME "qcom,qpnp-misc"
23
24#define REG_DIG_MAJOR_REV 0x01
25#define REG_SUBTYPE 0x05
26#define REG_PWM_SEL 0x49
27#define REG_GP_DRIVER_EN 0x4C
28
29#define PWM_SEL_MAX 0x03
30#define GP_DRIVER_EN_BIT BIT(0)
31
32static DEFINE_MUTEX(qpnp_misc_dev_list_mutex);
33static LIST_HEAD(qpnp_misc_dev_list);
34
35struct qpnp_misc_version {
36 u8 subtype;
37 u8 dig_major_rev;
38};
39
40/**
41 * struct qpnp_misc_dev - holds controller device specific information
42 * @list: Doubly-linked list parameter linking to other
43 * qpnp_misc devices.
44 * @mutex: Mutex lock that is used to ensure mutual
45 * exclusion between probing and accessing misc
46 * driver information
47 * @dev: Device pointer to the misc device
48 * @regmap: Regmap pointer to the misc device
49 * @version: struct that holds the subtype and dig_major_rev
50 * of the chip.
51 */
52struct qpnp_misc_dev {
53 struct list_head list;
54 struct mutex mutex;
55 struct device *dev;
56 struct regmap *regmap;
57 struct qpnp_misc_version version;
58
59 u32 base;
60 u8 pwm_sel;
61 bool enable_gp_driver;
62};
63
64static const struct of_device_id qpnp_misc_match_table[] = {
65 { .compatible = QPNP_MISC_DEV_NAME },
66 {}
67};
68
69enum qpnp_misc_version_name {
70 INVALID,
71 PM8941,
72 PM8226,
73 PMA8084,
74 PMDCALIFORNIUM,
75};
76
77static struct qpnp_misc_version irq_support_version[] = {
78 {0x00, 0x00}, /* INVALID */
79 {0x01, 0x02}, /* PM8941 */
80 {0x07, 0x00}, /* PM8226 */
81 {0x09, 0x00}, /* PMA8084 */
82 {0x16, 0x00}, /* PMDCALIFORNIUM */
83};
84
85static int qpnp_write_byte(struct qpnp_misc_dev *mdev, u16 addr, u8 val)
86{
87 int rc;
88
89 rc = regmap_write(mdev->regmap, mdev->base + addr, val);
90 if (rc)
91 pr_err("regmap write failed rc=%d\n", rc);
92
93 return rc;
94}
95
96static int qpnp_read_byte(struct qpnp_misc_dev *mdev, u16 addr, u8 *val)
97{
98 unsigned int temp;
99 int rc;
100
101 rc = regmap_read(mdev->regmap, mdev->base + addr, &temp);
102 if (rc) {
103 pr_err("regmap read failed rc=%d\n", rc);
104 return rc;
105 }
106
107 *val = (u8)temp;
108 return rc;
109}
110
111static int get_qpnp_misc_version_name(struct qpnp_misc_dev *dev)
112{
113 int i;
114
115 for (i = 1; i < ARRAY_SIZE(irq_support_version); i++)
116 if (dev->version.subtype == irq_support_version[i].subtype &&
117 dev->version.dig_major_rev >=
118 irq_support_version[i].dig_major_rev)
119 return i;
120
121 return INVALID;
122}
123
124static bool __misc_irqs_available(struct qpnp_misc_dev *dev)
125{
126 int version_name = get_qpnp_misc_version_name(dev);
127
128 if (version_name == INVALID)
129 return 0;
130 return 1;
131}
132
133int qpnp_misc_read_reg(struct device_node *node, u16 addr, u8 *val)
134{
135 struct qpnp_misc_dev *mdev = NULL;
136 struct qpnp_misc_dev *mdev_found = NULL;
137 int rc;
Lingutla Chandrasekhar5c338452017-09-14 19:29:18 +0530138 u8 temp = 0;
Subbaraman Narayanamurthye68d90f2017-04-03 15:05:21 -0700139
140 if (IS_ERR_OR_NULL(node)) {
141 pr_err("Invalid device node pointer\n");
142 return -EINVAL;
143 }
144
145 mutex_lock(&qpnp_misc_dev_list_mutex);
146 list_for_each_entry(mdev, &qpnp_misc_dev_list, list) {
147 if (mdev->dev->of_node == node) {
148 mdev_found = mdev;
149 break;
150 }
151 }
152 mutex_unlock(&qpnp_misc_dev_list_mutex);
153
154 if (!mdev_found) {
155 /*
156 * No MISC device was found. This API should only
157 * be called by drivers which have specified the
158 * misc phandle in their device tree node.
159 */
160 pr_err("no probed misc device found\n");
161 return -EPROBE_DEFER;
162 }
163
164 rc = qpnp_read_byte(mdev, addr, &temp);
165 if (rc < 0) {
166 dev_err(mdev->dev, "Failed to read addr %x, rc=%d\n", addr, rc);
167 return rc;
168 }
169
170 *val = temp;
171 return 0;
172}
173
174int qpnp_misc_irqs_available(struct device *consumer_dev)
175{
176 struct device_node *misc_node = NULL;
177 struct qpnp_misc_dev *mdev = NULL;
178 struct qpnp_misc_dev *mdev_found = NULL;
179
180 if (IS_ERR_OR_NULL(consumer_dev)) {
181 pr_err("Invalid consumer device pointer\n");
182 return -EINVAL;
183 }
184
185 misc_node = of_parse_phandle(consumer_dev->of_node, "qcom,misc-ref", 0);
186 if (!misc_node) {
187 pr_debug("Could not find qcom,misc-ref property in %s\n",
188 consumer_dev->of_node->full_name);
189 return 0;
190 }
191
192 mutex_lock(&qpnp_misc_dev_list_mutex);
193 list_for_each_entry(mdev, &qpnp_misc_dev_list, list) {
194 if (mdev->dev->of_node == misc_node) {
195 mdev_found = mdev;
196 break;
197 }
198 }
199 mutex_unlock(&qpnp_misc_dev_list_mutex);
200
201 if (!mdev_found) {
202 /*
203 * No MISC device was found. This API should only
204 * be called by drivers which have specified the
205 * misc phandle in their device tree node.
206 */
207 pr_err("no probed misc device found\n");
208 return -EPROBE_DEFER;
209 }
210
211 return __misc_irqs_available(mdev_found);
212}
213
214static int qpnp_misc_dt_init(struct qpnp_misc_dev *mdev)
215{
216 struct device_node *node = mdev->dev->of_node;
217 u32 val;
218 int rc;
219
220 rc = of_property_read_u32(node, "reg", &mdev->base);
221 if (rc < 0 || !mdev->base) {
222 dev_err(mdev->dev, "Base address not defined or invalid\n");
223 return -EINVAL;
224 }
225
226 if (!of_property_read_u32(node, "qcom,pwm-sel", &val)) {
227 if (val > PWM_SEL_MAX) {
228 dev_err(mdev->dev, "Invalid value for pwm-sel\n");
229 return -EINVAL;
230 }
231 mdev->pwm_sel = (u8)val;
232 }
233 mdev->enable_gp_driver = of_property_read_bool(node,
234 "qcom,enable-gp-driver");
235
236 WARN((mdev->pwm_sel > 0 && !mdev->enable_gp_driver),
237 "Setting PWM source without enabling gp driver\n");
238 WARN((mdev->pwm_sel == 0 && mdev->enable_gp_driver),
239 "Enabling gp driver without setting PWM source\n");
240
241 return 0;
242}
243
244static int qpnp_misc_config(struct qpnp_misc_dev *mdev)
245{
246 int rc, version_name;
247
248 version_name = get_qpnp_misc_version_name(mdev);
249
250 switch (version_name) {
251 case PMDCALIFORNIUM:
252 if (mdev->pwm_sel > 0 && mdev->enable_gp_driver) {
253 rc = qpnp_write_byte(mdev, REG_PWM_SEL, mdev->pwm_sel);
254 if (rc < 0) {
255 dev_err(mdev->dev,
256 "Failed to write PWM_SEL reg\n");
257 return rc;
258 }
259
260 rc = qpnp_write_byte(mdev, REG_GP_DRIVER_EN,
261 GP_DRIVER_EN_BIT);
262 if (rc < 0) {
263 dev_err(mdev->dev,
264 "Failed to write GP_DRIVER_EN reg\n");
265 return rc;
266 }
267 }
268 break;
269 default:
270 break;
271 }
272
273 return 0;
274}
275
276static int qpnp_misc_probe(struct platform_device *pdev)
277{
278 struct qpnp_misc_dev *mdev = ERR_PTR(-EINVAL);
279 int rc;
280
281 mdev = devm_kzalloc(&pdev->dev, sizeof(*mdev), GFP_KERNEL);
282 if (!mdev)
283 return -ENOMEM;
284
285 mdev->dev = &pdev->dev;
286 mdev->regmap = dev_get_regmap(mdev->dev->parent, NULL);
287 if (!mdev->regmap) {
288 dev_err(mdev->dev, "Parent regmap is unavailable\n");
289 return -ENXIO;
290 }
291
292 rc = qpnp_misc_dt_init(mdev);
293 if (rc < 0) {
294 dev_err(mdev->dev,
295 "Error reading device tree properties, rc=%d\n", rc);
296 return rc;
297 }
298
299
300 rc = qpnp_read_byte(mdev, REG_SUBTYPE, &mdev->version.subtype);
301 if (rc < 0) {
302 dev_err(mdev->dev, "Failed to read subtype, rc=%d\n", rc);
303 return rc;
304 }
305
306 rc = qpnp_read_byte(mdev, REG_DIG_MAJOR_REV,
307 &mdev->version.dig_major_rev);
308 if (rc < 0) {
309 dev_err(mdev->dev, "Failed to read dig_major_rev, rc=%d\n", rc);
310 return rc;
311 }
312
313 mutex_lock(&qpnp_misc_dev_list_mutex);
314 list_add_tail(&mdev->list, &qpnp_misc_dev_list);
315 mutex_unlock(&qpnp_misc_dev_list_mutex);
316
317 rc = qpnp_misc_config(mdev);
318 if (rc < 0) {
319 dev_err(mdev->dev,
320 "Error configuring module registers, rc=%d\n", rc);
321 return rc;
322 }
323
324 dev_info(mdev->dev, "probe successful\n");
325 return 0;
326}
327
328static struct platform_driver qpnp_misc_driver = {
329 .probe = qpnp_misc_probe,
330 .driver = {
331 .name = QPNP_MISC_DEV_NAME,
332 .owner = THIS_MODULE,
333 .of_match_table = qpnp_misc_match_table,
334 },
335};
336
337static int __init qpnp_misc_init(void)
338{
339 return platform_driver_register(&qpnp_misc_driver);
340}
341
342static void __exit qpnp_misc_exit(void)
343{
344 return platform_driver_unregister(&qpnp_misc_driver);
345}
346
347subsys_initcall(qpnp_misc_init);
348module_exit(qpnp_misc_exit);
349
350MODULE_DESCRIPTION(QPNP_MISC_DEV_NAME);
351MODULE_LICENSE("GPL v2");
352MODULE_ALIAS("platform:" QPNP_MISC_DEV_NAME);