diff options
Diffstat (limited to 'sound/ppc')
-rw-r--r-- | sound/ppc/Kconfig | 23 | ||||
-rw-r--r-- | sound/ppc/Makefile | 9 | ||||
-rw-r--r-- | sound/ppc/awacs.c | 903 | ||||
-rw-r--r-- | sound/ppc/awacs.h | 192 | ||||
-rw-r--r-- | sound/ppc/beep.c | 262 | ||||
-rw-r--r-- | sound/ppc/burgundy.c | 439 | ||||
-rw-r--r-- | sound/ppc/burgundy.h | 95 | ||||
-rw-r--r-- | sound/ppc/daca.c | 283 | ||||
-rw-r--r-- | sound/ppc/keywest.c | 143 | ||||
-rw-r--r-- | sound/ppc/pmac.c | 1328 | ||||
-rw-r--r-- | sound/ppc/pmac.h | 214 | ||||
-rw-r--r-- | sound/ppc/powermac.c | 159 | ||||
-rw-r--r-- | sound/ppc/tumbler.c | 1175 | ||||
-rw-r--r-- | sound/ppc/tumbler_volume.h | 250 |
14 files changed, 5475 insertions, 0 deletions
diff --git a/sound/ppc/Kconfig b/sound/ppc/Kconfig new file mode 100644 index 00000000000..b0a9ebf8bf3 --- /dev/null +++ b/sound/ppc/Kconfig @@ -0,0 +1,23 @@ +# ALSA PowerMac drivers + +menu "ALSA PowerMac devices" + depends on SND!=n && PPC + +comment "ALSA PowerMac requires I2C" + depends on SND && I2C=n + +comment "ALSA PowerMac requires INPUT" + depends on SND && INPUT=n + +config SND_POWERMAC + tristate "PowerMac (AWACS, DACA, Burgundy, Tumbler, Keywest)" + depends on SND && I2C && INPUT + select SND_PCM + help + Say Y here to include support for the integrated sound device. + + To compile this driver as a module, choose M here: the module + will be called snd-powermac. + +endmenu + diff --git a/sound/ppc/Makefile b/sound/ppc/Makefile new file mode 100644 index 00000000000..4d95c652c8c --- /dev/null +++ b/sound/ppc/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz> +# + +snd-powermac-objs := powermac.o pmac.o awacs.o burgundy.o daca.o tumbler.o keywest.o beep.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_POWERMAC) += snd-powermac.o diff --git a/sound/ppc/awacs.c b/sound/ppc/awacs.c new file mode 100644 index 00000000000..e052bd071e5 --- /dev/null +++ b/sound/ppc/awacs.c @@ -0,0 +1,903 @@ +/* + * PMac AWACS lowlevel functions + * + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * code based on dmasound.c. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include <sound/driver.h> +#include <asm/io.h> +#include <asm/nvram.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <sound/core.h> +#include "pmac.h" + + +#ifdef CONFIG_ADB_CUDA +#define PMAC_AMP_AVAIL +#endif + +#ifdef PMAC_AMP_AVAIL +typedef struct awacs_amp { + unsigned char amp_master; + unsigned char amp_vol[2][2]; + unsigned char amp_tone[2]; +} awacs_amp_t; + +#define CHECK_CUDA_AMP() (sys_ctrler == SYS_CTRLER_CUDA) + +#endif /* PMAC_AMP_AVAIL */ + + +static void snd_pmac_screamer_wait(pmac_t *chip) +{ + long timeout = 2000; + while (!(in_le32(&chip->awacs->codec_stat) & MASK_VALID)) { + mdelay(1); + if (! --timeout) { + snd_printd("snd_pmac_screamer_wait timeout\n"); + break; + } + } +} + +/* + * write AWACS register + */ +static void +snd_pmac_awacs_write(pmac_t *chip, int val) +{ + long timeout = 5000000; + + if (chip->model == PMAC_SCREAMER) + snd_pmac_screamer_wait(chip); + out_le32(&chip->awacs->codec_ctrl, val | (chip->subframe << 22)); + while (in_le32(&chip->awacs->codec_ctrl) & MASK_NEWECMD) { + if (! --timeout) { + snd_printd("snd_pmac_awacs_write timeout\n"); + break; + } + } +} + +static void +snd_pmac_awacs_write_reg(pmac_t *chip, int reg, int val) +{ + snd_pmac_awacs_write(chip, val | (reg << 12)); + chip->awacs_reg[reg] = val; +} + +static void +snd_pmac_awacs_write_noreg(pmac_t *chip, int reg, int val) +{ + snd_pmac_awacs_write(chip, val | (reg << 12)); +} + +#ifdef CONFIG_PMAC_PBOOK +/* Recalibrate chip */ +static void screamer_recalibrate(pmac_t *chip) +{ + if (chip->model != PMAC_SCREAMER) + return; + + /* Sorry for the horrible delays... I hope to get that improved + * by making the whole PM process asynchronous in a future version + */ + snd_pmac_awacs_write_noreg(chip, 1, chip->awacs_reg[1]); + if (chip->manufacturer == 0x1) + /* delay for broken crystal part */ + big_mdelay(750); + snd_pmac_awacs_write_noreg(chip, 1, + chip->awacs_reg[1] | MASK_RECALIBRATE | MASK_CMUTE | MASK_AMUTE); + snd_pmac_awacs_write_noreg(chip, 1, chip->awacs_reg[1]); + snd_pmac_awacs_write_noreg(chip, 6, chip->awacs_reg[6]); +} + +#else +#define screamer_recalibrate(chip) /* NOP */ +#endif + + +/* + * additional callback to set the pcm format + */ +static void snd_pmac_awacs_set_format(pmac_t *chip) +{ + chip->awacs_reg[1] &= ~MASK_SAMPLERATE; + chip->awacs_reg[1] |= chip->rate_index << 3; + snd_pmac_awacs_write_reg(chip, 1, chip->awacs_reg[1]); +} + + +/* + * AWACS volume callbacks + */ +/* + * volumes: 0-15 stereo + */ +static int snd_pmac_awacs_info_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 15; + return 0; +} + +static int snd_pmac_awacs_get_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int lshift = (kcontrol->private_value >> 8) & 0xff; + int inverted = (kcontrol->private_value >> 16) & 1; + unsigned long flags; + int vol[2]; + + spin_lock_irqsave(&chip->reg_lock, flags); + vol[0] = (chip->awacs_reg[reg] >> lshift) & 0xf; + vol[1] = chip->awacs_reg[reg] & 0xf; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (inverted) { + vol[0] = 0x0f - vol[0]; + vol[1] = 0x0f - vol[1]; + } + ucontrol->value.integer.value[0] = vol[0]; + ucontrol->value.integer.value[1] = vol[1]; + return 0; +} + +static int snd_pmac_awacs_put_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int lshift = (kcontrol->private_value >> 8) & 0xff; + int inverted = (kcontrol->private_value >> 16) & 1; + int val, oldval; + unsigned long flags; + int vol[2]; + + vol[0] = ucontrol->value.integer.value[0]; + vol[1] = ucontrol->value.integer.value[1]; + if (inverted) { + vol[0] = 0x0f - vol[0]; + vol[1] = 0x0f - vol[1]; + } + vol[0] &= 0x0f; + vol[1] &= 0x0f; + spin_lock_irqsave(&chip->reg_lock, flags); + oldval = chip->awacs_reg[reg]; + val = oldval & ~(0xf | (0xf << lshift)); + val |= vol[0] << lshift; + val |= vol[1]; + if (oldval != val) + snd_pmac_awacs_write_reg(chip, reg, val); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return oldval != reg; +} + + +#define AWACS_VOLUME(xname, xreg, xshift, xinverted) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \ + .info = snd_pmac_awacs_info_volume, \ + .get = snd_pmac_awacs_get_volume, \ + .put = snd_pmac_awacs_put_volume, \ + .private_value = (xreg) | ((xshift) << 8) | ((xinverted) << 16) } + +/* + * mute master/ogain for AWACS: mono + */ +static int snd_pmac_awacs_get_switch(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int invert = (kcontrol->private_value >> 16) & 1; + int val; + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + val = (chip->awacs_reg[reg] >> shift) & 1; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (invert) + val = 1 - val; + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int snd_pmac_awacs_put_switch(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int invert = (kcontrol->private_value >> 16) & 1; + int mask = 1 << shift; + int val, changed; + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + val = chip->awacs_reg[reg] & ~mask; + if (ucontrol->value.integer.value[0] != invert) + val |= mask; + changed = chip->awacs_reg[reg] != val; + if (changed) + snd_pmac_awacs_write_reg(chip, reg, val); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return changed; +} + +#define AWACS_SWITCH(xname, xreg, xshift, xinvert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \ + .info = snd_pmac_boolean_mono_info, \ + .get = snd_pmac_awacs_get_switch, \ + .put = snd_pmac_awacs_put_switch, \ + .private_value = (xreg) | ((xshift) << 8) | ((xinvert) << 16) } + + +#ifdef PMAC_AMP_AVAIL +/* + * controls for perch/whisper extension cards, e.g. G3 desktop + * + * TDA7433 connected via i2c address 0x45 (= 0x8a), + * accessed through cuda + */ +static void awacs_set_cuda(int reg, int val) +{ + struct adb_request req; + cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC, 0x8a, reg, val); + while (! req.complete) + cuda_poll(); +} + +/* + * level = 0 - 14, 7 = 0 dB + */ +static void awacs_amp_set_tone(awacs_amp_t *amp, int bass, int treble) +{ + amp->amp_tone[0] = bass; + amp->amp_tone[1] = treble; + if (bass > 7) + bass = (14 - bass) + 8; + if (treble > 7) + treble = (14 - treble) + 8; + awacs_set_cuda(2, (bass << 4) | treble); +} + +/* + * vol = 0 - 31 (attenuation), 32 = mute bit, stereo + */ +static int awacs_amp_set_vol(awacs_amp_t *amp, int index, int lvol, int rvol, int do_check) +{ + if (do_check && amp->amp_vol[index][0] == lvol && + amp->amp_vol[index][1] == rvol) + return 0; + awacs_set_cuda(3 + index, lvol); + awacs_set_cuda(5 + index, rvol); + amp->amp_vol[index][0] = lvol; + amp->amp_vol[index][1] = rvol; + return 1; +} + +/* + * 0 = -79 dB, 79 = 0 dB, 99 = +20 dB + */ +static void awacs_amp_set_master(awacs_amp_t *amp, int vol) +{ + amp->amp_master = vol; + if (vol <= 79) + vol = 32 + (79 - vol); + else + vol = 32 - (vol - 79); + awacs_set_cuda(1, vol); +} + +static void awacs_amp_free(pmac_t *chip) +{ + awacs_amp_t *amp = chip->mixer_data; + snd_assert(amp, return); + kfree(amp); + chip->mixer_data = NULL; + chip->mixer_free = NULL; +} + + +/* + * mixer controls + */ +static int snd_pmac_awacs_info_volume_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 31; + return 0; +} + +static int snd_pmac_awacs_get_volume_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + awacs_amp_t *amp = chip->mixer_data; + snd_assert(amp, return -EINVAL); + snd_assert(index >= 0 && index <= 1, return -EINVAL); + ucontrol->value.integer.value[0] = 31 - (amp->amp_vol[index][0] & 31); + ucontrol->value.integer.value[1] = 31 - (amp->amp_vol[index][1] & 31); + return 0; +} + +static int snd_pmac_awacs_put_volume_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + int vol[2]; + awacs_amp_t *amp = chip->mixer_data; + snd_assert(amp, return -EINVAL); + snd_assert(index >= 0 && index <= 1, return -EINVAL); + + vol[0] = (31 - (ucontrol->value.integer.value[0] & 31)) | (amp->amp_vol[index][0] & 32); + vol[1] = (31 - (ucontrol->value.integer.value[1] & 31)) | (amp->amp_vol[index][1] & 32); + return awacs_amp_set_vol(amp, index, vol[0], vol[1], 1); +} + +static int snd_pmac_awacs_get_switch_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + awacs_amp_t *amp = chip->mixer_data; + snd_assert(amp, return -EINVAL); + snd_assert(index >= 0 && index <= 1, return -EINVAL); + ucontrol->value.integer.value[0] = (amp->amp_vol[index][0] & 32) ? 0 : 1; + ucontrol->value.integer.value[1] = (amp->amp_vol[index][1] & 32) ? 0 : 1; + return 0; +} + +static int snd_pmac_awacs_put_switch_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + int vol[2]; + awacs_amp_t *amp = chip->mixer_data; + snd_assert(amp, return -EINVAL); + snd_assert(index >= 0 && index <= 1, return -EINVAL); + + vol[0] = (ucontrol->value.integer.value[0] ? 0 : 32) | (amp->amp_vol[index][0] & 31); + vol[1] = (ucontrol->value.integer.value[1] ? 0 : 32) | (amp->amp_vol[index][1] & 31); + return awacs_amp_set_vol(amp, index, vol[0], vol[1], 1); +} + +static int snd_pmac_awacs_info_tone_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 14; + return 0; +} + +static int snd_pmac_awacs_get_tone_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + awacs_amp_t *amp = chip->mixer_data; + snd_assert(amp, return -EINVAL); + snd_assert(index >= 0 && index <= 1, return -EINVAL); + ucontrol->value.integer.value[0] = amp->amp_tone[index]; + return 0; +} + +static int snd_pmac_awacs_put_tone_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + awacs_amp_t *amp = chip->mixer_data; + snd_assert(amp, return -EINVAL); + snd_assert(index >= 0 && index <= 1, return -EINVAL); + if (ucontrol->value.integer.value[0] != amp->amp_tone[index]) { + amp->amp_tone[index] = ucontrol->value.integer.value[0]; + awacs_amp_set_tone(amp, amp->amp_tone[0], amp->amp_tone[1]); + return 1; + } + return 0; +} + +static int snd_pmac_awacs_info_master_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 99; + return 0; +} + +static int snd_pmac_awacs_get_master_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + awacs_amp_t *amp = chip->mixer_data; + snd_assert(amp, return -EINVAL); + ucontrol->value.integer.value[0] = amp->amp_master; + return 0; +} + +static int snd_pmac_awacs_put_master_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + awacs_amp_t *amp = chip->mixer_data; + snd_assert(amp, return -EINVAL); + if (ucontrol->value.integer.value[0] != amp->amp_master) { + amp->amp_master = ucontrol->value.integer.value[0]; + awacs_amp_set_master(amp, amp->amp_master); + return 1; + } + return 0; +} + +#define AMP_CH_SPK 0 +#define AMP_CH_HD 1 + +static snd_kcontrol_new_t snd_pmac_awacs_amp_vol[] __initdata = { + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PC Speaker Playback Volume", + .info = snd_pmac_awacs_info_volume_amp, + .get = snd_pmac_awacs_get_volume_amp, + .put = snd_pmac_awacs_put_volume_amp, + .private_value = AMP_CH_SPK, + }, + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Playback Volume", + .info = snd_pmac_awacs_info_volume_amp, + .get = snd_pmac_awacs_get_volume_amp, + .put = snd_pmac_awacs_put_volume_amp, + .private_value = AMP_CH_HD, + }, + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Tone Control - Bass", + .info = snd_pmac_awacs_info_tone_amp, + .get = snd_pmac_awacs_get_tone_amp, + .put = snd_pmac_awacs_put_tone_amp, + .private_value = 0, + }, + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Tone Control - Treble", + .info = snd_pmac_awacs_info_tone_amp, + .get = snd_pmac_awacs_get_tone_amp, + .put = snd_pmac_awacs_put_tone_amp, + .private_value = 1, + }, + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Amp Master Playback Volume", + .info = snd_pmac_awacs_info_master_amp, + .get = snd_pmac_awacs_get_master_amp, + .put = snd_pmac_awacs_put_master_amp, + }, +}; + +static snd_kcontrol_new_t snd_pmac_awacs_amp_hp_sw __initdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Playback Switch", + .info = snd_pmac_boolean_stereo_info, + .get = snd_pmac_awacs_get_switch_amp, + .put = snd_pmac_awacs_put_switch_amp, + .private_value = AMP_CH_HD, +}; + +static snd_kcontrol_new_t snd_pmac_awacs_amp_spk_sw __initdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PC Speaker Playback Switch", + .info = snd_pmac_boolean_stereo_info, + .get = snd_pmac_awacs_get_switch_amp, + .put = snd_pmac_awacs_put_switch_amp, + .private_value = AMP_CH_SPK, +}; + +#endif /* PMAC_AMP_AVAIL */ + + +/* + * mic boost for screamer + */ +static int snd_pmac_screamer_mic_boost_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 2; + return 0; +} + +static int snd_pmac_screamer_mic_boost_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + int val; + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + if (chip->awacs_reg[6] & MASK_MIC_BOOST) + val = 2; + else if (chip->awacs_reg[0] & MASK_GAINLINE) + val = 1; + else + val = 0; + spin_unlock_irqrestore(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int snd_pmac_screamer_mic_boost_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int val0, val6; + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + val0 = chip->awacs_reg[0] & ~MASK_GAINLINE; + val6 = chip->awacs_reg[6] & ~MASK_MIC_BOOST; + if (ucontrol->value.integer.value[0] > 0) { + val0 |= MASK_GAINLINE; + if (ucontrol->value.integer.value[0] > 1) + val6 |= MASK_MIC_BOOST; + } + if (val0 != chip->awacs_reg[0]) { + snd_pmac_awacs_write_reg(chip, 0, val0); + changed = 1; + } + if (val6 != chip->awacs_reg[6]) { + snd_pmac_awacs_write_reg(chip, 6, val6); + changed = 1; + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + return changed; +} + +/* + * lists of mixer elements + */ +static snd_kcontrol_new_t snd_pmac_awacs_mixers[] __initdata = { + AWACS_VOLUME("Master Playback Volume", 2, 6, 1), + AWACS_SWITCH("Master Capture Switch", 1, SHIFT_LOOPTHRU, 0), + AWACS_VOLUME("Capture Volume", 0, 4, 0), + AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0), +}; + +/* FIXME: is this correct order? + * screamer (powerbook G3 pismo) seems to have different bits... + */ +static snd_kcontrol_new_t snd_pmac_awacs_mixers2[] __initdata = { + AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_LINE, 0), + AWACS_SWITCH("Mic Capture Switch", 0, SHIFT_MUX_MIC, 0), +}; + +static snd_kcontrol_new_t snd_pmac_screamer_mixers2[] __initdata = { + AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_MIC, 0), + AWACS_SWITCH("Mic Capture Switch", 0, SHIFT_MUX_LINE, 0), +}; + +static snd_kcontrol_new_t snd_pmac_awacs_master_sw __initdata = +AWACS_SWITCH("Master Playback Switch", 1, SHIFT_HDMUTE, 1); + +static snd_kcontrol_new_t snd_pmac_awacs_mic_boost[] __initdata = { + AWACS_SWITCH("Mic Boost", 0, SHIFT_GAINLINE, 0), +}; + +static snd_kcontrol_new_t snd_pmac_screamer_mic_boost[] __initdata = { + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Boost", + .info = snd_pmac_screamer_mic_boost_info, + .get = snd_pmac_screamer_mic_boost_get, + .put = snd_pmac_screamer_mic_boost_put, + }, +}; + +static snd_kcontrol_new_t snd_pmac_awacs_speaker_vol[] __initdata = { + AWACS_VOLUME("PC Speaker Playback Volume", 4, 6, 1), +}; +static snd_kcontrol_new_t snd_pmac_awacs_speaker_sw __initdata = +AWACS_SWITCH("PC Speaker Playback Switch", 1, SHIFT_SPKMUTE, 1); + + +/* + * add new mixer elements to the card + */ +static int build_mixers(pmac_t *chip, int nums, snd_kcontrol_new_t *mixers) +{ + int i, err; + + for (i = 0; i < nums; i++) { + if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&mixers[i], chip))) < 0) + return err; + } + return 0; +} + + +/* + * restore all registers + */ +static void awacs_restore_all_regs(pmac_t *chip) +{ + snd_pmac_awacs_write_noreg(chip, 0, chip->awacs_reg[0]); + snd_pmac_awacs_write_noreg(chip, 1, chip->awacs_reg[1]); + snd_pmac_awacs_write_noreg(chip, 2, chip->awacs_reg[2]); + snd_pmac_awacs_write_noreg(chip, 4, chip->awacs_reg[4]); + if (chip->model == PMAC_SCREAMER) { + snd_pmac_awacs_write_noreg(chip, 5, chip->awacs_reg[5]); + snd_pmac_awacs_write_noreg(chip, 6, chip->awacs_reg[6]); + snd_pmac_awacs_write_noreg(chip, 7, chip->awacs_reg[7]); + } +} + +#ifdef CONFIG_PMAC_PBOOK +static void snd_pmac_awacs_suspend(pmac_t *chip) +{ + snd_pmac_awacs_write_noreg(chip, 1, (chip->awacs_reg[1] + | MASK_AMUTE | MASK_CMUTE)); +} + +static void snd_pmac_awacs_resume(pmac_t *chip) +{ + if (machine_is_compatible("PowerBook3,1") + || machine_is_compatible("PowerBook3,2")) { + big_mdelay(100); + snd_pmac_awacs_write_reg(chip, 1, + chip->awacs_reg[1] & ~MASK_PAROUT); + big_mdelay(300); + } + + awacs_restore_all_regs(chip); + if (chip->model == PMAC_SCREAMER) { + /* reset power bits in reg 6 */ + mdelay(5); + snd_pmac_awacs_write_noreg(chip, 6, chip->awacs_reg[6]); + } + screamer_recalibrate(chip); +#ifdef PMAC_AMP_AVAIL + if (chip->mixer_data) { + awacs_amp_t *amp = chip->mixer_data; + awacs_amp_set_vol(amp, 0, amp->amp_vol[0][0], amp->amp_vol[0][1], 0); + awacs_amp_set_vol(amp, 1, amp->amp_vol[1][0], amp->amp_vol[1][1], 0); + awacs_amp_set_tone(amp, amp->amp_tone[0], amp->amp_tone[1]); + awacs_amp_set_master(amp, amp->amp_master); + } +#endif +} +#endif /* CONFIG_PMAC_PBOOK */ + +#ifdef PMAC_SUPPORT_AUTOMUTE +/* + * auto-mute stuffs + */ +static int snd_pmac_awacs_detect_headphone(pmac_t *chip) +{ + return (in_le32(&chip->awacs->codec_stat) & chip->hp_stat_mask) ? 1 : 0; +} + +#ifdef PMAC_AMP_AVAIL +static int toggle_amp_mute(awacs_amp_t *amp, int index, int mute) +{ + int vol[2]; + vol[0] = amp->amp_vol[index][0] & 31; + vol[1] = amp->amp_vol[index][1] & 31; + if (mute) { + vol[0] |= 32; + vol[1] |= 32; + } + return awacs_amp_set_vol(amp, index, vol[0], vol[1], 1); +} +#endif + +static void snd_pmac_awacs_update_automute(pmac_t *chip, int do_notify) +{ + if (chip->auto_mute) { +#ifdef PMAC_AMP_AVAIL + if (chip->mixer_data) { + awacs_amp_t *amp = chip->mixer_data; + int changed; + if (snd_pmac_awacs_detect_headphone(chip)) { + changed = toggle_amp_mute(amp, AMP_CH_HD, 0); + changed |= toggle_amp_mute(amp, AMP_CH_SPK, 1); + } else { + changed = toggle_amp_mute(amp, AMP_CH_HD, 1); + changed |= toggle_amp_mute(amp, AMP_CH_SPK, 0); + } + if (do_notify && ! changed) + return; + } else +#endif + { + int reg = chip->awacs_reg[1] | (MASK_HDMUTE|MASK_SPKMUTE); + if (snd_pmac_awacs_detect_headphone(chip)) + reg &= ~MASK_HDMUTE; + else + reg &= ~MASK_SPKMUTE; + if (do_notify && reg == chip->awacs_reg[1]) + return; + snd_pmac_awacs_write_reg(chip, 1, reg); + } + if (do_notify) { + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->master_sw_ctl->id); + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->speaker_sw_ctl->id); + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->hp_detect_ctl->id); + } + } +} +#endif /* PMAC_SUPPORT_AUTOMUTE */ + + +/* + * initialize chip + */ +int __init +snd_pmac_awacs_init(pmac_t *chip) +{ + int err, vol; + + /* looks like MASK_GAINLINE triggers something, so we set here + * as start-up + */ + chip->awacs_reg[0] = MASK_MUX_CD | 0xff | MASK_GAINLINE; + chip->awacs_reg[1] = MASK_CMUTE | MASK_AMUTE; + /* FIXME: Only machines with external SRS module need MASK_PAROUT */ + if (chip->has_iic || chip->device_id == 0x5 || + /*chip->_device_id == 0x8 || */ + chip->device_id == 0xb) + chip->awacs_reg[1] |= MASK_PAROUT; + /* get default volume from nvram */ + // vol = (~nvram_read_byte(0x1308) & 7) << 1; + // vol = ((pmac_xpram_read( 8 ) & 7 ) << 1 ); + vol = 0x0f; /* no, on alsa, muted as default */ + vol = vol + (vol << 6); + chip->awacs_reg[2] = vol; + chip->awacs_reg[4] = vol; + if (chip->model == PMAC_SCREAMER) { + chip->awacs_reg[5] = vol; /* FIXME: screamer has loopthru vol control */ + chip->awacs_reg[6] = MASK_MIC_BOOST; /* FIXME: maybe should be vol << 3 for PCMCIA speaker */ + chip->awacs_reg[7] = 0; + } + + awacs_restore_all_regs(chip); + chip->manufacturer = (in_le32(&chip->awacs->codec_stat) >> 8) & 0xf; + screamer_recalibrate(chip); + + chip->revision = (in_le32(&chip->awacs->codec_stat) >> 12) & 0xf; +#ifdef PMAC_AMP_AVAIL + if (chip->revision == 3 && chip->has_iic && CHECK_CUDA_AMP()) { + awacs_amp_t *amp = kmalloc(sizeof(*amp), GFP_KERNEL); + if (! amp) + return -ENOMEM; + chip->mixer_data = amp; + memset(amp, 0, sizeof(*amp)); + chip->mixer_free = awacs_amp_free; + awacs_amp_set_vol(amp, 0, 63, 63, 0); /* mute and zero vol */ + awacs_amp_set_vol(amp, 1, 63, 63, 0); + awacs_amp_set_tone(amp, 7, 7); /* 0 dB */ + awacs_amp_set_master(amp, 79); /* 0 dB */ + } +#endif /* PMAC_AMP_AVAIL */ + + if (chip->hp_stat_mask == 0) { + /* set headphone-jack detection bit */ + switch (chip->model) { + case PMAC_AWACS: + chip->hp_stat_mask = 0x04; + break; + case PMAC_SCREAMER: + switch (chip->device_id) { + case 0x08: + /* 1 = side jack, 2 = front jack */ + chip->hp_stat_mask = 0x03; + break; + case 0x00: + case 0x05: + chip->hp_stat_mask = 0x04; + break; + default: + chip->hp_stat_mask = 0x08; + break; + } + break; + default: + snd_BUG(); + break; + } + } + + /* + * build mixers + */ + strcpy(chip->card->mixername, "PowerMac AWACS"); + + if ((err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_mixers), + snd_pmac_awacs_mixers)) < 0) + return err; + if (chip->model == PMAC_SCREAMER) + err = build_mixers(chip, ARRAY_SIZE(snd_pmac_screamer_mixers2), + snd_pmac_screamer_mixers2); + else + err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_mixers2), + snd_pmac_awacs_mixers2); + if (err < 0) + return err; + chip->master_sw_ctl = snd_ctl_new1(&snd_pmac_awacs_master_sw, chip); + if ((err = snd_ctl_add(chip->card, chip->master_sw_ctl)) < 0) + return err; +#ifdef PMAC_AMP_AVAIL + if (chip->mixer_data) { + /* use amplifier. the signal is connected from route A + * to the amp. the amp has its headphone and speaker + * volumes and mute switches, so we use them instead of + * screamer registers. + * in this case, it seems the route C is not used. + */ + if ((err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_amp_vol), + snd_pmac_awacs_amp_vol)) < 0) + return err; + /* overwrite */ + chip->master_sw_ctl = snd_ctl_new1(&snd_pmac_awacs_amp_hp_sw, chip); + if ((err = snd_ctl_add(chip->card, chip->master_sw_ctl)) < 0) + return err; + chip->speaker_sw_ctl = snd_ctl_new1(&snd_pmac_awacs_amp_spk_sw, chip); + if ((err = snd_ctl_add(chip->card, chip->speaker_sw_ctl)) < 0) + return err; + } else +#endif /* PMAC_AMP_AVAIL */ + { + /* route A = headphone, route C = speaker */ + if ((err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_speaker_vol), + snd_pmac_awacs_speaker_vol)) < 0) + return err; + chip->speaker_sw_ctl = snd_ctl_new1(&snd_pmac_awacs_speaker_sw, chip); + if ((err = snd_ctl_add(chip->card, chip->speaker_sw_ctl)) < 0) + return err; + } + + if (chip->model == PMAC_SCREAMER) { + if ((err = build_mixers(chip, ARRAY_SIZE(snd_pmac_screamer_mic_boost), + snd_pmac_screamer_mic_boost)) < 0) + return err; + } else { + if ((err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_mic_boost), + snd_pmac_awacs_mic_boost)) < 0) + return err; + } + + /* + * set lowlevel callbacks + */ + chip->set_format = snd_pmac_awacs_set_format; +#ifdef CONFIG_PMAC_PBOOK + chip->suspend = snd_pmac_awacs_suspend; + chip->resume = snd_pmac_awacs_resume; +#endif +#ifdef PMAC_SUPPORT_AUTOMUTE + if ((err = snd_pmac_add_automute(chip)) < 0) + return err; + chip->detect_headphone = snd_pmac_awacs_detect_headphone; + chip->update_automute = snd_pmac_awacs_update_automute; + snd_pmac_awacs_update_automute(chip, 0); /* update the status only */ +#endif + if (chip->model == PMAC_SCREAMER) { + snd_pmac_awacs_write_noreg(chip, 6, chip->awacs_reg[6]); + snd_pmac_awacs_write_noreg(chip, 0, chip->awacs_reg[0]); + } + + return 0; +} diff --git a/sound/ppc/awacs.h b/sound/ppc/awacs.h new file mode 100644 index 00000000000..1b2cc44eda5 --- /dev/null +++ b/sound/ppc/awacs.h @@ -0,0 +1,192 @@ +/* + * Driver for PowerMac AWACS onboard soundchips + * Copyright (c) 2001 by Takashi Iwai <tiwai@suse.de> + * based on dmasound.c. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef __AWACS_H +#define __AWACS_H + +/*******************************/ +/* AWACs Audio Register Layout */ +/*******************************/ + +struct awacs_regs { + unsigned control; /* Audio control register */ + unsigned pad0[3]; + unsigned codec_ctrl; /* Codec control register */ + unsigned pad1[3]; + unsigned codec_stat; /* Codec status register */ + unsigned pad2[3]; + unsigned clip_count; /* Clipping count register */ + unsigned pad3[3]; + unsigned byteswap; /* Data is little-endian if 1 */ +}; + +/*******************/ +/* Audio Bit Masks */ +/*******************/ + +/* Audio Control Reg Bit Masks */ +/* ----- ------- --- --- ----- */ +#define MASK_ISFSEL (0xf) /* Input SubFrame Select */ +#define MASK_OSFSEL (0xf << 4) /* Output SubFrame Select */ +#define MASK_RATE (0x7 << 8) /* Sound Rate */ +#define MASK_CNTLERR (0x1 << 11) /* Error */ +#define MASK_PORTCHG (0x1 << 12) /* Port Change */ +#define MASK_IEE (0x1 << 13) /* Enable Interrupt on Error */ +#define MASK_IEPC (0x1 << 14) /* Enable Interrupt on Port Change */ +#define MASK_SSFSEL (0x3 << 15) /* Status SubFrame Select */ + +/* Audio Codec Control Reg Bit Masks */ +/* ----- ----- ------- --- --- ----- */ +#define MASK_NEWECMD (0x1 << 24) /* Lock: don't write to reg when 1 */ +#define MASK_EMODESEL (0x3 << 22) /* Send info out on which frame? */ +#define MASK_EXMODEADDR (0x3ff << 12) /* Extended Mode Address -- 10 bits */ +#define MASK_EXMODEDATA (0xfff) /* Extended Mode Data -- 12 bits */ + +/* Audio Codec Control Address Values / Masks */ +/* ----- ----- ------- ------- ------ - ----- */ +#define MASK_ADDR0 (0x0 << 12) /* Expanded Data Mode Address 0 */ +#define MASK_ADDR_MUX MASK_ADDR0 /* Mux Control */ +#define MASK_ADDR_GAIN MASK_ADDR0 + +#define MASK_ADDR1 (0x1 << 12) /* Expanded Data Mode Address 1 */ +#define MASK_ADDR_MUTE MASK_ADDR1 +#define MASK_ADDR_RATE MASK_ADDR1 + +#define MASK_ADDR2 (0x2 << 12) /* Expanded Data Mode Address 2 */ +#define MASK_ADDR_VOLA MASK_ADDR2 /* Volume Control A -- Headphones */ +#define MASK_ADDR_VOLHD MASK_ADDR2 + +#define MASK_ADDR4 (0x4 << 12) /* Expanded Data Mode Address 4 */ +#define MASK_ADDR_VOLC MASK_ADDR4 /* Volume Control C -- Speaker */ +#define MASK_ADDR_VOLSPK MASK_ADDR4 + +/* additional registers of screamer */ +#define MASK_ADDR5 (0x5 << 12) /* Expanded Data Mode Address 5 */ +#define MASK_ADDR6 (0x6 << 12) /* Expanded Data Mode Address 6 */ +#define MASK_ADDR7 (0x7 << 12) /* Expanded Data Mode Address 7 */ + +/* Address 0 Bit Masks & Macros */ +/* ------- - --- ----- - ------ */ +#define MASK_GAINRIGHT (0xf) /* Gain Right Mask */ +#define MASK_GAINLEFT (0xf << 4) /* Gain Left Mask */ +#define MASK_GAINLINE (0x1 << 8) /* Disable Mic preamp */ +#define MASK_GAINMIC (0x0 << 8) /* Enable Mic preamp */ +#define MASK_MUX_CD (0x1 << 9) /* Select CD in MUX */ +#define MASK_MUX_MIC (0x1 << 10) /* Select Mic in MUX */ +#define MASK_MUX_AUDIN (0x1 << 11) /* Select Audio In in MUX */ +#define MASK_MUX_LINE MASK_MUX_AUDIN +#define SHIFT_GAINLINE 8 +#define SHIFT_MUX_CD 9 +#define SHIFT_MUX_MIC 10 +#define SHIFT_MUX_LINE 11 + +#define GAINRIGHT(x) ((x) & MASK_GAINRIGHT) +#define GAINLEFT(x) (((x) << 4) & MASK_GAINLEFT) + +/* Address 1 Bit Masks */ +/* ------- - --- ----- */ +#define MASK_ADDR1RES1 (0x3) /* Reserved */ +#define MASK_RECALIBRATE (0x1 << 2) /* Recalibrate */ +#define MASK_SAMPLERATE (0x7 << 3) /* Sample Rate: */ +#define MASK_LOOPTHRU (0x1 << 6) /* Loopthrough Enable */ +#define SHIFT_LOOPTHRU 6 +#define MASK_CMUTE (0x1 << 7) /* Output C (Speaker) Mute when 1 */ +#define MASK_SPKMUTE MASK_CMUTE +#define SHIFT_SPKMUTE 7 +#define MASK_ADDR1RES2 (0x1 << 8) /* Reserved */ +#define MASK_AMUTE (0x1 << 9) /* Output A (Headphone) Mute when 1 */ +#define MASK_HDMUTE MASK_AMUTE +#define SHIFT_HDMUTE 9 +#define MASK_PAROUT (0x3 << 10) /* Parallel Out (???) */ + +#define SAMPLERATE_48000 (0x0 << 3) /* 48 or 44.1 kHz */ +#define SAMPLERATE_32000 (0x1 << 3) /* 32 or 29.4 kHz */ +#define SAMPLERATE_24000 (0x2 << 3) /* 24 or 22.05 kHz */ +#define SAMPLERATE_19200 (0x3 << 3) /* 19.2 or 17.64 kHz */ +#define SAMPLERATE_16000 (0x4 << 3) /* 16 or 14.7 kHz */ +#define SAMPLERATE_12000 (0x5 << 3) /* 12 or 11.025 kHz */ +#define SAMPLERATE_9600 (0x6 << 3) /* 9.6 or 8.82 kHz */ +#define SAMPLERATE_8000 (0x7 << 3) /* 8 or 7.35 kHz */ + +/* Address 2 & 4 Bit Masks & Macros */ +/* ------- - - - --- ----- - ------ */ +#define MASK_OUTVOLRIGHT (0xf) /* Output Right Volume */ +#define MASK_ADDR2RES1 (0x2 << 4) /* Reserved */ +#define MASK_ADDR4RES1 MASK_ADDR2RES1 +#define MASK_OUTVOLLEFT (0xf << 6) /* Output Left Volume */ +#define MASK_ADDR2RES2 (0x2 << 10) /* Reserved */ +#define MASK_ADDR4RES2 MASK_ADDR2RES2 + +#define VOLRIGHT(x) (((~(x)) & MASK_OUTVOLRIGHT)) +#define VOLLEFT(x) (((~(x)) << 6) & MASK_OUTVOLLEFT) + +/* address 6 */ +#define MASK_MIC_BOOST (0x4) /* screamer mic boost */ +#define SHIFT_MIC_BOOST 2 + +/* Audio Codec Status Reg Bit Masks */ +/* ----- ----- ------ --- --- ----- */ +#define MASK_EXTEND (0x1 << 23) /* Extend */ +#define MASK_VALID (0x1 << 22) /* Valid Data? */ +#define MASK_OFLEFT (0x1 << 21) /* Overflow Left */ +#define MASK_OFRIGHT (0x1 << 20) /* Overflow Right */ +#define MASK_ERRCODE (0xf << 16) /* Error Code */ +#define MASK_REVISION (0xf << 12) /* Revision Number */ +#define MASK_MFGID (0xf << 8) /* Mfg. ID */ +#define MASK_CODSTATRES (0xf << 4) /* bits 4 - 7 reserved */ +#define MASK_INPPORT (0xf) /* Input Port */ +#define MASK_HDPCONN 8 /* headphone plugged in */ + +/* Clipping Count Reg Bit Masks */ +/* -------- ----- --- --- ----- */ +#define MASK_CLIPLEFT (0xff << 7) /* Clipping Count, Left Channel */ +#define MASK_CLIPRIGHT (0xff) /* Clipping Count, Right Channel */ + +/* DBDMA ChannelStatus Bit Masks */ +/* ----- ------------- --- ----- */ +#define MASK_CSERR (0x1 << 7) /* Error */ +#define MASK_EOI (0x1 << 6) /* End of Input -- only for Input Channel */ +#define MASK_CSUNUSED (0x1f << 1) /* bits 1-5 not used */ +#define MASK_WAIT (0x1) /* Wait */ + +/* Various Rates */ +/* ------- ----- */ +#define RATE_48000 (0x0 << 8) /* 48 kHz */ +#define RATE_44100 (0x0 << 8) /* 44.1 kHz */ +#define RATE_32000 (0x1 << 8) /* 32 kHz */ +#define RATE_29400 (0x1 << 8) /* 29.4 kHz */ +#define RATE_24000 (0x2 << 8) /* 24 kHz */ +#define RATE_22050 (0x2 << 8) /* 22.05 kHz */ +#define RATE_19200 (0x3 << 8) /* 19.2 kHz */ +#define RATE_17640 (0x3 << 8) /* 17.64 kHz */ +#define RATE_16000 (0x4 << 8) /* 16 kHz */ +#define RATE_14700 (0x4 << 8) /* 14.7 kHz */ +#define RATE_12000 (0x5 << 8) /* 12 kHz */ +#define RATE_11025 (0x5 << 8) /* 11.025 kHz */ +#define RATE_9600 (0x6 << 8) /* 9.6 kHz */ +#define RATE_8820 (0x6 << 8) /* 8.82 kHz */ +#define RATE_8000 (0x7 << 8) /* 8 kHz */ +#define RATE_7350 (0x7 << 8) /* 7.35 kHz */ + +#define RATE_LOW 1 /* HIGH = 48kHz, etc; LOW = 44.1kHz, etc. */ + + +#endif /* __AWACS_H */ diff --git a/sound/ppc/beep.c b/sound/ppc/beep.c new file mode 100644 index 00000000000..c23f601a37f --- /dev/null +++ b/sound/ppc/beep.c @@ -0,0 +1,262 @@ +/* + * Beep using pcm + * + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <sound/driver.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <sound/core.h> +#include <sound/control.h> +#include "pmac.h" + +struct snd_pmac_beep { + int running; /* boolean */ + int volume; /* mixer volume: 0-100 */ + int volume_play; /* currently playing volume */ + int hz; + int nsamples; + short *buf; /* allocated wave buffer */ + unsigned long addr; /* physical address of buffer */ + struct input_dev dev; +}; + +/* + * stop beep if running + */ +void snd_pmac_beep_stop(pmac_t *chip) +{ + pmac_beep_t *beep = chip->beep; + if (beep && beep->running) { + beep->running = 0; + snd_pmac_beep_dma_stop(chip); + } +} + +/* + * Stuff for outputting a beep. The values range from -327 to +327 + * so we can multiply by an amplitude in the range 0..100 to get a + * signed short value to put in the output buffer. + */ +static short beep_wform[256] = { + 0, 40, 79, 117, 153, 187, 218, 245, + 269, 288, 304, 316, 323, 327, 327, 324, + 318, 310, 299, 288, 275, 262, 249, 236, + 224, 213, 204, 196, 190, 186, 183, 182, + 182, 183, 186, 189, 192, 196, 200, 203, + 206, 208, 209, 209, 209, 207, 204, 201, + 197, 193, 188, 183, 179, 174, 170, 166, + 163, 161, 160, 159, 159, 160, 161, 162, + 164, 166, 168, 169, 171, 171, 171, 170, + 169, 167, 163, 159, 155, 150, 144, 139, + 133, 128, 122, 117, 113, 110, 107, 105, + 103, 103, 103, 103, 104, 104, 105, 105, + 105, 103, 101, 97, 92, 86, 78, 68, + 58, 45, 32, 18, 3, -11, -26, -41, + -55, -68, -79, -88, -95, -100, -102, -102, + -99, -93, -85, -75, -62, -48, -33, -16, + 0, 16, 33, 48, 62, 75, 85, 93, + 99, 102, 102, 100, 95, 88, 79, 68, + 55, 41, 26, 11, -3, -18, -32, -45, + -58, -68, -78, -86, -92, -97, -101, -103, + -105, -105, -105, -104, -104, -103, -103, -103, + -103, -105, -107, -110, -113, -117, -122, -128, + -133, -139, -144, -150, -155, -159, -163, -167, + -169, -170, -171, -171, -171, -169, -168, -166, + -164, -162, -161, -160, -159, -159, -160, -161, + -163, -166, -170, -174, -179, -183, -188, -193, + -197, -201, -204, -207, -209, -209, -209, -208, + -206, -203, -200, -196, -192, -189, -186, -183, + -182, -182, -183, -186, -190, -196, -204, -213, + -224, -236, -249, -262, -275, -288, -299, -310, + -318, -324, -327, -327, -323, -316, -304, -288, + -269, -245, -218, -187, -153, -117, -79, -40, +}; + +#define BEEP_SRATE 22050 /* 22050 Hz sample rate */ +#define BEEP_BUFLEN 512 +#define BEEP_VOLUME 15 /* 0 - 100 */ + +static int snd_pmac_beep_event(struct input_dev *dev, unsigned int type, unsigned int code, int hz) +{ + pmac_t *chip; + pmac_beep_t *beep; + unsigned long flags; + int beep_speed = 0; + int srate; + int period, ncycles, nsamples; + int i, j, f; + short *p; + + if (type != EV_SND) + return -1; + + switch (code) { + case SND_BELL: if (hz) hz = 1000; + case SND_TONE: break; + default: return -1; + } + + chip = dev->private; + if (! chip || (beep = chip->beep) == NULL) + return -1; + + if (! hz) { + spin_lock_irqsave(&chip->reg_lock, flags); + if (beep->running) + snd_pmac_beep_stop(chip); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; + } + + beep_speed = snd_pmac_rate_index(chip, &chip->playback, BEEP_SRATE); + srate = chip->freq_table[beep_speed]; + + if (hz <= srate / BEEP_BUFLEN || hz > srate / 2) + hz = 1000; + + spin_lock_irqsave(&chip->reg_lock, flags); + if (chip->playback.running || chip->capture.running || beep->running) { + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; + } + beep->running = 1; + spin_unlock_irqrestore(&chip->reg_lock, flags); + + if (hz == beep->hz && beep->volume == beep->volume_play) { + nsamples = beep->nsamples; + } else { + period = srate * 256 / hz; /* fixed point */ + ncycles = BEEP_BUFLEN * 256 / period; + nsamples = (period * ncycles) >> 8; + f = ncycles * 65536 / nsamples; + j = 0; + p = beep->buf; + for (i = 0; i < nsamples; ++i, p += 2) { + p[0] = p[1] = beep_wform[j >> 8] * beep->volume; + j = (j + f) & 0xffff; + } + beep->hz = hz; + beep->volume_play = beep->volume; + beep->nsamples = nsamples; + } + + spin_lock_irqsave(&chip->reg_lock, flags); + snd_pmac_beep_dma_start(chip, beep->nsamples * 4, beep->addr, beep_speed); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +/* + * beep volume mixer + */ + +#define chip_t pmac_t + +static int snd_pmac_info_beep(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + return 0; +} + +static int snd_pmac_get_beep(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + snd_assert(chip->beep, return -ENXIO); + ucontrol->value.integer.value[0] = chip->beep->volume; + return 0; +} + +static int snd_pmac_put_beep(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + int oval; + snd_assert(chip->beep, return -ENXIO); + oval = chip->beep->volume; + chip->beep->volume = ucontrol->value.integer.value[0]; + return oval != chip->beep->volume; +} + +static snd_kcontrol_new_t snd_pmac_beep_mixer = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Beep Playback Volume", + .info = snd_pmac_info_beep, + .get = snd_pmac_get_beep, + .put = snd_pmac_put_beep, +}; + +/* Initialize beep stuff */ +int __init snd_pmac_attach_beep(pmac_t *chip) +{ + pmac_beep_t *beep; + int err; + + beep = kmalloc(sizeof(*beep), GFP_KERNEL); + if (! beep) + return -ENOMEM; + + memset(beep, 0, sizeof(*beep)); + beep->buf = (short *) kmalloc(BEEP_BUFLEN * 4, GFP_KERNEL); + if (! beep->buf) { + kfree(beep); + return -ENOMEM; + } + beep->addr = virt_to_bus(beep->buf); + + beep->dev.evbit[0] = BIT(EV_SND); + beep->dev.sndbit[0] = BIT(SND_BELL) | BIT(SND_TONE); + beep->dev.event = snd_pmac_beep_event; + beep->dev.private = chip; + + /* FIXME: set more better values */ + beep->dev.name = "PowerMac Beep"; + beep->dev.phys = "powermac/beep"; + beep->dev.id.bustype = BUS_ADB; + beep->dev.id.vendor = 0x001f; + beep->dev.id.product = 0x0001; + beep->dev.id.version = 0x0100; + + beep->volume = BEEP_VOLUME; + beep->running = 0; + if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_pmac_beep_mixer, chip))) < 0) { + kfree(beep->buf); + kfree(beep); + return err; + } + + chip->beep = beep; + input_register_device(&beep->dev); + + return 0; +} + +void snd_pmac_detach_beep(pmac_t *chip) +{ + if (chip->beep) { + input_unregister_device(&chip->beep->dev); + kfree(chip->beep->buf); + kfree(chip->beep); + chip->beep = NULL; + } +} diff --git a/sound/ppc/burgundy.c b/sound/ppc/burgundy.c new file mode 100644 index 00000000000..3f837d9f3eb --- /dev/null +++ b/sound/ppc/burgundy.c @@ -0,0 +1,439 @@ +/* + * PMac Burgundy lowlevel functions + * + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * code based on dmasound.c. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <sound/driver.h> +#include <asm/io.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <sound/core.h> +#include "pmac.h" +#include "burgundy.h" + + +/* Waits for busy flag to clear */ +inline static void +snd_pmac_burgundy_busy_wait(pmac_t *chip) +{ + int timeout = 50; + while ((in_le32(&chip->awacs->codec_ctrl) & MASK_NEWECMD) && timeout--) + udelay(1); + if (! timeout) + printk(KERN_DEBUG "burgundy_busy_wait: timeout\n"); +} + +inline static void +snd_pmac_burgundy_extend_wait(pmac_t *chip) +{ + int timeout; + timeout = 50; + while (!(in_le32(&chip->awacs->codec_stat) & MASK_EXTEND) && timeout--) + udelay(1); + if (! timeout) + printk(KERN_DEBUG "burgundy_extend_wait: timeout #1\n"); + timeout = 50; + while ((in_le32(&chip->awacs->codec_stat) & MASK_EXTEND) && timeout--) + udelay(1); + if (! timeout) + printk(KERN_DEBUG "burgundy_extend_wait: timeout #2\n"); +} + +static void +snd_pmac_burgundy_wcw(pmac_t *chip, unsigned addr, unsigned val) +{ + out_le32(&chip->awacs->codec_ctrl, addr + 0x200c00 + (val & 0xff)); + snd_pmac_burgundy_busy_wait(chip); + out_le32(&chip->awacs->codec_ctrl, addr + 0x200d00 +((val>>8) & 0xff)); + snd_pmac_burgundy_busy_wait(chip); + out_le32(&chip->awacs->codec_ctrl, addr + 0x200e00 +((val>>16) & 0xff)); + snd_pmac_burgundy_busy_wait(chip); + out_le32(&chip->awacs->codec_ctrl, addr + 0x200f00 +((val>>24) & 0xff)); + snd_pmac_burgundy_busy_wait(chip); +} + +static unsigned +snd_pmac_burgundy_rcw(pmac_t *chip, unsigned addr) +{ + unsigned val = 0; + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + + out_le32(&chip->awacs->codec_ctrl, addr + 0x100000); + snd_pmac_burgundy_busy_wait(chip); + snd_pmac_burgundy_extend_wait(chip); + val += (in_le32(&chip->awacs->codec_stat) >> 4) & 0xff; + + out_le32(&chip->awacs->codec_ctrl, addr + 0x100100); + snd_pmac_burgundy_busy_wait(chip); + snd_pmac_burgundy_extend_wait(chip); + val += ((in_le32(&chip->awacs->codec_stat)>>4) & 0xff) <<8; + + out_le32(&chip->awacs->codec_ctrl, addr + 0x100200); + snd_pmac_burgundy_busy_wait(chip); + snd_pmac_burgundy_extend_wait(chip); + val += ((in_le32(&chip->awacs->codec_stat)>>4) & 0xff) <<16; + + out_le32(&chip->awacs->codec_ctrl, addr + 0x100300); + snd_pmac_burgundy_busy_wait(chip); + snd_pmac_burgundy_extend_wait(chip); + val += ((in_le32(&chip->awacs->codec_stat)>>4) & 0xff) <<24; + + spin_unlock_irqrestore(&chip->reg_lock, flags); + + return val; +} + +static void +snd_pmac_burgundy_wcb(pmac_t *chip, unsigned int addr, unsigned int val) +{ + out_le32(&chip->awacs->codec_ctrl, addr + 0x300000 + (val & 0xff)); + snd_pmac_burgundy_busy_wait(chip); +} + +static unsigned +snd_pmac_burgundy_rcb(pmac_t *chip, unsigned int addr) +{ + unsigned val = 0; + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + + out_le32(&chip->awacs->codec_ctrl, addr + 0x100000); + snd_pmac_burgundy_busy_wait(chip); + snd_pmac_burgundy_extend_wait(chip); + val += (in_le32(&chip->awacs->codec_stat) >> 4) & 0xff; + + spin_unlock_irqrestore(&chip->reg_lock, flags); + + return val; +} + +/* + * Burgundy volume: 0 - 100, stereo + */ +static void +snd_pmac_burgundy_write_volume(pmac_t *chip, unsigned int address, long *volume, int shift) +{ + int hardvolume, lvolume, rvolume; + + lvolume = volume[0] ? volume[0] + BURGUNDY_VOLUME_OFFSET : 0; + rvolume = volume[1] ? volume[1] + BURGUNDY_VOLUME_OFFSET : 0; + + hardvolume = lvolume + (rvolume << shift); + if (shift == 8) + hardvolume |= hardvolume << 16; + + snd_pmac_burgundy_wcw(chip, address, hardvolume); +} + +static void +snd_pmac_burgundy_read_volume(pmac_t *chip, unsigned int address, long *volume, int shift) +{ + int wvolume; + + wvolume = snd_pmac_burgundy_rcw(chip, address); + + volume[0] = wvolume & 0xff; + if (volume[0] >= BURGUNDY_VOLUME_OFFSET) + volume[0] -= BURGUNDY_VOLUME_OFFSET; + else + volume[0] = 0; + volume[1] = (wvolume >> shift) & 0xff; + if (volume[1] >= BURGUNDY_VOLUME_OFFSET) + volume[1] -= BURGUNDY_VOLUME_OFFSET; + else + volume[1] = 0; +} + + +/* + */ + +#define BASE2ADDR(base) ((base) << 12) +#define ADDR2BASE(addr) ((addr) >> 12) + +static int snd_pmac_burgundy_info_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + return 0; +} + +static int snd_pmac_burgundy_get_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff); + int shift = (kcontrol->private_value >> 8) & 0xff; + snd_pmac_burgundy_read_volume(chip, addr, ucontrol->value.integer.value, shift); + return 0; +} + +static int snd_pmac_burgundy_put_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff); + int shift = (kcontrol->private_value >> 8) & 0xff; + long nvoices[2]; + + snd_pmac_burgundy_write_volume(chip, addr, ucontrol->value.integer.value, shift); + snd_pmac_burgundy_read_volume(chip, addr, nvoices, shift); + return (nvoices[0] != ucontrol->value.integer.value[0] || + nvoices[1] != ucontrol->value.integer.value[1]); +} + +#define BURGUNDY_VOLUME(xname, xindex, addr, shift) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\ + .info = snd_pmac_burgundy_info_volume,\ + .get = snd_pmac_burgundy_get_volume,\ + .put = snd_pmac_burgundy_put_volume,\ + .private_value = ((ADDR2BASE(addr) & 0xff) | ((shift) << 8)) } + +/* lineout/speaker */ + +static int snd_pmac_burgundy_info_switch_out(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + int stereo = (kcontrol->private_value >> 24) & 1; + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = stereo + 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int snd_pmac_burgundy_get_switch_out(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + int lmask = kcontrol->private_value & 0xff; + int rmask = (kcontrol->private_value >> 8) & 0xff; + int stereo = (kcontrol->private_value >> 24) & 1; + int val = snd_pmac_burgundy_rcb(chip, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES); + ucontrol->value.integer.value[0] = (val & lmask) ? 1 : 0; + if (stereo) + ucontrol->value.integer.value[1] = (val & rmask) ? 1 : 0; + return 0; +} + +static int snd_pmac_burgundy_put_switch_out(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + int lmask = kcontrol->private_value & 0xff; + int rmask = (kcontrol->private_value >> 8) & 0xff; + int stereo = (kcontrol->private_value >> 24) & 1; + int val, oval; + oval = snd_pmac_burgundy_rcb(chip, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES); + val = oval & ~(lmask | rmask); + if (ucontrol->value.integer.value[0]) + val |= lmask; + if (stereo && ucontrol->value.integer.value[1]) + val |= rmask; + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, val); + return val != oval; +} + +#define BURGUNDY_OUTPUT_SWITCH(xname, xindex, lmask, rmask, stereo) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\ + .info = snd_pmac_burgundy_info_switch_out,\ + .get = snd_pmac_burgundy_get_switch_out,\ + .put = snd_pmac_burgundy_put_switch_out,\ + .private_value = ((lmask) | ((rmask) << 8) | ((stereo) << 24)) } + +/* line/speaker output volume */ +static int snd_pmac_burgundy_info_volume_out(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + int stereo = (kcontrol->private_value >> 24) & 1; + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = stereo + 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 15; + return 0; +} + +static int snd_pmac_burgundy_get_volume_out(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff); + int stereo = (kcontrol->private_value >> 24) & 1; + int oval; + + oval = ~snd_pmac_burgundy_rcb(chip, addr) & 0xff; + ucontrol->value.integer.value[0] = oval & 0xf; + if (stereo) + ucontrol->value.integer.value[1] = (oval >> 4) & 0xf; + return 0; +} + +static int snd_pmac_burgundy_put_volume_out(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff); + int stereo = (kcontrol->private_value >> 24) & 1; + int oval, val; + + oval = ~snd_pmac_burgundy_rcb(chip, addr) & 0xff; + val = ucontrol->value.integer.value[0]; + if (stereo) + val |= ucontrol->value.integer.value[1] << 4; + else + val |= ucontrol->value.integer.value[0] << 4; + val = ~val & 0xff; + snd_pmac_burgundy_wcb(chip, addr, val); + return val != oval; +} + +#define BURGUNDY_OUTPUT_VOLUME(xname, xindex, addr, stereo) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\ + .info = snd_pmac_burgundy_info_volume_out,\ + .get = snd_pmac_burgundy_get_volume_out,\ + .put = snd_pmac_burgundy_put_volume_out,\ + .private_value = (ADDR2BASE(addr) | ((stereo) << 24)) } + +static snd_kcontrol_new_t snd_pmac_burgundy_mixers[] __initdata = { + BURGUNDY_VOLUME("Master Playback Volume", 0, MASK_ADDR_BURGUNDY_MASTER_VOLUME, 8), + BURGUNDY_VOLUME("Line Playback Volume", 0, MASK_ADDR_BURGUNDY_VOLLINE, 16), + BURGUNDY_VOLUME("CD Playback Volume", 0, MASK_ADDR_BURGUNDY_VOLCD, 16), + BURGUNDY_VOLUME("Mic Playback Volume", 0, MASK_ADDR_BURGUNDY_VOLMIC, 16), + BURGUNDY_OUTPUT_VOLUME("PC Speaker Playback Volume", 0, MASK_ADDR_BURGUNDY_ATTENHP, 0), + /*BURGUNDY_OUTPUT_VOLUME("PCM Playback Volume", 0, MASK_ADDR_BURGUNDY_ATTENLINEOUT, 1),*/ + BURGUNDY_OUTPUT_VOLUME("Headphone Playback Volume", 0, MASK_ADDR_BURGUNDY_ATTENSPEAKER, 1), +}; +static snd_kcontrol_new_t snd_pmac_burgundy_master_sw __initdata = +BURGUNDY_OUTPUT_SWITCH("Headphone Playback Switch", 0, BURGUNDY_OUTPUT_LEFT, BURGUNDY_OUTPUT_RIGHT, 1); +static snd_kcontrol_new_t snd_pmac_burgundy_speaker_sw __initdata = +BURGUNDY_OUTPUT_SWITCH("PC Speaker Playback Switch", 0, BURGUNDY_OUTPUT_INTERN, 0, 0); + + +#ifdef PMAC_SUPPORT_AUTOMUTE +/* + * auto-mute stuffs + */ +static int snd_pmac_burgundy_detect_headphone(pmac_t *chip) +{ + return (in_le32(&chip->awacs->codec_stat) & chip->hp_stat_mask) ? 1 : 0; +} + +static void snd_pmac_burgundy_update_automute(pmac_t *chip, int do_notify) +{ + if (chip->auto_mute) { + int reg, oreg; + reg = oreg = snd_pmac_burgundy_rcb(chip, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES); + reg &= ~(BURGUNDY_OUTPUT_LEFT | BURGUNDY_OUTPUT_RIGHT | BURGUNDY_OUTPUT_INTERN); + if (snd_pmac_burgundy_detect_headphone(chip)) + reg |= BURGUNDY_OUTPUT_LEFT | BURGUNDY_OUTPUT_RIGHT; + else + reg |= BURGUNDY_OUTPUT_INTERN; + if (do_notify && reg == oreg) + return; + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, reg); + if (do_notify) { + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->master_sw_ctl->id); + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->speaker_sw_ctl->id); + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->hp_detect_ctl->id); + } + } +} +#endif /* PMAC_SUPPORT_AUTOMUTE */ + + +/* + * initialize burgundy + */ +int __init snd_pmac_burgundy_init(pmac_t *chip) +{ + int i, err; + + /* Checks to see the chip is alive and kicking */ + if ((in_le32(&chip->awacs->codec_ctrl) & MASK_ERRCODE) == 0xf0000) { + printk(KERN_WARNING "pmac burgundy: disabled by MacOS :-(\n"); + return 1; + } + + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_OUTPUTENABLES, + DEF_BURGUNDY_OUTPUTENABLES); + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + DEF_BURGUNDY_MORE_OUTPUTENABLES); + snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_OUTPUTSELECTS, + DEF_BURGUNDY_OUTPUTSELECTS); + + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_INPSEL21, + DEF_BURGUNDY_INPSEL21); + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_INPSEL3, + DEF_BURGUNDY_INPSEL3); + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_GAINCD, + DEF_BURGUNDY_GAINCD); + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_GAINLINE, + DEF_BURGUNDY_GAINLINE); + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_GAINMIC, + DEF_BURGUNDY_GAINMIC); + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_GAINMODEM, + DEF_BURGUNDY_GAINMODEM); + + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_ATTENSPEAKER, + DEF_BURGUNDY_ATTENSPEAKER); + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_ATTENLINEOUT, + DEF_BURGUNDY_ATTENLINEOUT); + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_ATTENHP, + DEF_BURGUNDY_ATTENHP); + + snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_MASTER_VOLUME, + DEF_BURGUNDY_MASTER_VOLUME); + snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_VOLCD, + DEF_BURGUNDY_VOLCD); + snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_VOLLINE, + DEF_BURGUNDY_VOLLINE); + snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_VOLMIC, + DEF_BURGUNDY_VOLMIC); + + if (chip->hp_stat_mask == 0) + /* set headphone-jack detection bit */ + chip->hp_stat_mask = 0x04; + + /* + * build burgundy mixers + */ + strcpy(chip->card->mixername, "PowerMac Burgundy"); + + for (i = 0; i < ARRAY_SIZE(snd_pmac_burgundy_mixers); i++) { + if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_pmac_burgundy_mixers[i], chip))) < 0) + return err; + } + chip->master_sw_ctl = snd_ctl_new1(&snd_pmac_burgundy_master_sw, chip); + if ((err = snd_ctl_add(chip->card, chip->master_sw_ctl)) < 0) + return err; + chip->speaker_sw_ctl = snd_ctl_new1(&snd_pmac_burgundy_speaker_sw, chip); + if ((err = snd_ctl_add(chip->card, chip->speaker_sw_ctl)) < 0) + return err; +#ifdef PMAC_SUPPORT_AUTOMUTE + if ((err = snd_pmac_add_automute(chip)) < 0) + return err; + + chip->detect_headphone = snd_pmac_burgundy_detect_headphone; + chip->update_automute = snd_pmac_burgundy_update_automute; + snd_pmac_burgundy_update_automute(chip, 0); /* update the status only */ +#endif + + return 0; +} diff --git a/sound/ppc/burgundy.h b/sound/ppc/burgundy.h new file mode 100644 index 00000000000..ebb457a8342 --- /dev/null +++ b/sound/ppc/burgundy.h @@ -0,0 +1,95 @@ +/* + * Driver for PowerMac Burgundy onboard soundchips + * Copyright (c) 2001 by Takashi Iwai <tiwai@suse.de> + * based on dmasound.c. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef __BURGUNDY_H +#define __BURGUNDY_H + +#define MASK_ADDR_BURGUNDY_INPSEL21 (0x11 << 12) +#define MASK_ADDR_BURGUNDY_INPSEL3 (0x12 << 12) + +#define MASK_ADDR_BURGUNDY_GAINCH1 (0x13 << 12) +#define MASK_ADDR_BURGUNDY_GAINCH2 (0x14 << 12) +#define MASK_ADDR_BURGUNDY_GAINCH3 (0x15 << 12) +#define MASK_ADDR_BURGUNDY_GAINCH4 (0x16 << 12) + +#define MASK_ADDR_BURGUNDY_VOLCH1 (0x20 << 12) +#define MASK_ADDR_BURGUNDY_VOLCH2 (0x21 << 12) +#define MASK_ADDR_BURGUNDY_VOLCH3 (0x22 << 12) +#define MASK_ADDR_BURGUNDY_VOLCH4 (0x23 << 12) + +#define MASK_ADDR_BURGUNDY_OUTPUTSELECTS (0x2B << 12) +#define MASK_ADDR_BURGUNDY_OUTPUTENABLES (0x2F << 12) + +#define MASK_ADDR_BURGUNDY_MASTER_VOLUME (0x30 << 12) + +#define MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES (0x60 << 12) + +#define MASK_ADDR_BURGUNDY_ATTENSPEAKER (0x62 << 12) +#define MASK_ADDR_BURGUNDY_ATTENLINEOUT (0x63 << 12) +#define MASK_ADDR_BURGUNDY_ATTENHP (0x64 << 12) + +#define MASK_ADDR_BURGUNDY_VOLCD (MASK_ADDR_BURGUNDY_VOLCH1) +#define MASK_ADDR_BURGUNDY_VOLLINE (MASK_ADDR_BURGUNDY_VOLCH2) +#define MASK_ADDR_BURGUNDY_VOLMIC (MASK_ADDR_BURGUNDY_VOLCH3) +#define MASK_ADDR_BURGUNDY_VOLMODEM (MASK_ADDR_BURGUNDY_VOLCH4) + +#define MASK_ADDR_BURGUNDY_GAINCD (MASK_ADDR_BURGUNDY_GAINCH1) +#define MASK_ADDR_BURGUNDY_GAINLINE (MASK_ADDR_BURGUNDY_GAINCH2) +#define MASK_ADDR_BURGUNDY_GAINMIC (MASK_ADDR_BURGUNDY_GAINCH3) +#define MASK_ADDR_BURGUNDY_GAINMODEM (MASK_ADDR_BURGUNDY_VOLCH4) + + +/* These are all default values for the burgundy */ +#define DEF_BURGUNDY_INPSEL21 (0xAA) +#define DEF_BURGUNDY_INPSEL3 (0x0A) + +#define DEF_BURGUNDY_GAINCD (0x33) +#define DEF_BURGUNDY_GAINLINE (0x44) +#define DEF_BURGUNDY_GAINMIC (0x44) +#define DEF_BURGUNDY_GAINMODEM (0x06) + +/* Remember: lowest volume here is 0x9b */ +#define DEF_BURGUNDY_VOLCD (0xCCCCCCCC) +#define DEF_BURGUNDY_VOLLINE (0x00000000) +#define DEF_BURGUNDY_VOLMIC (0x00000000) +#define DEF_BURGUNDY_VOLMODEM (0xCCCCCCCC) + +#define DEF_BURGUNDY_OUTPUTSELECTS (0x010f010f) +#define DEF_BURGUNDY_OUTPUTENABLES (0x0A) + +/* #define DEF_BURGUNDY_MASTER_VOLUME (0xFFFFFFFF) */ /* too loud */ +#define DEF_BURGUNDY_MASTER_VOLUME (0xDDDDDDDD) + +#define DEF_BURGUNDY_MORE_OUTPUTENABLES (0x7E) + +#define DEF_BURGUNDY_ATTENSPEAKER (0x44) +#define DEF_BURGUNDY_ATTENLINEOUT (0xCC) +#define DEF_BURGUNDY_ATTENHP (0xCC) + +/* OUTPUTENABLES bits */ +#define BURGUNDY_OUTPUT_LEFT 0x02 +#define BURGUNDY_OUTPUT_RIGHT 0x04 +#define BURGUNDY_OUTPUT_INTERN 0x80 + +/* volume offset */ +#define BURGUNDY_VOLUME_OFFSET 155 + +#endif /* __BURGUNDY_H */ diff --git a/sound/ppc/daca.c b/sound/ppc/daca.c new file mode 100644 index 00000000000..f24a9169361 --- /dev/null +++ b/sound/ppc/daca.c @@ -0,0 +1,283 @@ +/* + * PMac DACA lowlevel functions + * + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/kmod.h> +#include <linux/slab.h> +#include <sound/core.h> +#include "pmac.h" + +/* i2c address */ +#define DACA_I2C_ADDR 0x4d + +/* registers */ +#define DACA_REG_SR 0x01 +#define DACA_REG_AVOL 0x02 +#define DACA_REG_GCFG 0x03 + +/* maximum volume value */ +#define DACA_VOL_MAX 0x38 + + +typedef struct pmac_daca_t { + pmac_keywest_t i2c; + int left_vol, right_vol; + unsigned int deemphasis : 1; + unsigned int amp_on : 1; +} pmac_daca_t; + + +/* + * initialize / detect DACA + */ +static int daca_init_client(pmac_keywest_t *i2c) +{ + unsigned short wdata = 0x00; + /* SR: no swap, 1bit delay, 32-48kHz */ + /* GCFG: power amp inverted, DAC on */ + if (i2c_smbus_write_byte_data(i2c->client, DACA_REG_SR, 0x08) < 0 || + i2c_smbus_write_byte_data(i2c->client, DACA_REG_GCFG, 0x05) < 0) + return -EINVAL; + return i2c_smbus_write_block_data(i2c->client, DACA_REG_AVOL, + 2, (unsigned char*)&wdata); +} + +/* + * update volume + */ +static int daca_set_volume(pmac_daca_t *mix) +{ + unsigned char data[2]; + + if (! mix->i2c.client) + return -ENODEV; + + if (mix->left_vol > DACA_VOL_MAX) + data[0] = DACA_VOL_MAX; + else + data[0] = mix->left_vol; + if (mix->right_vol > DACA_VOL_MAX) + data[1] = DACA_VOL_MAX; + else + data[1] = mix->right_vol; + data[1] |= mix->deemphasis ? 0x40 : 0; + if (i2c_smbus_write_block_data(mix->i2c.client, DACA_REG_AVOL, + 2, data) < 0) { + snd_printk("failed to set volume \n"); + return -EINVAL; + } + return 0; +} + + +/* deemphasis switch */ +static int daca_info_deemphasis(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int daca_get_deemphasis(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_daca_t *mix; + if (! (mix = chip->mixer_data)) + return -ENODEV; + ucontrol->value.integer.value[0] = mix->deemphasis ? 1 : 0; + return 0; +} + +static int daca_put_deemphasis(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_daca_t *mix; + int change; + + if (! (mix = chip->mixer_data)) + return -ENODEV; + change = mix->deemphasis != ucontrol->value.integer.value[0]; + if (change) { + mix->deemphasis = ucontrol->value.integer.value[0]; + daca_set_volume(mix); + } + return change; +} + +/* output volume */ +static int daca_info_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = DACA_VOL_MAX; + return 0; +} + +static int daca_get_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_daca_t *mix; + if (! (mix = chip->mixer_data)) + return -ENODEV; + ucontrol->value.integer.value[0] = mix->left_vol; + ucontrol->value.integer.value[1] = mix->right_vol; + return 0; +} + +static int daca_put_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_daca_t *mix; + int change; + + if (! (mix = chip->mixer_data)) + return -ENODEV; + change = mix->left_vol != ucontrol->value.integer.value[0] || + mix->right_vol != ucontrol->value.integer.value[1]; + if (change) { + mix->left_vol = ucontrol->value.integer.value[0]; + mix->right_vol = ucontrol->value.integer.value[1]; + daca_set_volume(mix); + } + return change; +} + +/* amplifier switch */ +#define daca_info_amp daca_info_deemphasis + +static int daca_get_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_daca_t *mix; + if (! (mix = chip->mixer_data)) + return -ENODEV; + ucontrol->value.integer.value[0] = mix->amp_on ? 1 : 0; + return 0; +} + +static int daca_put_amp(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_daca_t *mix; + int change; + + if (! (mix = chip->mixer_data)) + return -ENODEV; + change = mix->amp_on != ucontrol->value.integer.value[0]; + if (change) { + mix->amp_on = ucontrol->value.integer.value[0]; + i2c_smbus_write_byte_data(mix->i2c.client, DACA_REG_GCFG, + mix->amp_on ? 0x05 : 0x04); + } + return change; +} + +static snd_kcontrol_new_t daca_mixers[] = { + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Deemphasis Switch", + .info = daca_info_deemphasis, + .get = daca_get_deemphasis, + .put = daca_put_deemphasis + }, + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Volume", + .info = daca_info_volume, + .get = daca_get_volume, + .put = daca_put_volume + }, + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Power Amplifier Switch", + .info = daca_info_amp, + .get = daca_get_amp, + .put = daca_put_amp + }, +}; + + +#ifdef CONFIG_PMAC_PBOOK +static void daca_resume(pmac_t *chip) +{ + pmac_daca_t *mix = chip->mixer_data; + i2c_smbus_write_byte_data(mix->i2c.client, DACA_REG_SR, 0x08); + i2c_smbus_write_byte_data(mix->i2c.client, DACA_REG_GCFG, + mix->amp_on ? 0x05 : 0x04); + daca_set_volume(mix); +} +#endif /* CONFIG_PMAC_PBOOK */ + + +static void daca_cleanup(pmac_t *chip) +{ + pmac_daca_t *mix = chip->mixer_data; + if (! mix) + return; + snd_pmac_keywest_cleanup(&mix->i2c); + kfree(mix); + chip->mixer_data = NULL; +} + +/* exported */ +int __init snd_pmac_daca_init(pmac_t *chip) +{ + int i, err; + pmac_daca_t *mix; + +#ifdef CONFIG_KMOD + if (current->fs->root) + request_module("i2c-keywest"); +#endif /* CONFIG_KMOD */ + + mix = kmalloc(sizeof(*mix), GFP_KERNEL); + if (! mix) + return -ENOMEM; + memset(mix, 0, sizeof(*mix)); + chip->mixer_data = mix; + chip->mixer_free = daca_cleanup; + mix->amp_on = 1; /* default on */ + + mix->i2c.addr = DACA_I2C_ADDR; + mix->i2c.init_client = daca_init_client; + mix->i2c.name = "DACA"; + if ((err = snd_pmac_keywest_init(&mix->i2c)) < 0) + return err; + + /* + * build mixers + */ + strcpy(chip->card->mixername, "PowerMac DACA"); + + for (i = 0; i < ARRAY_SIZE(daca_mixers); i++) { + if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&daca_mixers[i], chip))) < 0) + return err; + } + +#ifdef CONFIG_PMAC_PBOOK + chip->resume = daca_resume; +#endif + + return 0; +} diff --git a/sound/ppc/keywest.c b/sound/ppc/keywest.c new file mode 100644 index 00000000000..df073a05b5d --- /dev/null +++ b/sound/ppc/keywest.c @@ -0,0 +1,143 @@ +/* + * common keywest i2c layer + * + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/i2c-dev.h> +#include <linux/slab.h> +#include <sound/core.h> +#include "pmac.h" + +/* + * we have to keep a static variable here since i2c attach_adapter + * callback cannot pass a private data. + */ +static pmac_keywest_t *keywest_ctx; + + +#define I2C_DRIVERID_KEYWEST 0xFEBA + +static int keywest_attach_adapter(struct i2c_adapter *adapter); +static int keywest_detach_client(struct i2c_client *client); + +struct i2c_driver keywest_driver = { + .name = "PMac Keywest Audio", + .id = I2C_DRIVERID_KEYWEST, + .flags = I2C_DF_NOTIFY, + .attach_adapter = &keywest_attach_adapter, + .detach_client = &keywest_detach_client, +}; + + +#ifndef i2c_device_name +#define i2c_device_name(x) ((x)->name) +#endif + +static int keywest_attach_adapter(struct i2c_adapter *adapter) +{ + int err; + struct i2c_client *new_client; + + if (! keywest_ctx) + return -EINVAL; + + if (strncmp(i2c_device_name(adapter), "mac-io", 6)) + return 0; /* ignored */ + + new_client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (! new_client) + return -ENOMEM; + + memset(new_client, 0, sizeof(*new_client)); + new_client->addr = keywest_ctx->addr; + i2c_set_clientdata(new_client, keywest_ctx); + new_client->adapter = adapter; + new_client->driver = &keywest_driver; + new_client->flags = 0; + + strcpy(i2c_device_name(new_client), keywest_ctx->name); + keywest_ctx->client = new_client; + + /* Tell the i2c layer a new client has arrived */ + if (i2c_attach_client(new_client)) { + snd_printk(KERN_ERR "tumbler: cannot attach i2c client\n"); + err = -ENODEV; + goto __err; + } + + return 0; + + __err: + kfree(new_client); + keywest_ctx->client = NULL; + return err; +} + +static int keywest_detach_client(struct i2c_client *client) +{ + if (! keywest_ctx) + return 0; + if (client == keywest_ctx->client) + keywest_ctx->client = NULL; + + i2c_detach_client(client); + kfree(client); + return 0; +} + +/* exported */ +void snd_pmac_keywest_cleanup(pmac_keywest_t *i2c) +{ + if (keywest_ctx && keywest_ctx == i2c) { + i2c_del_driver(&keywest_driver); + keywest_ctx = NULL; + } +} + +int __init snd_pmac_tumbler_post_init(void) +{ + int err; + + if ((err = keywest_ctx->init_client(keywest_ctx)) < 0) { + snd_printk(KERN_ERR "tumbler: %i :cannot initialize the MCS\n", err); + return err; + } + return 0; +} + +/* exported */ +int __init snd_pmac_keywest_init(pmac_keywest_t *i2c) +{ + int err; + + if (keywest_ctx) + return -EBUSY; + + keywest_ctx = i2c; + + if ((err = i2c_add_driver(&keywest_driver))) { + snd_printk(KERN_ERR "cannot register keywest i2c driver\n"); + return err; + } + return 0; +} diff --git a/sound/ppc/pmac.c b/sound/ppc/pmac.c new file mode 100644 index 00000000000..6c4ed90f490 --- /dev/null +++ b/sound/ppc/pmac.c @@ -0,0 +1,1328 @@ +/* + * PMac DBDMA lowlevel functions + * + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * code based on dmasound.c. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include <sound/driver.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <sound/core.h> +#include "pmac.h" +#include <sound/pcm_params.h> +#ifdef CONFIG_PPC_HAS_FEATURE_CALLS +#include <asm/pmac_feature.h> +#else +#include <asm/feature.h> +#endif + + +#if defined(CONFIG_PM) && defined(CONFIG_PMAC_PBOOK) +static int snd_pmac_register_sleep_notifier(pmac_t *chip); +static int snd_pmac_unregister_sleep_notifier(pmac_t *chip); +static int snd_pmac_suspend(snd_card_t *card, pm_message_t state); +static int snd_pmac_resume(snd_card_t *card); +#endif + + +/* fixed frequency table for awacs, screamer, burgundy, DACA (44100 max) */ +static int awacs_freqs[8] = { + 44100, 29400, 22050, 17640, 14700, 11025, 8820, 7350 +}; +/* fixed frequency table for tumbler */ +static int tumbler_freqs[1] = { + 44100 +}; + +/* + * allocate DBDMA command arrays + */ +static int snd_pmac_dbdma_alloc(pmac_dbdma_t *rec, int size) +{ + rec->space = kmalloc(sizeof(struct dbdma_cmd) * (size + 1), GFP_KERNEL); + if (rec->space == NULL) + return -ENOMEM; + rec->size = size; + memset(rec->space, 0, sizeof(struct dbdma_cmd) * (size + 1)); + rec->cmds = (void __iomem *)DBDMA_ALIGN(rec->space); + rec->addr = virt_to_bus(rec->cmds); + return 0; +} + +static void snd_pmac_dbdma_free(pmac_dbdma_t *rec) +{ + if (rec) + kfree(rec->space); +} + + +/* + * pcm stuff + */ + +/* + * look up frequency table + */ + +unsigned int snd_pmac_rate_index(pmac_t *chip, pmac_stream_t *rec, unsigned int rate) +{ + int i, ok, found; + + ok = rec->cur_freqs; + if (rate > chip->freq_table[0]) + return 0; + found = 0; + for (i = 0; i < chip->num_freqs; i++, ok >>= 1) { + if (! (ok & 1)) continue; + found = i; + if (rate >= chip->freq_table[i]) + break; + } + return found; +} + +/* + * check whether another stream is active + */ +static inline int another_stream(int stream) +{ + return (stream == SNDRV_PCM_STREAM_PLAYBACK) ? + SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; +} + +/* + * allocate buffers + */ +static int snd_pmac_pcm_hw_params(snd_pcm_substream_t *subs, + snd_pcm_hw_params_t *hw_params) +{ + return snd_pcm_lib_malloc_pages(subs, params_buffer_bytes(hw_params)); +} + +/* + * release buffers + */ +static int snd_pmac_pcm_hw_free(snd_pcm_substream_t *subs) +{ + snd_pcm_lib_free_pages(subs); + return 0; +} + +/* + * get a stream of the opposite direction + */ +static pmac_stream_t *snd_pmac_get_stream(pmac_t *chip, int stream) +{ + switch (stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + return &chip->playback; + case SNDRV_PCM_STREAM_CAPTURE: + return &chip->capture; + default: + snd_BUG(); + return NULL; + } +} + +/* + * wait while run status is on + */ +inline static void +snd_pmac_wait_ack(pmac_stream_t *rec) +{ + int timeout = 50000; + while ((in_le32(&rec->dma->status) & RUN) && timeout-- > 0) + udelay(1); +} + +/* + * set the format and rate to the chip. + * call the lowlevel function if defined (e.g. for AWACS). + */ +static void snd_pmac_pcm_set_format(pmac_t *chip) +{ + /* set up frequency and format */ + out_le32(&chip->awacs->control, chip->control_mask | (chip->rate_index << 8)); + out_le32(&chip->awacs->byteswap, chip->format == SNDRV_PCM_FORMAT_S16_LE ? 1 : 0); + if (chip->set_format) + chip->set_format(chip); +} + +/* + * stop the DMA transfer + */ +inline static void snd_pmac_dma_stop(pmac_stream_t *rec) +{ + out_le32(&rec->dma->control, (RUN|WAKE|FLUSH|PAUSE) << 16); + snd_pmac_wait_ack(rec); +} + +/* + * set the command pointer address + */ +inline static void snd_pmac_dma_set_command(pmac_stream_t *rec, pmac_dbdma_t *cmd) +{ + out_le32(&rec->dma->cmdptr, cmd->addr); +} + +/* + * start the DMA + */ +inline static void snd_pmac_dma_run(pmac_stream_t *rec, int status) +{ + out_le32(&rec->dma->control, status | (status << 16)); +} + + +/* + * prepare playback/capture stream + */ +static int snd_pmac_pcm_prepare(pmac_t *chip, pmac_stream_t *rec, snd_pcm_substream_t *subs) +{ + int i; + volatile struct dbdma_cmd __iomem *cp; + snd_pcm_runtime_t *runtime = subs->runtime; + int rate_index; + long offset; + pmac_stream_t *astr; + + rec->dma_size = snd_pcm_lib_buffer_bytes(subs); + rec->period_size = snd_pcm_lib_period_bytes(subs); + rec->nperiods = rec->dma_size / rec->period_size; + rec->cur_period = 0; + rate_index = snd_pmac_rate_index(chip, rec, runtime->rate); + + /* set up constraints */ + astr = snd_pmac_get_stream(chip, another_stream(rec->stream)); + snd_runtime_check(astr, return -EINVAL); + astr->cur_freqs = 1 << rate_index; + astr->cur_formats = 1 << runtime->format; + chip->rate_index = rate_index; + chip->format = runtime->format; + + /* We really want to execute a DMA stop command, after the AWACS + * is initialized. + * For reasons I don't understand, it stops the hissing noise + * common to many PowerBook G3 systems and random noise otherwise + * captured on iBook2's about every third time. -ReneR + */ + spin_lock_irq(&chip->reg_lock); + snd_pmac_dma_stop(rec); + st_le16(&chip->extra_dma.cmds->command, DBDMA_STOP); + snd_pmac_dma_set_command(rec, &chip->extra_dma); + snd_pmac_dma_run(rec, RUN); + spin_unlock_irq(&chip->reg_lock); + mdelay(5); + spin_lock_irq(&chip->reg_lock); + /* continuous DMA memory type doesn't provide the physical address, + * so we need to resolve the address here... + */ + offset = virt_to_bus(runtime->dma_area); + for (i = 0, cp = rec->cmd.cmds; i < rec->nperiods; i++, cp++) { + st_le32(&cp->phy_addr, offset); + st_le16(&cp->req_count, rec->period_size); + /*st_le16(&cp->res_count, 0);*/ + st_le16(&cp->xfer_status, 0); + offset += rec->period_size; + } + /* make loop */ + st_le16(&cp->command, DBDMA_NOP + BR_ALWAYS); + st_le32(&cp->cmd_dep, rec->cmd.addr); + + snd_pmac_dma_stop(rec); + snd_pmac_dma_set_command(rec, &rec->cmd); + spin_unlock_irq(&chip->reg_lock); + + return 0; +} + + +/* + * PCM trigger/stop + */ +static int snd_pmac_pcm_trigger(pmac_t *chip, pmac_stream_t *rec, + snd_pcm_substream_t *subs, int cmd) +{ + volatile struct dbdma_cmd __iomem *cp; + int i, command; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (rec->running) + return -EBUSY; + command = (subs->stream == SNDRV_PCM_STREAM_PLAYBACK ? + OUTPUT_MORE : INPUT_MORE) + INTR_ALWAYS; + spin_lock(&chip->reg_lock); + snd_pmac_beep_stop(chip); + snd_pmac_pcm_set_format(chip); + for (i = 0, cp = rec->cmd.cmds; i < rec->nperiods; i++, cp++) + out_le16(&cp->command, command); + snd_pmac_dma_set_command(rec, &rec->cmd); + (void)in_le32(&rec->dma->status); + snd_pmac_dma_run(rec, RUN|WAKE); + rec->running = 1; + spin_unlock(&chip->reg_lock); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + spin_lock(&chip->reg_lock); + rec->running = 0; + /*printk("stopped!!\n");*/ + snd_pmac_dma_stop(rec); + for (i = 0, cp = rec->cmd.cmds; i < rec->nperiods; i++, cp++) + out_le16(&cp->command, DBDMA_STOP); + spin_unlock(&chip->reg_lock); + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* + * return the current pointer + */ +inline +static snd_pcm_uframes_t snd_pmac_pcm_pointer(pmac_t *chip, pmac_stream_t *rec, + snd_pcm_substream_t *subs) +{ + int count = 0; + +#if 1 /* hmm.. how can we get the current dma pointer?? */ + int stat; + volatile struct dbdma_cmd __iomem *cp = &rec->cmd.cmds[rec->cur_period]; + stat = ld_le16(&cp->xfer_status); + if (stat & (ACTIVE|DEAD)) { + count = in_le16(&cp->res_count); + if (count) + count = rec->period_size - count; + } +#endif + count += rec->cur_period * rec->period_size; + /*printk("pointer=%d\n", count);*/ + return bytes_to_frames(subs->runtime, count); +} + +/* + * playback + */ + +static int snd_pmac_playback_prepare(snd_pcm_substream_t *subs) +{ + pmac_t *chip = snd_pcm_substream_chip(subs); + return snd_pmac_pcm_prepare(chip, &chip->playback, subs); +} + +static int snd_pmac_playback_trigger(snd_pcm_substream_t *subs, + int cmd) +{ + pmac_t *chip = snd_pcm_substream_chip(subs); + return snd_pmac_pcm_trigger(chip, &chip->playback, subs, cmd); +} + +static snd_pcm_uframes_t snd_pmac_playback_pointer(snd_pcm_substream_t *subs) +{ + pmac_t *chip = snd_pcm_substream_chip(subs); + return snd_pmac_pcm_pointer(chip, &chip->playback, subs); +} + + +/* + * capture + */ + +static int snd_pmac_capture_prepare(snd_pcm_substream_t *subs) +{ + pmac_t *chip = snd_pcm_substream_chip(subs); + return snd_pmac_pcm_prepare(chip, &chip->capture, subs); +} + +static int snd_pmac_capture_trigger(snd_pcm_substream_t *subs, + int cmd) +{ + pmac_t *chip = snd_pcm_substream_chip(subs); + return snd_pmac_pcm_trigger(chip, &chip->capture, subs, cmd); +} + +static snd_pcm_uframes_t snd_pmac_capture_pointer(snd_pcm_substream_t *subs) +{ + pmac_t *chip = snd_pcm_substream_chip(subs); + return snd_pmac_pcm_pointer(chip, &chip->capture, subs); +} + + +/* + * update playback/capture pointer from interrupts + */ +static void snd_pmac_pcm_update(pmac_t *chip, pmac_stream_t *rec) +{ + volatile struct dbdma_cmd __iomem *cp; + int c; + int stat; + + spin_lock(&chip->reg_lock); + if (rec->running) { + cp = &rec->cmd.cmds[rec->cur_period]; + for (c = 0; c < rec->nperiods; c++) { /* at most all fragments */ + stat = ld_le16(&cp->xfer_status); + if (! (stat & ACTIVE)) + break; + /*printk("update frag %d\n", rec->cur_period);*/ + st_le16(&cp->xfer_status, 0); + st_le16(&cp->req_count, rec->period_size); + /*st_le16(&cp->res_count, 0);*/ + rec->cur_period++; + if (rec->cur_period >= rec->nperiods) { + rec->cur_period = 0; + cp = rec->cmd.cmds; + } else + cp++; + spin_unlock(&chip->reg_lock); + snd_pcm_period_elapsed(rec->substream); + spin_lock(&chip->reg_lock); + } + } + spin_unlock(&chip->reg_lock); +} + + +/* + * hw info + */ + +static snd_pcm_hardware_t snd_pmac_playback = +{ + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_44100, + .rate_min = 7350, + .rate_max = 44100, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 131072, + .period_bytes_min = 256, + .period_bytes_max = 16384, + .periods_min = 3, + .periods_max = PMAC_MAX_FRAGS, +}; + +static snd_pcm_hardware_t snd_pmac_capture = +{ + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_44100, + .rate_min = 7350, + .rate_max = 44100, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 131072, + .period_bytes_min = 256, + .period_bytes_max = 16384, + .periods_min = 3, + .periods_max = PMAC_MAX_FRAGS, +}; + + +#if 0 // NYI +static int snd_pmac_hw_rule_rate(snd_pcm_hw_params_t *params, + snd_pcm_hw_rule_t *rule) +{ + pmac_t *chip = rule->private; + pmac_stream_t *rec = snd_pmac_get_stream(chip, rule->deps[0]); + int i, freq_table[8], num_freqs; + + snd_runtime_check(rec, return -EINVAL); + num_freqs = 0; + for (i = chip->num_freqs - 1; i >= 0; i--) { + if (rec->cur_freqs & (1 << i)) + freq_table[num_freqs++] = chip->freq_table[i]; + } + + return snd_interval_list(hw_param_interval(params, rule->var), + num_freqs, freq_table, 0); +} + +static int snd_pmac_hw_rule_format(snd_pcm_hw_params_t *params, + snd_pcm_hw_rule_t *rule) +{ + pmac_t *chip = rule->private; + pmac_stream_t *rec = snd_pmac_get_stream(chip, rule->deps[0]); + + snd_runtime_check(rec, return -EINVAL); + return snd_mask_refine_set(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), + rec->cur_formats); +} +#endif // NYI + +static int snd_pmac_pcm_open(pmac_t *chip, pmac_stream_t *rec, snd_pcm_substream_t *subs) +{ + snd_pcm_runtime_t *runtime = subs->runtime; + int i, j, fflags; + static int typical_freqs[] = { + 44100, + 22050, + 11025, + 0, + }; + static int typical_freq_flags[] = { + SNDRV_PCM_RATE_44100, + SNDRV_PCM_RATE_22050, + SNDRV_PCM_RATE_11025, + 0, + }; + + /* look up frequency table and fill bit mask */ + runtime->hw.rates = 0; + fflags = chip->freqs_ok; + for (i = 0; typical_freqs[i]; i++) { + for (j = 0; j < chip->num_freqs; j++) { + if ((chip->freqs_ok & (1 << j)) && + chip->freq_table[j] == typical_freqs[i]) { + runtime->hw.rates |= typical_freq_flags[i]; + fflags &= ~(1 << j); + break; + } + } + } + if (fflags) /* rest */ + runtime->hw.rates |= SNDRV_PCM_RATE_KNOT; + + /* check for minimum and maximum rates */ + for (i = 0; i < chip->num_freqs; i++) { + if (chip->freqs_ok & (1 << i)) { + runtime->hw.rate_max = chip->freq_table[i]; + break; + } + } + for (i = chip->num_freqs - 1; i >= 0; i--) { + if (chip->freqs_ok & (1 << i)) { + runtime->hw.rate_min = chip->freq_table[i]; + break; + } + } + runtime->hw.formats = chip->formats_ok; + if (chip->can_capture) { + if (! chip->can_duplex) + runtime->hw.info |= SNDRV_PCM_INFO_HALF_DUPLEX; + runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; + } + runtime->private_data = rec; + rec->substream = subs; + +#if 0 /* FIXME: still under development.. */ + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + snd_pmac_hw_rule_rate, chip, rec->stream, -1); + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, + snd_pmac_hw_rule_format, chip, rec->stream, -1); +#endif + + runtime->hw.periods_max = rec->cmd.size - 1; + + if (chip->can_duplex) + snd_pcm_set_sync(subs); + + /* constraints to fix choppy sound */ + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + return 0; +} + +static int snd_pmac_pcm_close(pmac_t *chip, pmac_stream_t *rec, snd_pcm_substream_t *subs) +{ + pmac_stream_t *astr; + + snd_pmac_dma_stop(rec); + + astr = snd_pmac_get_stream(chip, another_stream(rec->stream)); + snd_runtime_check(astr, return -EINVAL); + + /* reset constraints */ + astr->cur_freqs = chip->freqs_ok; + astr->cur_formats = chip->formats_ok; + + return 0; +} + +static int snd_pmac_playback_open(snd_pcm_substream_t *subs) +{ + pmac_t *chip = snd_pcm_substream_chip(subs); + + subs->runtime->hw = snd_pmac_playback; + return snd_pmac_pcm_open(chip, &chip->playback, subs); +} + +static int snd_pmac_capture_open(snd_pcm_substream_t *subs) +{ + pmac_t *chip = snd_pcm_substream_chip(subs); + + subs->runtime->hw = snd_pmac_capture; + return snd_pmac_pcm_open(chip, &chip->capture, subs); +} + +static int snd_pmac_playback_close(snd_pcm_substream_t *subs) +{ + pmac_t *chip = snd_pcm_substream_chip(subs); + + return snd_pmac_pcm_close(chip, &chip->playback, subs); +} + +static int snd_pmac_capture_close(snd_pcm_substream_t *subs) +{ + pmac_t *chip = snd_pcm_substream_chip(subs); + + return snd_pmac_pcm_close(chip, &chip->capture, subs); +} + +/* + */ + +static snd_pcm_ops_t snd_pmac_playback_ops = { + .open = snd_pmac_playback_open, + .close = snd_pmac_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_pmac_pcm_hw_params, + .hw_free = snd_pmac_pcm_hw_free, + .prepare = snd_pmac_playback_prepare, + .trigger = snd_pmac_playback_trigger, + .pointer = snd_pmac_playback_pointer, +}; + +static snd_pcm_ops_t snd_pmac_capture_ops = { + .open = snd_pmac_capture_open, + .close = snd_pmac_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_pmac_pcm_hw_params, + .hw_free = snd_pmac_pcm_hw_free, + .prepare = snd_pmac_capture_prepare, + .trigger = snd_pmac_capture_trigger, + .pointer = snd_pmac_capture_pointer, +}; + +static void pmac_pcm_free(snd_pcm_t *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +int __init snd_pmac_pcm_new(pmac_t *chip) +{ + snd_pcm_t *pcm; + int err; + int num_captures = 1; + + if (! chip->can_capture) + num_captures = 0; + err = snd_pcm_new(chip->card, chip->card->driver, 0, 1, num_captures, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_pmac_playback_ops); + if (chip->can_capture) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_pmac_capture_ops); + + pcm->private_data = chip; + pcm->private_free = pmac_pcm_free; + pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX; + strcpy(pcm->name, chip->card->shortname); + chip->pcm = pcm; + + chip->formats_ok = SNDRV_PCM_FMTBIT_S16_BE; + if (chip->can_byte_swap) + chip->formats_ok |= SNDRV_PCM_FMTBIT_S16_LE; + + chip->playback.cur_formats = chip->formats_ok; + chip->capture.cur_formats = chip->formats_ok; + chip->playback.cur_freqs = chip->freqs_ok; + chip->capture.cur_freqs = chip->freqs_ok; + + /* preallocate 64k buffer */ + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + 64 * 1024, 64 * 1024); + + return 0; +} + + +static void snd_pmac_dbdma_reset(pmac_t *chip) +{ + out_le32(&chip->playback.dma->control, (RUN|PAUSE|FLUSH|WAKE|DEAD) << 16); + snd_pmac_wait_ack(&chip->playback); + out_le32(&chip->capture.dma->control, (RUN|PAUSE|FLUSH|WAKE|DEAD) << 16); + snd_pmac_wait_ack(&chip->capture); +} + + +/* + * handling beep + */ +void snd_pmac_beep_dma_start(pmac_t *chip, int bytes, unsigned long addr, int speed) +{ + pmac_stream_t *rec = &chip->playback; + + snd_pmac_dma_stop(rec); + st_le16(&chip->extra_dma.cmds->req_count, bytes); + st_le16(&chip->extra_dma.cmds->xfer_status, 0); + st_le32(&chip->extra_dma.cmds->cmd_dep, chip->extra_dma.addr); + st_le32(&chip->extra_dma.cmds->phy_addr, addr); + st_le16(&chip->extra_dma.cmds->command, OUTPUT_MORE + BR_ALWAYS); + out_le32(&chip->awacs->control, + (in_le32(&chip->awacs->control) & ~0x1f00) + | (speed << 8)); + out_le32(&chip->awacs->byteswap, 0); + snd_pmac_dma_set_command(rec, &chip->extra_dma); + snd_pmac_dma_run(rec, RUN); +} + +void snd_pmac_beep_dma_stop(pmac_t *chip) +{ + snd_pmac_dma_stop(&chip->playback); + st_le16(&chip->extra_dma.cmds->command, DBDMA_STOP); + snd_pmac_pcm_set_format(chip); /* reset format */ +} + + +/* + * interrupt handlers + */ +static irqreturn_t +snd_pmac_tx_intr(int irq, void *devid, struct pt_regs *regs) +{ + pmac_t *chip = devid; + snd_pmac_pcm_update(chip, &chip->playback); + return IRQ_HANDLED; +} + + +static irqreturn_t +snd_pmac_rx_intr(int irq, void *devid, struct pt_regs *regs) +{ + pmac_t *chip = devid; + snd_pmac_pcm_update(chip, &chip->capture); + return IRQ_HANDLED; +} + + +static irqreturn_t +snd_pmac_ctrl_intr(int irq, void *devid, struct pt_regs *regs) +{ + pmac_t *chip = devid; + int ctrl = in_le32(&chip->awacs->control); + + /*printk("pmac: control interrupt.. 0x%x\n", ctrl);*/ + if (ctrl & MASK_PORTCHG) { + /* do something when headphone is plugged/unplugged? */ + if (chip->update_automute) + chip->update_automute(chip, 1); + } + if (ctrl & MASK_CNTLERR) { + int err = (in_le32(&chip->awacs->codec_stat) & MASK_ERRCODE) >> 16; + if (err && chip->model <= PMAC_SCREAMER) + snd_printk(KERN_DEBUG "error %x\n", err); + } + /* Writing 1s to the CNTLERR and PORTCHG bits clears them... */ + out_le32(&chip->awacs->control, ctrl); + return IRQ_HANDLED; +} + + +/* + * a wrapper to feature call for compatibility + */ +#if defined(CONFIG_PM) && defined(CONFIG_PMAC_PBOOK) +static void snd_pmac_sound_feature(pmac_t *chip, int enable) +{ +#ifdef CONFIG_PPC_HAS_FEATURE_CALLS + ppc_md.feature_call(PMAC_FTR_SOUND_CHIP_ENABLE, chip->node, 0, enable); +#else + if (chip->is_pbook_G3) { + pmu_suspend(); + feature_clear(chip->node, FEATURE_Sound_power); + feature_clear(chip->node, FEATURE_Sound_CLK_enable); + big_mdelay(1000); /* XXX */ + pmu_resume(); + } + if (chip->is_pbook_3400) { + feature_set(chip->node, FEATURE_IOBUS_enable); + udelay(10); + } +#endif +} +#else /* CONFIG_PM && CONFIG_PMAC_PBOOK */ +#define snd_pmac_sound_feature(chip,enable) /**/ +#endif /* CONFIG_PM && CONFIG_PMAC_PBOOK */ + +/* + * release resources + */ + +static int snd_pmac_free(pmac_t *chip) +{ + int i; + + /* stop sounds */ + if (chip->initialized) { + snd_pmac_dbdma_reset(chip); + /* disable interrupts from awacs interface */ + out_le32(&chip->awacs->control, in_le32(&chip->awacs->control) & 0xfff); + } + + snd_pmac_sound_feature(chip, 0); +#if defined(CONFIG_PM) && defined(CONFIG_PMAC_PBOOK) + snd_pmac_unregister_sleep_notifier(chip); +#endif + + /* clean up mixer if any */ + if (chip->mixer_free) + chip->mixer_free(chip); + + snd_pmac_detach_beep(chip); + + /* release resources */ + if (chip->irq >= 0) + free_irq(chip->irq, (void*)chip); + if (chip->tx_irq >= 0) + free_irq(chip->tx_irq, (void*)chip); + if (chip->rx_irq >= 0) + free_irq(chip->rx_irq, (void*)chip); + snd_pmac_dbdma_free(&chip->playback.cmd); + snd_pmac_dbdma_free(&chip->capture.cmd); + snd_pmac_dbdma_free(&chip->extra_dma); + if (chip->macio_base) + iounmap(chip->macio_base); + if (chip->latch_base) + iounmap(chip->latch_base); + if (chip->awacs) + iounmap(chip->awacs); + if (chip->playback.dma) + iounmap(chip->playback.dma); + if (chip->capture.dma) + iounmap(chip->capture.dma); + if (chip->node) { + for (i = 0; i < 3; i++) { + if (chip->of_requested & (1 << i)) + release_OF_resource(chip->node, i); + } + } + kfree(chip); + return 0; +} + + +/* + * free the device + */ +static int snd_pmac_dev_free(snd_device_t *device) +{ + pmac_t *chip = device->device_data; + return snd_pmac_free(chip); +} + + +/* + * check the machine support byteswap (little-endian) + */ + +static void __init detect_byte_swap(pmac_t *chip) +{ + struct device_node *mio; + + /* if seems that Keylargo can't byte-swap */ + for (mio = chip->node->parent; mio; mio = mio->parent) { + if (strcmp(mio->name, "mac-io") == 0) { + if (device_is_compatible(mio, "Keylargo")) + chip->can_byte_swap = 0; + break; + } + } + + /* it seems the Pismo & iBook can't byte-swap in hardware. */ + if (machine_is_compatible("PowerBook3,1") || + machine_is_compatible("PowerBook2,1")) + chip->can_byte_swap = 0 ; + + if (machine_is_compatible("PowerBook2,1")) + chip->can_duplex = 0; +} + + +/* + * detect a sound chip + */ +static int __init snd_pmac_detect(pmac_t *chip) +{ + struct device_node *sound; + unsigned int *prop, l; + + if (_machine != _MACH_Pmac) + return -ENODEV; + + chip->subframe = 0; + chip->revision = 0; + chip->freqs_ok = 0xff; /* all ok */ + chip->model = PMAC_AWACS; + chip->can_byte_swap = 1; + chip->can_duplex = 1; + chip->can_capture = 1; + chip->num_freqs = ARRAY_SIZE(awacs_freqs); + chip->freq_table = awacs_freqs; + + chip->control_mask = MASK_IEPC | MASK_IEE | 0x11; /* default */ + + /* check machine type */ + if (machine_is_compatible("AAPL,3400/2400") + || machine_is_compatible("AAPL,3500")) + chip->is_pbook_3400 = 1; + else if (machine_is_compatible("PowerBook1,1") + || machine_is_compatible("AAPL,PowerBook1998")) + chip->is_pbook_G3 = 1; + chip->node = find_devices("awacs"); + if (chip->node) + return 0; /* ok */ + + /* + * powermac G3 models have a node called "davbus" + * with a child called "sound". + */ + chip->node = find_devices("davbus"); + /* + * if we didn't find a davbus device, try 'i2s-a' since + * this seems to be what iBooks have + */ + if (! chip->node) + chip->node = find_devices("i2s-a"); + if (! chip->node) + return -ENODEV; + sound = find_devices("sound"); + while (sound && sound->parent != chip->node) + sound = sound->next; + if (! sound) + return -ENODEV; + prop = (unsigned int *) get_property(sound, "sub-frame", NULL); + if (prop && *prop < 16) + chip->subframe = *prop; + /* This should be verified on older screamers */ + if (device_is_compatible(sound, "screamer")) { + chip->model = PMAC_SCREAMER; + // chip->can_byte_swap = 0; /* FIXME: check this */ + } + if (device_is_compatible(sound, "burgundy")) { + chip->model = PMAC_BURGUNDY; + chip->control_mask = MASK_IEPC | 0x11; /* disable IEE */ + } + if (device_is_compatible(sound, "daca")) { + chip->model = PMAC_DACA; + chip->can_capture = 0; /* no capture */ + chip->can_duplex = 0; + // chip->can_byte_swap = 0; /* FIXME: check this */ + chip->control_mask = MASK_IEPC | 0x11; /* disable IEE */ + } + if (device_is_compatible(sound, "tumbler")) { + chip->model = PMAC_TUMBLER; + chip->can_capture = 0; /* no capture */ + chip->can_duplex = 0; + // chip->can_byte_swap = 0; /* FIXME: check this */ + chip->num_freqs = ARRAY_SIZE(tumbler_freqs); + chip->freq_table = tumbler_freqs; + chip->control_mask = MASK_IEPC | 0x11; /* disable IEE */ + } + if (device_is_compatible(sound, "snapper")) { + chip->model = PMAC_SNAPPER; + // chip->can_byte_swap = 0; /* FIXME: check this */ + chip->num_freqs = ARRAY_SIZE(tumbler_freqs); + chip->freq_table = tumbler_freqs; + chip->control_mask = MASK_IEPC | 0x11; /* disable IEE */ + } + if (device_is_compatible(sound, "AOAKeylargo")) { + /* Seems to support the stock AWACS frequencies, but has + a snapper mixer */ + chip->model = PMAC_SNAPPER; + // chip->can_byte_swap = 0; /* FIXME: check this */ + chip->control_mask = MASK_IEPC | 0x11; /* disable IEE */ + } + prop = (unsigned int *)get_property(sound, "device-id", NULL); + if (prop) + chip->device_id = *prop; + chip->has_iic = (find_devices("perch") != NULL); + + detect_byte_swap(chip); + + /* look for a property saying what sample rates + are available */ + prop = (unsigned int *) get_property(sound, "sample-rates", &l); + if (! prop) + prop = (unsigned int *) get_property(sound, "output-frame-rates", &l); + if (prop) { + int i; + chip->freqs_ok = 0; + for (l /= sizeof(int); l > 0; --l) { + unsigned int r = *prop++; + /* Apple 'Fixed' format */ + if (r >= 0x10000) + r >>= 16; + for (i = 0; i < chip->num_freqs; ++i) { + if (r == chip->freq_table[i]) { + chip->freqs_ok |= (1 << i); + break; + } + } + } + } else { + /* assume only 44.1khz */ + chip->freqs_ok = 1; + } + + return 0; +} + +/* + * exported - boolean info callbacks for ease of programming + */ +int snd_pmac_boolean_stereo_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +int snd_pmac_boolean_mono_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +#ifdef PMAC_SUPPORT_AUTOMUTE +/* + * auto-mute + */ +static int pmac_auto_mute_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = chip->auto_mute; + return 0; +} + +static int pmac_auto_mute_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + if (ucontrol->value.integer.value[0] != chip->auto_mute) { + chip->auto_mute = ucontrol->value.integer.value[0]; + if (chip->update_automute) + chip->update_automute(chip, 1); + return 1; + } + return 0; +} + +static int pmac_hp_detect_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + if (chip->detect_headphone) + ucontrol->value.integer.value[0] = chip->detect_headphone(chip); + else + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static snd_kcontrol_new_t auto_mute_controls[] __initdata = { + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Auto Mute Switch", + .info = snd_pmac_boolean_mono_info, + .get = pmac_auto_mute_get, + .put = pmac_auto_mute_put, + }, + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Detection", + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_pmac_boolean_mono_info, + .get = pmac_hp_detect_get, + }, +}; + +int __init snd_pmac_add_automute(pmac_t *chip) +{ + int err; + chip->auto_mute = 1; + err = snd_ctl_add(chip->card, snd_ctl_new1(&auto_mute_controls[0], chip)); + if (err < 0) + return err; + chip->hp_detect_ctl = snd_ctl_new1(&auto_mute_controls[1], chip); + return snd_ctl_add(chip->card, chip->hp_detect_ctl); +} +#endif /* PMAC_SUPPORT_AUTOMUTE */ + +/* + * create and detect a pmac chip record + */ +int __init snd_pmac_new(snd_card_t *card, pmac_t **chip_return) +{ + pmac_t *chip; + struct device_node *np; + int i, err; + static snd_device_ops_t ops = { + .dev_free = snd_pmac_dev_free, + }; + + snd_runtime_check(chip_return, return -EINVAL); + *chip_return = NULL; + + chip = kcalloc(1, sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + chip->card = card; + + spin_lock_init(&chip->reg_lock); + chip->irq = chip->tx_irq = chip->rx_irq = -1; + + chip->playback.stream = SNDRV_PCM_STREAM_PLAYBACK; + chip->capture.stream = SNDRV_PCM_STREAM_CAPTURE; + + if ((err = snd_pmac_detect(chip)) < 0) + goto __error; + + if (snd_pmac_dbdma_alloc(&chip->playback.cmd, PMAC_MAX_FRAGS + 1) < 0 || + snd_pmac_dbdma_alloc(&chip->capture.cmd, PMAC_MAX_FRAGS + 1) < 0 || + snd_pmac_dbdma_alloc(&chip->extra_dma, 2) < 0) { + err = -ENOMEM; + goto __error; + } + + np = chip->node; + if (np->n_addrs < 3 || np->n_intrs < 3) { + err = -ENODEV; + goto __error; + } + + for (i = 0; i < 3; i++) { + static char *name[3] = { NULL, "- Tx DMA", "- Rx DMA" }; + if (! request_OF_resource(np, i, name[i])) { + snd_printk(KERN_ERR "pmac: can't request resource %d!\n", i); + err = -ENODEV; + goto __error; + } + chip->of_requested |= (1 << i); + } + + chip->awacs = ioremap(np->addrs[0].address, 0x1000); + chip->playback.dma = ioremap(np->addrs[1].address, 0x100); + chip->capture.dma = ioremap(np->addrs[2].address, 0x100); + if (chip->model <= PMAC_BURGUNDY) { + if (request_irq(np->intrs[0].line, snd_pmac_ctrl_intr, 0, + "PMac", (void*)chip)) { + snd_printk(KERN_ERR "pmac: unable to grab IRQ %d\n", np->intrs[0].line); + err = -EBUSY; + goto __error; + } + chip->irq = np->intrs[0].line; + } + if (request_irq(np->intrs[1].line, snd_pmac_tx_intr, 0, + "PMac Output", (void*)chip)) { + snd_printk(KERN_ERR "pmac: unable to grab IRQ %d\n", np->intrs[1].line); + err = -EBUSY; + goto __error; + } + chip->tx_irq = np->intrs[1].line; + if (request_irq(np->intrs[2].line, snd_pmac_rx_intr, 0, + "PMac Input", (void*)chip)) { + snd_printk(KERN_ERR "pmac: unable to grab IRQ %d\n", np->intrs[2].line); + err = -EBUSY; + goto __error; + } + chip->rx_irq = np->intrs[2].line; + + snd_pmac_sound_feature(chip, 1); + + /* reset */ + out_le32(&chip->awacs->control, 0x11); + + /* Powerbooks have odd ways of enabling inputs such as + an expansion-bay CD or sound from an internal modem + or a PC-card modem. */ + if (chip->is_pbook_3400) { + /* Enable CD and PC-card sound inputs. */ + /* This is done by reading from address + * f301a000, + 0x10 to enable the expansion-bay + * CD sound input, + 0x80 to enable the PC-card + * sound input. The 0x100 enables the SCSI bus + * terminator power. + */ + chip->latch_base = ioremap (0xf301a000, 0x1000); + in_8(chip->latch_base + 0x190); + } else if (chip->is_pbook_G3) { + struct device_node* mio; + for (mio = chip->node->parent; mio; mio = mio->parent) { + if (strcmp(mio->name, "mac-io") == 0 + && mio->n_addrs > 0) { + chip->macio_base = ioremap(mio->addrs[0].address, 0x40); + break; + } + } + /* Enable CD sound input. */ + /* The relevant bits for writing to this byte are 0x8f. + * I haven't found out what the 0x80 bit does. + * For the 0xf bits, writing 3 or 7 enables the CD + * input, any other value disables it. Values + * 1, 3, 5, 7 enable the microphone. Values 0, 2, + * 4, 6, 8 - f enable the input from the modem. + */ + if (chip->macio_base) + out_8(chip->macio_base + 0x37, 3); + } + + /* Reset dbdma channels */ + snd_pmac_dbdma_reset(chip); + +#if defined(CONFIG_PM) && defined(CONFIG_PMAC_PBOOK) + /* add sleep notifier */ + if (! snd_pmac_register_sleep_notifier(chip)) + snd_card_set_pm_callback(chip->card, snd_pmac_suspend, snd_pmac_resume, chip); +#endif + + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) + goto __error; + + *chip_return = chip; + return 0; + + __error: + snd_pmac_free(chip); + return err; +} + + +/* + * sleep notify for powerbook + */ + +#if defined(CONFIG_PM) && defined(CONFIG_PMAC_PBOOK) + +/* + * Save state when going to sleep, restore it afterwards. + */ + +static int snd_pmac_suspend(snd_card_t *card, pm_message_t state) +{ + pmac_t *chip = card->pm_private_data; + unsigned long flags; + + if (chip->suspend) + chip->suspend(chip); + snd_pcm_suspend_all(chip->pcm); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_pmac_beep_stop(chip); + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (chip->irq >= 0) + disable_irq(chip->irq); + if (chip->tx_irq >= 0) + disable_irq(chip->tx_irq); + if (chip->rx_irq >= 0) + disable_irq(chip->rx_irq); + snd_pmac_sound_feature(chip, 0); + return 0; +} + +static int snd_pmac_resume(snd_card_t *card) +{ + pmac_t *chip = card->pm_private_data; + + snd_pmac_sound_feature(chip, 1); + if (chip->resume) + chip->resume(chip); + /* enable CD sound input */ + if (chip->macio_base && chip->is_pbook_G3) { + out_8(chip->macio_base + 0x37, 3); + } else if (chip->is_pbook_3400) { + in_8(chip->latch_base + 0x190); + } + + snd_pmac_pcm_set_format(chip); + + if (chip->irq >= 0) + enable_irq(chip->irq); + if (chip->tx_irq >= 0) + enable_irq(chip->tx_irq); + if (chip->rx_irq >= 0) + enable_irq(chip->rx_irq); + + return 0; +} + +/* the chip is stored statically by snd_pmac_register_sleep_notifier + * because we can't have any private data for notify callback. + */ +static pmac_t *sleeping_pmac = NULL; + +static int snd_pmac_sleep_notify(struct pmu_sleep_notifier *self, int when) +{ + pmac_t *chip; + + chip = sleeping_pmac; + snd_runtime_check(chip, return 0); + + switch (when) { + case PBOOK_SLEEP_NOW: + snd_pmac_suspend(chip->card, PMSG_SUSPEND); + break; + case PBOOK_WAKE: + snd_pmac_resume(chip->card); + break; + } + return PBOOK_SLEEP_OK; +} + +static struct pmu_sleep_notifier snd_pmac_sleep_notifier = { + snd_pmac_sleep_notify, SLEEP_LEVEL_SOUND, +}; + +static int __init snd_pmac_register_sleep_notifier(pmac_t *chip) +{ + /* should be protected here.. */ + snd_assert(! sleeping_pmac, return -EBUSY); + sleeping_pmac = chip; + pmu_register_sleep_notifier(&snd_pmac_sleep_notifier); + return 0; +} + +static int snd_pmac_unregister_sleep_notifier(pmac_t *chip) +{ + /* should be protected here.. */ + snd_assert(sleeping_pmac == chip, return -ENODEV); + pmu_unregister_sleep_notifier(&snd_pmac_sleep_notifier); + sleeping_pmac = NULL; + return 0; +} + +#endif /* CONFIG_PM && CONFIG_PMAC_PBOOK */ diff --git a/sound/ppc/pmac.h b/sound/ppc/pmac.h new file mode 100644 index 00000000000..a699b01210e --- /dev/null +++ b/sound/ppc/pmac.h @@ -0,0 +1,214 @@ +/* + * Driver for PowerMac onboard soundchips + * Copyright (c) 2001 by Takashi Iwai <tiwai@suse.de> + * based on dmasound.c. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef __PMAC_H +#define __PMAC_H + +#include <linux/version.h> +#include <sound/control.h> +#include <sound/pcm.h> +#include "awacs.h" + +#include <linux/adb.h> +#ifdef CONFIG_ADB_CUDA +#include <linux/cuda.h> +#endif +#ifdef CONFIG_ADB_PMU +#include <linux/pmu.h> +#endif +#include <linux/nvram.h> +#include <linux/tty.h> +#include <linux/vt_kern.h> +#include <asm/dbdma.h> +#include <asm/prom.h> +#include <asm/machdep.h> + +/* maximum number of fragments */ +#define PMAC_MAX_FRAGS 32 + + +#define PMAC_SUPPORT_AUTOMUTE + +/* + * typedefs + */ +typedef struct snd_pmac pmac_t; +typedef struct snd_pmac_stream pmac_stream_t; +typedef struct snd_pmac_beep pmac_beep_t; +typedef struct snd_pmac_dbdma pmac_dbdma_t; + + +/* + * DBDMA space + */ +struct snd_pmac_dbdma { + unsigned long addr; + struct dbdma_cmd __iomem *cmds; + void *space; + int size; +}; + +/* + * playback/capture stream + */ +struct snd_pmac_stream { + int running; /* boolean */ + + int stream; /* PLAYBACK/CAPTURE */ + + int dma_size; /* in bytes */ + int period_size; /* in bytes */ + int buffer_size; /* in kbytes */ + int nperiods, cur_period; + + pmac_dbdma_t cmd; + volatile struct dbdma_regs __iomem *dma; + + snd_pcm_substream_t *substream; + + unsigned int cur_freqs; /* currently available frequencies */ + unsigned int cur_formats; /* currently available formats */ +}; + + +/* + */ + +enum snd_pmac_model { + PMAC_AWACS, PMAC_SCREAMER, PMAC_BURGUNDY, PMAC_DACA, PMAC_TUMBLER, PMAC_SNAPPER +}; + +struct snd_pmac { + snd_card_t *card; + + /* h/w info */ + struct device_node *node; + unsigned int revision; + unsigned int manufacturer; + unsigned int subframe; + unsigned int device_id; + enum snd_pmac_model model; + + unsigned int has_iic : 1; + unsigned int is_pbook_3400 : 1; + unsigned int is_pbook_G3 : 1; + + unsigned int can_byte_swap : 1; + unsigned int can_duplex : 1; + unsigned int can_capture : 1; + + unsigned int auto_mute : 1; + unsigned int initialized : 1; + unsigned int feature_is_set : 1; + + unsigned int of_requested; + + int num_freqs; + int *freq_table; + unsigned int freqs_ok; /* bit flags */ + unsigned int formats_ok; /* pcm hwinfo */ + int active; + int rate_index; + int format; /* current format */ + + spinlock_t reg_lock; + volatile struct awacs_regs __iomem *awacs; + int awacs_reg[8]; /* register cache */ + unsigned int hp_stat_mask; + + unsigned char __iomem *latch_base; + unsigned char __iomem *macio_base; + + pmac_stream_t playback; + pmac_stream_t capture; + + pmac_dbdma_t extra_dma; + + int irq, tx_irq, rx_irq; + + snd_pcm_t *pcm; + + pmac_beep_t *beep; + + unsigned int control_mask; /* control mask */ + + /* mixer stuffs */ + void *mixer_data; + void (*mixer_free)(pmac_t *); + snd_kcontrol_t *master_sw_ctl; + snd_kcontrol_t *speaker_sw_ctl; + snd_kcontrol_t *drc_sw_ctl; /* only used for tumbler -ReneR */ + snd_kcontrol_t *hp_detect_ctl; + + /* lowlevel callbacks */ + void (*set_format)(pmac_t *chip); + void (*update_automute)(pmac_t *chip, int do_notify); + int (*detect_headphone)(pmac_t *chip); +#ifdef CONFIG_PMAC_PBOOK + void (*suspend)(pmac_t *chip); + void (*resume)(pmac_t *chip); +#endif + +}; + + +/* exported functions */ +int snd_pmac_new(snd_card_t *card, pmac_t **chip_return); +int snd_pmac_pcm_new(pmac_t *chip); +int snd_pmac_attach_beep(pmac_t *chip); +void snd_pmac_detach_beep(pmac_t *chip); +void snd_pmac_beep_stop(pmac_t *chip); +unsigned int snd_pmac_rate_index(pmac_t *chip, pmac_stream_t *rec, unsigned int rate); + +void snd_pmac_beep_dma_start(pmac_t *chip, int bytes, unsigned long addr, int speed); +void snd_pmac_beep_dma_stop(pmac_t *chip); + +/* initialize mixer */ +int snd_pmac_awacs_init(pmac_t *chip); +int snd_pmac_burgundy_init(pmac_t *chip); +int snd_pmac_daca_init(pmac_t *chip); +int snd_pmac_tumbler_init(pmac_t *chip); +int snd_pmac_tumbler_post_init(void); + +/* i2c functions */ +typedef struct snd_pmac_keywest { + int addr; + struct i2c_client *client; + int id; + int (*init_client)(struct snd_pmac_keywest *i2c); + char *name; +} pmac_keywest_t; + +int snd_pmac_keywest_init(pmac_keywest_t *i2c); +void snd_pmac_keywest_cleanup(pmac_keywest_t *i2c); + +/* misc */ +int snd_pmac_boolean_stereo_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo); +int snd_pmac_boolean_mono_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo); + +int snd_pmac_add_automute(pmac_t *chip); + +#define big_mdelay(msec) do {\ + set_current_state(TASK_UNINTERRUPTIBLE);\ + schedule_timeout(((msec) * HZ + 999) / 1000);\ +} while (0) + +#endif /* __PMAC_H */ diff --git a/sound/ppc/powermac.c b/sound/ppc/powermac.c new file mode 100644 index 00000000000..8f1953a8e29 --- /dev/null +++ b/sound/ppc/powermac.c @@ -0,0 +1,159 @@ +/* + * Driver for PowerMac AWACS + * Copyright (c) 2001 by Takashi Iwai <tiwai@suse.de> + * based on dmasound.c. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/initval.h> +#include "pmac.h" +#include "awacs.h" +#include "burgundy.h" + +#define CHIP_NAME "PMac" + +MODULE_DESCRIPTION("PowerMac"); +MODULE_SUPPORTED_DEVICE("{{Apple,PowerMac}}"); +MODULE_LICENSE("GPL"); + +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +static int enable_beep = 1; + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for " CHIP_NAME " soundchip."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for " CHIP_NAME " soundchip."); +module_param(enable_beep, bool, 0444); +MODULE_PARM_DESC(enable_beep, "Enable beep using PCM."); + + +/* + * card entry + */ + +static snd_card_t *snd_pmac_card = NULL; + +/* + */ + +static int __init snd_pmac_probe(void) +{ + snd_card_t *card; + pmac_t *chip; + char *name_ext; + int err; + + card = snd_card_new(index, id, THIS_MODULE, 0); + if (card == NULL) + return -ENOMEM; + + if ((err = snd_pmac_new(card, &chip)) < 0) + goto __error; + + switch (chip->model) { + case PMAC_BURGUNDY: + strcpy(card->driver, "PMac Burgundy"); + strcpy(card->shortname, "PowerMac Burgundy"); + sprintf(card->longname, "%s (Dev %d) Sub-frame %d", + card->shortname, chip->device_id, chip->subframe); + if ((err = snd_pmac_burgundy_init(chip)) < 0) + goto __error; + break; + case PMAC_DACA: + strcpy(card->driver, "PMac DACA"); + strcpy(card->shortname, "PowerMac DACA"); + sprintf(card->longname, "%s (Dev %d) Sub-frame %d", + card->shortname, chip->device_id, chip->subframe); + if ((err = snd_pmac_daca_init(chip)) < 0) + goto __error; + break; + case PMAC_TUMBLER: + case PMAC_SNAPPER: + name_ext = chip->model == PMAC_TUMBLER ? "Tumbler" : "Snapper"; + sprintf(card->driver, "PMac %s", name_ext); + sprintf(card->shortname, "PowerMac %s", name_ext); + sprintf(card->longname, "%s (Dev %d) Sub-frame %d", + card->shortname, chip->device_id, chip->subframe); + if ( snd_pmac_tumbler_init(chip) < 0 || snd_pmac_tumbler_post_init() < 0) + goto __error; + break; + case PMAC_AWACS: + case PMAC_SCREAMER: + name_ext = chip->model == PMAC_SCREAMER ? "Screamer" : "AWACS"; + sprintf(card->driver, "PMac %s", name_ext); + sprintf(card->shortname, "PowerMac %s", name_ext); + if (chip->is_pbook_3400) + name_ext = " [PB3400]"; + else if (chip->is_pbook_G3) + name_ext = " [PBG3]"; + else + name_ext = ""; + sprintf(card->longname, "%s%s Rev %d", + card->shortname, name_ext, chip->revision); + if ((err = snd_pmac_awacs_init(chip)) < 0) + goto __error; + break; + default: + snd_printk("unsupported hardware %d\n", chip->model); + err = -EINVAL; + goto __error; + } + + if ((err = snd_pmac_pcm_new(chip)) < 0) + goto __error; + + chip->initialized = 1; + if (enable_beep) + snd_pmac_attach_beep(chip); + + if ((err = snd_card_register(card)) < 0) + goto __error; + + snd_pmac_card = card; + return 0; + +__error: + snd_card_free(card); + return err; +} + + +/* + * MODULE stuff + */ + +static int __init alsa_card_pmac_init(void) +{ + int err; + if ((err = snd_pmac_probe()) < 0) + return err; + return 0; + +} + +static void __exit alsa_card_pmac_exit(void) +{ + if (snd_pmac_card) + snd_card_free(snd_pmac_card); +} + +module_init(alsa_card_pmac_init) +module_exit(alsa_card_pmac_exit) diff --git a/sound/ppc/tumbler.c b/sound/ppc/tumbler.c new file mode 100644 index 00000000000..7d10385f0a7 --- /dev/null +++ b/sound/ppc/tumbler.c @@ -0,0 +1,1175 @@ +/* + * PMac Tumbler/Snapper lowlevel functions + * + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Rene Rebe <rene.rebe@gmx.net>: + * * update from shadow registers on wakeup and headphone plug + * * automatically toggle DRC on headphone plug + * + */ + + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/kmod.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <sound/core.h> +#include <asm/io.h> +#include <asm/irq.h> +#ifdef CONFIG_PPC_HAS_FEATURE_CALLS +#include <asm/pmac_feature.h> +#endif +#include "pmac.h" +#include "tumbler_volume.h" + +/* i2c address for tumbler */ +#define TAS_I2C_ADDR 0x34 + +/* registers */ +#define TAS_REG_MCS 0x01 /* main control */ +#define TAS_REG_DRC 0x02 +#define TAS_REG_VOL 0x04 +#define TAS_REG_TREBLE 0x05 +#define TAS_REG_BASS 0x06 +#define TAS_REG_INPUT1 0x07 +#define TAS_REG_INPUT2 0x08 + +/* tas3001c */ +#define TAS_REG_PCM TAS_REG_INPUT1 + +/* tas3004 */ +#define TAS_REG_LMIX TAS_REG_INPUT1 +#define TAS_REG_RMIX TAS_REG_INPUT2 +#define TAS_REG_MCS2 0x43 /* main control 2 */ +#define TAS_REG_ACS 0x40 /* analog control */ + +/* mono volumes for tas3001c/tas3004 */ +enum { + VOL_IDX_PCM_MONO, /* tas3001c only */ + VOL_IDX_BASS, VOL_IDX_TREBLE, + VOL_IDX_LAST_MONO +}; + +/* stereo volumes for tas3004 */ +enum { + VOL_IDX_PCM, VOL_IDX_PCM2, VOL_IDX_ADC, + VOL_IDX_LAST_MIX +}; + +typedef struct pmac_gpio { +#ifdef CONFIG_PPC_HAS_FEATURE_CALLS + unsigned int addr; +#else + void __iomem *addr; +#endif + int active_state; +} pmac_gpio_t; + +typedef struct pmac_tumbler_t { + pmac_keywest_t i2c; + pmac_gpio_t audio_reset; + pmac_gpio_t amp_mute; + pmac_gpio_t hp_mute; + pmac_gpio_t hp_detect; + int headphone_irq; + unsigned int master_vol[2]; + unsigned int master_switch[2]; + unsigned int mono_vol[VOL_IDX_LAST_MONO]; + unsigned int mix_vol[VOL_IDX_LAST_MIX][2]; /* stereo volumes for tas3004 */ + int drc_range; + int drc_enable; + int capture_source; +} pmac_tumbler_t; + + +/* + */ + +static int send_init_client(pmac_keywest_t *i2c, unsigned int *regs) +{ + while (*regs > 0) { + int err, count = 10; + do { + err = i2c_smbus_write_byte_data(i2c->client, + regs[0], regs[1]); + if (err >= 0) + break; + mdelay(10); + } while (count--); + if (err < 0) + return -ENXIO; + regs += 2; + } + return 0; +} + + +static int tumbler_init_client(pmac_keywest_t *i2c) +{ + static unsigned int regs[] = { + /* normal operation, SCLK=64fps, i2s output, i2s input, 16bit width */ + TAS_REG_MCS, (1<<6)|(2<<4)|(2<<2)|0, + 0, /* terminator */ + }; + return send_init_client(i2c, regs); +} + +static int snapper_init_client(pmac_keywest_t *i2c) +{ + static unsigned int regs[] = { + /* normal operation, SCLK=64fps, i2s output, 16bit width */ + TAS_REG_MCS, (1<<6)|(2<<4)|0, + /* normal operation, all-pass mode */ + TAS_REG_MCS2, (1<<1), + /* normal output, no deemphasis, A input, power-up, line-in */ + TAS_REG_ACS, 0, + 0, /* terminator */ + }; + return send_init_client(i2c, regs); +} + +/* + * gpio access + */ +#ifdef CONFIG_PPC_HAS_FEATURE_CALLS +#define do_gpio_write(gp, val) \ + pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, (gp)->addr, val) +#define do_gpio_read(gp) \ + pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, (gp)->addr, 0) +#define tumbler_gpio_free(gp) /* NOP */ +#else +#define do_gpio_write(gp, val) writeb(val, (gp)->addr) +#define do_gpio_read(gp) readb((gp)->addr) +static inline void tumbler_gpio_free(pmac_gpio_t *gp) +{ + if (gp->addr) { + iounmap(gp->addr); + gp->addr = NULL; + } +} +#endif /* CONFIG_PPC_HAS_FEATURE_CALLS */ + +static void write_audio_gpio(pmac_gpio_t *gp, int active) +{ + if (! gp->addr) + return; + active = active ? gp->active_state : !gp->active_state; + do_gpio_write(gp, active ? 0x05 : 0x04); +} + +static int read_audio_gpio(pmac_gpio_t *gp) +{ + int ret; + if (! gp->addr) + return 0; + ret = ((do_gpio_read(gp) & 0x02) !=0); + return ret == gp->active_state; +} + +/* + * update master volume + */ +static int tumbler_set_master_volume(pmac_tumbler_t *mix) +{ + unsigned char block[6]; + unsigned int left_vol, right_vol; + + if (! mix->i2c.client) + return -ENODEV; + + if (! mix->master_switch[0]) + left_vol = 0; + else { + left_vol = mix->master_vol[0]; + if (left_vol >= ARRAY_SIZE(master_volume_table)) + left_vol = ARRAY_SIZE(master_volume_table) - 1; + left_vol = master_volume_table[left_vol]; + } + if (! mix->master_switch[1]) + right_vol = 0; + else { + right_vol = mix->master_vol[1]; + if (right_vol >= ARRAY_SIZE(master_volume_table)) + right_vol = ARRAY_SIZE(master_volume_table) - 1; + right_vol = master_volume_table[right_vol]; + } + + block[0] = (left_vol >> 16) & 0xff; + block[1] = (left_vol >> 8) & 0xff; + block[2] = (left_vol >> 0) & 0xff; + + block[3] = (right_vol >> 16) & 0xff; + block[4] = (right_vol >> 8) & 0xff; + block[5] = (right_vol >> 0) & 0xff; + + if (i2c_smbus_write_block_data(mix->i2c.client, TAS_REG_VOL, + 6, block) < 0) { + snd_printk("failed to set volume \n"); + return -EINVAL; + } + return 0; +} + + +/* output volume */ +static int tumbler_info_master_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = ARRAY_SIZE(master_volume_table) - 1; + return 0; +} + +static int tumbler_get_master_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_tumbler_t *mix = chip->mixer_data; + snd_assert(mix, return -ENODEV); + ucontrol->value.integer.value[0] = mix->master_vol[0]; + ucontrol->value.integer.value[1] = mix->master_vol[1]; + return 0; +} + +static int tumbler_put_master_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_tumbler_t *mix = chip->mixer_data; + int change; + + snd_assert(mix, return -ENODEV); + change = mix->master_vol[0] != ucontrol->value.integer.value[0] || + mix->master_vol[1] != ucontrol->value.integer.value[1]; + if (change) { + mix->master_vol[0] = ucontrol->value.integer.value[0]; + mix->master_vol[1] = ucontrol->value.integer.value[1]; + tumbler_set_master_volume(mix); + } + return change; +} + +/* output switch */ +static int tumbler_get_master_switch(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_tumbler_t *mix = chip->mixer_data; + snd_assert(mix, return -ENODEV); + ucontrol->value.integer.value[0] = mix->master_switch[0]; + ucontrol->value.integer.value[1] = mix->master_switch[1]; + return 0; +} + +static int tumbler_put_master_switch(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_tumbler_t *mix = chip->mixer_data; + int change; + + snd_assert(mix, return -ENODEV); + change = mix->master_switch[0] != ucontrol->value.integer.value[0] || + mix->master_switch[1] != ucontrol->value.integer.value[1]; + if (change) { + mix->master_switch[0] = !!ucontrol->value.integer.value[0]; + mix->master_switch[1] = !!ucontrol->value.integer.value[1]; + tumbler_set_master_volume(mix); + } + return change; +} + + +/* + * TAS3001c dynamic range compression + */ + +#define TAS3001_DRC_MAX 0x5f + +static int tumbler_set_drc(pmac_tumbler_t *mix) +{ + unsigned char val[2]; + + if (! mix->i2c.client) + return -ENODEV; + + if (mix->drc_enable) { + val[0] = 0xc1; /* enable, 3:1 compression */ + if (mix->drc_range > TAS3001_DRC_MAX) + val[1] = 0xf0; + else if (mix->drc_range < 0) + val[1] = 0x91; + else + val[1] = mix->drc_range + 0x91; + } else { + val[0] = 0; + val[1] = 0; + } + + if (i2c_smbus_write_block_data(mix->i2c.client, TAS_REG_DRC, + 2, val) < 0) { + snd_printk("failed to set DRC\n"); + return -EINVAL; + } + return 0; +} + +/* + * TAS3004 + */ + +#define TAS3004_DRC_MAX 0xef + +static int snapper_set_drc(pmac_tumbler_t *mix) +{ + unsigned char val[6]; + + if (! mix->i2c.client) + return -ENODEV; + + if (mix->drc_enable) + val[0] = 0x50; /* 3:1 above threshold */ + else + val[0] = 0x51; /* disabled */ + val[1] = 0x02; /* 1:1 below threshold */ + if (mix->drc_range > 0xef) + val[2] = 0xef; + else if (mix->drc_range < 0) + val[2] = 0x00; + else + val[2] = mix->drc_range; + val[3] = 0xb0; + val[4] = 0x60; + val[5] = 0xa0; + + if (i2c_smbus_write_block_data(mix->i2c.client, TAS_REG_DRC, + 6, val) < 0) { + snd_printk("failed to set DRC\n"); + return -EINVAL; + } + return 0; +} + +static int tumbler_info_drc_value(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = + chip->model == PMAC_TUMBLER ? TAS3001_DRC_MAX : TAS3004_DRC_MAX; + return 0; +} + +static int tumbler_get_drc_value(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_tumbler_t *mix; + if (! (mix = chip->mixer_data)) + return -ENODEV; + ucontrol->value.integer.value[0] = mix->drc_range; + return 0; +} + +static int tumbler_put_drc_value(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_tumbler_t *mix; + int change; + + if (! (mix = chip->mixer_data)) + return -ENODEV; + change = mix->drc_range != ucontrol->value.integer.value[0]; + if (change) { + mix->drc_range = ucontrol->value.integer.value[0]; + if (chip->model == PMAC_TUMBLER) + tumbler_set_drc(mix); + else + snapper_set_drc(mix); + } + return change; +} + +static int tumbler_get_drc_switch(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_tumbler_t *mix; + if (! (mix = chip->mixer_data)) + return -ENODEV; + ucontrol->value.integer.value[0] = mix->drc_enable; + return 0; +} + +static int tumbler_put_drc_switch(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_tumbler_t *mix; + int change; + + if (! (mix = chip->mixer_data)) + return -ENODEV; + change = mix->drc_enable != ucontrol->value.integer.value[0]; + if (change) { + mix->drc_enable = !!ucontrol->value.integer.value[0]; + if (chip->model == PMAC_TUMBLER) + tumbler_set_drc(mix); + else + snapper_set_drc(mix); + } + return change; +} + + +/* + * mono volumes + */ + +struct tumbler_mono_vol { + int index; + int reg; + int bytes; + unsigned int max; + unsigned int *table; +}; + +static int tumbler_set_mono_volume(pmac_tumbler_t *mix, struct tumbler_mono_vol *info) +{ + unsigned char block[4]; + unsigned int vol; + int i; + + if (! mix->i2c.client) + return -ENODEV; + + vol = mix->mono_vol[info->index]; + if (vol >= info->max) + vol = info->max - 1; + vol = info->table[vol]; + for (i = 0; i < info->bytes; i++) + block[i] = (vol >> ((info->bytes - i - 1) * 8)) & 0xff; + if (i2c_smbus_write_block_data(mix->i2c.client, info->reg, + info->bytes, block) < 0) { + snd_printk("failed to set mono volume %d\n", info->index); + return -EINVAL; + } + return 0; +} + +static int tumbler_info_mono(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + struct tumbler_mono_vol *info = (struct tumbler_mono_vol *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = info->max - 1; + return 0; +} + +static int tumbler_get_mono(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + struct tumbler_mono_vol *info = (struct tumbler_mono_vol *)kcontrol->private_value; + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_tumbler_t *mix; + if (! (mix = chip->mixer_data)) + return -ENODEV; + ucontrol->value.integer.value[0] = mix->mono_vol[info->index]; + return 0; +} + +static int tumbler_put_mono(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + struct tumbler_mono_vol *info = (struct tumbler_mono_vol *)kcontrol->private_value; + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_tumbler_t *mix; + int change; + + if (! (mix = chip->mixer_data)) + return -ENODEV; + change = mix->mono_vol[info->index] != ucontrol->value.integer.value[0]; + if (change) { + mix->mono_vol[info->index] = ucontrol->value.integer.value[0]; + tumbler_set_mono_volume(mix, info); + } + return change; +} + +/* TAS3001c mono volumes */ +static struct tumbler_mono_vol tumbler_pcm_vol_info = { + .index = VOL_IDX_PCM_MONO, + .reg = TAS_REG_PCM, + .bytes = 3, + .max = ARRAY_SIZE(mixer_volume_table), + .table = mixer_volume_table, +}; + +static struct tumbler_mono_vol tumbler_bass_vol_info = { + .index = VOL_IDX_BASS, + .reg = TAS_REG_BASS, + .bytes = 1, + .max = ARRAY_SIZE(bass_volume_table), + .table = bass_volume_table, +}; + +static struct tumbler_mono_vol tumbler_treble_vol_info = { + .index = VOL_IDX_TREBLE, + .reg = TAS_REG_TREBLE, + .bytes = 1, + .max = ARRAY_SIZE(treble_volume_table), + .table = treble_volume_table, +}; + +/* TAS3004 mono volumes */ +static struct tumbler_mono_vol snapper_bass_vol_info = { + .index = VOL_IDX_BASS, + .reg = TAS_REG_BASS, + .bytes = 1, + .max = ARRAY_SIZE(snapper_bass_volume_table), + .table = snapper_bass_volume_table, +}; + +static struct tumbler_mono_vol snapper_treble_vol_info = { + .index = VOL_IDX_TREBLE, + .reg = TAS_REG_TREBLE, + .bytes = 1, + .max = ARRAY_SIZE(snapper_treble_volume_table), + .table = snapper_treble_volume_table, +}; + + +#define DEFINE_MONO(xname,type) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER,\ + .name = xname, \ + .info = tumbler_info_mono, \ + .get = tumbler_get_mono, \ + .put = tumbler_put_mono, \ + .private_value = (unsigned long)(&tumbler_##type##_vol_info), \ +} + +#define DEFINE_SNAPPER_MONO(xname,type) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER,\ + .name = xname, \ + .info = tumbler_info_mono, \ + .get = tumbler_get_mono, \ + .put = tumbler_put_mono, \ + .private_value = (unsigned long)(&snapper_##type##_vol_info), \ +} + + +/* + * snapper mixer volumes + */ + +static int snapper_set_mix_vol1(pmac_tumbler_t *mix, int idx, int ch, int reg) +{ + int i, j, vol; + unsigned char block[9]; + + vol = mix->mix_vol[idx][ch]; + if (vol >= ARRAY_SIZE(mixer_volume_table)) { + vol = ARRAY_SIZE(mixer_volume_table) - 1; + mix->mix_vol[idx][ch] = vol; + } + + for (i = 0; i < 3; i++) { + vol = mix->mix_vol[i][ch]; + vol = mixer_volume_table[vol]; + for (j = 0; j < 3; j++) + block[i * 3 + j] = (vol >> ((2 - j) * 8)) & 0xff; + } + if (i2c_smbus_write_block_data(mix->i2c.client, reg, 9, block) < 0) { + snd_printk("failed to set mono volume %d\n", reg); + return -EINVAL; + } + return 0; +} + +static int snapper_set_mix_vol(pmac_tumbler_t *mix, int idx) +{ + if (! mix->i2c.client) + return -ENODEV; + if (snapper_set_mix_vol1(mix, idx, 0, TAS_REG_LMIX) < 0 || + snapper_set_mix_vol1(mix, idx, 1, TAS_REG_RMIX) < 0) + return -EINVAL; + return 0; +} + +static int snapper_info_mix(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = ARRAY_SIZE(mixer_volume_table) - 1; + return 0; +} + +static int snapper_get_mix(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + int idx = (int)kcontrol->private_value; + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_tumbler_t *mix; + if (! (mix = chip->mixer_data)) + return -ENODEV; + ucontrol->value.integer.value[0] = mix->mix_vol[idx][0]; + ucontrol->value.integer.value[1] = mix->mix_vol[idx][1]; + return 0; +} + +static int snapper_put_mix(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + int idx = (int)kcontrol->private_value; + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_tumbler_t *mix; + int change; + + if (! (mix = chip->mixer_data)) + return -ENODEV; + change = mix->mix_vol[idx][0] != ucontrol->value.integer.value[0] || + mix->mix_vol[idx][1] != ucontrol->value.integer.value[1]; + if (change) { + mix->mix_vol[idx][0] = ucontrol->value.integer.value[0]; + mix->mix_vol[idx][1] = ucontrol->value.integer.value[1]; + snapper_set_mix_vol(mix, idx); + } + return change; +} + + +/* + * mute switches + */ + +enum { TUMBLER_MUTE_HP, TUMBLER_MUTE_AMP }; + +static int tumbler_get_mute_switch(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_tumbler_t *mix; + pmac_gpio_t *gp; + if (! (mix = chip->mixer_data)) + return -ENODEV; + gp = (kcontrol->private_value == TUMBLER_MUTE_HP) ? &mix->hp_mute : &mix->amp_mute; + ucontrol->value.integer.value[0] = ! read_audio_gpio(gp); + return 0; +} + +static int tumbler_put_mute_switch(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_tumbler_t *mix; + pmac_gpio_t *gp; + int val; +#ifdef PMAC_SUPPORT_AUTOMUTE + if (chip->update_automute && chip->auto_mute) + return 0; /* don't touch in the auto-mute mode */ +#endif + if (! (mix = chip->mixer_data)) + return -ENODEV; + gp = (kcontrol->private_value == TUMBLER_MUTE_HP) ? &mix->hp_mute : &mix->amp_mute; + val = ! read_audio_gpio(gp); + if (val != ucontrol->value.integer.value[0]) { + write_audio_gpio(gp, ! ucontrol->value.integer.value[0]); + return 1; + } + return 0; +} + +static int snapper_set_capture_source(pmac_tumbler_t *mix) +{ + if (! mix->i2c.client) + return -ENODEV; + return i2c_smbus_write_byte_data(mix->i2c.client, TAS_REG_ACS, + mix->capture_source ? 2 : 0); +} + +static int snapper_info_capture_source(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + static char *texts[2] = { + "Line", "Mic" + }; + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 2; + if (uinfo->value.enumerated.item > 1) + uinfo->value.enumerated.item = 1; + strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); + return 0; +} + +static int snapper_get_capture_source(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_tumbler_t *mix = chip->mixer_data; + + snd_assert(mix, return -ENODEV); + ucontrol->value.integer.value[0] = mix->capture_source; + return 0; +} + +static int snapper_put_capture_source(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + pmac_t *chip = snd_kcontrol_chip(kcontrol); + pmac_tumbler_t *mix = chip->mixer_data; + int change; + + snd_assert(mix, return -ENODEV); + change = ucontrol->value.integer.value[0] != mix->capture_source; + if (change) { + mix->capture_source = !!ucontrol->value.integer.value[0]; + snapper_set_capture_source(mix); + } + return change; +} + +#define DEFINE_SNAPPER_MIX(xname,idx,ofs) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER,\ + .name = xname, \ + .info = snapper_info_mix, \ + .get = snapper_get_mix, \ + .put = snapper_put_mix, \ + .index = idx,\ + .private_value = ofs, \ +} + + +/* + */ +static snd_kcontrol_new_t tumbler_mixers[] __initdata = { + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Volume", + .info = tumbler_info_master_volume, + .get = tumbler_get_master_volume, + .put = tumbler_put_master_volume + }, + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_pmac_boolean_stereo_info, + .get = tumbler_get_master_switch, + .put = tumbler_put_master_switch + }, + DEFINE_MONO("Tone Control - Bass", bass), + DEFINE_MONO("Tone Control - Treble", treble), + DEFINE_MONO("PCM Playback Volume", pcm), + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DRC Range", + .info = tumbler_info_drc_value, + .get = tumbler_get_drc_value, + .put = tumbler_put_drc_value + }, +}; + +static snd_kcontrol_new_t snapper_mixers[] __initdata = { + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Volume", + .info = tumbler_info_master_volume, + .get = tumbler_get_master_volume, + .put = tumbler_put_master_volume + }, + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_pmac_boolean_stereo_info, + .get = tumbler_get_master_switch, + .put = tumbler_put_master_switch + }, + DEFINE_SNAPPER_MIX("PCM Playback Volume", 0, VOL_IDX_PCM), + DEFINE_SNAPPER_MIX("PCM Playback Volume", 1, VOL_IDX_PCM2), + DEFINE_SNAPPER_MIX("Monitor Mix Volume", 0, VOL_IDX_ADC), + DEFINE_SNAPPER_MONO("Tone Control - Bass", bass), + DEFINE_SNAPPER_MONO("Tone Control - Treble", treble), + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DRC Range", + .info = tumbler_info_drc_value, + .get = tumbler_get_drc_value, + .put = tumbler_put_drc_value + }, + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Source", /* FIXME: "Capture Source" doesn't work properly */ + .info = snapper_info_capture_source, + .get = snapper_get_capture_source, + .put = snapper_put_capture_source + }, +}; + +static snd_kcontrol_new_t tumbler_hp_sw __initdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Playback Switch", + .info = snd_pmac_boolean_mono_info, + .get = tumbler_get_mute_switch, + .put = tumbler_put_mute_switch, + .private_value = TUMBLER_MUTE_HP, +}; +static snd_kcontrol_new_t tumbler_speaker_sw __initdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PC Speaker Playback Switch", + .info = snd_pmac_boolean_mono_info, + .get = tumbler_get_mute_switch, + .put = tumbler_put_mute_switch, + .private_value = TUMBLER_MUTE_AMP, +}; +static snd_kcontrol_new_t tumbler_drc_sw __initdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DRC Switch", + .info = snd_pmac_boolean_mono_info, + .get = tumbler_get_drc_switch, + .put = tumbler_put_drc_switch +}; + + +#ifdef PMAC_SUPPORT_AUTOMUTE +/* + * auto-mute stuffs + */ +static int tumbler_detect_headphone(pmac_t *chip) +{ + pmac_tumbler_t *mix = chip->mixer_data; + return read_audio_gpio(&mix->hp_detect); +} + +static void check_mute(pmac_t *chip, pmac_gpio_t *gp, int val, int do_notify, snd_kcontrol_t *sw) +{ + //pmac_tumbler_t *mix = chip->mixer_data; + if (val != read_audio_gpio(gp)) { + write_audio_gpio(gp, val); + if (do_notify) + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &sw->id); + } +} + +static struct work_struct device_change; + +static void +device_change_handler(void *self) +{ + pmac_t *chip = (pmac_t*) self; + pmac_tumbler_t *mix; + + if (!chip) + return; + + mix = chip->mixer_data; + + /* first set the DRC so the speaker do not explode -ReneR */ + if (chip->model == PMAC_TUMBLER) + tumbler_set_drc(mix); + else + snapper_set_drc(mix); + + /* reset the master volume so the correct amplification is applied */ + tumbler_set_master_volume(mix); +} + +static void tumbler_update_automute(pmac_t *chip, int do_notify) +{ + if (chip->auto_mute) { + pmac_tumbler_t *mix = chip->mixer_data; + snd_assert(mix, return); + if (tumbler_detect_headphone(chip)) { + /* mute speaker */ + check_mute(chip, &mix->amp_mute, 1, do_notify, chip->speaker_sw_ctl); + check_mute(chip, &mix->hp_mute, 0, do_notify, chip->master_sw_ctl); + mix->drc_enable = 0; + + } else { + /* unmute speaker */ + check_mute(chip, &mix->amp_mute, 0, do_notify, chip->speaker_sw_ctl); + check_mute(chip, &mix->hp_mute, 1, do_notify, chip->master_sw_ctl); + mix->drc_enable = 1; + } + if (do_notify) { + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->hp_detect_ctl->id); + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->drc_sw_ctl->id); + } + + /* finally we need to schedule an update of the mixer values + (master and DRC are enough for now) -ReneR */ + schedule_work(&device_change); + + } +} +#endif /* PMAC_SUPPORT_AUTOMUTE */ + + +/* interrupt - headphone plug changed */ +static irqreturn_t headphone_intr(int irq, void *devid, struct pt_regs *regs) +{ + pmac_t *chip = devid; + if (chip->update_automute && chip->initialized) { + chip->update_automute(chip, 1); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +/* look for audio-gpio device */ +static struct device_node *find_audio_device(const char *name) +{ + struct device_node *np; + + if (! (np = find_devices("gpio"))) + return NULL; + + for (np = np->child; np; np = np->sibling) { + char *property = get_property(np, "audio-gpio", NULL); + if (property && strcmp(property, name) == 0) + return np; + } + return NULL; +} + +/* look for audio-gpio device */ +static struct device_node *find_compatible_audio_device(const char *name) +{ + struct device_node *np; + + if (! (np = find_devices("gpio"))) + return NULL; + + for (np = np->child; np; np = np->sibling) { + if (device_is_compatible(np, name)) + return np; + } + return NULL; +} + +/* find an audio device and get its address */ +static unsigned long tumbler_find_device(const char *device, pmac_gpio_t *gp, int is_compatible) +{ + struct device_node *node; + u32 *base; + + if (is_compatible) + node = find_compatible_audio_device(device); + else + node = find_audio_device(device); + if (! node) { + snd_printdd("cannot find device %s\n", device); + return -ENODEV; + } + + base = (u32 *)get_property(node, "AAPL,address", NULL); + if (! base) { + snd_printd("cannot find address for device %s\n", device); + return -ENODEV; + } + +#ifdef CONFIG_PPC_HAS_FEATURE_CALLS + gp->addr = (*base) & 0x0000ffff; +#else + gp->addr = ioremap((unsigned long)(*base), 1); +#endif + base = (u32 *)get_property(node, "audio-gpio-active-state", NULL); + if (base) + gp->active_state = *base; + else + gp->active_state = 1; + + + return (node->n_intrs > 0) ? node->intrs[0].line : 0; +} + +/* reset audio */ +static void tumbler_reset_audio(pmac_t *chip) +{ + pmac_tumbler_t *mix = chip->mixer_data; + + write_audio_gpio(&mix->audio_reset, 0); + big_mdelay(200); + write_audio_gpio(&mix->audio_reset, 1); + big_mdelay(100); + write_audio_gpio(&mix->audio_reset, 0); + big_mdelay(100); +} + +#ifdef CONFIG_PMAC_PBOOK +/* resume mixer */ +static void tumbler_resume(pmac_t *chip) +{ + pmac_tumbler_t *mix = chip->mixer_data; + + snd_assert(mix, return); + + tumbler_reset_audio(chip); + if (mix->i2c.client && mix->i2c.init_client) { + if (mix->i2c.init_client(&mix->i2c) < 0) + printk(KERN_ERR "tumbler_init_client error\n"); + } else + printk(KERN_ERR "tumbler: i2c is not initialized\n"); + if (chip->model == PMAC_TUMBLER) { + tumbler_set_mono_volume(mix, &tumbler_pcm_vol_info); + tumbler_set_mono_volume(mix, &tumbler_bass_vol_info); + tumbler_set_mono_volume(mix, &tumbler_treble_vol_info); + tumbler_set_drc(mix); + } else { + snapper_set_mix_vol(mix, VOL_IDX_PCM); + snapper_set_mix_vol(mix, VOL_IDX_PCM2); + snapper_set_mix_vol(mix, VOL_IDX_ADC); + tumbler_set_mono_volume(mix, &snapper_bass_vol_info); + tumbler_set_mono_volume(mix, &snapper_treble_vol_info); + snapper_set_drc(mix); + snapper_set_capture_source(mix); + } + tumbler_set_master_volume(mix); + if (chip->update_automute) + chip->update_automute(chip, 0); +} +#endif + +/* initialize tumbler */ +static int __init tumbler_init(pmac_t *chip) +{ + int irq, err; + pmac_tumbler_t *mix = chip->mixer_data; + snd_assert(mix, return -EINVAL); + + tumbler_find_device("audio-hw-reset", &mix->audio_reset, 0); + tumbler_find_device("amp-mute", &mix->amp_mute, 0); + tumbler_find_device("headphone-mute", &mix->hp_mute, 0); + irq = tumbler_find_device("headphone-detect", &mix->hp_detect, 0); + if (irq < 0) + irq = tumbler_find_device("keywest-gpio15", &mix->hp_detect, 1); + + tumbler_reset_audio(chip); + + /* activate headphone status interrupts */ + if (irq >= 0) { + unsigned char val; + if ((err = request_irq(irq, headphone_intr, 0, + "Tumbler Headphone Detection", chip)) < 0) + return err; + /* activate headphone status interrupts */ + val = do_gpio_read(&mix->hp_detect); + do_gpio_write(&mix->hp_detect, val | 0x80); + } + mix->headphone_irq = irq; + + return 0; +} + +static void tumbler_cleanup(pmac_t *chip) +{ + pmac_tumbler_t *mix = chip->mixer_data; + if (! mix) + return; + + if (mix->headphone_irq >= 0) + free_irq(mix->headphone_irq, chip); + tumbler_gpio_free(&mix->audio_reset); + tumbler_gpio_free(&mix->amp_mute); + tumbler_gpio_free(&mix->hp_mute); + tumbler_gpio_free(&mix->hp_detect); + snd_pmac_keywest_cleanup(&mix->i2c); + kfree(mix); + chip->mixer_data = NULL; +} + +/* exported */ +int __init snd_pmac_tumbler_init(pmac_t *chip) +{ + int i, err; + pmac_tumbler_t *mix; + u32 *paddr; + struct device_node *tas_node; + char *chipname; + +#ifdef CONFIG_KMOD + if (current->fs->root) + request_module("i2c-keywest"); +#endif /* CONFIG_KMOD */ + + mix = kmalloc(sizeof(*mix), GFP_KERNEL); + if (! mix) + return -ENOMEM; + memset(mix, 0, sizeof(*mix)); + mix->headphone_irq = -1; + + chip->mixer_data = mix; + chip->mixer_free = tumbler_cleanup; + + if ((err = tumbler_init(chip)) < 0) + return err; + + /* set up TAS */ + tas_node = find_devices("deq"); + if (tas_node == NULL) + return -ENODEV; + + paddr = (u32 *)get_property(tas_node, "i2c-address", NULL); + if (paddr) + mix->i2c.addr = (*paddr) >> 1; + else + mix->i2c.addr = TAS_I2C_ADDR; + + if (chip->model == PMAC_TUMBLER) { + mix->i2c.init_client = tumbler_init_client; + mix->i2c.name = "TAS3001c"; + chipname = "Tumbler"; + } else { + mix->i2c.init_client = snapper_init_client; + mix->i2c.name = "TAS3004"; + chipname = "Snapper"; + } + + if ((err = snd_pmac_keywest_init(&mix->i2c)) < 0) + return err; + + /* + * build mixers + */ + sprintf(chip->card->mixername, "PowerMac %s", chipname); + + if (chip->model == PMAC_TUMBLER) { + for (i = 0; i < ARRAY_SIZE(tumbler_mixers); i++) { + if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&tumbler_mixers[i], chip))) < 0) + return err; + } + } else { + for (i = 0; i < ARRAY_SIZE(snapper_mixers); i++) { + if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snapper_mixers[i], chip))) < 0) + return err; + } + } + chip->master_sw_ctl = snd_ctl_new1(&tumbler_hp_sw, chip); + if ((err = snd_ctl_add(chip->card, chip->master_sw_ctl)) < 0) + return err; + chip->speaker_sw_ctl = snd_ctl_new1(&tumbler_speaker_sw, chip); + if ((err = snd_ctl_add(chip->card, chip->speaker_sw_ctl)) < 0) + return err; + chip->drc_sw_ctl = snd_ctl_new1(&tumbler_drc_sw, chip); + if ((err = snd_ctl_add(chip->card, chip->drc_sw_ctl)) < 0) + return err; + + +#ifdef CONFIG_PMAC_PBOOK + chip->resume = tumbler_resume; +#endif + + INIT_WORK(&device_change, device_change_handler, (void *)chip); + +#ifdef PMAC_SUPPORT_AUTOMUTE + if (mix->headphone_irq >=0 && (err = snd_pmac_add_automute(chip)) < 0) + return err; + chip->detect_headphone = tumbler_detect_headphone; + chip->update_automute = tumbler_update_automute; + tumbler_update_automute(chip, 0); /* update the status only */ +#endif + + return 0; +} diff --git a/sound/ppc/tumbler_volume.h b/sound/ppc/tumbler_volume.h new file mode 100644 index 00000000000..ef8d85d58b0 --- /dev/null +++ b/sound/ppc/tumbler_volume.h @@ -0,0 +1,250 @@ +/* volume tables, taken from TAS3001c data manual */ +/* volume gain values */ +/* 0 = -70 dB, 175 = 18.0 dB in 0.5 dB step */ +static unsigned int master_volume_table[] = { + 0x00000015, 0x00000016, 0x00000017, + 0x00000019, 0x0000001a, 0x0000001c, + 0x0000001d, 0x0000001f, 0x00000021, + 0x00000023, 0x00000025, 0x00000027, + 0x00000029, 0x0000002c, 0x0000002e, + 0x00000031, 0x00000034, 0x00000037, + 0x0000003a, 0x0000003e, 0x00000042, + 0x00000045, 0x0000004a, 0x0000004e, + 0x00000053, 0x00000057, 0x0000005d, + 0x00000062, 0x00000068, 0x0000006e, + 0x00000075, 0x0000007b, 0x00000083, + 0x0000008b, 0x00000093, 0x0000009b, + 0x000000a5, 0x000000ae, 0x000000b9, + 0x000000c4, 0x000000cf, 0x000000dc, + 0x000000e9, 0x000000f6, 0x00000105, + 0x00000114, 0x00000125, 0x00000136, + 0x00000148, 0x0000015c, 0x00000171, + 0x00000186, 0x0000019e, 0x000001b6, + 0x000001d0, 0x000001eb, 0x00000209, + 0x00000227, 0x00000248, 0x0000026b, + 0x0000028f, 0x000002b6, 0x000002df, + 0x0000030b, 0x00000339, 0x0000036a, + 0x0000039e, 0x000003d5, 0x0000040f, + 0x0000044c, 0x0000048d, 0x000004d2, + 0x0000051c, 0x00000569, 0x000005bb, + 0x00000612, 0x0000066e, 0x000006d0, + 0x00000737, 0x000007a5, 0x00000818, + 0x00000893, 0x00000915, 0x0000099f, + 0x00000a31, 0x00000acc, 0x00000b6f, + 0x00000c1d, 0x00000cd5, 0x00000d97, + 0x00000e65, 0x00000f40, 0x00001027, + 0x0000111c, 0x00001220, 0x00001333, + 0x00001456, 0x0000158a, 0x000016d1, + 0x0000182b, 0x0000199a, 0x00001b1e, + 0x00001cb9, 0x00001e6d, 0x0000203a, + 0x00002223, 0x00002429, 0x0000264e, + 0x00002893, 0x00002afa, 0x00002d86, + 0x00003039, 0x00003314, 0x0000361b, + 0x00003950, 0x00003cb5, 0x0000404e, + 0x0000441d, 0x00004827, 0x00004c6d, + 0x000050f4, 0x000055c0, 0x00005ad5, + 0x00006037, 0x000065ea, 0x00006bf4, + 0x0000725a, 0x00007920, 0x0000804e, + 0x000087e8, 0x00008ff6, 0x0000987d, + 0x0000a186, 0x0000ab19, 0x0000b53c, + 0x0000bff9, 0x0000cb59, 0x0000d766, + 0x0000e429, 0x0000f1ae, 0x00010000, + 0x00010f2b, 0x00011f3d, 0x00013042, + 0x00014249, 0x00015562, 0x0001699c, + 0x00017f09, 0x000195bc, 0x0001adc6, + 0x0001c73d, 0x0001e237, 0x0001feca, + 0x00021d0e, 0x00023d1d, 0x00025f12, + 0x0002830b, 0x0002a925, 0x0002d182, + 0x0002fc42, 0x0003298b, 0x00035983, + 0x00038c53, 0x0003c225, 0x0003fb28, + 0x0004378b, 0x00047783, 0x0004bb44, + 0x0005030a, 0x00054f10, 0x00059f98, + 0x0005f4e5, 0x00064f40, 0x0006aef6, + 0x00071457, 0x00077fbb, 0x0007f17b, +}; + +/* treble table for TAS3001c */ +/* 0 = -18 dB, 72 = 18 dB in 0.5 dB step */ +static unsigned int treble_volume_table[] = { + 0x96, 0x95, 0x94, + 0x93, 0x92, 0x91, + 0x90, 0x8f, 0x8e, + 0x8d, 0x8c, 0x8b, + 0x8a, 0x89, 0x88, + 0x87, 0x86, 0x85, + 0x84, 0x83, 0x82, + 0x81, 0x80, 0x7f, + 0x7e, 0x7d, 0x7c, + 0x7b, 0x7a, 0x79, + 0x78, 0x77, 0x76, + 0x75, 0x74, 0x73, + 0x72, 0x71, 0x70, + 0x6e, 0x6d, 0x6c, + 0x6b, 0x69, 0x68, + 0x66, 0x65, 0x63, + 0x62, 0x60, 0x5e, + 0x5c, 0x5a, 0x57, + 0x55, 0x52, 0x4f, + 0x4c, 0x49, 0x45, + 0x42, 0x3e, 0x3a, + 0x36, 0x32, 0x2d, + 0x28, 0x22, 0x1c, + 0x16, 0x10, 0x09, + 0x01, +}; + +/* bass table for TAS3001c */ +/* 0 = -18 dB, 72 = 18 dB in 0.5 dB step */ +static unsigned int bass_volume_table[] = { + 0x86, 0x82, 0x7f, + 0x7d, 0x7a, 0x78, + 0x76, 0x74, 0x72, + 0x70, 0x6e, 0x6d, + 0x6b, 0x69, 0x66, + 0x64, 0x61, 0x5f, + 0x5d, 0x5c, 0x5a, + 0x59, 0x58, 0x56, + 0x55, 0x54, 0x53, + 0x51, 0x4f, 0x4d, + 0x4b, 0x49, 0x46, + 0x44, 0x42, 0x40, + 0x3e, 0x3c, 0x3b, + 0x39, 0x38, 0x36, + 0x35, 0x33, 0x31, + 0x30, 0x2e, 0x2c, + 0x2b, 0x29, 0x28, + 0x26, 0x25, 0x23, + 0x21, 0x1f, 0x1c, + 0x19, 0x18, 0x17, + 0x16, 0x14, 0x13, + 0x12, 0x10, 0x0f, + 0x0d, 0x0b, 0x0a, + 0x08, 0x06, 0x03, + 0x01, +}; + +/* mixer (pcm) volume table */ +/* 0 = -70 dB, 175 = 18.0 dB in 0.5 dB step */ +static unsigned int mixer_volume_table[] = { + 0x00014b, 0x00015f, 0x000174, + 0x00018a, 0x0001a1, 0x0001ba, + 0x0001d4, 0x0001f0, 0x00020d, + 0x00022c, 0x00024d, 0x000270, + 0x000295, 0x0002bc, 0x0002e6, + 0x000312, 0x000340, 0x000372, + 0x0003a6, 0x0003dd, 0x000418, + 0x000456, 0x000498, 0x0004de, + 0x000528, 0x000576, 0x0005c9, + 0x000620, 0x00067d, 0x0006e0, + 0x000748, 0x0007b7, 0x00082c, + 0x0008a8, 0x00092b, 0x0009b6, + 0x000a49, 0x000ae5, 0x000b8b, + 0x000c3a, 0x000cf3, 0x000db8, + 0x000e88, 0x000f64, 0x00104e, + 0x001145, 0x00124b, 0x001361, + 0x001487, 0x0015be, 0x001708, + 0x001865, 0x0019d8, 0x001b60, + 0x001cff, 0x001eb7, 0x002089, + 0x002276, 0x002481, 0x0026ab, + 0x0028f5, 0x002b63, 0x002df5, + 0x0030ae, 0x003390, 0x00369e, + 0x0039db, 0x003d49, 0x0040ea, + 0x0044c3, 0x0048d6, 0x004d27, + 0x0051b9, 0x005691, 0x005bb2, + 0x006121, 0x0066e3, 0x006cfb, + 0x007370, 0x007a48, 0x008186, + 0x008933, 0x009154, 0x0099f1, + 0x00a310, 0x00acba, 0x00b6f6, + 0x00c1cd, 0x00cd49, 0x00d973, + 0x00e655, 0x00f3fb, 0x010270, + 0x0111c0, 0x0121f9, 0x013328, + 0x01455b, 0x0158a2, 0x016d0e, + 0x0182af, 0x019999, 0x01b1de, + 0x01cb94, 0x01e6cf, 0x0203a7, + 0x022235, 0x024293, 0x0264db, + 0x02892c, 0x02afa3, 0x02d862, + 0x03038a, 0x033142, 0x0361af, + 0x0394fa, 0x03cb50, 0x0404de, + 0x0441d5, 0x048268, 0x04c6d0, + 0x050f44, 0x055c04, 0x05ad50, + 0x06036e, 0x065ea5, 0x06bf44, + 0x07259d, 0x079207, 0x0804dc, + 0x087e80, 0x08ff59, 0x0987d5, + 0x0a1866, 0x0ab189, 0x0b53be, + 0x0bff91, 0x0cb591, 0x0d765a, + 0x0e4290, 0x0f1adf, 0x100000, + 0x10f2b4, 0x11f3c9, 0x13041a, + 0x14248e, 0x15561a, 0x1699c0, + 0x17f094, 0x195bb8, 0x1adc61, + 0x1c73d5, 0x1e236d, 0x1fec98, + 0x21d0d9, 0x23d1cd, 0x25f125, + 0x2830af, 0x2a9254, 0x2d1818, + 0x2fc420, 0x3298b0, 0x35982f, + 0x38c528, 0x3c224c, 0x3fb278, + 0x437880, 0x477828, 0x4bb446, + 0x5030a1, 0x54f106, 0x59f980, + 0x5f4e52, 0x64f403, 0x6aef5d, + 0x714575, 0x77fbaa, 0x7f17af, +}; + + +/* treble table for TAS3004 */ +/* 0 = -18 dB, 72 = 18 dB in 0.5 dB step */ +static unsigned int snapper_treble_volume_table[] = { + 0x96, 0x95, 0x94, + 0x93, 0x92, 0x91, + 0x90, 0x8f, 0x8e, + 0x8d, 0x8c, 0x8b, + 0x8a, 0x89, 0x88, + 0x87, 0x86, 0x85, + 0x84, 0x83, 0x82, + 0x81, 0x80, 0x7f, + 0x7e, 0x7d, 0x7c, + 0x7b, 0x7a, 0x79, + 0x78, 0x77, 0x76, + 0x75, 0x74, 0x73, + 0x72, 0x71, 0x70, + 0x6f, 0x6d, 0x6c, + 0x6b, 0x69, 0x68, + 0x67, 0x65, 0x63, + 0x62, 0x60, 0x5d, + 0x5b, 0x59, 0x56, + 0x53, 0x51, 0x4d, + 0x4a, 0x47, 0x43, + 0x3f, 0x3b, 0x36, + 0x31, 0x2c, 0x26, + 0x20, 0x1a, 0x13, + 0x08, 0x04, 0x01, + 0x01, +}; + +/* bass table for TAS3004 */ +/* 0 = -18 dB, 72 = 18 dB in 0.5 dB step */ +static unsigned int snapper_bass_volume_table[] = { + 0x96, 0x95, 0x94, + 0x93, 0x92, 0x91, + 0x90, 0x8f, 0x8e, + 0x8d, 0x8c, 0x8b, + 0x8a, 0x89, 0x88, + 0x87, 0x86, 0x85, + 0x84, 0x83, 0x82, + 0x81, 0x80, 0x7f, + 0x7e, 0x7d, 0x7c, + 0x7b, 0x7a, 0x79, + 0x78, 0x77, 0x76, + 0x75, 0x74, 0x73, + 0x72, 0x71, 0x6f, + 0x6e, 0x6d, 0x6b, + 0x6a, 0x69, 0x67, + 0x66, 0x65, 0x63, + 0x62, 0x61, 0x5f, + 0x5d, 0x5b, 0x58, + 0x55, 0x52, 0x4f, + 0x4c, 0x49, 0x46, + 0x43, 0x3f, 0x3b, + 0x37, 0x33, 0x2e, + 0x29, 0x24, 0x1e, + 0x18, 0x11, 0x0a, + 0x01, +}; + |