blob: 2f9336c3710c4f9845c359e26cd6a412571e5ef0 [file] [log] [blame]
Mike Rapoportc6c19332010-08-11 01:11:04 +02001/*
2 * Core driver for TI TPS6586x PMIC family
3 *
4 * Copyright (c) 2010 CompuLab Ltd.
5 * Mike Rapoport <mike@compulab.co.il>
6 *
7 * Based on da903x.c.
8 * Copyright (C) 2008 Compulab, Ltd.
9 * Mike Rapoport <mike@compulab.co.il>
10 * Copyright (C) 2006-2008 Marvell International Ltd.
11 * Eric Miao <eric.miao@marvell.com>
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License version 2 as
15 * published by the Free Software Foundation.
16 */
17
18#include <linux/kernel.h>
19#include <linux/module.h>
20#include <linux/mutex.h>
21#include <linux/slab.h>
22#include <linux/gpio.h>
23#include <linux/i2c.h>
24
25#include <linux/mfd/core.h>
26#include <linux/mfd/tps6586x.h>
27
28/* GPIO control registers */
29#define TPS6586X_GPIOSET1 0x5d
30#define TPS6586X_GPIOSET2 0x5e
31
32/* device id */
33#define TPS6586X_VERSIONCRC 0xcd
34#define TPS658621A_VERSIONCRC 0x15
35
36struct tps6586x {
37 struct mutex lock;
38 struct device *dev;
39 struct i2c_client *client;
40
41 struct gpio_chip gpio;
42};
43
44static inline int __tps6586x_read(struct i2c_client *client,
45 int reg, uint8_t *val)
46{
47 int ret;
48
49 ret = i2c_smbus_read_byte_data(client, reg);
50 if (ret < 0) {
51 dev_err(&client->dev, "failed reading at 0x%02x\n", reg);
52 return ret;
53 }
54
55 *val = (uint8_t)ret;
56
57 return 0;
58}
59
60static inline int __tps6586x_reads(struct i2c_client *client, int reg,
61 int len, uint8_t *val)
62{
63 int ret;
64
65 ret = i2c_smbus_read_i2c_block_data(client, reg, len, val);
66 if (ret < 0) {
67 dev_err(&client->dev, "failed reading from 0x%02x\n", reg);
68 return ret;
69 }
70
71 return 0;
72}
73
74static inline int __tps6586x_write(struct i2c_client *client,
75 int reg, uint8_t val)
76{
77 int ret;
78
79 ret = i2c_smbus_write_byte_data(client, reg, val);
80 if (ret < 0) {
81 dev_err(&client->dev, "failed writing 0x%02x to 0x%02x\n",
82 val, reg);
83 return ret;
84 }
85
86 return 0;
87}
88
89static inline int __tps6586x_writes(struct i2c_client *client, int reg,
90 int len, uint8_t *val)
91{
92 int ret;
93
94 ret = i2c_smbus_write_i2c_block_data(client, reg, len, val);
95 if (ret < 0) {
96 dev_err(&client->dev, "failed writings to 0x%02x\n", reg);
97 return ret;
98 }
99
100 return 0;
101}
102
103int tps6586x_write(struct device *dev, int reg, uint8_t val)
104{
105 return __tps6586x_write(to_i2c_client(dev), reg, val);
106}
107EXPORT_SYMBOL_GPL(tps6586x_write);
108
109int tps6586x_writes(struct device *dev, int reg, int len, uint8_t *val)
110{
111 return __tps6586x_writes(to_i2c_client(dev), reg, len, val);
112}
113EXPORT_SYMBOL_GPL(tps6586x_writes);
114
115int tps6586x_read(struct device *dev, int reg, uint8_t *val)
116{
117 return __tps6586x_read(to_i2c_client(dev), reg, val);
118}
119EXPORT_SYMBOL_GPL(tps6586x_read);
120
121int tps6586x_reads(struct device *dev, int reg, int len, uint8_t *val)
122{
123 return __tps6586x_reads(to_i2c_client(dev), reg, len, val);
124}
125EXPORT_SYMBOL_GPL(tps6586x_reads);
126
127int tps6586x_set_bits(struct device *dev, int reg, uint8_t bit_mask)
128{
129 struct tps6586x *tps6586x = dev_get_drvdata(dev);
130 uint8_t reg_val;
131 int ret = 0;
132
133 mutex_lock(&tps6586x->lock);
134
135 ret = __tps6586x_read(to_i2c_client(dev), reg, &reg_val);
136 if (ret)
137 goto out;
138
139 if ((reg_val & bit_mask) == 0) {
140 reg_val |= bit_mask;
141 ret = __tps6586x_write(to_i2c_client(dev), reg, reg_val);
142 }
143out:
144 mutex_unlock(&tps6586x->lock);
145 return ret;
146}
147EXPORT_SYMBOL_GPL(tps6586x_set_bits);
148
149int tps6586x_clr_bits(struct device *dev, int reg, uint8_t bit_mask)
150{
151 struct tps6586x *tps6586x = dev_get_drvdata(dev);
152 uint8_t reg_val;
153 int ret = 0;
154
155 mutex_lock(&tps6586x->lock);
156
157 ret = __tps6586x_read(to_i2c_client(dev), reg, &reg_val);
158 if (ret)
159 goto out;
160
161 if (reg_val & bit_mask) {
162 reg_val &= ~bit_mask;
163 ret = __tps6586x_write(to_i2c_client(dev), reg, reg_val);
164 }
165out:
166 mutex_unlock(&tps6586x->lock);
167 return ret;
168}
169EXPORT_SYMBOL_GPL(tps6586x_clr_bits);
170
171int tps6586x_update(struct device *dev, int reg, uint8_t val, uint8_t mask)
172{
173 struct tps6586x *tps6586x = dev_get_drvdata(dev);
174 uint8_t reg_val;
175 int ret = 0;
176
177 mutex_lock(&tps6586x->lock);
178
179 ret = __tps6586x_read(tps6586x->client, reg, &reg_val);
180 if (ret)
181 goto out;
182
183 if ((reg_val & mask) != val) {
184 reg_val = (reg_val & ~mask) | val;
185 ret = __tps6586x_write(tps6586x->client, reg, reg_val);
186 }
187out:
188 mutex_unlock(&tps6586x->lock);
189 return ret;
190}
191EXPORT_SYMBOL_GPL(tps6586x_update);
192
193static int tps6586x_gpio_get(struct gpio_chip *gc, unsigned offset)
194{
195 struct tps6586x *tps6586x = container_of(gc, struct tps6586x, gpio);
196 uint8_t val;
197 int ret;
198
199 ret = __tps6586x_read(tps6586x->client, TPS6586X_GPIOSET2, &val);
200 if (ret)
201 return ret;
202
203 return !!(val & (1 << offset));
204}
205
206
207static void tps6586x_gpio_set(struct gpio_chip *chip, unsigned offset,
208 int value)
209{
210 struct tps6586x *tps6586x = container_of(chip, struct tps6586x, gpio);
211
212 __tps6586x_write(tps6586x->client, TPS6586X_GPIOSET2,
213 value << offset);
214}
215
216static int tps6586x_gpio_output(struct gpio_chip *gc, unsigned offset,
217 int value)
218{
219 struct tps6586x *tps6586x = container_of(gc, struct tps6586x, gpio);
220 uint8_t val, mask;
221
222 tps6586x_gpio_set(gc, offset, value);
223
224 val = 0x1 << (offset * 2);
225 mask = 0x3 << (offset * 2);
226
227 return tps6586x_update(tps6586x->dev, TPS6586X_GPIOSET1, val, mask);
228}
229
230static void tps6586x_gpio_init(struct tps6586x *tps6586x, int gpio_base)
231{
232 int ret;
233
234 if (!gpio_base)
235 return;
236
237 tps6586x->gpio.owner = THIS_MODULE;
238 tps6586x->gpio.label = tps6586x->client->name;
239 tps6586x->gpio.dev = tps6586x->dev;
240 tps6586x->gpio.base = gpio_base;
241 tps6586x->gpio.ngpio = 4;
242 tps6586x->gpio.can_sleep = 1;
243
244 /* FIXME: add handling of GPIOs as dedicated inputs */
245 tps6586x->gpio.direction_output = tps6586x_gpio_output;
246 tps6586x->gpio.set = tps6586x_gpio_set;
247 tps6586x->gpio.get = tps6586x_gpio_get;
248
249 ret = gpiochip_add(&tps6586x->gpio);
250 if (ret)
251 dev_warn(tps6586x->dev, "GPIO registration failed: %d\n", ret);
252}
253
254static int __remove_subdev(struct device *dev, void *unused)
255{
256 platform_device_unregister(to_platform_device(dev));
257 return 0;
258}
259
260static int tps6586x_remove_subdevs(struct tps6586x *tps6586x)
261{
262 return device_for_each_child(tps6586x->dev, NULL, __remove_subdev);
263}
264
265static int __devinit tps6586x_add_subdevs(struct tps6586x *tps6586x,
266 struct tps6586x_platform_data *pdata)
267{
268 struct tps6586x_subdev_info *subdev;
269 struct platform_device *pdev;
270 int i, ret = 0;
271
272 for (i = 0; i < pdata->num_subdevs; i++) {
273 subdev = &pdata->subdevs[i];
274
275 pdev = platform_device_alloc(subdev->name, subdev->id);
Axel Lin929980a2010-08-24 13:47:22 +0800276 if (!pdev) {
277 ret = -ENOMEM;
278 goto failed;
279 }
Mike Rapoportc6c19332010-08-11 01:11:04 +0200280
281 pdev->dev.parent = tps6586x->dev;
282 pdev->dev.platform_data = subdev->platform_data;
283
284 ret = platform_device_add(pdev);
Axel Lin929980a2010-08-24 13:47:22 +0800285 if (ret) {
286 platform_device_put(pdev);
Mike Rapoportc6c19332010-08-11 01:11:04 +0200287 goto failed;
Axel Lin929980a2010-08-24 13:47:22 +0800288 }
Mike Rapoportc6c19332010-08-11 01:11:04 +0200289 }
290 return 0;
291
292failed:
293 tps6586x_remove_subdevs(tps6586x);
294 return ret;
295}
296
297static int __devinit tps6586x_i2c_probe(struct i2c_client *client,
298 const struct i2c_device_id *id)
299{
300 struct tps6586x_platform_data *pdata = client->dev.platform_data;
301 struct tps6586x *tps6586x;
302 int ret;
303
304 if (!pdata) {
305 dev_err(&client->dev, "tps6586x requires platform data\n");
306 return -ENOTSUPP;
307 }
308
309 ret = i2c_smbus_read_byte_data(client, TPS6586X_VERSIONCRC);
310 if (ret < 0) {
311 dev_err(&client->dev, "Chip ID read failed: %d\n", ret);
312 return -EIO;
313 }
314
315 if (ret != TPS658621A_VERSIONCRC) {
316 dev_err(&client->dev, "Unsupported chip ID: %x\n", ret);
317 return -ENODEV;
318 }
319
320 tps6586x = kzalloc(sizeof(struct tps6586x), GFP_KERNEL);
321 if (tps6586x == NULL)
322 return -ENOMEM;
323
324 tps6586x->client = client;
325 tps6586x->dev = &client->dev;
326 i2c_set_clientdata(client, tps6586x);
327
328 mutex_init(&tps6586x->lock);
329
330 ret = tps6586x_add_subdevs(tps6586x, pdata);
331 if (ret) {
332 dev_err(&client->dev, "add devices failed: %d\n", ret);
333 goto err_add_devs;
334 }
335
336 tps6586x_gpio_init(tps6586x, pdata->gpio_base);
337
338 return 0;
339
340err_add_devs:
341 kfree(tps6586x);
342 return ret;
343}
344
345static int __devexit tps6586x_i2c_remove(struct i2c_client *client)
346{
Axel Lin4b751cf2010-08-24 15:18:58 +0800347 struct tps6586x *tps6586x = i2c_get_clientdata(client);
348 struct tps6586x_platform_data *pdata = client->dev.platform_data;
349 int ret;
350
351 if (pdata->gpio_base) {
352 ret = gpiochip_remove(&tps6586x->gpio);
353 if (ret)
354 dev_err(&client->dev, "Can't remove gpio chip: %d\n",
355 ret);
356 }
357
358 tps6586x_remove_subdevs(tps6586x);
359 kfree(tps6586x);
Mike Rapoportc6c19332010-08-11 01:11:04 +0200360 return 0;
361}
362
363static const struct i2c_device_id tps6586x_id_table[] = {
364 { "tps6586x", 0 },
365 { },
366};
367MODULE_DEVICE_TABLE(i2c, tps6586x_id_table);
368
369static struct i2c_driver tps6586x_driver = {
370 .driver = {
371 .name = "tps6586x",
372 .owner = THIS_MODULE,
373 },
374 .probe = tps6586x_i2c_probe,
375 .remove = __devexit_p(tps6586x_i2c_remove),
376 .id_table = tps6586x_id_table,
377};
378
379static int __init tps6586x_init(void)
380{
381 return i2c_add_driver(&tps6586x_driver);
382}
383subsys_initcall(tps6586x_init);
384
385static void __exit tps6586x_exit(void)
386{
387 i2c_del_driver(&tps6586x_driver);
388}
389module_exit(tps6586x_exit);
390
391MODULE_DESCRIPTION("TPS6586X core driver");
392MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>");
393MODULE_LICENSE("GPL");
394