diff options
author | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
---|---|---|
committer | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
commit | fcc9d2e5a6c89d22b8b773a64fb4ad21ac318446 (patch) | |
tree | a57612d1888735a2ec7972891b68c1ac5ec8faea /drivers/mfd/max8907c.c | |
parent | 8dea78da5cee153b8af9c07a2745f6c55057fe12 (diff) |
Diffstat (limited to 'drivers/mfd/max8907c.c')
-rw-r--r-- | drivers/mfd/max8907c.c | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/drivers/mfd/max8907c.c b/drivers/mfd/max8907c.c new file mode 100644 index 00000000000..d03dbced21e --- /dev/null +++ b/drivers/mfd/max8907c.c | |||
@@ -0,0 +1,376 @@ | |||
1 | /* | ||
2 | * max8907c.c - mfd driver for MAX8907c | ||
3 | * | ||
4 | * Copyright (C) 2010 Gyungoh Yoo <jack.yoo@maxim-ic.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | */ | ||
10 | |||
11 | #include <linux/module.h> | ||
12 | #include <linux/init.h> | ||
13 | #include <linux/slab.h> | ||
14 | #include <linux/i2c.h> | ||
15 | #include <linux/mfd/core.h> | ||
16 | #include <linux/mfd/max8907c.h> | ||
17 | |||
18 | static struct mfd_cell cells[] = { | ||
19 | {.name = "max8907-regulator",}, | ||
20 | {.name = "max8907c-rtc",}, | ||
21 | }; | ||
22 | |||
23 | static int max8907c_i2c_read(struct i2c_client *i2c, u8 reg, u8 count, u8 *dest) | ||
24 | { | ||
25 | struct i2c_msg xfer[2]; | ||
26 | int ret = 0; | ||
27 | |||
28 | xfer[0].addr = i2c->addr; | ||
29 | xfer[0].flags = I2C_M_NOSTART; | ||
30 | xfer[0].len = 1; | ||
31 | xfer[0].buf = ® | ||
32 | |||
33 | xfer[1].addr = i2c->addr; | ||
34 | xfer[1].flags = I2C_M_RD; | ||
35 | xfer[1].len = count; | ||
36 | xfer[1].buf = dest; | ||
37 | |||
38 | ret = i2c_transfer(i2c->adapter, xfer, 2); | ||
39 | if (ret < 0) | ||
40 | return ret; | ||
41 | if (ret != 2) | ||
42 | return -EIO; | ||
43 | |||
44 | return 0; | ||
45 | } | ||
46 | |||
47 | static int max8907c_i2c_write(struct i2c_client *i2c, u8 reg, u8 count, const u8 *src) | ||
48 | { | ||
49 | u8 msg[0x100 + 1]; | ||
50 | int ret = 0; | ||
51 | |||
52 | msg[0] = reg; | ||
53 | memcpy(&msg[1], src, count); | ||
54 | |||
55 | ret = i2c_master_send(i2c, msg, count + 1); | ||
56 | if (ret < 0) | ||
57 | return ret; | ||
58 | if (ret != count + 1) | ||
59 | return -EIO; | ||
60 | |||
61 | return 0; | ||
62 | } | ||
63 | |||
64 | int max8907c_reg_read(struct i2c_client *i2c, u8 reg) | ||
65 | { | ||
66 | int ret; | ||
67 | u8 val; | ||
68 | |||
69 | ret = max8907c_i2c_read(i2c, reg, 1, &val); | ||
70 | |||
71 | pr_debug("max8907c: reg read reg=%x, val=%x\n", | ||
72 | (unsigned int)reg, (unsigned int)val); | ||
73 | |||
74 | if (ret != 0) | ||
75 | pr_err("Failed to read max8907c I2C driver: %d\n", ret); | ||
76 | return val; | ||
77 | } | ||
78 | EXPORT_SYMBOL_GPL(max8907c_reg_read); | ||
79 | |||
80 | int max8907c_reg_bulk_read(struct i2c_client *i2c, u8 reg, u8 count, u8 *val) | ||
81 | { | ||
82 | int ret; | ||
83 | |||
84 | ret = max8907c_i2c_read(i2c, reg, count, val); | ||
85 | |||
86 | pr_debug("max8907c: reg read reg=%x, val=%x\n", | ||
87 | (unsigned int)reg, (unsigned int)*val); | ||
88 | |||
89 | if (ret != 0) | ||
90 | pr_err("Failed to read max8907c I2C driver: %d\n", ret); | ||
91 | return ret; | ||
92 | } | ||
93 | EXPORT_SYMBOL_GPL(max8907c_reg_bulk_read); | ||
94 | |||
95 | int max8907c_reg_write(struct i2c_client *i2c, u8 reg, u8 val) | ||
96 | { | ||
97 | struct max8907c *max8907c = i2c_get_clientdata(i2c); | ||
98 | int ret; | ||
99 | |||
100 | pr_debug("max8907c: reg write reg=%x, val=%x\n", | ||
101 | (unsigned int)reg, (unsigned int)val); | ||
102 | |||
103 | mutex_lock(&max8907c->io_lock); | ||
104 | ret = max8907c_i2c_write(i2c, reg, 1, &val); | ||
105 | mutex_unlock(&max8907c->io_lock); | ||
106 | |||
107 | if (ret != 0) | ||
108 | pr_err("Failed to write max8907c I2C driver: %d\n", ret); | ||
109 | return ret; | ||
110 | } | ||
111 | EXPORT_SYMBOL_GPL(max8907c_reg_write); | ||
112 | |||
113 | int max8907c_reg_bulk_write(struct i2c_client *i2c, u8 reg, u8 count, u8 *val) | ||
114 | { | ||
115 | struct max8907c *max8907c = i2c_get_clientdata(i2c); | ||
116 | int ret; | ||
117 | |||
118 | pr_debug("max8907c: reg write reg=%x, val=%x\n", | ||
119 | (unsigned int)reg, (unsigned int)*val); | ||
120 | |||
121 | mutex_lock(&max8907c->io_lock); | ||
122 | ret = max8907c_i2c_write(i2c, reg, count, val); | ||
123 | mutex_unlock(&max8907c->io_lock); | ||
124 | |||
125 | if (ret != 0) | ||
126 | pr_err("Failed to write max8907c I2C driver: %d\n", ret); | ||
127 | return ret; | ||
128 | } | ||
129 | EXPORT_SYMBOL_GPL(max8907c_reg_bulk_write); | ||
130 | |||
131 | int max8907c_set_bits(struct i2c_client *i2c, u8 reg, u8 mask, u8 val) | ||
132 | { | ||
133 | struct max8907c *max8907c = i2c_get_clientdata(i2c); | ||
134 | u8 tmp; | ||
135 | int ret; | ||
136 | |||
137 | pr_debug("max8907c: reg write reg=%02X, val=%02X, mask=%02X\n", | ||
138 | (unsigned int)reg, (unsigned int)val, (unsigned int)mask); | ||
139 | |||
140 | mutex_lock(&max8907c->io_lock); | ||
141 | ret = max8907c_i2c_read(i2c, reg, 1, &tmp); | ||
142 | if (ret == 0) { | ||
143 | val = (tmp & ~mask) | (val & mask); | ||
144 | ret = max8907c_i2c_write(i2c, reg, 1, &val); | ||
145 | } | ||
146 | mutex_unlock(&max8907c->io_lock); | ||
147 | |||
148 | if (ret != 0) | ||
149 | pr_err("Failed to write max8907c I2C driver: %d\n", ret); | ||
150 | return ret; | ||
151 | } | ||
152 | EXPORT_SYMBOL_GPL(max8907c_set_bits); | ||
153 | |||
154 | static struct i2c_client *max8907c_client = NULL; | ||
155 | static void max8907c_power_off(void) | ||
156 | { | ||
157 | if (!max8907c_client) | ||
158 | return; | ||
159 | |||
160 | max8907c_set_bits(max8907c_client, MAX8907C_REG_RESET_CNFG, | ||
161 | MAX8907C_MASK_POWER_OFF, 0x40); | ||
162 | } | ||
163 | |||
164 | void max8907c_deep_sleep(int enter) | ||
165 | { | ||
166 | if (!max8907c_client) | ||
167 | return; | ||
168 | |||
169 | if (enter) { | ||
170 | max8907c_reg_write(max8907c_client, MAX8907C_REG_SDSEQCNT1, | ||
171 | MAX8907C_POWER_UP_DELAY_CNT12); | ||
172 | max8907c_reg_write(max8907c_client, MAX8907C_REG_SDSEQCNT2, | ||
173 | MAX8907C_DELAY_CNT0); | ||
174 | max8907c_reg_write(max8907c_client, MAX8907C_REG_SDCTL2, | ||
175 | MAX8907C_SD_SEQ2); | ||
176 | } else { | ||
177 | max8907c_reg_write(max8907c_client, MAX8907C_REG_SDSEQCNT1, | ||
178 | MAX8907C_DELAY_CNT0); | ||
179 | max8907c_reg_write(max8907c_client, MAX8907C_REG_SDCTL2, | ||
180 | MAX8907C_SD_SEQ1); | ||
181 | max8907c_reg_write(max8907c_client, MAX8907C_REG_SDSEQCNT2, | ||
182 | MAX8907C_POWER_UP_DELAY_CNT1 | MAX8907C_POWER_DOWN_DELAY_CNT12); | ||
183 | } | ||
184 | } | ||
185 | |||
186 | static int max8907c_remove_subdev(struct device *dev, void *unused) | ||
187 | { | ||
188 | platform_device_unregister(to_platform_device(dev)); | ||
189 | return 0; | ||
190 | } | ||
191 | |||
192 | static int max8907c_remove_subdevs(struct max8907c *max8907c) | ||
193 | { | ||
194 | return device_for_each_child(max8907c->dev, NULL, | ||
195 | max8907c_remove_subdev); | ||
196 | } | ||
197 | |||
198 | static int max8097c_add_subdevs(struct max8907c *max8907c, | ||
199 | struct max8907c_platform_data *pdata) | ||
200 | { | ||
201 | struct platform_device *pdev; | ||
202 | int ret; | ||
203 | int i; | ||
204 | |||
205 | for (i = 0; i < pdata->num_subdevs; i++) { | ||
206 | pdev = platform_device_alloc(pdata->subdevs[i]->name, | ||
207 | pdata->subdevs[i]->id); | ||
208 | |||
209 | pdev->dev.parent = max8907c->dev; | ||
210 | pdev->dev.platform_data = pdata->subdevs[i]->dev.platform_data; | ||
211 | |||
212 | ret = platform_device_add(pdev); | ||
213 | if (ret) | ||
214 | goto error; | ||
215 | } | ||
216 | return 0; | ||
217 | |||
218 | error: | ||
219 | max8907c_remove_subdevs(max8907c); | ||
220 | return ret; | ||
221 | } | ||
222 | |||
223 | int max8907c_pwr_en_config(void) | ||
224 | { | ||
225 | int ret; | ||
226 | u8 data; | ||
227 | |||
228 | if (!max8907c_client) | ||
229 | return -EINVAL; | ||
230 | |||
231 | /* | ||
232 | * Enable/disable PWREN h/w control mechanism (PWREN signal must be | ||
233 | * inactive = high at this time) | ||
234 | */ | ||
235 | ret = max8907c_set_bits(max8907c_client, MAX8907C_REG_RESET_CNFG, | ||
236 | MAX8907C_MASK_PWR_EN, MAX8907C_PWR_EN); | ||
237 | if (ret != 0) | ||
238 | return ret; | ||
239 | |||
240 | /* | ||
241 | * When enabled, connect PWREN to SEQ2 by clearing SEQ2 configuration | ||
242 | * settings for silicon revision that requires s/w WAR. On other | ||
243 | * MAX8907B revisions PWREN is always connected to SEQ2. | ||
244 | */ | ||
245 | data = max8907c_reg_read(max8907c_client, MAX8907C_REG_II2RR); | ||
246 | |||
247 | if (data == MAX8907B_II2RR_PWREN_WAR) { | ||
248 | data = 0x00; | ||
249 | ret = max8907c_reg_write(max8907c_client, MAX8907C_REG_SEQ2CNFG, data); | ||
250 | } | ||
251 | return ret; | ||
252 | } | ||
253 | |||
254 | int max8907c_pwr_en_attach(void) | ||
255 | { | ||
256 | int ret; | ||
257 | |||
258 | if (!max8907c_client) | ||
259 | return -EINVAL; | ||
260 | |||
261 | /* No sequencer delay for CPU rail when it is attached */ | ||
262 | ret = max8907c_reg_write(max8907c_client, MAX8907C_REG_SDSEQCNT1, | ||
263 | MAX8907C_DELAY_CNT0); | ||
264 | if (ret != 0) | ||
265 | return ret; | ||
266 | |||
267 | return max8907c_set_bits(max8907c_client, MAX8907C_REG_SDCTL1, | ||
268 | MAX8907C_MASK_CTL_SEQ, MAX8907C_CTL_SEQ); | ||
269 | } | ||
270 | |||
271 | static int max8907c_i2c_probe(struct i2c_client *i2c, | ||
272 | const struct i2c_device_id *id) | ||
273 | { | ||
274 | struct max8907c *max8907c; | ||
275 | struct max8907c_platform_data *pdata = i2c->dev.platform_data; | ||
276 | int ret; | ||
277 | int i; | ||
278 | |||
279 | max8907c = kzalloc(sizeof(struct max8907c), GFP_KERNEL); | ||
280 | if (max8907c == NULL) | ||
281 | return -ENOMEM; | ||
282 | |||
283 | max8907c->dev = &i2c->dev; | ||
284 | dev_set_drvdata(max8907c->dev, max8907c); | ||
285 | |||
286 | max8907c->i2c_power = i2c; | ||
287 | i2c_set_clientdata(i2c, max8907c); | ||
288 | |||
289 | max8907c->i2c_rtc = i2c_new_dummy(i2c->adapter, RTC_I2C_ADDR); | ||
290 | i2c_set_clientdata(max8907c->i2c_rtc, max8907c); | ||
291 | |||
292 | mutex_init(&max8907c->io_lock); | ||
293 | |||
294 | for (i = 0; i < ARRAY_SIZE(cells); i++) { | ||
295 | cells[i].platform_data = max8907c; | ||
296 | cells[i].pdata_size = sizeof(*max8907c); | ||
297 | } | ||
298 | ret = mfd_add_devices(max8907c->dev, -1, cells, ARRAY_SIZE(cells), | ||
299 | NULL, 0); | ||
300 | if (ret != 0) { | ||
301 | i2c_unregister_device(max8907c->i2c_rtc); | ||
302 | kfree(max8907c); | ||
303 | pr_debug("max8907c: failed to add MFD devices %X\n", ret); | ||
304 | return ret; | ||
305 | } | ||
306 | |||
307 | max8907c_client = i2c; | ||
308 | |||
309 | max8907c_irq_init(max8907c, i2c->irq, pdata->irq_base); | ||
310 | |||
311 | ret = max8097c_add_subdevs(max8907c, pdata); | ||
312 | |||
313 | if (pdata->use_power_off && !pm_power_off) | ||
314 | pm_power_off = max8907c_power_off; | ||
315 | |||
316 | if (pdata->max8907c_setup) | ||
317 | return pdata->max8907c_setup(); | ||
318 | |||
319 | return ret; | ||
320 | } | ||
321 | |||
322 | static int max8907c_i2c_remove(struct i2c_client *i2c) | ||
323 | { | ||
324 | struct max8907c *max8907c = i2c_get_clientdata(i2c); | ||
325 | |||
326 | max8907c_remove_subdevs(max8907c); | ||
327 | i2c_unregister_device(max8907c->i2c_rtc); | ||
328 | mfd_remove_devices(max8907c->dev); | ||
329 | max8907c_irq_free(max8907c); | ||
330 | kfree(max8907c); | ||
331 | |||
332 | return 0; | ||
333 | } | ||
334 | |||
335 | static const struct i2c_device_id max8907c_i2c_id[] = { | ||
336 | {"max8907c", 0}, | ||
337 | {} | ||
338 | }; | ||
339 | |||
340 | MODULE_DEVICE_TABLE(i2c, max8907c_i2c_id); | ||
341 | |||
342 | static struct i2c_driver max8907c_i2c_driver = { | ||
343 | .driver = { | ||
344 | .name = "max8907c", | ||
345 | .owner = THIS_MODULE, | ||
346 | }, | ||
347 | .probe = max8907c_i2c_probe, | ||
348 | .remove = max8907c_i2c_remove, | ||
349 | .suspend = max8907c_suspend, | ||
350 | .resume = max8907c_resume, | ||
351 | .id_table = max8907c_i2c_id, | ||
352 | }; | ||
353 | |||
354 | static int __init max8907c_i2c_init(void) | ||
355 | { | ||
356 | int ret = -ENODEV; | ||
357 | |||
358 | ret = i2c_add_driver(&max8907c_i2c_driver); | ||
359 | if (ret != 0) | ||
360 | pr_err("Failed to register I2C driver: %d\n", ret); | ||
361 | |||
362 | return ret; | ||
363 | } | ||
364 | |||
365 | subsys_initcall(max8907c_i2c_init); | ||
366 | |||
367 | static void __exit max8907c_i2c_exit(void) | ||
368 | { | ||
369 | i2c_del_driver(&max8907c_i2c_driver); | ||
370 | } | ||
371 | |||
372 | module_exit(max8907c_i2c_exit); | ||
373 | |||
374 | MODULE_DESCRIPTION("MAX8907C multi-function core driver"); | ||
375 | MODULE_AUTHOR("Gyungoh Yoo <jack.yoo@maxim-ic.com>"); | ||
376 | MODULE_LICENSE("GPL"); | ||