blob: eec08751182b0ef3be44e562ba0974501f027232 [file] [log] [blame]
Anirudh Ghayalfa0300c2018-04-12 21:30:29 +05301/* Copyright (c) 2013-2014,2016-2018, The Linux Foundation. All rights reserved.
Subbaraman Narayanamurthye68d90f2017-04-03 15:05:21 -07002 *
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>
Anirudh Ghayalfa0300c2018-04-12 21:30:29 +053020#include <linux/notifier.h>
Subbaraman Narayanamurthye68d90f2017-04-03 15:05:21 -070021#include <linux/qpnp/qpnp-misc.h>
22
23#define QPNP_MISC_DEV_NAME "qcom,qpnp-misc"
24
25#define REG_DIG_MAJOR_REV 0x01
26#define REG_SUBTYPE 0x05
27#define REG_PWM_SEL 0x49
28#define REG_GP_DRIVER_EN 0x4C
29
30#define PWM_SEL_MAX 0x03
31#define GP_DRIVER_EN_BIT BIT(0)
32
Anirudh Ghayalfa0300c2018-04-12 21:30:29 +053033enum twm {
34 TWM_MODE_1 = 1,
35 TWM_MODE_2,
36 TWM_MODE_3,
37};
38
39enum twm_attrib {
40 TWM_ENABLE,
41 TWM_EXIT,
42};
43
Subbaraman Narayanamurthye68d90f2017-04-03 15:05:21 -070044static DEFINE_MUTEX(qpnp_misc_dev_list_mutex);
45static LIST_HEAD(qpnp_misc_dev_list);
Anirudh Ghayalfa0300c2018-04-12 21:30:29 +053046static RAW_NOTIFIER_HEAD(twm_notifier);
Subbaraman Narayanamurthye68d90f2017-04-03 15:05:21 -070047
48struct qpnp_misc_version {
49 u8 subtype;
50 u8 dig_major_rev;
51};
52
53/**
54 * struct qpnp_misc_dev - holds controller device specific information
55 * @list: Doubly-linked list parameter linking to other
56 * qpnp_misc devices.
57 * @mutex: Mutex lock that is used to ensure mutual
58 * exclusion between probing and accessing misc
59 * driver information
60 * @dev: Device pointer to the misc device
61 * @regmap: Regmap pointer to the misc device
62 * @version: struct that holds the subtype and dig_major_rev
63 * of the chip.
64 */
65struct qpnp_misc_dev {
66 struct list_head list;
67 struct mutex mutex;
68 struct device *dev;
69 struct regmap *regmap;
70 struct qpnp_misc_version version;
Anirudh Ghayalfa0300c2018-04-12 21:30:29 +053071 struct class twm_class;
Subbaraman Narayanamurthye68d90f2017-04-03 15:05:21 -070072
Anirudh Ghayalfa0300c2018-04-12 21:30:29 +053073 u8 twm_mode;
Subbaraman Narayanamurthye68d90f2017-04-03 15:05:21 -070074 u32 base;
75 u8 pwm_sel;
76 bool enable_gp_driver;
Anirudh Ghayalfa0300c2018-04-12 21:30:29 +053077 bool support_twm_config;
78 bool twm_enable;
Subbaraman Narayanamurthye68d90f2017-04-03 15:05:21 -070079};
80
81static const struct of_device_id qpnp_misc_match_table[] = {
82 { .compatible = QPNP_MISC_DEV_NAME },
83 {}
84};
85
86enum qpnp_misc_version_name {
87 INVALID,
88 PM8941,
89 PM8226,
90 PMA8084,
91 PMDCALIFORNIUM,
92};
93
94static struct qpnp_misc_version irq_support_version[] = {
95 {0x00, 0x00}, /* INVALID */
96 {0x01, 0x02}, /* PM8941 */
97 {0x07, 0x00}, /* PM8226 */
98 {0x09, 0x00}, /* PMA8084 */
99 {0x16, 0x00}, /* PMDCALIFORNIUM */
100};
101
102static int qpnp_write_byte(struct qpnp_misc_dev *mdev, u16 addr, u8 val)
103{
104 int rc;
105
106 rc = regmap_write(mdev->regmap, mdev->base + addr, val);
107 if (rc)
108 pr_err("regmap write failed rc=%d\n", rc);
109
110 return rc;
111}
112
113static int qpnp_read_byte(struct qpnp_misc_dev *mdev, u16 addr, u8 *val)
114{
115 unsigned int temp;
116 int rc;
117
118 rc = regmap_read(mdev->regmap, mdev->base + addr, &temp);
119 if (rc) {
120 pr_err("regmap read failed rc=%d\n", rc);
121 return rc;
122 }
123
124 *val = (u8)temp;
125 return rc;
126}
127
128static int get_qpnp_misc_version_name(struct qpnp_misc_dev *dev)
129{
130 int i;
131
132 for (i = 1; i < ARRAY_SIZE(irq_support_version); i++)
133 if (dev->version.subtype == irq_support_version[i].subtype &&
134 dev->version.dig_major_rev >=
135 irq_support_version[i].dig_major_rev)
136 return i;
137
138 return INVALID;
139}
140
141static bool __misc_irqs_available(struct qpnp_misc_dev *dev)
142{
143 int version_name = get_qpnp_misc_version_name(dev);
144
145 if (version_name == INVALID)
146 return 0;
147 return 1;
148}
149
150int qpnp_misc_read_reg(struct device_node *node, u16 addr, u8 *val)
151{
152 struct qpnp_misc_dev *mdev = NULL;
153 struct qpnp_misc_dev *mdev_found = NULL;
154 int rc;
Lingutla Chandrasekhar5c338452017-09-14 19:29:18 +0530155 u8 temp = 0;
Subbaraman Narayanamurthye68d90f2017-04-03 15:05:21 -0700156
157 if (IS_ERR_OR_NULL(node)) {
158 pr_err("Invalid device node pointer\n");
159 return -EINVAL;
160 }
161
162 mutex_lock(&qpnp_misc_dev_list_mutex);
163 list_for_each_entry(mdev, &qpnp_misc_dev_list, list) {
164 if (mdev->dev->of_node == node) {
165 mdev_found = mdev;
166 break;
167 }
168 }
169 mutex_unlock(&qpnp_misc_dev_list_mutex);
170
171 if (!mdev_found) {
172 /*
173 * No MISC device was found. This API should only
174 * be called by drivers which have specified the
175 * misc phandle in their device tree node.
176 */
177 pr_err("no probed misc device found\n");
178 return -EPROBE_DEFER;
179 }
180
181 rc = qpnp_read_byte(mdev, addr, &temp);
182 if (rc < 0) {
183 dev_err(mdev->dev, "Failed to read addr %x, rc=%d\n", addr, rc);
184 return rc;
185 }
186
187 *val = temp;
188 return 0;
189}
190
191int qpnp_misc_irqs_available(struct device *consumer_dev)
192{
193 struct device_node *misc_node = NULL;
194 struct qpnp_misc_dev *mdev = NULL;
195 struct qpnp_misc_dev *mdev_found = NULL;
196
197 if (IS_ERR_OR_NULL(consumer_dev)) {
198 pr_err("Invalid consumer device pointer\n");
199 return -EINVAL;
200 }
201
202 misc_node = of_parse_phandle(consumer_dev->of_node, "qcom,misc-ref", 0);
203 if (!misc_node) {
204 pr_debug("Could not find qcom,misc-ref property in %s\n",
205 consumer_dev->of_node->full_name);
206 return 0;
207 }
208
209 mutex_lock(&qpnp_misc_dev_list_mutex);
210 list_for_each_entry(mdev, &qpnp_misc_dev_list, list) {
211 if (mdev->dev->of_node == misc_node) {
212 mdev_found = mdev;
213 break;
214 }
215 }
216 mutex_unlock(&qpnp_misc_dev_list_mutex);
217
218 if (!mdev_found) {
219 /*
220 * No MISC device was found. This API should only
221 * be called by drivers which have specified the
222 * misc phandle in their device tree node.
223 */
224 pr_err("no probed misc device found\n");
225 return -EPROBE_DEFER;
226 }
227
228 return __misc_irqs_available(mdev_found);
229}
230
Anirudh Ghayalfa0300c2018-04-12 21:30:29 +0530231#define MISC_SPARE_1 0x50
232#define MISC_SPARE_2 0x51
233#define ENABLE_TWM_MODE 0x80
234#define DISABLE_TWM_MODE 0x0
235#define TWM_EXIT_BIT BIT(0)
236static ssize_t twm_enable_store(struct class *c,
237 struct class_attribute *attr,
238 const char *buf, size_t count)
239{
240 struct qpnp_misc_dev *mdev = container_of(c,
241 struct qpnp_misc_dev, twm_class);
242 u8 val = 0;
243 ssize_t rc = 0;
244
245 rc = kstrtou8(buf, 10, &val);
246 if (rc < 0)
247 return rc;
248
249 mdev->twm_enable = val ? true : false;
250
251 /* Notify the TWM state */
252 raw_notifier_call_chain(&twm_notifier,
253 mdev->twm_enable ? PMIC_TWM_ENABLE : PMIC_TWM_CLEAR, NULL);
254
255 return count;
256}
257
258static ssize_t twm_enable_show(struct class *c,
259 struct class_attribute *attr, char *buf)
260{
261 struct qpnp_misc_dev *mdev = container_of(c,
262 struct qpnp_misc_dev, twm_class);
263
264 return snprintf(buf, PAGE_SIZE, "%d\n", mdev->twm_enable);
265}
266
267static ssize_t twm_exit_show(struct class *c,
268 struct class_attribute *attr, char *buf)
269{
270 struct qpnp_misc_dev *mdev = container_of(c,
271 struct qpnp_misc_dev, twm_class);
272 int rc = 0;
273 u8 val = 0;
274
275 rc = qpnp_read_byte(mdev, MISC_SPARE_1, &val);
276 if (rc < 0) {
277 pr_err("Failed to read TWM enable (misc_spare_1) rc=%d\n", rc);
278 return rc;
279 }
280
281 pr_debug("TWM_EXIT (misc_spare_1) register = 0x%02x\n", val);
282
283 return snprintf(buf, PAGE_SIZE, "%d\n", !!(val & TWM_EXIT_BIT));
284}
285
286static struct class_attribute twm_attributes[] = {
287 [TWM_ENABLE] = __ATTR(twm_enable, 0644,
288 twm_enable_show, twm_enable_store),
289 [TWM_EXIT] = __ATTR(twm_exit, 0644,
290 twm_exit_show, NULL),
291 __ATTR_NULL,
292};
293
294int qpnp_misc_twm_notifier_register(struct notifier_block *nb)
295{
296 return raw_notifier_chain_register(&twm_notifier, nb);
297}
298EXPORT_SYMBOL(qpnp_misc_twm_notifier_register);
299
300int qpnp_misc_twm_notifier_unregister(struct notifier_block *nb)
301{
302 return raw_notifier_chain_unregister(&twm_notifier, nb);
303}
304EXPORT_SYMBOL(qpnp_misc_twm_notifier_unregister);
305
Subbaraman Narayanamurthye68d90f2017-04-03 15:05:21 -0700306static int qpnp_misc_dt_init(struct qpnp_misc_dev *mdev)
307{
308 struct device_node *node = mdev->dev->of_node;
309 u32 val;
310 int rc;
311
Anirudh Ghayalfa0300c2018-04-12 21:30:29 +0530312 if (of_property_read_bool(mdev->dev->of_node,
313 "qcom,support-twm-config")) {
314 mdev->support_twm_config = true;
315 mdev->twm_mode = TWM_MODE_3;
316 rc = of_property_read_u8(mdev->dev->of_node, "qcom,twm-mode",
317 &mdev->twm_mode);
318 if (!rc && (mdev->twm_mode < TWM_MODE_1 ||
319 mdev->twm_mode > TWM_MODE_3)) {
320 pr_err("Invalid TWM mode %d\n", mdev->twm_mode);
321 return -EINVAL;
322 }
323 }
324
Subbaraman Narayanamurthye68d90f2017-04-03 15:05:21 -0700325 rc = of_property_read_u32(node, "reg", &mdev->base);
326 if (rc < 0 || !mdev->base) {
327 dev_err(mdev->dev, "Base address not defined or invalid\n");
328 return -EINVAL;
329 }
330
331 if (!of_property_read_u32(node, "qcom,pwm-sel", &val)) {
332 if (val > PWM_SEL_MAX) {
333 dev_err(mdev->dev, "Invalid value for pwm-sel\n");
334 return -EINVAL;
335 }
336 mdev->pwm_sel = (u8)val;
337 }
338 mdev->enable_gp_driver = of_property_read_bool(node,
339 "qcom,enable-gp-driver");
340
341 WARN((mdev->pwm_sel > 0 && !mdev->enable_gp_driver),
342 "Setting PWM source without enabling gp driver\n");
343 WARN((mdev->pwm_sel == 0 && mdev->enable_gp_driver),
344 "Enabling gp driver without setting PWM source\n");
345
346 return 0;
347}
348
349static int qpnp_misc_config(struct qpnp_misc_dev *mdev)
350{
351 int rc, version_name;
352
353 version_name = get_qpnp_misc_version_name(mdev);
354
355 switch (version_name) {
356 case PMDCALIFORNIUM:
357 if (mdev->pwm_sel > 0 && mdev->enable_gp_driver) {
358 rc = qpnp_write_byte(mdev, REG_PWM_SEL, mdev->pwm_sel);
359 if (rc < 0) {
360 dev_err(mdev->dev,
361 "Failed to write PWM_SEL reg\n");
362 return rc;
363 }
364
365 rc = qpnp_write_byte(mdev, REG_GP_DRIVER_EN,
366 GP_DRIVER_EN_BIT);
367 if (rc < 0) {
368 dev_err(mdev->dev,
369 "Failed to write GP_DRIVER_EN reg\n");
370 return rc;
371 }
372 }
373 break;
374 default:
375 break;
376 }
377
Anirudh Ghayalfa0300c2018-04-12 21:30:29 +0530378 if (mdev->support_twm_config) {
379 mdev->twm_class.name = "pmic_twm",
380 mdev->twm_class.owner = THIS_MODULE,
381 mdev->twm_class.class_attrs = twm_attributes;
382
383 rc = class_register(&mdev->twm_class);
384 if (rc < 0) {
385 pr_err("Failed to register pmic_twm class rc=%d\n", rc);
386 return rc;
387 }
388 }
389
Subbaraman Narayanamurthye68d90f2017-04-03 15:05:21 -0700390 return 0;
391}
392
393static int qpnp_misc_probe(struct platform_device *pdev)
394{
395 struct qpnp_misc_dev *mdev = ERR_PTR(-EINVAL);
396 int rc;
397
398 mdev = devm_kzalloc(&pdev->dev, sizeof(*mdev), GFP_KERNEL);
399 if (!mdev)
400 return -ENOMEM;
401
402 mdev->dev = &pdev->dev;
Anirudh Ghayalfa0300c2018-04-12 21:30:29 +0530403 dev_set_drvdata(&pdev->dev, mdev);
Subbaraman Narayanamurthye68d90f2017-04-03 15:05:21 -0700404 mdev->regmap = dev_get_regmap(mdev->dev->parent, NULL);
405 if (!mdev->regmap) {
406 dev_err(mdev->dev, "Parent regmap is unavailable\n");
407 return -ENXIO;
408 }
409
410 rc = qpnp_misc_dt_init(mdev);
411 if (rc < 0) {
412 dev_err(mdev->dev,
413 "Error reading device tree properties, rc=%d\n", rc);
414 return rc;
415 }
416
417
418 rc = qpnp_read_byte(mdev, REG_SUBTYPE, &mdev->version.subtype);
419 if (rc < 0) {
420 dev_err(mdev->dev, "Failed to read subtype, rc=%d\n", rc);
421 return rc;
422 }
423
424 rc = qpnp_read_byte(mdev, REG_DIG_MAJOR_REV,
425 &mdev->version.dig_major_rev);
426 if (rc < 0) {
427 dev_err(mdev->dev, "Failed to read dig_major_rev, rc=%d\n", rc);
428 return rc;
429 }
430
431 mutex_lock(&qpnp_misc_dev_list_mutex);
432 list_add_tail(&mdev->list, &qpnp_misc_dev_list);
433 mutex_unlock(&qpnp_misc_dev_list_mutex);
434
435 rc = qpnp_misc_config(mdev);
436 if (rc < 0) {
437 dev_err(mdev->dev,
438 "Error configuring module registers, rc=%d\n", rc);
439 return rc;
440 }
441
442 dev_info(mdev->dev, "probe successful\n");
443 return 0;
444}
445
Anirudh Ghayalfa0300c2018-04-12 21:30:29 +0530446static void qpnp_misc_shutdown(struct platform_device *pdev)
447{
448 struct qpnp_misc_dev *mdev = dev_get_drvdata(&pdev->dev);
449 int rc;
450
451 if (mdev->support_twm_config) {
452 rc = qpnp_write_byte(mdev, MISC_SPARE_2,
453 mdev->twm_enable ? mdev->twm_mode : 0x0);
454 if (rc < 0)
455 pr_err("Failed to write MISC_SPARE_2 (twm_mode) val=%d rc=%d\n",
456 mdev->twm_enable ? mdev->twm_mode : 0x0, rc);
457
458 rc = qpnp_write_byte(mdev, MISC_SPARE_1,
459 mdev->twm_enable ? ENABLE_TWM_MODE : 0x0);
460 if (rc < 0)
461 pr_err("Failed to write MISC_SPARE_1 (twm_state) val=%d rc=%d\n",
462 mdev->twm_enable ? ENABLE_TWM_MODE : 0x0, rc);
463
464 pr_debug("PMIC configured for TWM-%s MODE=%d\n",
465 mdev->twm_enable ? "enabled" : "disabled",
466 mdev->twm_mode);
467 }
468}
469
Subbaraman Narayanamurthye68d90f2017-04-03 15:05:21 -0700470static struct platform_driver qpnp_misc_driver = {
471 .probe = qpnp_misc_probe,
Anirudh Ghayalfa0300c2018-04-12 21:30:29 +0530472 .shutdown = qpnp_misc_shutdown,
Subbaraman Narayanamurthye68d90f2017-04-03 15:05:21 -0700473 .driver = {
474 .name = QPNP_MISC_DEV_NAME,
475 .owner = THIS_MODULE,
476 .of_match_table = qpnp_misc_match_table,
477 },
478};
479
480static int __init qpnp_misc_init(void)
481{
482 return platform_driver_register(&qpnp_misc_driver);
483}
484
485static void __exit qpnp_misc_exit(void)
486{
487 return platform_driver_unregister(&qpnp_misc_driver);
488}
489
490subsys_initcall(qpnp_misc_init);
491module_exit(qpnp_misc_exit);
492
493MODULE_DESCRIPTION(QPNP_MISC_DEV_NAME);
494MODULE_LICENSE("GPL v2");
495MODULE_ALIAS("platform:" QPNP_MISC_DEV_NAME);