diff options
author | Balaji T K <balajitk@ti.com> | 2009-12-13 18:25:31 -0500 |
---|---|---|
committer | Samuel Ortiz <sameo@linux.intel.com> | 2009-12-13 18:25:31 -0500 |
commit | e8deb28ca8e221de0239eafb3c3d431d8854278e (patch) | |
tree | 2dffdb207366aa0a8f229775515fc207c16f8574 /drivers/mfd | |
parent | c4aa6f314328142974c78377cd9476f8ec6f0eba (diff) |
mfd: Add support for twl6030 irq framework
This patch adds support for phoenix interrupt framework. New iInterrupt
status register A, B, C are introduced in Phoenix and are cleared on write.
Due to the differences in interrupt handling with respect to TWL4030,
twl6030-irq.c is created for TWL6030 PMIC
Signed-off-by: Rajendra Nayak <rnayak@ti.com>
Signed-off-by: Balaji T K <balajitk@ti.com>
Signed-off-by: Santosh Shilimkar <santosh.shilimkar@ti.com>
Reviewed-by: Tony Lindgren <tony@atomide.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Diffstat (limited to 'drivers/mfd')
-rw-r--r-- | drivers/mfd/Kconfig | 4 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 2 | ||||
-rw-r--r-- | drivers/mfd/twl-core.c | 120 | ||||
-rw-r--r-- | drivers/mfd/twl4030-irq.c | 6 | ||||
-rw-r--r-- | drivers/mfd/twl6030-irq.c | 299 |
5 files changed, 411 insertions, 20 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index b23343cdc196..87829789243e 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig | |||
@@ -103,10 +103,10 @@ config MENELAUS | |||
103 | cell phones and PDAs. | 103 | cell phones and PDAs. |
104 | 104 | ||
105 | config TWL4030_CORE | 105 | config TWL4030_CORE |
106 | bool "Texas Instruments TWL4030/TPS659x0 Support" | 106 | bool "Texas Instruments TWL4030/TWL5030/TWL6030/TPS659x0 Support" |
107 | depends on I2C=y && GENERIC_HARDIRQS | 107 | depends on I2C=y && GENERIC_HARDIRQS |
108 | help | 108 | help |
109 | Say yes here if you have TWL4030 family chip on your board. | 109 | Say yes here if you have TWL4030 / TWL6030 family chip on your board. |
110 | This core driver provides register access and IRQ handling | 110 | This core driver provides register access and IRQ handling |
111 | facilities, and registers devices for the various functions | 111 | facilities, and registers devices for the various functions |
112 | so that function-specific drivers can bind to them. | 112 | so that function-specific drivers can bind to them. |
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index f4d14b7589bf..ca2f2c4ff05e 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile | |||
@@ -26,7 +26,7 @@ obj-$(CONFIG_MFD_WM8350_I2C) += wm8350-i2c.o | |||
26 | obj-$(CONFIG_TPS65010) += tps65010.o | 26 | obj-$(CONFIG_TPS65010) += tps65010.o |
27 | obj-$(CONFIG_MENELAUS) += menelaus.o | 27 | obj-$(CONFIG_MENELAUS) += menelaus.o |
28 | 28 | ||
29 | obj-$(CONFIG_TWL4030_CORE) += twl-core.o twl4030-irq.o | 29 | obj-$(CONFIG_TWL4030_CORE) += twl-core.o twl4030-irq.o twl6030-irq.o |
30 | obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o | 30 | obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o |
31 | obj-$(CONFIG_TWL4030_CODEC) += twl4030-codec.o | 31 | obj-$(CONFIG_TWL4030_CODEC) += twl4030-codec.o |
32 | 32 | ||
diff --git a/drivers/mfd/twl-core.c b/drivers/mfd/twl-core.c index 79946fe800af..c48a6138c575 100644 --- a/drivers/mfd/twl-core.c +++ b/drivers/mfd/twl-core.c | |||
@@ -181,6 +181,30 @@ | |||
181 | /* Triton Core internal information (END) */ | 181 | /* Triton Core internal information (END) */ |
182 | 182 | ||
183 | 183 | ||
184 | /* subchip/slave 0 0x48 - POWER */ | ||
185 | #define TWL6030_BASEADD_RTC 0x0000 | ||
186 | #define TWL6030_BASEADD_MEM 0x0017 | ||
187 | #define TWL6030_BASEADD_PM_MASTER 0x001F | ||
188 | #define TWL6030_BASEADD_PM_SLAVE_MISC 0x0030 /* PM_RECEIVER */ | ||
189 | #define TWL6030_BASEADD_PM_MISC 0x00E2 | ||
190 | #define TWL6030_BASEADD_PM_PUPD 0x00F0 | ||
191 | |||
192 | /* subchip/slave 1 0x49 - FEATURE */ | ||
193 | #define TWL6030_BASEADD_USB 0x0000 | ||
194 | #define TWL6030_BASEADD_GPADC_CTRL 0x002E | ||
195 | #define TWL6030_BASEADD_AUX 0x0090 | ||
196 | #define TWL6030_BASEADD_PWM 0x00BA | ||
197 | #define TWL6030_BASEADD_GASGAUGE 0x00C0 | ||
198 | #define TWL6030_BASEADD_PIH 0x00D0 | ||
199 | #define TWL6030_BASEADD_CHARGER 0x00E0 | ||
200 | |||
201 | /* subchip/slave 2 0x4A - DFT */ | ||
202 | #define TWL6030_BASEADD_DIEID 0x00C0 | ||
203 | |||
204 | /* subchip/slave 3 0x4B - AUDIO */ | ||
205 | #define TWL6030_BASEADD_AUDIO 0x0000 | ||
206 | #define TWL6030_BASEADD_RSV 0x0000 | ||
207 | |||
184 | /* Few power values */ | 208 | /* Few power values */ |
185 | #define R_CFG_BOOT 0x05 | 209 | #define R_CFG_BOOT 0x05 |
186 | #define R_PROTECT_KEY 0x0E | 210 | #define R_PROTECT_KEY 0x0E |
@@ -202,13 +226,21 @@ | |||
202 | #define TWL4030_VAUX2 BIT(0) /* pre-5030 voltage ranges */ | 226 | #define TWL4030_VAUX2 BIT(0) /* pre-5030 voltage ranges */ |
203 | #define TPS_SUBSET BIT(1) /* tps659[23]0 have fewer LDOs */ | 227 | #define TPS_SUBSET BIT(1) /* tps659[23]0 have fewer LDOs */ |
204 | #define TWL5031 BIT(2) /* twl5031 has different registers */ | 228 | #define TWL5031 BIT(2) /* twl5031 has different registers */ |
229 | #define TWL6030_CLASS BIT(3) /* TWL6030 class */ | ||
205 | 230 | ||
206 | /*----------------------------------------------------------------------*/ | 231 | /*----------------------------------------------------------------------*/ |
207 | 232 | ||
208 | /* is driver active, bound to a chip? */ | 233 | /* is driver active, bound to a chip? */ |
209 | static bool inuse; | 234 | static bool inuse; |
210 | 235 | ||
211 | /* Structure for each TWL4030 Slave */ | 236 | static unsigned int twl_id; |
237 | unsigned int twl_rev(void) | ||
238 | { | ||
239 | return twl_id; | ||
240 | } | ||
241 | EXPORT_SYMBOL(twl_rev); | ||
242 | |||
243 | /* Structure for each TWL4030/TWL6030 Slave */ | ||
212 | struct twl_client { | 244 | struct twl_client { |
213 | struct i2c_client *client; | 245 | struct i2c_client *client; |
214 | u8 address; | 246 | u8 address; |
@@ -228,11 +260,12 @@ struct twl_mapping { | |||
228 | unsigned char sid; /* Slave ID */ | 260 | unsigned char sid; /* Slave ID */ |
229 | unsigned char base; /* base address */ | 261 | unsigned char base; /* base address */ |
230 | }; | 262 | }; |
263 | struct twl_mapping *twl_map; | ||
231 | 264 | ||
232 | static struct twl_mapping twl4030_map[TWL4030_MODULE_LAST + 1] = { | 265 | static struct twl_mapping twl4030_map[TWL4030_MODULE_LAST + 1] = { |
233 | /* | 266 | /* |
234 | * NOTE: don't change this table without updating the | 267 | * NOTE: don't change this table without updating the |
235 | * <linux/i2c/twl4030.h> defines for TWL4030_MODULE_* | 268 | * <linux/i2c/twl.h> defines for TWL4030_MODULE_* |
236 | * so they continue to match the order in this table. | 269 | * so they continue to match the order in this table. |
237 | */ | 270 | */ |
238 | 271 | ||
@@ -265,6 +298,40 @@ static struct twl_mapping twl4030_map[TWL4030_MODULE_LAST + 1] = { | |||
265 | { 3, TWL4030_BASEADD_SECURED_REG }, | 298 | { 3, TWL4030_BASEADD_SECURED_REG }, |
266 | }; | 299 | }; |
267 | 300 | ||
301 | static struct twl_mapping twl6030_map[] = { | ||
302 | /* | ||
303 | * NOTE: don't change this table without updating the | ||
304 | * <linux/i2c/twl.h> defines for TWL4030_MODULE_* | ||
305 | * so they continue to match the order in this table. | ||
306 | */ | ||
307 | { SUB_CHIP_ID1, TWL6030_BASEADD_USB }, | ||
308 | { SUB_CHIP_ID3, TWL6030_BASEADD_AUDIO }, | ||
309 | { SUB_CHIP_ID2, TWL6030_BASEADD_DIEID }, | ||
310 | { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, | ||
311 | { SUB_CHIP_ID1, TWL6030_BASEADD_PIH }, | ||
312 | |||
313 | { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, | ||
314 | { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, | ||
315 | { SUB_CHIP_ID1, TWL6030_BASEADD_GPADC_CTRL }, | ||
316 | { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, | ||
317 | { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, | ||
318 | |||
319 | { SUB_CHIP_ID1, TWL6030_BASEADD_CHARGER }, | ||
320 | { SUB_CHIP_ID1, TWL6030_BASEADD_GASGAUGE }, | ||
321 | { SUB_CHIP_ID1, TWL6030_BASEADD_PWM }, | ||
322 | { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, | ||
323 | { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, | ||
324 | |||
325 | { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, | ||
326 | { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, | ||
327 | { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, | ||
328 | { SUB_CHIP_ID0, TWL6030_BASEADD_PM_MASTER }, | ||
329 | { SUB_CHIP_ID0, TWL6030_BASEADD_PM_SLAVE_MISC }, | ||
330 | |||
331 | { SUB_CHIP_ID0, TWL6030_BASEADD_RTC }, | ||
332 | { SUB_CHIP_ID0, TWL6030_BASEADD_MEM }, | ||
333 | }; | ||
334 | |||
268 | /*----------------------------------------------------------------------*/ | 335 | /*----------------------------------------------------------------------*/ |
269 | 336 | ||
270 | /* Exported Functions */ | 337 | /* Exported Functions */ |
@@ -292,7 +359,7 @@ int twl_i2c_write(u8 mod_no, u8 *value, u8 reg, unsigned num_bytes) | |||
292 | pr_err("%s: invalid module number %d\n", DRIVER_NAME, mod_no); | 359 | pr_err("%s: invalid module number %d\n", DRIVER_NAME, mod_no); |
293 | return -EPERM; | 360 | return -EPERM; |
294 | } | 361 | } |
295 | sid = twl4030_map[mod_no].sid; | 362 | sid = twl_map[mod_no].sid; |
296 | twl = &twl_modules[sid]; | 363 | twl = &twl_modules[sid]; |
297 | 364 | ||
298 | if (unlikely(!inuse)) { | 365 | if (unlikely(!inuse)) { |
@@ -310,7 +377,7 @@ int twl_i2c_write(u8 mod_no, u8 *value, u8 reg, unsigned num_bytes) | |||
310 | msg->flags = 0; | 377 | msg->flags = 0; |
311 | msg->buf = value; | 378 | msg->buf = value; |
312 | /* over write the first byte of buffer with the register address */ | 379 | /* over write the first byte of buffer with the register address */ |
313 | *value = twl4030_map[mod_no].base + reg; | 380 | *value = twl_map[mod_no].base + reg; |
314 | ret = i2c_transfer(twl->client->adapter, twl->xfer_msg, 1); | 381 | ret = i2c_transfer(twl->client->adapter, twl->xfer_msg, 1); |
315 | mutex_unlock(&twl->xfer_lock); | 382 | mutex_unlock(&twl->xfer_lock); |
316 | 383 | ||
@@ -349,7 +416,7 @@ int twl_i2c_read(u8 mod_no, u8 *value, u8 reg, unsigned num_bytes) | |||
349 | pr_err("%s: invalid module number %d\n", DRIVER_NAME, mod_no); | 416 | pr_err("%s: invalid module number %d\n", DRIVER_NAME, mod_no); |
350 | return -EPERM; | 417 | return -EPERM; |
351 | } | 418 | } |
352 | sid = twl4030_map[mod_no].sid; | 419 | sid = twl_map[mod_no].sid; |
353 | twl = &twl_modules[sid]; | 420 | twl = &twl_modules[sid]; |
354 | 421 | ||
355 | if (unlikely(!inuse)) { | 422 | if (unlikely(!inuse)) { |
@@ -362,7 +429,7 @@ int twl_i2c_read(u8 mod_no, u8 *value, u8 reg, unsigned num_bytes) | |||
362 | msg->addr = twl->address; | 429 | msg->addr = twl->address; |
363 | msg->len = 1; | 430 | msg->len = 1; |
364 | msg->flags = 0; /* Read the register value */ | 431 | msg->flags = 0; /* Read the register value */ |
365 | val = twl4030_map[mod_no].base + reg; | 432 | val = twl_map[mod_no].base + reg; |
366 | msg->buf = &val; | 433 | msg->buf = &val; |
367 | /* [MSG2] fill the data rx buffer */ | 434 | /* [MSG2] fill the data rx buffer */ |
368 | msg = &twl->xfer_msg[1]; | 435 | msg = &twl->xfer_msg[1]; |
@@ -486,6 +553,7 @@ add_regulator_linked(int num, struct regulator_init_data *pdata, | |||
486 | struct regulator_consumer_supply *consumers, | 553 | struct regulator_consumer_supply *consumers, |
487 | unsigned num_consumers) | 554 | unsigned num_consumers) |
488 | { | 555 | { |
556 | unsigned sub_chip_id; | ||
489 | /* regulator framework demands init_data ... */ | 557 | /* regulator framework demands init_data ... */ |
490 | if (!pdata) | 558 | if (!pdata) |
491 | return NULL; | 559 | return NULL; |
@@ -496,7 +564,8 @@ add_regulator_linked(int num, struct regulator_init_data *pdata, | |||
496 | } | 564 | } |
497 | 565 | ||
498 | /* NOTE: we currently ignore regulator IRQs, e.g. for short circuits */ | 566 | /* NOTE: we currently ignore regulator IRQs, e.g. for short circuits */ |
499 | return add_numbered_child(3, "twl_reg", num, | 567 | sub_chip_id = twl_map[TWL_MODULE_PM_MASTER].sid; |
568 | return add_numbered_child(sub_chip_id, "twl_reg", num, | ||
500 | pdata, sizeof(*pdata), false, 0, 0); | 569 | pdata, sizeof(*pdata), false, 0, 0); |
501 | } | 570 | } |
502 | 571 | ||
@@ -516,6 +585,7 @@ static int | |||
516 | add_children(struct twl4030_platform_data *pdata, unsigned long features) | 585 | add_children(struct twl4030_platform_data *pdata, unsigned long features) |
517 | { | 586 | { |
518 | struct device *child; | 587 | struct device *child; |
588 | unsigned sub_chip_id; | ||
519 | 589 | ||
520 | if (twl_has_bci() && pdata->bci && | 590 | if (twl_has_bci() && pdata->bci && |
521 | !(features & (TPS_SUBSET | TWL5031))) { | 591 | !(features & (TPS_SUBSET | TWL5031))) { |
@@ -561,7 +631,8 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features) | |||
561 | * Eventually, Linux might become more aware of such | 631 | * Eventually, Linux might become more aware of such |
562 | * HW security concerns, and "least privilege". | 632 | * HW security concerns, and "least privilege". |
563 | */ | 633 | */ |
564 | child = add_child(3, "twl_rtc", | 634 | sub_chip_id = twl_map[TWL_MODULE_RTC].sid; |
635 | child = add_child(sub_chip_id, "twl_rtc", | ||
565 | NULL, 0, | 636 | NULL, 0, |
566 | true, pdata->irq_base + RTC_INTR_OFFSET, 0); | 637 | true, pdata->irq_base + RTC_INTR_OFFSET, 0); |
567 | if (IS_ERR(child)) | 638 | if (IS_ERR(child)) |
@@ -812,16 +883,22 @@ static void clocks_init(struct device *dev, | |||
812 | 883 | ||
813 | /*----------------------------------------------------------------------*/ | 884 | /*----------------------------------------------------------------------*/ |
814 | 885 | ||
815 | int twl_init_irq(int irq_num, unsigned irq_base, unsigned irq_end); | 886 | int twl4030_init_irq(int irq_num, unsigned irq_base, unsigned irq_end); |
816 | int twl_exit_irq(void); | 887 | int twl4030_exit_irq(void); |
817 | int twl_init_chip_irq(const char *chip); | 888 | int twl4030_init_chip_irq(const char *chip); |
889 | int twl6030_init_irq(int irq_num, unsigned irq_base, unsigned irq_end); | ||
890 | int twl6030_exit_irq(void); | ||
818 | 891 | ||
819 | static int twl_remove(struct i2c_client *client) | 892 | static int twl_remove(struct i2c_client *client) |
820 | { | 893 | { |
821 | unsigned i; | 894 | unsigned i; |
822 | int status; | 895 | int status; |
823 | 896 | ||
824 | status = twl_exit_irq(); | 897 | if (twl_class_is_4030()) |
898 | status = twl4030_exit_irq(); | ||
899 | else | ||
900 | status = twl6030_exit_irq(); | ||
901 | |||
825 | if (status < 0) | 902 | if (status < 0) |
826 | return status; | 903 | return status; |
827 | 904 | ||
@@ -878,6 +955,13 @@ twl_probe(struct i2c_client *client, const struct i2c_device_id *id) | |||
878 | mutex_init(&twl->xfer_lock); | 955 | mutex_init(&twl->xfer_lock); |
879 | } | 956 | } |
880 | inuse = true; | 957 | inuse = true; |
958 | if ((id->driver_data) & TWL6030_CLASS) { | ||
959 | twl_id = TWL6030_CLASS_ID; | ||
960 | twl_map = &twl6030_map[0]; | ||
961 | } else { | ||
962 | twl_id = TWL4030_CLASS_ID; | ||
963 | twl_map = &twl4030_map[0]; | ||
964 | } | ||
881 | 965 | ||
882 | /* setup clock framework */ | 966 | /* setup clock framework */ |
883 | clocks_init(&client->dev, pdata->clock); | 967 | clocks_init(&client->dev, pdata->clock); |
@@ -890,8 +974,15 @@ twl_probe(struct i2c_client *client, const struct i2c_device_id *id) | |||
890 | if (client->irq | 974 | if (client->irq |
891 | && pdata->irq_base | 975 | && pdata->irq_base |
892 | && pdata->irq_end > pdata->irq_base) { | 976 | && pdata->irq_end > pdata->irq_base) { |
893 | twl_init_chip_irq(id->name); | 977 | if (twl_class_is_4030()) { |
894 | status = twl_init_irq(client->irq, pdata->irq_base, pdata->irq_end); | 978 | twl4030_init_chip_irq(id->name); |
979 | status = twl4030_init_irq(client->irq, pdata->irq_base, | ||
980 | pdata->irq_end); | ||
981 | } else { | ||
982 | status = twl6030_init_irq(client->irq, pdata->irq_base, | ||
983 | pdata->irq_end); | ||
984 | } | ||
985 | |||
895 | if (status < 0) | 986 | if (status < 0) |
896 | goto fail; | 987 | goto fail; |
897 | } | 988 | } |
@@ -910,6 +1001,7 @@ static const struct i2c_device_id twl_ids[] = { | |||
910 | { "tps65950", 0 }, /* catalog version of twl5030 */ | 1001 | { "tps65950", 0 }, /* catalog version of twl5030 */ |
911 | { "tps65930", TPS_SUBSET }, /* fewer LDOs and DACs; no charger */ | 1002 | { "tps65930", TPS_SUBSET }, /* fewer LDOs and DACs; no charger */ |
912 | { "tps65920", TPS_SUBSET }, /* fewer LDOs; no codec or charger */ | 1003 | { "tps65920", TPS_SUBSET }, /* fewer LDOs; no codec or charger */ |
1004 | { "twl6030", TWL6030_CLASS }, /* "Phoenix power chip" */ | ||
913 | { /* end of list */ }, | 1005 | { /* end of list */ }, |
914 | }; | 1006 | }; |
915 | MODULE_DEVICE_TABLE(i2c, twl_ids); | 1007 | MODULE_DEVICE_TABLE(i2c, twl_ids); |
diff --git a/drivers/mfd/twl4030-irq.c b/drivers/mfd/twl4030-irq.c index 5a62cf916987..20d29bafc9f5 100644 --- a/drivers/mfd/twl4030-irq.c +++ b/drivers/mfd/twl4030-irq.c | |||
@@ -778,7 +778,7 @@ int twl4030_sih_setup(int module) | |||
778 | /* FIXME pass in which interrupt line we'll use ... */ | 778 | /* FIXME pass in which interrupt line we'll use ... */ |
779 | #define twl_irq_line 0 | 779 | #define twl_irq_line 0 |
780 | 780 | ||
781 | int twl_init_irq(int irq_num, unsigned irq_base, unsigned irq_end) | 781 | int twl4030_init_irq(int irq_num, unsigned irq_base, unsigned irq_end) |
782 | { | 782 | { |
783 | static struct irq_chip twl4030_irq_chip; | 783 | static struct irq_chip twl4030_irq_chip; |
784 | 784 | ||
@@ -858,7 +858,7 @@ fail: | |||
858 | return status; | 858 | return status; |
859 | } | 859 | } |
860 | 860 | ||
861 | int twl_exit_irq(void) | 861 | int twl4030_exit_irq(void) |
862 | { | 862 | { |
863 | /* FIXME undo twl_init_irq() */ | 863 | /* FIXME undo twl_init_irq() */ |
864 | if (twl4030_irq_base) { | 864 | if (twl4030_irq_base) { |
@@ -868,7 +868,7 @@ int twl_exit_irq(void) | |||
868 | return 0; | 868 | return 0; |
869 | } | 869 | } |
870 | 870 | ||
871 | int twl_init_chip_irq(const char *chip) | 871 | int twl4030_init_chip_irq(const char *chip) |
872 | { | 872 | { |
873 | if (!strcmp(chip, "twl5031")) { | 873 | if (!strcmp(chip, "twl5031")) { |
874 | sih_modules = sih_modules_twl5031; | 874 | sih_modules = sih_modules_twl5031; |
diff --git a/drivers/mfd/twl6030-irq.c b/drivers/mfd/twl6030-irq.c new file mode 100644 index 000000000000..10bf228ad626 --- /dev/null +++ b/drivers/mfd/twl6030-irq.c | |||
@@ -0,0 +1,299 @@ | |||
1 | /* | ||
2 | * twl6030-irq.c - TWL6030 irq support | ||
3 | * | ||
4 | * Copyright (C) 2005-2009 Texas Instruments, Inc. | ||
5 | * | ||
6 | * Modifications to defer interrupt handling to a kernel thread: | ||
7 | * Copyright (C) 2006 MontaVista Software, Inc. | ||
8 | * | ||
9 | * Based on tlv320aic23.c: | ||
10 | * Copyright (c) by Kai Svahn <kai.svahn@nokia.com> | ||
11 | * | ||
12 | * Code cleanup and modifications to IRQ handler. | ||
13 | * by syed khasim <x0khasim@ti.com> | ||
14 | * | ||
15 | * TWL6030 specific code and IRQ handling changes by | ||
16 | * Jagadeesh Bhaskar Pakaravoor <j-pakaravoor@ti.com> | ||
17 | * Balaji T K <balajitk@ti.com> | ||
18 | * | ||
19 | * This program is free software; you can redistribute it and/or modify | ||
20 | * it under the terms of the GNU General Public License as published by | ||
21 | * the Free Software Foundation; either version 2 of the License, or | ||
22 | * (at your option) any later version. | ||
23 | * | ||
24 | * This program is distributed in the hope that it will be useful, | ||
25 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
26 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
27 | * GNU General Public License for more details. | ||
28 | * | ||
29 | * You should have received a copy of the GNU General Public License | ||
30 | * along with this program; if not, write to the Free Software | ||
31 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
32 | */ | ||
33 | |||
34 | #include <linux/init.h> | ||
35 | #include <linux/interrupt.h> | ||
36 | #include <linux/irq.h> | ||
37 | #include <linux/kthread.h> | ||
38 | #include <linux/i2c/twl.h> | ||
39 | |||
40 | /* | ||
41 | * TWL6030 (unlike its predecessors, which had two level interrupt handling) | ||
42 | * three interrupt registers INT_STS_A, INT_STS_B and INT_STS_C. | ||
43 | * It exposes status bits saying who has raised an interrupt. There are | ||
44 | * three mask registers that corresponds to these status registers, that | ||
45 | * enables/disables these interrupts. | ||
46 | * | ||
47 | * We set up IRQs starting at a platform-specified base. An interrupt map table, | ||
48 | * specifies mapping between interrupt number and the associated module. | ||
49 | * | ||
50 | */ | ||
51 | |||
52 | static int twl6030_interrupt_mapping[24] = { | ||
53 | PWR_INTR_OFFSET, /* Bit 0 PWRON */ | ||
54 | PWR_INTR_OFFSET, /* Bit 1 RPWRON */ | ||
55 | PWR_INTR_OFFSET, /* Bit 2 BAT_VLOW */ | ||
56 | RTC_INTR_OFFSET, /* Bit 3 RTC_ALARM */ | ||
57 | RTC_INTR_OFFSET, /* Bit 4 RTC_PERIOD */ | ||
58 | HOTDIE_INTR_OFFSET, /* Bit 5 HOT_DIE */ | ||
59 | SMPSLDO_INTR_OFFSET, /* Bit 6 VXXX_SHORT */ | ||
60 | SMPSLDO_INTR_OFFSET, /* Bit 7 VMMC_SHORT */ | ||
61 | |||
62 | SMPSLDO_INTR_OFFSET, /* Bit 8 VUSIM_SHORT */ | ||
63 | BATDETECT_INTR_OFFSET, /* Bit 9 BAT */ | ||
64 | SIMDETECT_INTR_OFFSET, /* Bit 10 SIM */ | ||
65 | MMCDETECT_INTR_OFFSET, /* Bit 11 MMC */ | ||
66 | RSV_INTR_OFFSET, /* Bit 12 Reserved */ | ||
67 | MADC_INTR_OFFSET, /* Bit 13 GPADC_RT_EOC */ | ||
68 | MADC_INTR_OFFSET, /* Bit 14 GPADC_SW_EOC */ | ||
69 | GASGAUGE_INTR_OFFSET, /* Bit 15 CC_AUTOCAL */ | ||
70 | |||
71 | USBOTG_INTR_OFFSET, /* Bit 16 ID_WKUP */ | ||
72 | USBOTG_INTR_OFFSET, /* Bit 17 VBUS_WKUP */ | ||
73 | USBOTG_INTR_OFFSET, /* Bit 18 ID */ | ||
74 | USBOTG_INTR_OFFSET, /* Bit 19 VBUS */ | ||
75 | CHARGER_INTR_OFFSET, /* Bit 20 CHRG_CTRL */ | ||
76 | CHARGER_INTR_OFFSET, /* Bit 21 EXT_CHRG */ | ||
77 | CHARGER_INTR_OFFSET, /* Bit 22 INT_CHRG */ | ||
78 | RSV_INTR_OFFSET, /* Bit 23 Reserved */ | ||
79 | }; | ||
80 | /*----------------------------------------------------------------------*/ | ||
81 | |||
82 | static unsigned twl6030_irq_base; | ||
83 | |||
84 | static struct completion irq_event; | ||
85 | |||
86 | /* | ||
87 | * This thread processes interrupts reported by the Primary Interrupt Handler. | ||
88 | */ | ||
89 | static int twl6030_irq_thread(void *data) | ||
90 | { | ||
91 | long irq = (long)data; | ||
92 | static unsigned i2c_errors; | ||
93 | static const unsigned max_i2c_errors = 100; | ||
94 | int ret; | ||
95 | |||
96 | current->flags |= PF_NOFREEZE; | ||
97 | |||
98 | while (!kthread_should_stop()) { | ||
99 | int i; | ||
100 | union { | ||
101 | u8 bytes[4]; | ||
102 | u32 int_sts; | ||
103 | } sts; | ||
104 | |||
105 | /* Wait for IRQ, then read PIH irq status (also blocking) */ | ||
106 | wait_for_completion_interruptible(&irq_event); | ||
107 | |||
108 | /* read INT_STS_A, B and C in one shot using a burst read */ | ||
109 | ret = twl_i2c_read(TWL_MODULE_PIH, sts.bytes, | ||
110 | REG_INT_STS_A, 3); | ||
111 | if (ret) { | ||
112 | pr_warning("twl6030: I2C error %d reading PIH ISR\n", | ||
113 | ret); | ||
114 | if (++i2c_errors >= max_i2c_errors) { | ||
115 | printk(KERN_ERR "Maximum I2C error count" | ||
116 | " exceeded. Terminating %s.\n", | ||
117 | __func__); | ||
118 | break; | ||
119 | } | ||
120 | complete(&irq_event); | ||
121 | continue; | ||
122 | } | ||
123 | |||
124 | |||
125 | |||
126 | sts.bytes[3] = 0; /* Only 24 bits are valid*/ | ||
127 | |||
128 | for (i = 0; sts.int_sts; sts.int_sts >>= 1, i++) { | ||
129 | local_irq_disable(); | ||
130 | if (sts.int_sts & 0x1) { | ||
131 | int module_irq = twl6030_irq_base + | ||
132 | twl6030_interrupt_mapping[i]; | ||
133 | struct irq_desc *d = irq_to_desc(module_irq); | ||
134 | |||
135 | if (!d) { | ||
136 | pr_err("twl6030: Invalid SIH IRQ: %d\n", | ||
137 | module_irq); | ||
138 | return -EINVAL; | ||
139 | } | ||
140 | |||
141 | /* These can't be masked ... always warn | ||
142 | * if we get any surprises. | ||
143 | */ | ||
144 | if (d->status & IRQ_DISABLED) | ||
145 | note_interrupt(module_irq, d, | ||
146 | IRQ_NONE); | ||
147 | else | ||
148 | d->handle_irq(module_irq, d); | ||
149 | |||
150 | } | ||
151 | local_irq_enable(); | ||
152 | } | ||
153 | ret = twl_i2c_write(TWL_MODULE_PIH, sts.bytes, | ||
154 | REG_INT_STS_A, 3); /* clear INT_STS_A */ | ||
155 | if (ret) | ||
156 | pr_warning("twl6030: I2C error in clearing PIH ISR\n"); | ||
157 | |||
158 | enable_irq(irq); | ||
159 | } | ||
160 | |||
161 | return 0; | ||
162 | } | ||
163 | |||
164 | /* | ||
165 | * handle_twl6030_int() is the desc->handle method for the twl6030 interrupt. | ||
166 | * This is a chained interrupt, so there is no desc->action method for it. | ||
167 | * Now we need to query the interrupt controller in the twl6030 to determine | ||
168 | * which module is generating the interrupt request. However, we can't do i2c | ||
169 | * transactions in interrupt context, so we must defer that work to a kernel | ||
170 | * thread. All we do here is acknowledge and mask the interrupt and wakeup | ||
171 | * the kernel thread. | ||
172 | */ | ||
173 | static irqreturn_t handle_twl6030_pih(int irq, void *devid) | ||
174 | { | ||
175 | disable_irq_nosync(irq); | ||
176 | complete(devid); | ||
177 | return IRQ_HANDLED; | ||
178 | } | ||
179 | |||
180 | /*----------------------------------------------------------------------*/ | ||
181 | |||
182 | static inline void activate_irq(int irq) | ||
183 | { | ||
184 | #ifdef CONFIG_ARM | ||
185 | /* ARM requires an extra step to clear IRQ_NOREQUEST, which it | ||
186 | * sets on behalf of every irq_chip. Also sets IRQ_NOPROBE. | ||
187 | */ | ||
188 | set_irq_flags(irq, IRQF_VALID); | ||
189 | #else | ||
190 | /* same effect on other architectures */ | ||
191 | set_irq_noprobe(irq); | ||
192 | #endif | ||
193 | } | ||
194 | |||
195 | /*----------------------------------------------------------------------*/ | ||
196 | |||
197 | static unsigned twl6030_irq_next; | ||
198 | |||
199 | /*----------------------------------------------------------------------*/ | ||
200 | int twl6030_interrupt_unmask(u8 bit_mask, u8 offset) | ||
201 | { | ||
202 | int ret; | ||
203 | u8 unmask_value; | ||
204 | ret = twl_i2c_read_u8(TWL_MODULE_PIH, &unmask_value, | ||
205 | REG_INT_STS_A + offset); | ||
206 | unmask_value &= (~(bit_mask)); | ||
207 | ret |= twl_i2c_write_u8(TWL_MODULE_PIH, unmask_value, | ||
208 | REG_INT_STS_A + offset); /* unmask INT_MSK_A/B/C */ | ||
209 | return ret; | ||
210 | } | ||
211 | EXPORT_SYMBOL(twl6030_interrupt_unmask); | ||
212 | |||
213 | int twl6030_interrupt_mask(u8 bit_mask, u8 offset) | ||
214 | { | ||
215 | int ret; | ||
216 | u8 mask_value; | ||
217 | ret = twl_i2c_read_u8(TWL_MODULE_PIH, &mask_value, | ||
218 | REG_INT_STS_A + offset); | ||
219 | mask_value |= (bit_mask); | ||
220 | ret |= twl_i2c_write_u8(TWL_MODULE_PIH, mask_value, | ||
221 | REG_INT_STS_A + offset); /* mask INT_MSK_A/B/C */ | ||
222 | return ret; | ||
223 | } | ||
224 | EXPORT_SYMBOL(twl6030_interrupt_mask); | ||
225 | |||
226 | int twl6030_init_irq(int irq_num, unsigned irq_base, unsigned irq_end) | ||
227 | { | ||
228 | |||
229 | int status = 0; | ||
230 | int i; | ||
231 | struct task_struct *task; | ||
232 | int ret; | ||
233 | u8 mask[4]; | ||
234 | |||
235 | static struct irq_chip twl6030_irq_chip; | ||
236 | mask[1] = 0xFF; | ||
237 | mask[2] = 0xFF; | ||
238 | mask[3] = 0xFF; | ||
239 | ret = twl_i2c_write(TWL_MODULE_PIH, &mask[0], | ||
240 | REG_INT_MSK_LINE_A, 3); /* MASK ALL INT LINES */ | ||
241 | ret = twl_i2c_write(TWL_MODULE_PIH, &mask[0], | ||
242 | REG_INT_MSK_STS_A, 3); /* MASK ALL INT STS */ | ||
243 | ret = twl_i2c_write(TWL_MODULE_PIH, &mask[0], | ||
244 | REG_INT_STS_A, 3); /* clear INT_STS_A,B,C */ | ||
245 | |||
246 | twl6030_irq_base = irq_base; | ||
247 | |||
248 | /* install an irq handler for each of the modules; | ||
249 | * clone dummy irq_chip since PIH can't *do* anything | ||
250 | */ | ||
251 | twl6030_irq_chip = dummy_irq_chip; | ||
252 | twl6030_irq_chip.name = "twl6030"; | ||
253 | twl6030_irq_chip.set_type = NULL; | ||
254 | |||
255 | for (i = irq_base; i < irq_end; i++) { | ||
256 | set_irq_chip_and_handler(i, &twl6030_irq_chip, | ||
257 | handle_simple_irq); | ||
258 | activate_irq(i); | ||
259 | } | ||
260 | |||
261 | twl6030_irq_next = i; | ||
262 | pr_info("twl6030: %s (irq %d) chaining IRQs %d..%d\n", "PIH", | ||
263 | irq_num, irq_base, twl6030_irq_next - 1); | ||
264 | |||
265 | /* install an irq handler to demultiplex the TWL6030 interrupt */ | ||
266 | init_completion(&irq_event); | ||
267 | task = kthread_run(twl6030_irq_thread, (void *)irq_num, "twl6030-irq"); | ||
268 | if (IS_ERR(task)) { | ||
269 | pr_err("twl6030: could not create irq %d thread!\n", irq_num); | ||
270 | status = PTR_ERR(task); | ||
271 | goto fail_kthread; | ||
272 | } | ||
273 | |||
274 | status = request_irq(irq_num, handle_twl6030_pih, IRQF_DISABLED, | ||
275 | "TWL6030-PIH", &irq_event); | ||
276 | if (status < 0) { | ||
277 | pr_err("twl6030: could not claim irq%d: %d\n", irq_num, status); | ||
278 | goto fail_irq; | ||
279 | } | ||
280 | return status; | ||
281 | fail_irq: | ||
282 | free_irq(irq_num, &irq_event); | ||
283 | |||
284 | fail_kthread: | ||
285 | for (i = irq_base; i < irq_end; i++) | ||
286 | set_irq_chip_and_handler(i, NULL, NULL); | ||
287 | return status; | ||
288 | } | ||
289 | |||
290 | int twl6030_exit_irq(void) | ||
291 | { | ||
292 | |||
293 | if (twl6030_irq_base) { | ||
294 | pr_err("twl6030: can't yet clean up IRQs?\n"); | ||
295 | return -ENOSYS; | ||
296 | } | ||
297 | return 0; | ||
298 | } | ||
299 | |||