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 | |
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>
-rw-r--r-- | drivers/mfd/88pm860x-core.c | 226 | ||||
-rw-r--r-- | include/linux/mfd/88pm860x.h | 54 |
2 files changed, 247 insertions, 33 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 | ||
diff --git a/include/linux/mfd/88pm860x.h b/include/linux/mfd/88pm860x.h index 5845ae47df30..b4d6018ba0d6 100644 --- a/include/linux/mfd/88pm860x.h +++ b/include/linux/mfd/88pm860x.h | |||
@@ -12,6 +12,8 @@ | |||
12 | #ifndef __LINUX_MFD_88PM860X_H | 12 | #ifndef __LINUX_MFD_88PM860X_H |
13 | #define __LINUX_MFD_88PM860X_H | 13 | #define __LINUX_MFD_88PM860X_H |
14 | 14 | ||
15 | #include <linux/interrupt.h> | ||
16 | |||
15 | enum { | 17 | enum { |
16 | CHIP_INVALID = 0, | 18 | CHIP_INVALID = 0, |
17 | CHIP_PM8606, | 19 | CHIP_PM8606, |
@@ -109,33 +111,10 @@ enum { | |||
109 | 111 | ||
110 | /* Misc Registers */ | 112 | /* Misc Registers */ |
111 | #define PM8607_CHIP_ID (0x00) | 113 | #define PM8607_CHIP_ID (0x00) |
114 | #define PM8607_B0_MISC1 (0x0C) | ||
112 | #define PM8607_LDO1 (0x10) | 115 | #define PM8607_LDO1 (0x10) |
113 | #define PM8607_DVC3 (0x26) | 116 | #define PM8607_DVC3 (0x26) |
114 | #define PM8607_MISC1 (0x40) | 117 | #define PM8607_A1_MISC1 (0x40) |
115 | |||
116 | /* bit definitions for PM8607 events */ | ||
117 | #define PM8607_EVENT_ONKEY (1 << 0) | ||
118 | #define PM8607_EVENT_EXTON (1 << 1) | ||
119 | #define PM8607_EVENT_CHG (1 << 2) | ||
120 | #define PM8607_EVENT_BAT (1 << 3) | ||
121 | #define PM8607_EVENT_RTC (1 << 4) | ||
122 | #define PM8607_EVENT_CC (1 << 5) | ||
123 | #define PM8607_EVENT_VBAT (1 << 8) | ||
124 | #define PM8607_EVENT_VCHG (1 << 9) | ||
125 | #define PM8607_EVENT_VSYS (1 << 10) | ||
126 | #define PM8607_EVENT_TINT (1 << 11) | ||
127 | #define PM8607_EVENT_GPADC0 (1 << 12) | ||
128 | #define PM8607_EVENT_GPADC1 (1 << 13) | ||
129 | #define PM8607_EVENT_GPADC2 (1 << 14) | ||
130 | #define PM8607_EVENT_GPADC3 (1 << 15) | ||
131 | #define PM8607_EVENT_AUDIO_SHORT (1 << 16) | ||
132 | #define PM8607_EVENT_PEN (1 << 17) | ||
133 | #define PM8607_EVENT_HEADSET (1 << 18) | ||
134 | #define PM8607_EVENT_HOOK (1 << 19) | ||
135 | #define PM8607_EVENT_MICIN (1 << 20) | ||
136 | #define PM8607_EVENT_CHG_TIMEOUT (1 << 21) | ||
137 | #define PM8607_EVENT_CHG_DONE (1 << 22) | ||
138 | #define PM8607_EVENT_CHG_FAULT (1 << 23) | ||
139 | 118 | ||
140 | /* bit definitions of Status Query Interface */ | 119 | /* bit definitions of Status Query Interface */ |
141 | #define PM8607_STATUS_CC (1 << 3) | 120 | #define PM8607_STATUS_CC (1 << 3) |
@@ -154,7 +133,12 @@ enum { | |||
154 | #define PM8607_BUCK3_DOUBLE (1 << 6) | 133 | #define PM8607_BUCK3_DOUBLE (1 << 6) |
155 | 134 | ||
156 | /* bit definitions of Misc1 */ | 135 | /* bit definitions of Misc1 */ |
157 | #define PM8607_MISC1_PI2C (1 << 0) | 136 | #define PM8607_A1_MISC1_PI2C (1 << 0) |
137 | #define PM8607_B0_MISC1_INV_INT (1 << 0) | ||
138 | #define PM8607_B0_MISC1_INT_CLEAR (1 << 1) | ||
139 | #define PM8607_B0_MISC1_INT_MASK (1 << 2) | ||
140 | #define PM8607_B0_MISC1_PI2C (1 << 3) | ||
141 | #define PM8607_B0_MISC1_RESET (1 << 6) | ||
158 | 142 | ||
159 | /* Interrupt Number in 88PM8607 */ | 143 | /* Interrupt Number in 88PM8607 */ |
160 | enum { | 144 | enum { |
@@ -187,15 +171,26 @@ enum { | |||
187 | PM8607_CHIP_B0 = 0x48, | 171 | PM8607_CHIP_B0 = 0x48, |
188 | }; | 172 | }; |
189 | 173 | ||
174 | #define PM860X_NUM_IRQ 24 | ||
175 | |||
176 | struct pm860x_irq { | ||
177 | irq_handler_t handler; | ||
178 | void *data; | ||
179 | }; | ||
180 | |||
190 | struct pm860x_chip { | 181 | struct pm860x_chip { |
191 | struct device *dev; | 182 | struct device *dev; |
192 | struct mutex io_lock; | 183 | struct mutex io_lock; |
184 | struct mutex irq_lock; | ||
193 | struct i2c_client *client; | 185 | struct i2c_client *client; |
194 | struct i2c_client *companion; /* companion chip client */ | 186 | struct i2c_client *companion; /* companion chip client */ |
187 | struct pm860x_irq irq[PM860X_NUM_IRQ]; | ||
195 | 188 | ||
196 | int buck3_double; /* DVC ramp slope double */ | 189 | int buck3_double; /* DVC ramp slope double */ |
197 | unsigned short companion_addr; | 190 | unsigned short companion_addr; |
198 | int id; | 191 | int id; |
192 | int irq_mode; | ||
193 | int chip_irq; | ||
199 | unsigned char chip_version; | 194 | unsigned char chip_version; |
200 | 195 | ||
201 | }; | 196 | }; |
@@ -210,6 +205,7 @@ enum { | |||
210 | struct pm860x_platform_data { | 205 | struct pm860x_platform_data { |
211 | unsigned short companion_addr; /* I2C address of companion chip */ | 206 | unsigned short companion_addr; /* I2C address of companion chip */ |
212 | int i2c_port; /* Controlled by GI2C or PI2C */ | 207 | int i2c_port; /* Controlled by GI2C or PI2C */ |
208 | int irq_mode; /* Clear interrupt by read/write(0/1) */ | ||
213 | struct regulator_init_data *regulator[PM8607_MAX_REGULATOR]; | 209 | struct regulator_init_data *regulator[PM8607_MAX_REGULATOR]; |
214 | }; | 210 | }; |
215 | 211 | ||
@@ -220,6 +216,12 @@ extern int pm860x_bulk_write(struct i2c_client *, int, int, unsigned char *); | |||
220 | extern int pm860x_set_bits(struct i2c_client *, int, unsigned char, | 216 | extern int pm860x_set_bits(struct i2c_client *, int, unsigned char, |
221 | unsigned char); | 217 | unsigned char); |
222 | 218 | ||
219 | extern int pm860x_mask_irq(struct pm860x_chip *, int); | ||
220 | extern int pm860x_unmask_irq(struct pm860x_chip *, int); | ||
221 | extern int pm860x_request_irq(struct pm860x_chip *, int, | ||
222 | irq_handler_t handler, void *); | ||
223 | extern int pm860x_free_irq(struct pm860x_chip *, int); | ||
224 | |||
223 | extern int pm860x_device_init(struct pm860x_chip *chip, | 225 | extern int pm860x_device_init(struct pm860x_chip *chip, |
224 | struct pm860x_platform_data *pdata); | 226 | struct pm860x_platform_data *pdata); |
225 | extern void pm860x_device_exit(struct pm860x_chip *chip); | 227 | extern void pm860x_device_exit(struct pm860x_chip *chip); |