aboutsummaryrefslogtreecommitdiffstats
path: root/sound/ppc/toonie.c
diff options
context:
space:
mode:
authorBenjamin Herrenschmidt <benh@kernel.crashing.org>2005-05-01 11:58:43 -0400
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-05-01 11:58:43 -0400
commit1f7b49d042abfbda71f41b8aff6e1bf7685c1f00 (patch)
tree823c5b9d728fe11c42c9449b14f379164dd69e72 /sound/ppc/toonie.c
parent4be8dc7ff69182610b40a078b9815bcdf27e0c49 (diff)
[PATCH] ppc32: add sound support for Mac Mini
This patch applies on top of my previous g5 related sound patches and adds support for the Mac Mini to the PowerMac Alsa driver. However, I haven't found any kind of HW support for volume control on this machine. If it exist, it's well hidden. That means that you probably want to make sure you use software with the ability to do soft volume control, or use Alsa 0.9 pre-release with the softvol plugin. Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'sound/ppc/toonie.c')
-rw-r--r--sound/ppc/toonie.c380
1 files changed, 380 insertions, 0 deletions
diff --git a/sound/ppc/toonie.c b/sound/ppc/toonie.c
new file mode 100644
index 000000000000..0f909193b4fb
--- /dev/null
+++ b/sound/ppc/toonie.c
@@ -0,0 +1,380 @@
1/*
2 * Mac Mini "toonie" mixer control
3 *
4 * Copyright (c) 2005 by Benjamin Herrenschmidt <benh@kernel.crashing.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21#include <sound/driver.h>
22#include <linux/init.h>
23#include <linux/delay.h>
24#include <linux/i2c.h>
25#include <linux/i2c-dev.h>
26#include <linux/kmod.h>
27#include <linux/slab.h>
28#include <linux/interrupt.h>
29#include <sound/core.h>
30#include <asm/io.h>
31#include <asm/irq.h>
32#include <asm/machdep.h>
33#include <asm/pmac_feature.h>
34#include "pmac.h"
35
36#undef DEBUG
37
38#ifdef DEBUG
39#define DBG(fmt...) printk(fmt)
40#else
41#define DBG(fmt...)
42#endif
43
44struct pmac_gpio {
45 unsigned int addr;
46 u8 active_val;
47 u8 inactive_val;
48 u8 active_state;
49};
50
51struct pmac_toonie
52{
53 struct pmac_gpio hp_detect_gpio;
54 struct pmac_gpio hp_mute_gpio;
55 struct pmac_gpio amp_mute_gpio;
56 int hp_detect_irq;
57 int auto_mute_notify;
58 struct work_struct detect_work;
59};
60
61
62/*
63 * gpio access
64 */
65#define do_gpio_write(gp, val) \
66 pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, (gp)->addr, val)
67#define do_gpio_read(gp) \
68 pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, (gp)->addr, 0)
69#define tumbler_gpio_free(gp) /* NOP */
70
71static void write_audio_gpio(struct pmac_gpio *gp, int active)
72{
73 if (! gp->addr)
74 return;
75 active = active ? gp->active_val : gp->inactive_val;
76 do_gpio_write(gp, active);
77 DBG("(I) gpio %x write %d\n", gp->addr, active);
78}
79
80static int check_audio_gpio(struct pmac_gpio *gp)
81{
82 int ret;
83
84 if (! gp->addr)
85 return 0;
86
87 ret = do_gpio_read(gp);
88
89 return (ret & 0xd) == (gp->active_val & 0xd);
90}
91
92static int read_audio_gpio(struct pmac_gpio *gp)
93{
94 int ret;
95 if (! gp->addr)
96 return 0;
97 ret = ((do_gpio_read(gp) & 0x02) !=0);
98 return ret == gp->active_state;
99}
100
101
102enum { TOONIE_MUTE_HP, TOONIE_MUTE_AMP };
103
104static int toonie_get_mute_switch(snd_kcontrol_t *kcontrol,
105 snd_ctl_elem_value_t *ucontrol)
106{
107 pmac_t *chip = snd_kcontrol_chip(kcontrol);
108 struct pmac_toonie *mix = chip->mixer_data;
109 struct pmac_gpio *gp;
110
111 if (mix == NULL)
112 return -ENODEV;
113 switch(kcontrol->private_value) {
114 case TOONIE_MUTE_HP:
115 gp = &mix->hp_mute_gpio;
116 break;
117 case TOONIE_MUTE_AMP:
118 gp = &mix->amp_mute_gpio;
119 break;
120 default:
121 return -EINVAL;;
122 }
123 ucontrol->value.integer.value[0] = !check_audio_gpio(gp);
124 return 0;
125}
126
127static int toonie_put_mute_switch(snd_kcontrol_t *kcontrol,
128 snd_ctl_elem_value_t *ucontrol)
129{
130 pmac_t *chip = snd_kcontrol_chip(kcontrol);
131 struct pmac_toonie *mix = chip->mixer_data;
132 struct pmac_gpio *gp;
133 int val;
134
135 if (chip->update_automute && chip->auto_mute)
136 return 0; /* don't touch in the auto-mute mode */
137
138 if (mix == NULL)
139 return -ENODEV;
140
141 switch(kcontrol->private_value) {
142 case TOONIE_MUTE_HP:
143 gp = &mix->hp_mute_gpio;
144 break;
145 case TOONIE_MUTE_AMP:
146 gp = &mix->amp_mute_gpio;
147 break;
148 default:
149 return -EINVAL;;
150 }
151 val = ! check_audio_gpio(gp);
152 if (val != ucontrol->value.integer.value[0]) {
153 write_audio_gpio(gp, ! ucontrol->value.integer.value[0]);
154 return 1;
155 }
156 return 0;
157}
158
159static snd_kcontrol_new_t toonie_hp_sw __initdata = {
160 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
161 .name = "Headphone Playback Switch",
162 .info = snd_pmac_boolean_mono_info,
163 .get = toonie_get_mute_switch,
164 .put = toonie_put_mute_switch,
165 .private_value = TOONIE_MUTE_HP,
166};
167static snd_kcontrol_new_t toonie_speaker_sw __initdata = {
168 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
169 .name = "PC Speaker Playback Switch",
170 .info = snd_pmac_boolean_mono_info,
171 .get = toonie_get_mute_switch,
172 .put = toonie_put_mute_switch,
173 .private_value = TOONIE_MUTE_AMP,
174};
175
176/*
177 * auto-mute stuffs
178 */
179static int toonie_detect_headphone(pmac_t *chip)
180{
181 struct pmac_toonie *mix = chip->mixer_data;
182 int detect = 0;
183
184 if (mix->hp_detect_gpio.addr)
185 detect |= read_audio_gpio(&mix->hp_detect_gpio);
186 return detect;
187}
188
189static void toonie_check_mute(pmac_t *chip, struct pmac_gpio *gp, int val,
190 int do_notify, snd_kcontrol_t *sw)
191{
192 if (check_audio_gpio(gp) != val) {
193 write_audio_gpio(gp, val);
194 if (do_notify)
195 snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
196 &sw->id);
197 }
198}
199
200static void toonie_detect_handler(void *self)
201{
202 pmac_t *chip = (pmac_t*) self;
203 struct pmac_toonie *mix;
204 int headphone;
205
206 if (!chip)
207 return;
208
209 mix = chip->mixer_data;
210 snd_assert(mix, return);
211
212 headphone = toonie_detect_headphone(chip);
213
214 DBG("headphone: %d, lineout: %d\n", headphone, lineout);
215
216 if (headphone) {
217 /* unmute headphone/lineout & mute speaker */
218 toonie_check_mute(chip, &mix->hp_mute_gpio, 0,
219 mix->auto_mute_notify, chip->master_sw_ctl);
220 toonie_check_mute(chip, &mix->amp_mute_gpio, 1,
221 mix->auto_mute_notify, chip->speaker_sw_ctl);
222 } else {
223 /* unmute speaker, mute others */
224 toonie_check_mute(chip, &mix->amp_mute_gpio, 0,
225 mix->auto_mute_notify, chip->speaker_sw_ctl);
226 toonie_check_mute(chip, &mix->hp_mute_gpio, 1,
227 mix->auto_mute_notify, chip->master_sw_ctl);
228 }
229 if (mix->auto_mute_notify) {
230 snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
231 &chip->hp_detect_ctl->id);
232 }
233}
234
235static void toonie_update_automute(pmac_t *chip, int do_notify)
236{
237 if (chip->auto_mute) {
238 struct pmac_toonie *mix;
239 mix = chip->mixer_data;
240 snd_assert(mix, return);
241 mix->auto_mute_notify = do_notify;
242 schedule_work(&mix->detect_work);
243 }
244}
245
246/* interrupt - headphone plug changed */
247static irqreturn_t toonie_hp_intr(int irq, void *devid, struct pt_regs *regs)
248{
249 pmac_t *chip = devid;
250
251 if (chip->update_automute && chip->initialized) {
252 chip->update_automute(chip, 1);
253 return IRQ_HANDLED;
254 }
255 return IRQ_NONE;
256}
257
258/* look for audio gpio device */
259static int find_audio_gpio(const char *name, const char *platform,
260 struct pmac_gpio *gp)
261{
262 struct device_node *np;
263 u32 *base, addr;
264
265 if (! (np = find_devices("gpio")))
266 return -ENODEV;
267
268 for (np = np->child; np; np = np->sibling) {
269 char *property = get_property(np, "audio-gpio", NULL);
270 if (property && strcmp(property, name) == 0)
271 break;
272 if (device_is_compatible(np, name))
273 break;
274 }
275 if (np == NULL)
276 return -ENODEV;
277
278 base = (u32 *)get_property(np, "AAPL,address", NULL);
279 if (! base) {
280 base = (u32 *)get_property(np, "reg", NULL);
281 if (!base) {
282 DBG("(E) cannot find address for device %s !\n", device);
283 snd_printd("cannot find address for device %s\n", device);
284 return -ENODEV;
285 }
286 addr = *base;
287 if (addr < 0x50)
288 addr += 0x50;
289 } else
290 addr = *base;
291
292 gp->addr = addr & 0x0000ffff;
293
294 /* Try to find the active state, default to 0 ! */
295 base = (u32 *)get_property(np, "audio-gpio-active-state", NULL);
296 if (base) {
297 gp->active_state = *base;
298 gp->active_val = (*base) ? 0x5 : 0x4;
299 gp->inactive_val = (*base) ? 0x4 : 0x5;
300 } else {
301 u32 *prop = NULL;
302 gp->active_state = 0;
303 gp->active_val = 0x4;
304 gp->inactive_val = 0x5;
305 /* Here are some crude hacks to extract the GPIO polarity and
306 * open collector informations out of the do-platform script
307 * as we don't yet have an interpreter for these things
308 */
309 if (platform)
310 prop = (u32 *)get_property(np, platform, NULL);
311 if (prop) {
312 if (prop[3] == 0x9 && prop[4] == 0x9) {
313 gp->active_val = 0xd;
314 gp->inactive_val = 0xc;
315 }
316 if (prop[3] == 0x1 && prop[4] == 0x1) {
317 gp->active_val = 0x5;
318 gp->inactive_val = 0x4;
319 }
320 }
321 }
322
323 DBG("(I) GPIO device %s found, offset: %x, active state: %d !\n",
324 device, gp->addr, gp->active_state);
325
326 return (np->n_intrs > 0) ? np->intrs[0].line : 0;
327}
328
329static void toonie_cleanup(pmac_t *chip)
330{
331 struct pmac_toonie *mix = chip->mixer_data;
332 if (! mix)
333 return;
334 if (mix->hp_detect_irq >= 0)
335 free_irq(mix->hp_detect_irq, chip);
336 kfree(mix);
337 chip->mixer_data = NULL;
338}
339
340int snd_pmac_toonie_init(pmac_t *chip)
341{
342 struct pmac_toonie *mix;
343
344 mix = kmalloc(sizeof(*mix), GFP_KERNEL);
345 if (! mix)
346 return -ENOMEM;
347
348 chip->mixer_data = mix;
349 chip->mixer_free = toonie_cleanup;
350
351 find_audio_gpio("headphone-mute", NULL, &mix->hp_mute_gpio);
352 find_audio_gpio("amp-mute", NULL, &mix->amp_mute_gpio);
353 mix->hp_detect_irq = find_audio_gpio("headphone-detect",
354 NULL, &mix->hp_detect_gpio);
355
356 strcpy(chip->card->mixername, "PowerMac Toonie");
357
358 chip->master_sw_ctl = snd_ctl_new1(&toonie_hp_sw, chip);
359 snd_ctl_add(chip->card, chip->master_sw_ctl);
360
361 chip->speaker_sw_ctl = snd_ctl_new1(&toonie_speaker_sw, chip);
362 snd_ctl_add(chip->card, chip->speaker_sw_ctl);
363
364 INIT_WORK(&mix->detect_work, toonie_detect_handler, (void *)chip);
365
366 if (mix->hp_detect_irq >= 0) {
367 snd_pmac_add_automute(chip);
368
369 chip->detect_headphone = toonie_detect_headphone;
370 chip->update_automute = toonie_update_automute;
371 toonie_update_automute(chip, 0);
372
373 if (request_irq(mix->hp_detect_irq, toonie_hp_intr, 0,
374 "Sound Headphone Detection", chip) < 0)
375 mix->hp_detect_irq = -1;
376 }
377
378 return 0;
379}
380