Commit | Line | Data |
---|---|---|
bbd51b1f | 1 | /* |
53dbab7a | 2 | * I2C driver for Marvell 88PM860x |
bbd51b1f HZ |
3 | * |
4 | * Copyright (C) 2009 Marvell International Ltd. | |
5 | * Haojian Zhuang <haojian.zhuang@marvell.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License version 2 as | |
9 | * published by the Free Software Foundation. | |
10 | */ | |
11 | #include <linux/kernel.h> | |
12 | #include <linux/module.h> | |
13 | #include <linux/platform_device.h> | |
14 | #include <linux/i2c.h> | |
b46a36c0 JZ |
15 | #include <linux/err.h> |
16 | #include <linux/regmap.h> | |
53dbab7a | 17 | #include <linux/mfd/88pm860x.h> |
5a0e3ad6 | 18 | #include <linux/slab.h> |
bbd51b1f | 19 | |
53dbab7a | 20 | int pm860x_reg_read(struct i2c_client *i2c, int reg) |
bbd51b1f | 21 | { |
53dbab7a | 22 | struct pm860x_chip *chip = i2c_get_clientdata(i2c); |
b46a36c0 JZ |
23 | struct regmap *map = (i2c == chip->client) ? chip->regmap |
24 | : chip->regmap_companion; | |
25 | unsigned int data; | |
bbd51b1f HZ |
26 | int ret; |
27 | ||
b46a36c0 | 28 | ret = regmap_read(map, reg, &data); |
bbd51b1f HZ |
29 | if (ret < 0) |
30 | return ret; | |
31 | else | |
32 | return (int)data; | |
33 | } | |
53dbab7a | 34 | EXPORT_SYMBOL(pm860x_reg_read); |
bbd51b1f | 35 | |
53dbab7a | 36 | int pm860x_reg_write(struct i2c_client *i2c, int reg, |
bbd51b1f HZ |
37 | unsigned char data) |
38 | { | |
53dbab7a | 39 | struct pm860x_chip *chip = i2c_get_clientdata(i2c); |
b46a36c0 JZ |
40 | struct regmap *map = (i2c == chip->client) ? chip->regmap |
41 | : chip->regmap_companion; | |
bbd51b1f HZ |
42 | int ret; |
43 | ||
b46a36c0 | 44 | ret = regmap_write(map, reg, data); |
bbd51b1f HZ |
45 | return ret; |
46 | } | |
53dbab7a | 47 | EXPORT_SYMBOL(pm860x_reg_write); |
bbd51b1f | 48 | |
53dbab7a | 49 | int pm860x_bulk_read(struct i2c_client *i2c, int reg, |
bbd51b1f HZ |
50 | int count, unsigned char *buf) |
51 | { | |
53dbab7a | 52 | struct pm860x_chip *chip = i2c_get_clientdata(i2c); |
b46a36c0 JZ |
53 | struct regmap *map = (i2c == chip->client) ? chip->regmap |
54 | : chip->regmap_companion; | |
bbd51b1f HZ |
55 | int ret; |
56 | ||
b46a36c0 | 57 | ret = regmap_raw_read(map, reg, buf, count); |
bbd51b1f HZ |
58 | return ret; |
59 | } | |
53dbab7a | 60 | EXPORT_SYMBOL(pm860x_bulk_read); |
bbd51b1f | 61 | |
53dbab7a | 62 | int pm860x_bulk_write(struct i2c_client *i2c, int reg, |
bbd51b1f HZ |
63 | int count, unsigned char *buf) |
64 | { | |
53dbab7a | 65 | struct pm860x_chip *chip = i2c_get_clientdata(i2c); |
b46a36c0 JZ |
66 | struct regmap *map = (i2c == chip->client) ? chip->regmap |
67 | : chip->regmap_companion; | |
bbd51b1f HZ |
68 | int ret; |
69 | ||
b46a36c0 | 70 | ret = regmap_raw_write(map, reg, buf, count); |
bbd51b1f HZ |
71 | return ret; |
72 | } | |
53dbab7a | 73 | EXPORT_SYMBOL(pm860x_bulk_write); |
bbd51b1f | 74 | |
53dbab7a | 75 | int pm860x_set_bits(struct i2c_client *i2c, int reg, |
bbd51b1f HZ |
76 | unsigned char mask, unsigned char data) |
77 | { | |
53dbab7a | 78 | struct pm860x_chip *chip = i2c_get_clientdata(i2c); |
b46a36c0 JZ |
79 | struct regmap *map = (i2c == chip->client) ? chip->regmap |
80 | : chip->regmap_companion; | |
bbd51b1f HZ |
81 | int ret; |
82 | ||
b46a36c0 | 83 | ret = regmap_update_bits(map, reg, mask, data); |
bbd51b1f HZ |
84 | return ret; |
85 | } | |
53dbab7a | 86 | EXPORT_SYMBOL(pm860x_set_bits); |
bbd51b1f | 87 | |
5bdf7411 JZ |
88 | static int read_device(struct i2c_client *i2c, int reg, |
89 | int bytes, void *dest) | |
90 | { | |
91 | unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX + 3]; | |
92 | unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX + 2]; | |
93 | struct i2c_adapter *adap = i2c->adapter; | |
94 | struct i2c_msg msg[2] = {{i2c->addr, 0, 1, msgbuf0}, | |
95 | {i2c->addr, I2C_M_RD, 0, msgbuf1}, | |
96 | }; | |
97 | int num = 1, ret = 0; | |
98 | ||
99 | if (dest == NULL) | |
100 | return -EINVAL; | |
101 | msgbuf0[0] = (unsigned char)reg; /* command */ | |
102 | msg[1].len = bytes; | |
103 | ||
104 | /* if data needs to read back, num should be 2 */ | |
105 | if (bytes > 0) | |
106 | num = 2; | |
107 | ret = adap->algo->master_xfer(adap, msg, num); | |
108 | memcpy(dest, msgbuf1, bytes); | |
109 | if (ret < 0) | |
110 | return ret; | |
111 | return 0; | |
112 | } | |
113 | ||
114 | static int write_device(struct i2c_client *i2c, int reg, | |
115 | int bytes, void *src) | |
116 | { | |
117 | unsigned char buf[bytes + 1]; | |
118 | struct i2c_adapter *adap = i2c->adapter; | |
119 | struct i2c_msg msg; | |
120 | int ret; | |
121 | ||
122 | buf[0] = (unsigned char)reg; | |
123 | memcpy(&buf[1], src, bytes); | |
124 | msg.addr = i2c->addr; | |
125 | msg.flags = 0; | |
126 | msg.len = bytes + 1; | |
127 | msg.buf = buf; | |
128 | ||
129 | ret = adap->algo->master_xfer(adap, &msg, 1); | |
130 | if (ret < 0) | |
131 | return ret; | |
132 | return 0; | |
133 | } | |
134 | ||
09b03419 HZ |
135 | int pm860x_page_reg_read(struct i2c_client *i2c, int reg) |
136 | { | |
09b03419 HZ |
137 | unsigned char zero = 0; |
138 | unsigned char data; | |
139 | int ret; | |
140 | ||
5bdf7411 JZ |
141 | i2c_lock_adapter(i2c->adapter); |
142 | read_device(i2c, 0xFA, 0, &zero); | |
143 | read_device(i2c, 0xFB, 0, &zero); | |
144 | read_device(i2c, 0xFF, 0, &zero); | |
145 | ret = read_device(i2c, reg, 1, &data); | |
09b03419 HZ |
146 | if (ret >= 0) |
147 | ret = (int)data; | |
5bdf7411 JZ |
148 | read_device(i2c, 0xFE, 0, &zero); |
149 | read_device(i2c, 0xFC, 0, &zero); | |
150 | i2c_unlock_adapter(i2c->adapter); | |
09b03419 HZ |
151 | return ret; |
152 | } | |
153 | EXPORT_SYMBOL(pm860x_page_reg_read); | |
154 | ||
155 | int pm860x_page_reg_write(struct i2c_client *i2c, int reg, | |
156 | unsigned char data) | |
157 | { | |
09b03419 HZ |
158 | unsigned char zero; |
159 | int ret; | |
160 | ||
5bdf7411 JZ |
161 | i2c_lock_adapter(i2c->adapter); |
162 | read_device(i2c, 0xFA, 0, &zero); | |
163 | read_device(i2c, 0xFB, 0, &zero); | |
164 | read_device(i2c, 0xFF, 0, &zero); | |
165 | ret = write_device(i2c, reg, 1, &data); | |
166 | read_device(i2c, 0xFE, 0, &zero); | |
167 | read_device(i2c, 0xFC, 0, &zero); | |
168 | i2c_unlock_adapter(i2c->adapter); | |
09b03419 HZ |
169 | return ret; |
170 | } | |
171 | EXPORT_SYMBOL(pm860x_page_reg_write); | |
172 | ||
173 | int pm860x_page_bulk_read(struct i2c_client *i2c, int reg, | |
174 | int count, unsigned char *buf) | |
175 | { | |
09b03419 HZ |
176 | unsigned char zero = 0; |
177 | int ret; | |
178 | ||
5bdf7411 JZ |
179 | i2c_lock_adapter(i2c->adapter); |
180 | read_device(i2c, 0xfa, 0, &zero); | |
181 | read_device(i2c, 0xfb, 0, &zero); | |
182 | read_device(i2c, 0xff, 0, &zero); | |
183 | ret = read_device(i2c, reg, count, buf); | |
184 | read_device(i2c, 0xFE, 0, &zero); | |
185 | read_device(i2c, 0xFC, 0, &zero); | |
186 | i2c_unlock_adapter(i2c->adapter); | |
09b03419 HZ |
187 | return ret; |
188 | } | |
189 | EXPORT_SYMBOL(pm860x_page_bulk_read); | |
190 | ||
191 | int pm860x_page_bulk_write(struct i2c_client *i2c, int reg, | |
192 | int count, unsigned char *buf) | |
193 | { | |
09b03419 HZ |
194 | unsigned char zero = 0; |
195 | int ret; | |
196 | ||
5bdf7411 JZ |
197 | i2c_lock_adapter(i2c->adapter); |
198 | read_device(i2c, 0xFA, 0, &zero); | |
199 | read_device(i2c, 0xFB, 0, &zero); | |
200 | read_device(i2c, 0xFF, 0, &zero); | |
201 | ret = write_device(i2c, reg, count, buf); | |
202 | read_device(i2c, 0xFE, 0, &zero); | |
203 | read_device(i2c, 0xFC, 0, &zero); | |
204 | i2c_unlock_adapter(i2c->adapter); | |
205 | i2c_unlock_adapter(i2c->adapter); | |
09b03419 HZ |
206 | return ret; |
207 | } | |
208 | EXPORT_SYMBOL(pm860x_page_bulk_write); | |
209 | ||
210 | int pm860x_page_set_bits(struct i2c_client *i2c, int reg, | |
211 | unsigned char mask, unsigned char data) | |
212 | { | |
09b03419 HZ |
213 | unsigned char zero; |
214 | unsigned char value; | |
215 | int ret; | |
216 | ||
5bdf7411 JZ |
217 | i2c_lock_adapter(i2c->adapter); |
218 | read_device(i2c, 0xFA, 0, &zero); | |
219 | read_device(i2c, 0xFB, 0, &zero); | |
220 | read_device(i2c, 0xFF, 0, &zero); | |
221 | ret = read_device(i2c, reg, 1, &value); | |
09b03419 HZ |
222 | if (ret < 0) |
223 | goto out; | |
224 | value &= ~mask; | |
225 | value |= data; | |
5bdf7411 | 226 | ret = write_device(i2c, reg, 1, &value); |
09b03419 | 227 | out: |
5bdf7411 JZ |
228 | read_device(i2c, 0xFE, 0, &zero); |
229 | read_device(i2c, 0xFC, 0, &zero); | |
230 | i2c_unlock_adapter(i2c->adapter); | |
09b03419 HZ |
231 | return ret; |
232 | } | |
233 | EXPORT_SYMBOL(pm860x_page_set_bits); | |
bbd51b1f HZ |
234 | |
235 | static const struct i2c_device_id pm860x_id_table[] = { | |
53dbab7a | 236 | { "88PM860x", 0 }, |
bbd51b1f HZ |
237 | {} |
238 | }; | |
239 | MODULE_DEVICE_TABLE(i2c, pm860x_id_table); | |
240 | ||
53dbab7a HZ |
241 | static int verify_addr(struct i2c_client *i2c) |
242 | { | |
243 | unsigned short addr_8607[] = {0x30, 0x34}; | |
244 | unsigned short addr_8606[] = {0x10, 0x11}; | |
245 | int size, i; | |
246 | ||
247 | if (i2c == NULL) | |
248 | return 0; | |
249 | size = ARRAY_SIZE(addr_8606); | |
250 | for (i = 0; i < size; i++) { | |
251 | if (i2c->addr == *(addr_8606 + i)) | |
252 | return CHIP_PM8606; | |
253 | } | |
254 | size = ARRAY_SIZE(addr_8607); | |
255 | for (i = 0; i < size; i++) { | |
256 | if (i2c->addr == *(addr_8607 + i)) | |
257 | return CHIP_PM8607; | |
258 | } | |
259 | return 0; | |
260 | } | |
261 | ||
b46a36c0 JZ |
262 | static struct regmap_config pm860x_regmap_config = { |
263 | .reg_bits = 8, | |
264 | .val_bits = 8, | |
265 | }; | |
266 | ||
bbd51b1f HZ |
267 | static int __devinit pm860x_probe(struct i2c_client *client, |
268 | const struct i2c_device_id *id) | |
269 | { | |
53dbab7a | 270 | struct pm860x_platform_data *pdata = client->dev.platform_data; |
e8343dda | 271 | struct pm860x_chip *chip; |
b46a36c0 | 272 | int ret; |
e8343dda HZ |
273 | |
274 | if (!pdata) { | |
53dbab7a HZ |
275 | pr_info("No platform data in %s!\n", __func__); |
276 | return -EINVAL; | |
277 | } | |
278 | ||
e8343dda HZ |
279 | chip = kzalloc(sizeof(struct pm860x_chip), GFP_KERNEL); |
280 | if (chip == NULL) | |
281 | return -ENOMEM; | |
282 | ||
283 | chip->id = verify_addr(client); | |
b46a36c0 JZ |
284 | chip->regmap = regmap_init_i2c(client, &pm860x_regmap_config); |
285 | if (IS_ERR(chip->regmap)) { | |
286 | ret = PTR_ERR(chip->regmap); | |
287 | dev_err(&client->dev, "Failed to allocate register map: %d\n", | |
288 | ret); | |
e3380333 | 289 | kfree(chip); |
b46a36c0 JZ |
290 | return ret; |
291 | } | |
e8343dda HZ |
292 | chip->client = client; |
293 | i2c_set_clientdata(client, chip); | |
294 | chip->dev = &client->dev; | |
e8343dda HZ |
295 | dev_set_drvdata(chip->dev, chip); |
296 | ||
53dbab7a HZ |
297 | /* |
298 | * Both client and companion client shares same platform driver. | |
299 | * Driver distinguishes them by pdata->companion_addr. | |
300 | * pdata->companion_addr is only assigned if companion chip exists. | |
301 | * At the same time, the companion_addr shouldn't equal to client | |
302 | * address. | |
303 | */ | |
e8343dda HZ |
304 | if (pdata->companion_addr && (pdata->companion_addr != client->addr)) { |
305 | chip->companion_addr = pdata->companion_addr; | |
306 | chip->companion = i2c_new_dummy(chip->client->adapter, | |
307 | chip->companion_addr); | |
b46a36c0 JZ |
308 | chip->regmap_companion = regmap_init_i2c(chip->companion, |
309 | &pm860x_regmap_config); | |
310 | if (IS_ERR(chip->regmap_companion)) { | |
311 | ret = PTR_ERR(chip->regmap_companion); | |
312 | dev_err(&chip->companion->dev, | |
313 | "Failed to allocate register map: %d\n", ret); | |
314 | return ret; | |
315 | } | |
53dbab7a | 316 | i2c_set_clientdata(chip->companion, chip); |
53dbab7a | 317 | } |
e8343dda HZ |
318 | |
319 | pm860x_device_init(chip, pdata); | |
bbd51b1f | 320 | return 0; |
bbd51b1f HZ |
321 | } |
322 | ||
323 | static int __devexit pm860x_remove(struct i2c_client *client) | |
324 | { | |
53dbab7a HZ |
325 | struct pm860x_chip *chip = i2c_get_clientdata(client); |
326 | ||
53dbab7a | 327 | pm860x_device_exit(chip); |
b46a36c0 JZ |
328 | if (chip->companion) { |
329 | regmap_exit(chip->regmap_companion); | |
330 | i2c_unregister_device(chip->companion); | |
331 | } | |
332 | regmap_exit(chip->regmap); | |
bbd51b1f HZ |
333 | kfree(chip); |
334 | return 0; | |
335 | } | |
336 | ||
2853378b JZ |
337 | #ifdef CONFIG_PM_SLEEP |
338 | static int pm860x_suspend(struct device *dev) | |
339 | { | |
340 | struct i2c_client *client = container_of(dev, struct i2c_client, dev); | |
341 | struct pm860x_chip *chip = i2c_get_clientdata(client); | |
342 | ||
343 | if (device_may_wakeup(dev) && chip->wakeup_flag) | |
344 | enable_irq_wake(chip->core_irq); | |
345 | return 0; | |
346 | } | |
347 | ||
348 | static int pm860x_resume(struct device *dev) | |
349 | { | |
350 | struct i2c_client *client = container_of(dev, struct i2c_client, dev); | |
351 | struct pm860x_chip *chip = i2c_get_clientdata(client); | |
352 | ||
353 | if (device_may_wakeup(dev) && chip->wakeup_flag) | |
354 | disable_irq_wake(chip->core_irq); | |
355 | return 0; | |
356 | } | |
357 | #endif | |
358 | ||
359 | static SIMPLE_DEV_PM_OPS(pm860x_pm_ops, pm860x_suspend, pm860x_resume); | |
360 | ||
bbd51b1f HZ |
361 | static struct i2c_driver pm860x_driver = { |
362 | .driver = { | |
363 | .name = "88PM860x", | |
364 | .owner = THIS_MODULE, | |
2853378b | 365 | .pm = &pm860x_pm_ops, |
bbd51b1f HZ |
366 | }, |
367 | .probe = pm860x_probe, | |
368 | .remove = __devexit_p(pm860x_remove), | |
369 | .id_table = pm860x_id_table, | |
370 | }; | |
371 | ||
372 | static int __init pm860x_i2c_init(void) | |
373 | { | |
374 | int ret; | |
375 | ret = i2c_add_driver(&pm860x_driver); | |
376 | if (ret != 0) | |
377 | pr_err("Failed to register 88PM860x I2C driver: %d\n", ret); | |
378 | return ret; | |
379 | } | |
380 | subsys_initcall(pm860x_i2c_init); | |
381 | ||
382 | static void __exit pm860x_i2c_exit(void) | |
383 | { | |
384 | i2c_del_driver(&pm860x_driver); | |
385 | } | |
386 | module_exit(pm860x_i2c_exit); | |
387 | ||
388 | MODULE_DESCRIPTION("I2C Driver for Marvell 88PM860x"); | |
389 | MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); | |
390 | MODULE_LICENSE("GPL"); |