]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blob - drivers/i2c/chips/twl4030-pwrirq.c
ce66dd4424d8ff20ad7b99e60c51713f63e2e9f4
[linux-2.6-omap-h63xx.git] / drivers / i2c / chips / twl4030-pwrirq.c
1 /*
2  * twl4030-pwrirq.c - handle power interrupts from TWL3040
3  *
4  * Copyright (C) 2008 Nokia Corporation
5  *
6  * Written by Peter De Schrijver <peter.de-schrijver@nokia.com>
7  *
8  * This file is subject to the terms and conditions of the GNU General
9  * Public License. See the file "COPYING" in the main directory of this
10  * archive for more details.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21
22 #include <linux/kernel_stat.h>
23 #include <linux/module.h>
24 #include <linux/interrupt.h>
25 #include <linux/irq.h>
26 #include <linux/i2c.h>
27 #include <linux/random.h>
28 #include <linux/kthread.h>
29 #include <linux/i2c/twl4030.h>
30
31
32 static DEFINE_SPINLOCK(pwr_lock);
33 static u8 twl4030_pwrirq_mask;
34
35 static struct task_struct *twl4030_pwrirq_unmask_thread;
36
37 static void twl4030_pwrirq_ack(unsigned int irq) {}
38
39 static void twl4030_pwrirq_disableint(unsigned int irq)
40 {
41         unsigned long flags;
42
43         spin_lock_irqsave(&pwr_lock, flags);
44         twl4030_pwrirq_mask |= 1 << (irq - TWL4030_PWR_IRQ_BASE);
45         if (twl4030_pwrirq_unmask_thread
46                         && twl4030_pwrirq_unmask_thread->state != TASK_RUNNING)
47                 wake_up_process(twl4030_pwrirq_unmask_thread);
48         spin_unlock_irqrestore(&pwr_lock, flags);
49 }
50
51 static void twl4030_pwrirq_enableint(unsigned int irq)
52 {
53         unsigned long flags;
54
55         spin_lock_irqsave(&pwr_lock, flags);
56         twl4030_pwrirq_mask &= ~(1 << (irq - TWL4030_PWR_IRQ_BASE));
57         if (twl4030_pwrirq_unmask_thread
58                         && twl4030_pwrirq_unmask_thread->state != TASK_RUNNING)
59                 wake_up_process(twl4030_pwrirq_unmask_thread);
60         spin_unlock_irqrestore(&pwr_lock, flags);
61 }
62
63 static struct irq_chip twl4030_pwrirq_chip = {
64         .name   = "twl4030-pwr",
65         .ack    = twl4030_pwrirq_ack,
66         .mask   = twl4030_pwrirq_disableint,
67         .unmask = twl4030_pwrirq_enableint,
68 };
69
70 static void do_twl4030_pwrirq(unsigned int irq, irq_desc_t *desc)
71 {
72         const unsigned int cpu = smp_processor_id();
73
74         desc->status |= IRQ_LEVEL;
75
76         desc->chip->ack(irq);
77
78         if (!desc->depth) {
79                 int ret;
80                 int module_irq;
81                 u8 pwr_isr;
82
83                 kstat_cpu(cpu).irqs[irq]++;
84
85                 local_irq_enable();
86                 ret = twl4030_i2c_read_u8(TWL4030_MODULE_INT, &pwr_isr,
87                                           TWL4030_INT_PWR_ISR1);
88                 local_irq_disable();
89                 if (ret) {
90                         printk(KERN_WARNING
91                                 "I2C error %d while reading TWL4030"
92                                 "INT PWR_ISR1 register\n", ret);
93                         return;
94                 }
95
96                 for (module_irq = TWL4030_PWR_IRQ_BASE; pwr_isr != 0;
97                         module_irq++, pwr_isr >>= 1) {
98                         if (pwr_isr & 1)
99                                 generic_handle_irq(module_irq);
100                 }
101
102                 desc->chip->unmask(irq);
103         }
104 }
105
106 static int twl4030_pwrirq_thread(void *data)
107 {
108         current->flags |= PF_NOFREEZE;
109
110         while (!kthread_should_stop()) {
111                 u8 local_mask;
112
113                 spin_lock_irq(&pwr_lock);
114                 local_mask = twl4030_pwrirq_mask;
115                 spin_unlock_irq(&pwr_lock);
116
117                 twl4030_i2c_write_u8(TWL4030_MODULE_INT, local_mask,
118                                      TWL4030_INT_PWR_IMR1);
119
120                 spin_lock_irq(&pwr_lock);
121                 if (local_mask == twl4030_pwrirq_mask)
122                         set_current_state(TASK_INTERRUPTIBLE);
123                 spin_unlock_irq(&pwr_lock);
124
125                 schedule();
126         }
127         set_current_state(TASK_RUNNING);
128         return 0;
129 }
130
131 static int __init twl4030_pwrirq_init(void)
132 {
133         int i, err;
134
135         twl4030_pwrirq_mask = 0xff;
136
137         err = twl4030_i2c_write_u8(TWL4030_MODULE_INT, twl4030_pwrirq_mask,
138                                         TWL4030_INT_PWR_IMR1);
139         if (err)
140                 return err;
141
142         /* Enable clear on read */
143
144         err = twl4030_i2c_write_u8(TWL4030_MODULE_INT,
145                                 TWL4030_SIH_CTRL_COR_MASK,
146                                 TWL4030_INT_PWR_SIH_CTRL);
147         if (err)
148                 return err;
149
150         twl4030_pwrirq_unmask_thread = kthread_create(twl4030_pwrirq_thread,
151                 NULL, "twl4030 pwrirq");
152         if (!twl4030_pwrirq_unmask_thread) {
153                 printk(KERN_ERR
154                         "%s: could not create twl4030 pwrirq unmask thread!\n",
155                                 __func__);
156                 return -ENOMEM;
157         }
158
159         for (i = TWL4030_PWR_IRQ_BASE; i < TWL4030_PWR_IRQ_END; i++) {
160                 set_irq_chip_and_handler(i, &twl4030_pwrirq_chip,
161                                 handle_edge_irq);
162                 set_irq_flags(i, IRQF_VALID);
163         }
164
165         set_irq_chained_handler(TWL4030_MODIRQ_PWR, do_twl4030_pwrirq);
166
167         return 0;
168 }
169 subsys_initcall(twl4030_pwrirq_init);
170
171 static void __exit twl4030_pwrirq_exit(void)
172 {
173
174         int i;
175
176         /* FIXME the irqs are left enabled; trouble when they arrive... */
177
178         set_irq_handler(TWL4030_MODIRQ_PWR, NULL);
179         set_irq_flags(TWL4030_MODIRQ_PWR, 0);
180
181         for (i = TWL4030_PWR_IRQ_BASE; i < TWL4030_PWR_IRQ_END; i++) {
182                 set_irq_handler(i, NULL);
183                 set_irq_flags(i, 0);
184         }
185
186         if (twl4030_pwrirq_unmask_thread) {
187                 kthread_stop(twl4030_pwrirq_unmask_thread);
188                 twl4030_pwrirq_unmask_thread = NULL;
189         }
190 }
191 module_exit(twl4030_pwrirq_exit);