blob: b427f43e76df128cf817cb0e636028da83f670cd [file] [log] [blame]
David Collins8885f792017-01-26 14:36:34 -08001/* Copyright (c) 2013-2015, 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
13#define pr_fmt(fmt) "%s: " fmt, __func__
14
15#include <linux/kernel.h>
16#include <linux/regmap.h>
17#include <linux/module.h>
18#include <linux/slab.h>
19#include <linux/spmi.h>
20#include <linux/platform_device.h>
21#include <linux/of.h>
22#include <linux/of_device.h>
23#include <linux/platform_device.h>
24
25#define QPNP_COINCELL_DRIVER_NAME "qcom,qpnp-coincell"
26
27struct qpnp_coincell {
28 struct platform_device *pdev;
29 struct regmap *regmap;
30 u16 base_addr;
31};
32
33#define QPNP_COINCELL_REG_TYPE 0x04
34#define QPNP_COINCELL_REG_SUBTYPE 0x05
35#define QPNP_COINCELL_REG_RSET 0x44
36#define QPNP_COINCELL_REG_VSET 0x45
37#define QPNP_COINCELL_REG_ENABLE 0x46
38
39#define QPNP_COINCELL_TYPE 0x02
40#define QPNP_COINCELL_SUBTYPE 0x20
41#define QPNP_COINCELL_ENABLE 0x80
42#define QPNP_COINCELL_DISABLE 0x00
43
44static const int qpnp_rset_map[] = {2100, 1700, 1200, 800};
45static const int qpnp_vset_map[] = {2500, 3200, 3100, 3000};
46
47static int qpnp_coincell_set_resistance(struct qpnp_coincell *chip, int rset)
48{
49 int i, rc;
50 u8 reg;
51
52 for (i = 0; i < ARRAY_SIZE(qpnp_rset_map); i++)
53 if (rset == qpnp_rset_map[i])
54 break;
55
56 if (i >= ARRAY_SIZE(qpnp_rset_map)) {
57 pr_err("invalid rset=%d value\n", rset);
58 return -EINVAL;
59 }
60
61 reg = i;
62 rc = regmap_write(chip->regmap,
63 chip->base_addr + QPNP_COINCELL_REG_RSET, reg);
64 if (rc)
65 dev_err(&chip->pdev->dev,
66 "%s: could not write to RSET register, rc=%d\n",
67 __func__, rc);
68
69 return rc;
70}
71
72static int qpnp_coincell_set_voltage(struct qpnp_coincell *chip, int vset)
73{
74 int i, rc;
75 u8 reg;
76
77 for (i = 0; i < ARRAY_SIZE(qpnp_vset_map); i++)
78 if (vset == qpnp_vset_map[i])
79 break;
80
81 if (i >= ARRAY_SIZE(qpnp_vset_map)) {
82 pr_err("invalid vset=%d value\n", vset);
83 return -EINVAL;
84 }
85
86 reg = i;
87 rc = regmap_write(chip->regmap,
88 chip->base_addr + QPNP_COINCELL_REG_VSET, reg);
89 if (rc)
90 dev_err(&chip->pdev->dev,
91 "%s: could not write to VSET register, rc=%d\n",
92 __func__, rc);
93
94 return rc;
95}
96
97static int qpnp_coincell_set_charge(struct qpnp_coincell *chip, bool enabled)
98{
99 int rc;
100 u8 reg;
101
102 reg = enabled ? QPNP_COINCELL_ENABLE : QPNP_COINCELL_DISABLE;
103 rc = regmap_write(chip->regmap,
104 chip->base_addr + QPNP_COINCELL_REG_ENABLE, reg);
105 if (rc)
106 dev_err(&chip->pdev->dev,
107 "%s: could not write to ENABLE register, rc=%d\n",
108 __func__, rc);
109
110 return rc;
111}
112
113static void qpnp_coincell_charger_show_state(struct qpnp_coincell *chip)
114{
115 int rc, rset, vset, temp;
116 bool enabled;
117 u8 reg[QPNP_COINCELL_REG_ENABLE - QPNP_COINCELL_REG_RSET + 1];
118
119 rc = regmap_bulk_read(chip->regmap,
120 chip->base_addr + QPNP_COINCELL_REG_RSET, reg,
121 ARRAY_SIZE(reg));
122 if (rc) {
123 dev_err(&chip->pdev->dev,
124 "%s: could not read RSET register, rc=%d\n",
125 __func__, rc);
126 return;
127 }
128
129 temp = reg[QPNP_COINCELL_REG_RSET - QPNP_COINCELL_REG_RSET];
130 if (temp >= ARRAY_SIZE(qpnp_rset_map)) {
131 dev_err(&chip->pdev->dev,
132 "unknown RSET=0x%02X register value\n",
133 temp);
134 return;
135 }
136 rset = qpnp_rset_map[temp];
137
138 temp = reg[QPNP_COINCELL_REG_VSET - QPNP_COINCELL_REG_RSET];
139 if (temp >= ARRAY_SIZE(qpnp_vset_map)) {
140 dev_err(&chip->pdev->dev,
141 "unknown VSET=0x%02X register value\n",
142 temp);
143 return;
144 }
145 vset = qpnp_vset_map[temp];
146
147 temp = reg[QPNP_COINCELL_REG_ENABLE - QPNP_COINCELL_REG_RSET];
148 enabled = temp & QPNP_COINCELL_ENABLE;
149
150 pr_info("enabled=%c, voltage=%d mV, resistance=%d ohm\n",
151 (enabled ? 'Y' : 'N'), vset, rset);
152}
153
154static int qpnp_coincell_check_type(struct qpnp_coincell *chip)
155{
156 int rc;
157 u8 type[2];
158
159 rc = regmap_bulk_read(chip->regmap,
160 chip->base_addr + QPNP_COINCELL_REG_TYPE, type,
161 2);
162 if (rc) {
163 dev_err(&chip->pdev->dev,
164 "%s: could not read type register, rc=%d\n",
165 __func__, rc);
166 return rc;
167 }
168
169 if (type[0] != QPNP_COINCELL_TYPE || type[1] != QPNP_COINCELL_SUBTYPE) {
170 dev_err(&chip->pdev->dev,
171 "%s: invalid type=0x%02X or subtype=0x%02X register value\n",
172 __func__, type[0], type[1]);
173 return -ENODEV;
174 }
175
176 return rc;
177}
178
179static int qpnp_coincell_probe(struct platform_device *pdev)
180{
181 struct device_node *node = pdev->dev.of_node;
182 struct qpnp_coincell *chip;
183 unsigned int base;
184 u32 temp;
185 int rc = 0;
186
187 if (!node) {
188 dev_err(&pdev->dev, "%s: device node missing\n", __func__);
189 return -ENODEV;
190 }
191
192 chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
193 if (!chip)
194 return -ENOMEM;
195
196 chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
197 if (!chip->regmap) {
198 dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
199 return -EINVAL;
200 }
201 chip->pdev = pdev;
202
203 rc = of_property_read_u32(pdev->dev.of_node, "reg", &base);
204 if (rc < 0) {
205 dev_err(&pdev->dev,
206 "Couldn't find reg in node = %s rc = %d\n",
207 pdev->dev.of_node->full_name, rc);
208 return rc;
209 }
210 chip->base_addr = base;
211
212 rc = qpnp_coincell_check_type(chip);
213 if (rc)
214 return rc;
215
216 rc = of_property_read_u32(node, "qcom,rset-ohms", &temp);
217 if (!rc) {
218 rc = qpnp_coincell_set_resistance(chip, temp);
219 if (rc)
220 return rc;
221 }
222
223 rc = of_property_read_u32(node, "qcom,vset-millivolts", &temp);
224 if (!rc) {
225 rc = qpnp_coincell_set_voltage(chip, temp);
226 if (rc)
227 return rc;
228 }
229
230 rc = of_property_read_u32(node, "qcom,charge-enable", &temp);
231 if (!rc) {
232 rc = qpnp_coincell_set_charge(chip, temp);
233 if (rc)
234 return rc;
235 }
236
237 qpnp_coincell_charger_show_state(chip);
238
239 return 0;
240}
241
242static int qpnp_coincell_remove(struct platform_device *pdev)
243{
244 return 0;
245}
246
247static const struct of_device_id qpnp_coincell_match_table[] = {
248 { .compatible = QPNP_COINCELL_DRIVER_NAME, },
249 {}
250};
251
252static const struct platform_device_id qpnp_coincell_id[] = {
253 { QPNP_COINCELL_DRIVER_NAME, 0 },
254 {}
255};
256MODULE_DEVICE_TABLE(spmi, qpnp_coincell_id);
257
258static struct platform_driver qpnp_coincell_driver = {
259 .driver = {
260 .name = QPNP_COINCELL_DRIVER_NAME,
261 .of_match_table = qpnp_coincell_match_table,
262 .owner = THIS_MODULE,
263 },
264 .probe = qpnp_coincell_probe,
265 .remove = qpnp_coincell_remove,
266 .id_table = qpnp_coincell_id,
267};
268
269static int __init qpnp_coincell_init(void)
270{
271 return platform_driver_register(&qpnp_coincell_driver);
272}
273
274static void __exit qpnp_coincell_exit(void)
275{
276 platform_driver_unregister(&qpnp_coincell_driver);
277}
278
279MODULE_DESCRIPTION("QPNP PMIC coincell charger driver");
280MODULE_LICENSE("GPL v2");
281
282module_init(qpnp_coincell_init);
283module_exit(qpnp_coincell_exit);