blob: 9185f0d945f4df55ee5b19d401f27924d06fc64f [file] [log] [blame]
Haojian Zhuangbbd51b12010-01-06 17:04:18 -05001/*
2 * Base driver for Marvell 88PM8607
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
12#include <linux/kernel.h>
13#include <linux/module.h>
Haojian Zhuang5c42e8c2009-12-15 16:01:47 -050014#include <linux/i2c.h>
Haojian Zhuangbbd51b12010-01-06 17:04:18 -050015#include <linux/interrupt.h>
16#include <linux/platform_device.h>
17#include <linux/mfd/core.h>
Haojian Zhuang53dbab72010-01-08 06:01:24 -050018#include <linux/mfd/88pm860x.h>
Haojian Zhuangbbd51b12010-01-06 17:04:18 -050019
20
21#define PM8607_REG_RESOURCE(_start, _end) \
22{ \
23 .start = PM8607_##_start, \
24 .end = PM8607_##_end, \
25 .flags = IORESOURCE_IO, \
26}
27
28static struct resource pm8607_regulator_resources[] = {
29 PM8607_REG_RESOURCE(BUCK1, BUCK1),
30 PM8607_REG_RESOURCE(BUCK2, BUCK2),
31 PM8607_REG_RESOURCE(BUCK3, BUCK3),
32 PM8607_REG_RESOURCE(LDO1, LDO1),
33 PM8607_REG_RESOURCE(LDO2, LDO2),
34 PM8607_REG_RESOURCE(LDO3, LDO3),
35 PM8607_REG_RESOURCE(LDO4, LDO4),
36 PM8607_REG_RESOURCE(LDO5, LDO5),
37 PM8607_REG_RESOURCE(LDO6, LDO6),
38 PM8607_REG_RESOURCE(LDO7, LDO7),
39 PM8607_REG_RESOURCE(LDO8, LDO8),
40 PM8607_REG_RESOURCE(LDO9, LDO9),
41 PM8607_REG_RESOURCE(LDO10, LDO10),
42 PM8607_REG_RESOURCE(LDO12, LDO12),
43 PM8607_REG_RESOURCE(LDO14, LDO14),
44};
45
46#define PM8607_REG_DEVS(_name, _id) \
47{ \
48 .name = "88pm8607-" #_name, \
49 .num_resources = 1, \
50 .resources = &pm8607_regulator_resources[PM8607_ID_##_id], \
51}
52
53static struct mfd_cell pm8607_devs[] = {
54 PM8607_REG_DEVS(buck1, BUCK1),
55 PM8607_REG_DEVS(buck2, BUCK2),
56 PM8607_REG_DEVS(buck3, BUCK3),
57 PM8607_REG_DEVS(ldo1, LDO1),
58 PM8607_REG_DEVS(ldo2, LDO2),
59 PM8607_REG_DEVS(ldo3, LDO3),
60 PM8607_REG_DEVS(ldo4, LDO4),
61 PM8607_REG_DEVS(ldo5, LDO5),
62 PM8607_REG_DEVS(ldo6, LDO6),
63 PM8607_REG_DEVS(ldo7, LDO7),
64 PM8607_REG_DEVS(ldo8, LDO8),
65 PM8607_REG_DEVS(ldo9, LDO9),
66 PM8607_REG_DEVS(ldo10, LDO10),
67 PM8607_REG_DEVS(ldo12, LDO12),
68 PM8607_REG_DEVS(ldo14, LDO14),
69};
70
Haojian Zhuang5c42e8c2009-12-15 16:01:47 -050071#define CHECK_IRQ(irq) \
72do { \
73 if ((irq < 0) || (irq >= PM860X_NUM_IRQ)) \
74 return -EINVAL; \
75} while (0)
76
77/* IRQs only occur on 88PM8607 */
78int pm860x_mask_irq(struct pm860x_chip *chip, int irq)
79{
80 struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \
81 : chip->companion;
82 int offset, data, ret;
83
84 CHECK_IRQ(irq);
85
86 offset = (irq >> 3) + PM8607_INT_MASK_1;
87 data = 1 << (irq % 8);
88 ret = pm860x_set_bits(i2c, offset, data, 0);
89
90 return ret;
91}
92EXPORT_SYMBOL(pm860x_mask_irq);
93
94int pm860x_unmask_irq(struct pm860x_chip *chip, int irq)
95{
96 struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \
97 : chip->companion;
98 int offset, data, ret;
99
100 CHECK_IRQ(irq);
101
102 offset = (irq >> 3) + PM8607_INT_MASK_1;
103 data = 1 << (irq % 8);
104 ret = pm860x_set_bits(i2c, offset, data, data);
105
106 return ret;
107}
108EXPORT_SYMBOL(pm860x_unmask_irq);
109
110#define INT_STATUS_NUM (3)
111
112static irqreturn_t pm8607_irq_thread(int irq, void *data)
113{
114 DECLARE_BITMAP(irq_status, PM860X_NUM_IRQ);
115 struct pm860x_chip *chip = data;
116 struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \
117 : chip->companion;
118 unsigned char status_buf[INT_STATUS_NUM << 1];
119 unsigned long value;
120 int i, ret;
121
122 irq_status[0] = 0;
123
124 /* read out status register */
125 ret = pm860x_bulk_read(i2c, PM8607_INT_STATUS1,
126 INT_STATUS_NUM << 1, status_buf);
127 if (ret < 0)
128 goto out;
129 if (chip->irq_mode) {
130 /* 0, clear by read. 1, clear by write */
131 ret = pm860x_bulk_write(i2c, PM8607_INT_STATUS1,
132 INT_STATUS_NUM, status_buf);
133 if (ret < 0)
134 goto out;
135 }
136
137 /* clear masked interrupt status */
138 for (i = 0, value = 0; i < INT_STATUS_NUM; i++) {
139 status_buf[i] &= status_buf[i + INT_STATUS_NUM];
140 irq_status[0] |= status_buf[i] << (i * 8);
141 }
142
143 while (!bitmap_empty(irq_status, PM860X_NUM_IRQ)) {
144 irq = find_first_bit(irq_status, PM860X_NUM_IRQ);
145 clear_bit(irq, irq_status);
146 dev_dbg(chip->dev, "Servicing IRQ #%d\n", irq);
147
148 mutex_lock(&chip->irq_lock);
149 if (chip->irq[irq].handler)
150 chip->irq[irq].handler(irq, chip->irq[irq].data);
151 else {
152 pm860x_mask_irq(chip, irq);
153 dev_err(chip->dev, "Nobody cares IRQ %d. "
154 "Now mask it.\n", irq);
155 for (i = 0; i < (INT_STATUS_NUM << 1); i++) {
156 dev_err(chip->dev, "status[%d]:%x\n", i,
157 status_buf[i]);
158 }
159 }
160 mutex_unlock(&chip->irq_lock);
161 }
162out:
163 return IRQ_HANDLED;
164}
165
166int pm860x_request_irq(struct pm860x_chip *chip, int irq,
167 irq_handler_t handler, void *data)
168{
169 CHECK_IRQ(irq);
170 if (!handler)
171 return -EINVAL;
172
173 mutex_lock(&chip->irq_lock);
174 chip->irq[irq].handler = handler;
175 chip->irq[irq].data = data;
176 mutex_unlock(&chip->irq_lock);
177
178 return 0;
179}
180EXPORT_SYMBOL(pm860x_request_irq);
181
182int pm860x_free_irq(struct pm860x_chip *chip, int irq)
183{
184 CHECK_IRQ(irq);
185
186 mutex_lock(&chip->irq_lock);
187 chip->irq[irq].handler = NULL;
188 chip->irq[irq].data = NULL;
189 mutex_unlock(&chip->irq_lock);
190
191 return 0;
192}
193EXPORT_SYMBOL(pm860x_free_irq);
194
195static int __devinit device_irq_init(struct pm860x_chip *chip,
196 struct pm860x_platform_data *pdata)
197{
198 struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \
199 : chip->companion;
200 unsigned char status_buf[INT_STATUS_NUM];
201 int data, mask, ret = -EINVAL;
202
203 mutex_init(&chip->irq_lock);
204
205 mask = PM8607_B0_MISC1_INV_INT | PM8607_B0_MISC1_INT_CLEAR
206 | PM8607_B0_MISC1_INT_MASK;
207 data = 0;
208 chip->irq_mode = 0;
209 if (pdata && pdata->irq_mode) {
210 /*
211 * irq_mode defines the way of clearing interrupt. If it's 1,
212 * clear IRQ by write. Otherwise, clear it by read.
213 * This control bit is valid from 88PM8607 B0 steping.
214 */
215 data |= PM8607_B0_MISC1_INT_CLEAR;
216 chip->irq_mode = 1;
217 }
218 ret = pm860x_set_bits(i2c, PM8607_B0_MISC1, mask, data);
219 if (ret < 0)
220 goto out;
221
222 /* mask all IRQs */
223 memset(status_buf, 0, INT_STATUS_NUM);
224 ret = pm860x_bulk_write(i2c, PM8607_INT_MASK_1,
225 INT_STATUS_NUM, status_buf);
226 if (ret < 0)
227 goto out;
228
229 if (chip->irq_mode) {
230 /* clear interrupt status by write */
231 memset(status_buf, 0xFF, INT_STATUS_NUM);
232 ret = pm860x_bulk_write(i2c, PM8607_INT_STATUS1,
233 INT_STATUS_NUM, status_buf);
234 } else {
235 /* clear interrupt status by read */
236 ret = pm860x_bulk_read(i2c, PM8607_INT_STATUS1,
237 INT_STATUS_NUM, status_buf);
238 }
239 if (ret < 0)
240 goto out;
241
242 memset(chip->irq, 0, sizeof(struct pm860x_irq) * PM860X_NUM_IRQ);
243
244 ret = request_threaded_irq(i2c->irq, NULL, pm8607_irq_thread,
245 IRQF_ONESHOT | IRQF_TRIGGER_LOW,
246 "88PM8607", chip);
247 if (ret < 0) {
248 dev_err(chip->dev, "Failed to request IRQ #%d.\n", i2c->irq);
249 goto out;
250 }
251 chip->chip_irq = i2c->irq;
252 return 0;
253out:
254 return ret;
255}
256
257static void __devexit device_irq_exit(struct pm860x_chip *chip)
258{
259 if (chip->chip_irq >= 0)
260 free_irq(chip->chip_irq, chip);
261}
262
263static void __devinit device_8606_init(struct pm860x_chip *chip,
264 struct i2c_client *i2c,
265 struct pm860x_platform_data *pdata)
Haojian Zhuang53dbab72010-01-08 06:01:24 -0500266{
267}
268
Haojian Zhuang5c42e8c2009-12-15 16:01:47 -0500269static void __devinit device_8607_init(struct pm860x_chip *chip,
270 struct i2c_client *i2c,
271 struct pm860x_platform_data *pdata)
Haojian Zhuangbbd51b12010-01-06 17:04:18 -0500272{
Haojian Zhuang5c42e8c2009-12-15 16:01:47 -0500273 int i, count, data;
Haojian Zhuangbbd51b12010-01-06 17:04:18 -0500274 int ret;
275
Haojian Zhuang53dbab72010-01-08 06:01:24 -0500276 ret = pm860x_reg_read(i2c, PM8607_CHIP_ID);
Haojian Zhuangbbd51b12010-01-06 17:04:18 -0500277 if (ret < 0) {
278 dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret);
279 goto out;
280 }
Haojian Zhuang53dbab72010-01-08 06:01:24 -0500281 if ((ret & PM8607_VERSION_MASK) == PM8607_VERSION)
Haojian Zhuangbbd51b12010-01-06 17:04:18 -0500282 dev_info(chip->dev, "Marvell 88PM8607 (ID: %02x) detected\n",
283 ret);
284 else {
285 dev_err(chip->dev, "Failed to detect Marvell 88PM8607. "
286 "Chip ID: %02x\n", ret);
287 goto out;
288 }
Haojian Zhuangbbd51b12010-01-06 17:04:18 -0500289
Haojian Zhuang53dbab72010-01-08 06:01:24 -0500290 ret = pm860x_reg_read(i2c, PM8607_BUCK3);
Haojian Zhuangbbd51b12010-01-06 17:04:18 -0500291 if (ret < 0) {
292 dev_err(chip->dev, "Failed to read BUCK3 register: %d\n", ret);
293 goto out;
294 }
295 if (ret & PM8607_BUCK3_DOUBLE)
296 chip->buck3_double = 1;
297
Haojian Zhuang5c42e8c2009-12-15 16:01:47 -0500298 ret = pm860x_reg_read(i2c, PM8607_B0_MISC1);
Haojian Zhuangbbd51b12010-01-06 17:04:18 -0500299 if (ret < 0) {
300 dev_err(chip->dev, "Failed to read MISC1 register: %d\n", ret);
301 goto out;
302 }
Haojian Zhuangbbd51b12010-01-06 17:04:18 -0500303
Haojian Zhuang5c42e8c2009-12-15 16:01:47 -0500304 if (pdata && (pdata->i2c_port == PI2C_PORT))
305 data = PM8607_B0_MISC1_PI2C;
306 else
307 data = 0;
308 ret = pm860x_set_bits(i2c, PM8607_B0_MISC1, PM8607_B0_MISC1_PI2C, data);
309 if (ret < 0) {
310 dev_err(chip->dev, "Failed to access MISC1:%d\n", ret);
311 goto out;
312 }
313
314 ret = device_irq_init(chip, pdata);
315 if (ret < 0)
316 goto out;
317
Haojian Zhuangbbd51b12010-01-06 17:04:18 -0500318 count = ARRAY_SIZE(pm8607_devs);
319 for (i = 0; i < count; i++) {
320 ret = mfd_add_devices(chip->dev, i, &pm8607_devs[i],
321 1, NULL, 0);
322 if (ret != 0) {
323 dev_err(chip->dev, "Failed to add subdevs\n");
324 goto out;
325 }
326 }
327out:
Haojian Zhuang53dbab72010-01-08 06:01:24 -0500328 return;
Haojian Zhuangbbd51b12010-01-06 17:04:18 -0500329}
330
Haojian Zhuang53dbab72010-01-08 06:01:24 -0500331int pm860x_device_init(struct pm860x_chip *chip,
332 struct pm860x_platform_data *pdata)
333{
Haojian Zhuang5c42e8c2009-12-15 16:01:47 -0500334 chip->chip_irq = -EINVAL;
335
Haojian Zhuang53dbab72010-01-08 06:01:24 -0500336 switch (chip->id) {
337 case CHIP_PM8606:
338 device_8606_init(chip, chip->client, pdata);
339 break;
340 case CHIP_PM8607:
341 device_8607_init(chip, chip->client, pdata);
342 break;
343 }
344
345 if (chip->companion) {
346 switch (chip->id) {
347 case CHIP_PM8607:
348 device_8606_init(chip, chip->companion, pdata);
349 break;
350 case CHIP_PM8606:
351 device_8607_init(chip, chip->companion, pdata);
352 break;
353 }
354 }
Haojian Zhuang5c42e8c2009-12-15 16:01:47 -0500355
Haojian Zhuang53dbab72010-01-08 06:01:24 -0500356 return 0;
357}
358
359void pm860x_device_exit(struct pm860x_chip *chip)
Haojian Zhuangbbd51b12010-01-06 17:04:18 -0500360{
Haojian Zhuang5c42e8c2009-12-15 16:01:47 -0500361 device_irq_exit(chip);
Haojian Zhuangbbd51b12010-01-06 17:04:18 -0500362 mfd_remove_devices(chip->dev);
363}
364
Haojian Zhuang53dbab72010-01-08 06:01:24 -0500365MODULE_DESCRIPTION("PMIC Driver for Marvell 88PM860x");
Haojian Zhuangbbd51b12010-01-06 17:04:18 -0500366MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
367MODULE_LICENSE("GPL");