[ALSA] snd-aoa: add snd-aoa
[linux-2.6.git] / sound / aoa / core / snd-aoa-gpio-pmf.c
1 /*
2  * Apple Onboard Audio pmf GPIOs
3  *
4  * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
5  *
6  * GPL v2, can be found in COPYING.
7  */
8
9 #include <asm/pmac_feature.h>
10 #include <asm/pmac_pfunc.h>
11 #include "../aoa.h"
12
13 #define PMF_GPIO(name, bit)                                     \
14 static void pmf_gpio_set_##name(struct gpio_runtime *rt, int on)\
15 {                                                               \
16         struct pmf_args args = { .count = 1, .u[0].v = !on };   \
17                                                                 \
18         if (unlikely(!rt)) return;                              \
19         pmf_call_function(rt->node, #name "-mute", &args);      \
20         rt->implementation_private &= ~(1<<bit);                \
21         rt->implementation_private |= (!!on << bit);            \
22 }                                                               \
23 static int pmf_gpio_get_##name(struct gpio_runtime *rt)         \
24 {                                                               \
25         if (unlikely(!rt)) return 0;                            \
26         return (rt->implementation_private>>bit)&1;             \
27 }
28
29 PMF_GPIO(headphone, 0);
30 PMF_GPIO(amp, 1);
31 PMF_GPIO(lineout, 2);
32
33 static void pmf_gpio_set_hw_reset(struct gpio_runtime *rt, int on)
34 {
35         struct pmf_args args = { .count = 1, .u[0].v = !!on };
36
37         if (unlikely(!rt)) return;
38         pmf_call_function(rt->node, "hw-reset", &args);
39 }
40
41 static void pmf_gpio_all_amps_off(struct gpio_runtime *rt)
42 {
43         int saved;
44
45         if (unlikely(!rt)) return;
46         saved = rt->implementation_private;
47         pmf_gpio_set_headphone(rt, 0);
48         pmf_gpio_set_amp(rt, 0);
49         pmf_gpio_set_lineout(rt, 0);
50         rt->implementation_private = saved;
51 }
52
53 static void pmf_gpio_all_amps_restore(struct gpio_runtime *rt)
54 {
55         int s;
56
57         if (unlikely(!rt)) return;
58         s = rt->implementation_private;
59         pmf_gpio_set_headphone(rt, (s>>0)&1);
60         pmf_gpio_set_amp(rt, (s>>1)&1);
61         pmf_gpio_set_lineout(rt, (s>>2)&1);
62 }
63
64 static void pmf_handle_notify(void *data)
65 {
66         struct gpio_notification *notif = data;
67
68         mutex_lock(&notif->mutex);
69         if (notif->notify)
70                 notif->notify(notif->data);
71         mutex_unlock(&notif->mutex);
72 }
73
74 static void pmf_gpio_init(struct gpio_runtime *rt)
75 {
76         pmf_gpio_all_amps_off(rt);
77         rt->implementation_private = 0;
78         INIT_WORK(&rt->headphone_notify.work, pmf_handle_notify,
79                   &rt->headphone_notify);
80         INIT_WORK(&rt->line_in_notify.work, pmf_handle_notify,
81                   &rt->line_in_notify);
82         INIT_WORK(&rt->line_out_notify.work, pmf_handle_notify,
83                   &rt->line_out_notify);
84         mutex_init(&rt->headphone_notify.mutex);
85         mutex_init(&rt->line_in_notify.mutex);
86         mutex_init(&rt->line_out_notify.mutex);
87 }
88
89 static void pmf_gpio_exit(struct gpio_runtime *rt)
90 {
91         pmf_gpio_all_amps_off(rt);
92         rt->implementation_private = 0;
93
94         if (rt->headphone_notify.gpio_private)
95                 pmf_unregister_irq_client(rt->headphone_notify.gpio_private);
96         if (rt->line_in_notify.gpio_private)
97                 pmf_unregister_irq_client(rt->line_in_notify.gpio_private);
98         if (rt->line_out_notify.gpio_private)
99                 pmf_unregister_irq_client(rt->line_out_notify.gpio_private);
100
101         /* make sure no work is pending before freeing
102          * all things */
103         cancel_delayed_work(&rt->headphone_notify.work);
104         cancel_delayed_work(&rt->line_in_notify.work);
105         cancel_delayed_work(&rt->line_out_notify.work);
106         flush_scheduled_work();
107
108         mutex_destroy(&rt->headphone_notify.mutex);
109         mutex_destroy(&rt->line_in_notify.mutex);
110         mutex_destroy(&rt->line_out_notify.mutex);
111
112         if (rt->headphone_notify.gpio_private)
113                 kfree(rt->headphone_notify.gpio_private);
114         if (rt->line_in_notify.gpio_private)
115                 kfree(rt->line_in_notify.gpio_private);
116         if (rt->line_out_notify.gpio_private)
117                 kfree(rt->line_out_notify.gpio_private);
118 }
119
120 static void pmf_handle_notify_irq(void *data)
121 {
122         struct gpio_notification *notif = data;
123
124         schedule_work(&notif->work);
125 }
126
127 static int pmf_set_notify(struct gpio_runtime *rt,
128                           enum notify_type type,
129                           notify_func_t notify,
130                           void *data)
131 {
132         struct gpio_notification *notif;
133         notify_func_t old;
134         struct pmf_irq_client *irq_client;
135         char *name;
136         int err = -EBUSY;
137
138         switch (type) {
139         case AOA_NOTIFY_HEADPHONE:
140                 notif = &rt->headphone_notify;
141                 name = "headphone-detect";
142                 break;
143         case AOA_NOTIFY_LINE_IN:
144                 notif = &rt->line_in_notify;
145                 name = "linein-detect";
146                 break;
147         case AOA_NOTIFY_LINE_OUT:
148                 notif = &rt->line_out_notify;
149                 name = "lineout-detect";
150                 break;
151         default:
152                 return -EINVAL;
153         }
154
155         mutex_lock(&notif->mutex);
156
157         old = notif->notify;
158
159         if (!old && !notify) {
160                 err = 0;
161                 goto out_unlock;
162         }
163
164         if (old && notify) {
165                 if (old == notify && notif->data == data)
166                         err = 0;
167                 goto out_unlock;
168         }
169
170         if (old && !notify) {
171                 irq_client = notif->gpio_private;
172                 pmf_unregister_irq_client(irq_client);
173                 kfree(irq_client);
174                 notif->gpio_private = NULL;
175         }
176         if (!old && notify) {
177                 irq_client = kzalloc(sizeof(struct pmf_irq_client),
178                                      GFP_KERNEL);
179                 irq_client->data = notif;
180                 irq_client->handler = pmf_handle_notify_irq;
181                 irq_client->owner = THIS_MODULE;
182                 err = pmf_register_irq_client(rt->node,
183                                               name,
184                                               irq_client);
185                 if (err) {
186                         printk(KERN_ERR "snd-aoa: gpio layer failed to"
187                                         " register %s irq (%d)\n", name, err);
188                         kfree(irq_client);
189                         goto out_unlock;
190                 }
191                 notif->gpio_private = irq_client;
192         }
193         notif->notify = notify;
194         notif->data = data;
195
196         err = 0;
197  out_unlock:
198         mutex_unlock(&notif->mutex);
199         return err;
200 }
201
202 static int pmf_get_detect(struct gpio_runtime *rt,
203                           enum notify_type type)
204 {
205         char *name;
206         int err = -EBUSY, ret;
207         struct pmf_args args = { .count = 1, .u[0].p = &ret };
208
209         switch (type) {
210         case AOA_NOTIFY_HEADPHONE:
211                 name = "headphone-detect";
212                 break;
213         case AOA_NOTIFY_LINE_IN:
214                 name = "linein-detect";
215                 break;
216         case AOA_NOTIFY_LINE_OUT:
217                 name = "lineout-detect";
218                 break;
219         default:
220                 return -EINVAL;
221         }
222
223         err = pmf_call_function(rt->node, name, &args);
224         if (err)
225                 return err;
226         return ret;
227 }
228
229 static struct gpio_methods methods = {
230         .init                   = pmf_gpio_init,
231         .exit                   = pmf_gpio_exit,
232         .all_amps_off           = pmf_gpio_all_amps_off,
233         .all_amps_restore       = pmf_gpio_all_amps_restore,
234         .set_headphone          = pmf_gpio_set_headphone,
235         .set_speakers           = pmf_gpio_set_amp,
236         .set_lineout            = pmf_gpio_set_lineout,
237         .set_hw_reset           = pmf_gpio_set_hw_reset,
238         .get_headphone          = pmf_gpio_get_headphone,
239         .get_speakers           = pmf_gpio_get_amp,
240         .get_lineout            = pmf_gpio_get_lineout,
241         .set_notify             = pmf_set_notify,
242         .get_detect             = pmf_get_detect,
243 };
244
245 struct gpio_methods *pmf_gpio_methods = &methods;
246 EXPORT_SYMBOL_GPL(pmf_gpio_methods);