diff options
author | Haojian Zhuang <haojian.zhuang@marvell.com> | 2009-12-15 16:01:47 -0500 |
---|---|---|
committer | Samuel Ortiz <sameo@linux.intel.com> | 2010-03-07 16:17:03 -0500 |
commit | 5c42e8c4a9c86ea26ed4ecb732a842dea0dfb6b6 (patch) | |
tree | ffac1c091a0bedde01c802123e7a602945fd6f62 /drivers | |
parent | 2cc50bee9934deb6dfe32929a4c1742cf83d6db3 (diff) |
mfd: Add irq support in 88pm860x
88PM860x is a complex PMIC device. It contains touch, charger, sound, rtc,
backlight, led, and so on.
Host communicates to 88PM860x by I2C bus. Use thread irq to support this
usage case.
Signed-off-by: Haojian Zhuang <haojian.zhuang@marvell.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/mfd/88pm860x-core.c | 226 |
1 files changed, 219 insertions, 7 deletions
diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c index 72b00304dc3a..9185f0d945f4 100644 --- a/drivers/mfd/88pm860x-core.c +++ b/drivers/mfd/88pm860x-core.c | |||
@@ -11,6 +11,7 @@ | |||
11 | 11 | ||
12 | #include <linux/kernel.h> | 12 | #include <linux/kernel.h> |
13 | #include <linux/module.h> | 13 | #include <linux/module.h> |
14 | #include <linux/i2c.h> | ||
14 | #include <linux/interrupt.h> | 15 | #include <linux/interrupt.h> |
15 | #include <linux/platform_device.h> | 16 | #include <linux/platform_device.h> |
16 | #include <linux/mfd/core.h> | 17 | #include <linux/mfd/core.h> |
@@ -67,15 +68,209 @@ static struct mfd_cell pm8607_devs[] = { | |||
67 | PM8607_REG_DEVS(ldo14, LDO14), | 68 | PM8607_REG_DEVS(ldo14, LDO14), |
68 | }; | 69 | }; |
69 | 70 | ||
70 | static void device_8606_init(struct pm860x_chip *chip, struct i2c_client *i2c, | 71 | #define CHECK_IRQ(irq) \ |
71 | struct pm860x_platform_data *pdata) | 72 | do { \ |
73 | if ((irq < 0) || (irq >= PM860X_NUM_IRQ)) \ | ||
74 | return -EINVAL; \ | ||
75 | } while (0) | ||
76 | |||
77 | /* IRQs only occur on 88PM8607 */ | ||
78 | int 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 | } | ||
92 | EXPORT_SYMBOL(pm860x_mask_irq); | ||
93 | |||
94 | int 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 | } | ||
108 | EXPORT_SYMBOL(pm860x_unmask_irq); | ||
109 | |||
110 | #define INT_STATUS_NUM (3) | ||
111 | |||
112 | static 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 | } | ||
162 | out: | ||
163 | return IRQ_HANDLED; | ||
164 | } | ||
165 | |||
166 | int pm860x_request_irq(struct pm860x_chip *chip, int irq, | ||
167 | irq_handler_t handler, void *data) | ||
72 | { | 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; | ||
73 | } | 179 | } |
180 | EXPORT_SYMBOL(pm860x_request_irq); | ||
74 | 181 | ||
75 | static void device_8607_init(struct pm860x_chip *chip, struct i2c_client *i2c, | 182 | int pm860x_free_irq(struct pm860x_chip *chip, int irq) |
76 | struct pm860x_platform_data *pdata) | ||
77 | { | 183 | { |
78 | int i, count; | 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 | } | ||
193 | EXPORT_SYMBOL(pm860x_free_irq); | ||
194 | |||
195 | static 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; | ||
253 | out: | ||
254 | return ret; | ||
255 | } | ||
256 | |||
257 | static 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 | |||
263 | static void __devinit device_8606_init(struct pm860x_chip *chip, | ||
264 | struct i2c_client *i2c, | ||
265 | struct pm860x_platform_data *pdata) | ||
266 | { | ||
267 | } | ||
268 | |||
269 | static void __devinit device_8607_init(struct pm860x_chip *chip, | ||
270 | struct i2c_client *i2c, | ||
271 | struct pm860x_platform_data *pdata) | ||
272 | { | ||
273 | int i, count, data; | ||
79 | int ret; | 274 | int ret; |
80 | 275 | ||
81 | ret = pm860x_reg_read(i2c, PM8607_CHIP_ID); | 276 | ret = pm860x_reg_read(i2c, PM8607_CHIP_ID); |
@@ -91,7 +286,6 @@ static void device_8607_init(struct pm860x_chip *chip, struct i2c_client *i2c, | |||
91 | "Chip ID: %02x\n", ret); | 286 | "Chip ID: %02x\n", ret); |
92 | goto out; | 287 | goto out; |
93 | } | 288 | } |
94 | chip->chip_version = ret; | ||
95 | 289 | ||
96 | ret = pm860x_reg_read(i2c, PM8607_BUCK3); | 290 | ret = pm860x_reg_read(i2c, PM8607_BUCK3); |
97 | if (ret < 0) { | 291 | if (ret < 0) { |
@@ -101,12 +295,26 @@ static void device_8607_init(struct pm860x_chip *chip, struct i2c_client *i2c, | |||
101 | if (ret & PM8607_BUCK3_DOUBLE) | 295 | if (ret & PM8607_BUCK3_DOUBLE) |
102 | chip->buck3_double = 1; | 296 | chip->buck3_double = 1; |
103 | 297 | ||
104 | ret = pm860x_reg_read(i2c, PM8607_MISC1); | 298 | ret = pm860x_reg_read(i2c, PM8607_B0_MISC1); |
105 | if (ret < 0) { | 299 | if (ret < 0) { |
106 | dev_err(chip->dev, "Failed to read MISC1 register: %d\n", ret); | 300 | dev_err(chip->dev, "Failed to read MISC1 register: %d\n", ret); |
107 | goto out; | 301 | goto out; |
108 | } | 302 | } |
109 | 303 | ||
304 | 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 | |||
110 | count = ARRAY_SIZE(pm8607_devs); | 318 | count = ARRAY_SIZE(pm8607_devs); |
111 | for (i = 0; i < count; i++) { | 319 | for (i = 0; i < count; i++) { |
112 | ret = mfd_add_devices(chip->dev, i, &pm8607_devs[i], | 320 | ret = mfd_add_devices(chip->dev, i, &pm8607_devs[i], |
@@ -123,6 +331,8 @@ out: | |||
123 | int pm860x_device_init(struct pm860x_chip *chip, | 331 | int pm860x_device_init(struct pm860x_chip *chip, |
124 | struct pm860x_platform_data *pdata) | 332 | struct pm860x_platform_data *pdata) |
125 | { | 333 | { |
334 | chip->chip_irq = -EINVAL; | ||
335 | |||
126 | switch (chip->id) { | 336 | switch (chip->id) { |
127 | case CHIP_PM8606: | 337 | case CHIP_PM8606: |
128 | device_8606_init(chip, chip->client, pdata); | 338 | device_8606_init(chip, chip->client, pdata); |
@@ -142,11 +352,13 @@ int pm860x_device_init(struct pm860x_chip *chip, | |||
142 | break; | 352 | break; |
143 | } | 353 | } |
144 | } | 354 | } |
355 | |||
145 | return 0; | 356 | return 0; |
146 | } | 357 | } |
147 | 358 | ||
148 | void pm860x_device_exit(struct pm860x_chip *chip) | 359 | void pm860x_device_exit(struct pm860x_chip *chip) |
149 | { | 360 | { |
361 | device_irq_exit(chip); | ||
150 | mfd_remove_devices(chip->dev); | 362 | mfd_remove_devices(chip->dev); |
151 | } | 363 | } |
152 | 364 | ||