diff options
| author | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 09:55:01 -0400 |
|---|---|---|
| committer | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 11:08:21 -0400 |
| commit | 4617721c502b2ddaa4e324e86da4997edf738fa5 (patch) | |
| tree | 350aa83b47da0b574a814795392b20b0bed83cbc /sound/ppc | |
| download | linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.tar.gz linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.zip | |
ftrace: Synchronize the initialization of ftrace_opsgrafted
There's some internal state that ftrace_ops needs to have set, but since
it can be declared outside of the ftrace.c code, it calls
ftrace_ops_init() on the ops in every global function. The issue is that
if two tasks call it on the same ops at the same time it is possible to
have the initialization of one corrupt the initialization of the other
call.
Create a ops_mutex to use to synchronize every initialization of the
ftrace_ops. The mutex is taken within checking the ftrace_ops flag that
states it was initializied but the flag is checked again after the mutex
has been taken. Checking first outside the mutex allows it to shortcut
having to take the mutex. But then the check needs to be done again after
the mute is taken in case of races.
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home
Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock")
Reported-by: sashiko-bot@kernel.org
Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Diffstat (limited to 'sound/ppc')
| -rw-r--r-- | sound/ppc/Kconfig | 53 | ||||
| -rw-r--r-- | sound/ppc/Makefile | 11 | ||||
| -rw-r--r-- | sound/ppc/awacs.c | 1123 | ||||
| -rw-r--r-- | sound/ppc/awacs.h | 192 | ||||
| -rw-r--r-- | sound/ppc/beep.c | 270 | ||||
| -rw-r--r-- | sound/ppc/burgundy.c | 714 | ||||
| -rw-r--r-- | sound/ppc/burgundy.h | 101 | ||||
| -rw-r--r-- | sound/ppc/daca.c | 277 | ||||
| -rw-r--r-- | sound/ppc/keywest.c | 155 | ||||
| -rw-r--r-- | sound/ppc/pmac.c | 1363 | ||||
| -rw-r--r-- | sound/ppc/pmac.h | 198 | ||||
| -rw-r--r-- | sound/ppc/powermac.c | 190 | ||||
| -rw-r--r-- | sound/ppc/snd_ps3.c | 1102 | ||||
| -rw-r--r-- | sound/ppc/snd_ps3.h | 124 | ||||
| -rw-r--r-- | sound/ppc/snd_ps3_reg.h | 879 | ||||
| -rw-r--r-- | sound/ppc/tumbler.c | 1497 | ||||
| -rw-r--r-- | sound/ppc/tumbler_volume.h | 251 |
17 files changed, 8500 insertions, 0 deletions
diff --git a/sound/ppc/Kconfig b/sound/ppc/Kconfig new file mode 100644 index 000000000..8789cb503 --- /dev/null +++ b/sound/ppc/Kconfig @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: GPL-2.0-only +# ALSA PowerMac drivers + +menuconfig SND_PPC + bool "PowerPC sound devices" + depends on PPC + default y + help + Support for sound devices specific to PowerPC architectures. + +if SND_PPC + +config SND_POWERMAC + tristate "PowerMac (AWACS, DACA, Burgundy, Tumbler, Keywest)" + depends on I2C && INPUT && PPC_PMAC + select SND_PCM + select SND_VMASTER + 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. + +config SND_POWERMAC_AUTO_DRC + bool "Toggle DRC automatically at headphone/line plug-in" + depends on SND_POWERMAC + default y + help + Say Y here to enable the automatic toggle of DRC (dynamic + range compression) on Tumbler/Snapper. + If this feature is enabled, DRC is turned off when the + headphone/line jack is plugged, and turned on when unplugged. + + Note that you can turn on/off DRC manually even without this + option. + +config SND_PS3 + tristate "PS3 Audio support" + depends on PS3_PS3AV + select SND_PCM + default m + help + Say Y here to include support for audio on the PS3 + + To compile this driver as a module, choose M here: the module + will be called snd_ps3. + +config SND_PS3_DEFAULT_START_DELAY + int "Startup delay time in ms" + depends on SND_PS3 + default "2000" + +endif # SND_PPC diff --git a/sound/ppc/Makefile b/sound/ppc/Makefile new file mode 100644 index 000000000..655bcffba --- /dev/null +++ b/sound/ppc/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz> +# + +snd-powermac-y := 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 +obj-$(CONFIG_SND_PS3) += snd_ps3.o diff --git a/sound/ppc/awacs.c b/sound/ppc/awacs.c new file mode 100644 index 000000000..ee36ea284 --- /dev/null +++ b/sound/ppc/awacs.c @@ -0,0 +1,1123 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PMac AWACS lowlevel functions + * + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * code based on dmasound.c. + */ + + +#include <linux/io.h> +#include <asm/nvram.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/of.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 +struct awacs_amp { + unsigned char amp_master; + unsigned char amp_vol[2][2]; + unsigned char amp_tone[2]; +}; + +#define CHECK_CUDA_AMP() (sys_ctrler == SYS_CTRLER_CUDA) + +#endif /* PMAC_AMP_AVAIL */ + + +static void snd_pmac_screamer_wait(struct snd_pmac *chip) +{ + long timeout = 2000; + while (!(in_le32(&chip->awacs->codec_stat) & MASK_VALID)) { + mdelay(1); + if (! --timeout) { + dev_dbg(chip->card->dev, "%s timeout\n", __func__); + break; + } + } +} + +/* + * write AWACS register + */ +static void +snd_pmac_awacs_write(struct snd_pmac *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) { + dev_dbg(chip->card->dev, "%s timeout\n", __func__); + break; + } + } +} + +static void +snd_pmac_awacs_write_reg(struct snd_pmac *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(struct snd_pmac *chip, int reg, int val) +{ + snd_pmac_awacs_write(chip, val | (reg << 12)); +} + +#ifdef CONFIG_PM +/* Recalibrate chip */ +static void screamer_recalibrate(struct snd_pmac *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 */ + msleep(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(struct snd_pmac *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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *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 vol[2]; + + guard(spinlock_irqsave)(&chip->reg_lock); + vol[0] = (chip->awacs_reg[reg] >> lshift) & 0xf; + vol[1] = chip->awacs_reg[reg] & 0xf; + 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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *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 int vol[2]; + + vol[0] = ucontrol->value.integer.value[0]; + vol[1] = ucontrol->value.integer.value[1]; + if (vol[0] > 0x0f || vol[1] > 0x0f) + return -EINVAL; + if (inverted) { + vol[0] = 0x0f - vol[0]; + vol[1] = 0x0f - vol[1]; + } + vol[0] &= 0x0f; + vol[1] &= 0x0f; + guard(spinlock_irqsave)(&chip->reg_lock); + 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); + 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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *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; + + guard(spinlock_irqsave)(&chip->reg_lock); + val = (chip->awacs_reg[reg] >> shift) & 1; + if (invert) + val = 1 - val; + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int snd_pmac_awacs_put_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *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; + + guard(spinlock_irqsave)(&chip->reg_lock); + 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); + 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(struct awacs_amp *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(struct awacs_amp *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(struct awacs_amp *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(struct snd_pmac *chip) +{ + struct awacs_amp *amp = chip->mixer_data; + if (!amp) + return; + kfree(amp); + chip->mixer_data = NULL; + chip->mixer_free = NULL; +} + + +/* + * mixer controls + */ +static int snd_pmac_awacs_info_volume_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + struct awacs_amp *amp = chip->mixer_data; + + 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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + int vol[2]; + struct awacs_amp *amp = chip->mixer_data; + + 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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + struct awacs_amp *amp = chip->mixer_data; + + 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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + int vol[2]; + struct awacs_amp *amp = chip->mixer_data; + + 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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + struct awacs_amp *amp = chip->mixer_data; + + ucontrol->value.integer.value[0] = amp->amp_tone[index]; + return 0; +} + +static int snd_pmac_awacs_put_tone_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + struct awacs_amp *amp = chip->mixer_data; + unsigned int val; + + val = ucontrol->value.integer.value[0]; + if (val > 14) + return -EINVAL; + if (val != amp->amp_tone[index]) { + amp->amp_tone[index] = val; + 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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct awacs_amp *amp = chip->mixer_data; + + ucontrol->value.integer.value[0] = amp->amp_master; + return 0; +} + +static int snd_pmac_awacs_put_master_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct awacs_amp *amp = chip->mixer_data; + unsigned int val; + + val = ucontrol->value.integer.value[0]; + if (val > 99) + return -EINVAL; + if (val != amp->amp_master) { + amp->amp_master = val; + awacs_amp_set_master(amp, amp->amp_master); + return 1; + } + return 0; +} + +#define AMP_CH_SPK 0 +#define AMP_CH_HD 1 + +static const struct snd_kcontrol_new snd_pmac_awacs_amp_vol[] = { + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "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 const struct snd_kcontrol_new snd_pmac_awacs_amp_hp_sw = { + .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 const struct snd_kcontrol_new snd_pmac_awacs_amp_spk_sw = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 3; + return 0; +} + +static int snd_pmac_screamer_mic_boost_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + int val = 0; + + guard(spinlock_irqsave)(&chip->reg_lock); + if (chip->awacs_reg[6] & MASK_MIC_BOOST) + val |= 2; + if (chip->awacs_reg[0] & MASK_GAINLINE) + val |= 1; + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int snd_pmac_screamer_mic_boost_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int val0, val6; + + guard(spinlock_irqsave)(&chip->reg_lock); + val0 = chip->awacs_reg[0] & ~MASK_GAINLINE; + val6 = chip->awacs_reg[6] & ~MASK_MIC_BOOST; + if (ucontrol->value.integer.value[0] & 1) + val0 |= MASK_GAINLINE; + if (ucontrol->value.integer.value[0] & 2) + 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; + } + return changed; +} + +/* + * lists of mixer elements + */ +static const struct snd_kcontrol_new snd_pmac_awacs_mixers[] = { + AWACS_SWITCH("Master Capture Switch", 1, SHIFT_LOOPTHRU, 0), + AWACS_VOLUME("Master Capture Volume", 0, 4, 0), +/* AWACS_SWITCH("Unknown Playback Switch", 6, SHIFT_PAROUT0, 0), */ +}; + +static const struct snd_kcontrol_new snd_pmac_screamer_mixers_beige[] = { + AWACS_VOLUME("Master Playback Volume", 2, 6, 1), + AWACS_VOLUME("Play-through Playback Volume", 5, 6, 1), + AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_MIC, 0), + AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_LINE, 0), +}; + +static const struct snd_kcontrol_new snd_pmac_screamer_mixers_lo[] = { + AWACS_VOLUME("Line out Playback Volume", 2, 6, 1), +}; + +static const struct snd_kcontrol_new snd_pmac_screamer_mixers_imac[] = { + AWACS_VOLUME("Play-through Playback Volume", 5, 6, 1), + AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0), +}; + +static const struct snd_kcontrol_new snd_pmac_screamer_mixers_g4agp[] = { + AWACS_VOLUME("Line out Playback Volume", 2, 6, 1), + AWACS_VOLUME("Master Playback Volume", 5, 6, 1), + AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0), + AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_MIC, 0), +}; + +static const struct snd_kcontrol_new snd_pmac_awacs_mixers_pmac7500[] = { + AWACS_VOLUME("Line out Playback Volume", 2, 6, 1), + AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0), + AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_MIC, 0), +}; + +static const struct snd_kcontrol_new snd_pmac_awacs_mixers_pmac5500[] = { + AWACS_VOLUME("Headphone Playback Volume", 2, 6, 1), +}; + +static const struct snd_kcontrol_new snd_pmac_awacs_mixers_pmac[] = { + AWACS_VOLUME("Master Playback Volume", 2, 6, 1), + 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 const struct snd_kcontrol_new snd_pmac_awacs_mixers2[] = { + AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_LINE, 0), + AWACS_SWITCH("Mic Capture Switch", 0, SHIFT_MUX_MIC, 0), +}; + +static const struct snd_kcontrol_new snd_pmac_screamer_mixers2[] = { + AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_MIC, 0), + AWACS_SWITCH("Mic Capture Switch", 0, SHIFT_MUX_LINE, 0), +}; + +static const struct snd_kcontrol_new snd_pmac_awacs_mixers2_pmac5500[] = { + AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0), +}; + +static const struct snd_kcontrol_new snd_pmac_awacs_master_sw = +AWACS_SWITCH("Master Playback Switch", 1, SHIFT_HDMUTE, 1); + +static const struct snd_kcontrol_new snd_pmac_awacs_master_sw_imac = +AWACS_SWITCH("Line out Playback Switch", 1, SHIFT_HDMUTE, 1); + +static const struct snd_kcontrol_new snd_pmac_awacs_master_sw_pmac5500 = +AWACS_SWITCH("Headphone Playback Switch", 1, SHIFT_HDMUTE, 1); + +static const struct snd_kcontrol_new snd_pmac_awacs_mic_boost[] = { + AWACS_SWITCH("Mic Boost Capture Switch", 0, SHIFT_GAINLINE, 0), +}; + +static const struct snd_kcontrol_new snd_pmac_screamer_mic_boost[] = { + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Boost Capture Volume", + .info = snd_pmac_screamer_mic_boost_info, + .get = snd_pmac_screamer_mic_boost_get, + .put = snd_pmac_screamer_mic_boost_put, + }, +}; + +static const struct snd_kcontrol_new snd_pmac_awacs_mic_boost_pmac7500[] = +{ + AWACS_SWITCH("Line Boost Capture Switch", 0, SHIFT_GAINLINE, 0), +}; + +static const struct snd_kcontrol_new snd_pmac_screamer_mic_boost_beige[] = +{ + AWACS_SWITCH("Line Boost Capture Switch", 0, SHIFT_GAINLINE, 0), + AWACS_SWITCH("CD Boost Capture Switch", 6, SHIFT_MIC_BOOST, 0), +}; + +static const struct snd_kcontrol_new snd_pmac_screamer_mic_boost_imac[] = +{ + AWACS_SWITCH("Line Boost Capture Switch", 0, SHIFT_GAINLINE, 0), + AWACS_SWITCH("Mic Boost Capture Switch", 6, SHIFT_MIC_BOOST, 0), +}; + +static const struct snd_kcontrol_new snd_pmac_awacs_speaker_vol[] = { + AWACS_VOLUME("Speaker Playback Volume", 4, 6, 1), +}; + +static const struct snd_kcontrol_new snd_pmac_awacs_speaker_sw = +AWACS_SWITCH("Speaker Playback Switch", 1, SHIFT_SPKMUTE, 1); + +static const struct snd_kcontrol_new snd_pmac_awacs_speaker_sw_imac1 = +AWACS_SWITCH("Speaker Playback Switch", 1, SHIFT_PAROUT1, 1); + +static const struct snd_kcontrol_new snd_pmac_awacs_speaker_sw_imac2 = +AWACS_SWITCH("Speaker Playback Switch", 1, SHIFT_PAROUT1, 0); + + +/* + * add new mixer elements to the card + */ +static int build_mixers(struct snd_pmac *chip, int nums, + const struct snd_kcontrol_new *mixers) +{ + int i, err; + + for (i = 0; i < nums; i++) { + err = snd_ctl_add(chip->card, snd_ctl_new1(&mixers[i], chip)); + if (err < 0) + return err; + } + return 0; +} + + +/* + * restore all registers + */ +static void awacs_restore_all_regs(struct snd_pmac *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_PM +static void snd_pmac_awacs_suspend(struct snd_pmac *chip) +{ + snd_pmac_awacs_write_noreg(chip, 1, (chip->awacs_reg[1] + | MASK_AMUTE | MASK_CMUTE)); +} + +static void snd_pmac_awacs_resume(struct snd_pmac *chip) +{ + if (of_machine_is_compatible("PowerBook3,1") + || of_machine_is_compatible("PowerBook3,2")) { + msleep(100); + snd_pmac_awacs_write_reg(chip, 1, + chip->awacs_reg[1] & ~MASK_PAROUT); + msleep(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) { + struct awacs_amp *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_PM */ + +#define IS_PM7500 (of_machine_is_compatible("AAPL,7500") \ + || of_machine_is_compatible("AAPL,8500") \ + || of_machine_is_compatible("AAPL,9500")) +#define IS_PM5500 (of_machine_is_compatible("AAPL,e411")) +#define IS_BEIGE (of_machine_is_compatible("AAPL,Gossamer")) +#define IS_IMAC1 (of_machine_is_compatible("PowerMac2,1")) +#define IS_IMAC2 (of_machine_is_compatible("PowerMac2,2") \ + || of_machine_is_compatible("PowerMac4,1")) +#define IS_G4AGP (of_machine_is_compatible("PowerMac3,1")) +#define IS_LOMBARD (of_machine_is_compatible("PowerBook1,1")) + +static int imac1, imac2; + +#ifdef PMAC_SUPPORT_AUTOMUTE +/* + * auto-mute stuffs + */ +static int snd_pmac_awacs_detect_headphone(struct snd_pmac *chip) +{ + return (in_le32(&chip->awacs->codec_stat) & chip->hp_stat_mask) ? 1 : 0; +} + +#ifdef PMAC_AMP_AVAIL +static int toggle_amp_mute(struct awacs_amp *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(struct snd_pmac *chip, int do_notify) +{ + if (chip->auto_mute) { +#ifdef PMAC_AMP_AVAIL + if (chip->mixer_data) { + struct awacs_amp *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 (imac1) { + reg &= ~MASK_SPKMUTE; + reg |= MASK_PAROUT1; + } else if (imac2) { + reg &= ~MASK_SPKMUTE; + reg &= ~MASK_PAROUT1; + } + if (snd_pmac_awacs_detect_headphone(chip)) + reg &= ~MASK_HDMUTE; + else if (imac1) + reg &= ~MASK_PAROUT1; + else if (imac2) + reg |= MASK_PAROUT1; + 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 +snd_pmac_awacs_init(struct snd_pmac *chip) +{ + int pm7500 = IS_PM7500; + int pm5500 = IS_PM5500; + int beige = IS_BEIGE; + int g4agp = IS_G4AGP; + int lombard = IS_LOMBARD; + int imac; + int err, vol; + struct snd_kcontrol *vmaster_sw, *vmaster_vol; + struct snd_kcontrol *master_vol, *speaker_vol; + + imac1 = IS_IMAC1; + imac2 = IS_IMAC2; + imac = imac1 || imac2; + /* 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) { + /* FIXME: screamer has loopthru vol control */ + chip->awacs_reg[5] = vol; + /* FIXME: maybe should be vol << 3 for PCMCIA speaker */ + chip->awacs_reg[6] = MASK_MIC_BOOST; + 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()) { + struct awacs_amp *amp = kzalloc_obj(*amp); + if (! amp) + return -ENOMEM; + chip->mixer_data = amp; + chip->mixer_free = awacs_amp_free; + /* mute and zero vol */ + awacs_amp_set_vol(amp, 0, 63, 63, 0); + 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 = pm7500 || pm5500 ? MASK_HDPCONN + : MASK_LOCONN; + break; + case PMAC_SCREAMER: + switch (chip->device_id) { + case 0x08: + case 0x0B: + chip->hp_stat_mask = imac + ? MASK_LOCONN_IMAC | + MASK_HDPLCONN_IMAC | + MASK_HDPRCONN_IMAC + : MASK_HDPCONN; + break; + case 0x00: + case 0x05: + chip->hp_stat_mask = MASK_LOCONN; + break; + default: + chip->hp_stat_mask = MASK_HDPCONN; + break; + } + break; + default: + snd_BUG(); + break; + } + } + + /* + * build mixers + */ + strscpy(chip->card->mixername, "PowerMac AWACS"); + + err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_mixers), + snd_pmac_awacs_mixers); + if (err < 0) + return err; + if (beige || g4agp) + ; + else if (chip->model == PMAC_SCREAMER || pm5500) + err = build_mixers(chip, ARRAY_SIZE(snd_pmac_screamer_mixers2), + snd_pmac_screamer_mixers2); + else if (!pm7500) + err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_mixers2), + snd_pmac_awacs_mixers2); + if (err < 0) + return err; + if (pm5500) { + err = build_mixers(chip, + ARRAY_SIZE(snd_pmac_awacs_mixers2_pmac5500), + snd_pmac_awacs_mixers2_pmac5500); + if (err < 0) + return err; + } + master_vol = NULL; + if (pm7500) + err = build_mixers(chip, + ARRAY_SIZE(snd_pmac_awacs_mixers_pmac7500), + snd_pmac_awacs_mixers_pmac7500); + else if (pm5500) + err = snd_ctl_add(chip->card, + (master_vol = snd_ctl_new1(snd_pmac_awacs_mixers_pmac5500, + chip))); + else if (beige) + err = build_mixers(chip, + ARRAY_SIZE(snd_pmac_screamer_mixers_beige), + snd_pmac_screamer_mixers_beige); + else if (imac || lombard) { + err = snd_ctl_add(chip->card, + (master_vol = snd_ctl_new1(snd_pmac_screamer_mixers_lo, + chip))); + if (err < 0) + return err; + err = build_mixers(chip, + ARRAY_SIZE(snd_pmac_screamer_mixers_imac), + snd_pmac_screamer_mixers_imac); + } else if (g4agp) + err = build_mixers(chip, + ARRAY_SIZE(snd_pmac_screamer_mixers_g4agp), + snd_pmac_screamer_mixers_g4agp); + else + err = build_mixers(chip, + ARRAY_SIZE(snd_pmac_awacs_mixers_pmac), + snd_pmac_awacs_mixers_pmac); + if (err < 0) + return err; + chip->master_sw_ctl = snd_ctl_new1((pm7500 || imac || g4agp || lombard) + ? &snd_pmac_awacs_master_sw_imac + : pm5500 + ? &snd_pmac_awacs_master_sw_pmac5500 + : &snd_pmac_awacs_master_sw, chip); + err = snd_ctl_add(chip->card, chip->master_sw_ctl); + if (err < 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. + */ + err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_amp_vol), + snd_pmac_awacs_amp_vol); + if (err < 0) + return err; + /* overwrite */ + chip->master_sw_ctl = snd_ctl_new1(&snd_pmac_awacs_amp_hp_sw, + chip); + err = snd_ctl_add(chip->card, chip->master_sw_ctl); + if (err < 0) + return err; + chip->speaker_sw_ctl = snd_ctl_new1(&snd_pmac_awacs_amp_spk_sw, + chip); + err = snd_ctl_add(chip->card, chip->speaker_sw_ctl); + if (err < 0) + return err; + } else +#endif /* PMAC_AMP_AVAIL */ + { + /* route A = headphone, route C = speaker */ + err = snd_ctl_add(chip->card, + (speaker_vol = snd_ctl_new1(snd_pmac_awacs_speaker_vol, + chip))); + if (err < 0) + return err; + chip->speaker_sw_ctl = snd_ctl_new1(imac1 + ? &snd_pmac_awacs_speaker_sw_imac1 + : imac2 + ? &snd_pmac_awacs_speaker_sw_imac2 + : &snd_pmac_awacs_speaker_sw, chip); + err = snd_ctl_add(chip->card, chip->speaker_sw_ctl); + if (err < 0) + return err; + } + + if (pm5500 || imac || lombard) { + vmaster_sw = snd_ctl_make_virtual_master( + "Master Playback Switch", (unsigned int *) NULL); + err = snd_ctl_add_follower_uncached(vmaster_sw, + chip->master_sw_ctl); + if (err < 0) + return err; + err = snd_ctl_add_follower_uncached(vmaster_sw, + chip->speaker_sw_ctl); + if (err < 0) + return err; + err = snd_ctl_add(chip->card, vmaster_sw); + if (err < 0) + return err; + vmaster_vol = snd_ctl_make_virtual_master( + "Master Playback Volume", (unsigned int *) NULL); + err = snd_ctl_add_follower(vmaster_vol, master_vol); + if (err < 0) + return err; + err = snd_ctl_add_follower(vmaster_vol, speaker_vol); + if (err < 0) + return err; + err = snd_ctl_add(chip->card, vmaster_vol); + if (err < 0) + return err; + } + + if (beige || g4agp) + err = build_mixers(chip, + ARRAY_SIZE(snd_pmac_screamer_mic_boost_beige), + snd_pmac_screamer_mic_boost_beige); + else if (imac) + err = build_mixers(chip, + ARRAY_SIZE(snd_pmac_screamer_mic_boost_imac), + snd_pmac_screamer_mic_boost_imac); + else if (chip->model == PMAC_SCREAMER) + err = build_mixers(chip, + ARRAY_SIZE(snd_pmac_screamer_mic_boost), + snd_pmac_screamer_mic_boost); + else if (pm7500) + err = build_mixers(chip, + ARRAY_SIZE(snd_pmac_awacs_mic_boost_pmac7500), + snd_pmac_awacs_mic_boost_pmac7500); + else + err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_mic_boost), + snd_pmac_awacs_mic_boost); + if (err < 0) + return err; + + /* + * set lowlevel callbacks + */ + chip->set_format = snd_pmac_awacs_set_format; +#ifdef CONFIG_PM + chip->suspend = snd_pmac_awacs_suspend; + chip->resume = snd_pmac_awacs_resume; +#endif +#ifdef PMAC_SUPPORT_AUTOMUTE + err = snd_pmac_add_automute(chip); + if (err < 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 000000000..bde22668c --- /dev/null +++ b/sound/ppc/awacs.h @@ -0,0 +1,192 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for PowerMac AWACS onboard soundchips + * Copyright (c) 2001 by Takashi Iwai <tiwai@suse.de> + * based on dmasound.c. + */ + + +#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 MASK_PAROUT0 (0x1 << 10) /* Parallel Out (???) */ +#define MASK_PAROUT1 (0x1 << 11) /* Parallel Out (enable speaker) */ +#define SHIFT_PAROUT 10 +#define SHIFT_PAROUT0 10 +#define SHIFT_PAROUT1 11 + +#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_INSENSE (0xf) /* port sense bits: */ +#define MASK_HDPCONN 8 /* headphone plugged in */ +#define MASK_LOCONN 4 /* line-out plugged in */ +#define MASK_LICONN 2 /* line-in plugged in */ +#define MASK_MICCONN 1 /* microphone plugged in */ +#define MASK_LICONN_IMAC 8 /* line-in plugged in */ +#define MASK_HDPRCONN_IMAC 4 /* headphone right plugged in */ +#define MASK_HDPLCONN_IMAC 2 /* headphone left plugged in */ +#define MASK_LOCONN_IMAC 1 /* line-out 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 000000000..1b03a7961 --- /dev/null +++ b/sound/ppc/beep.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Beep using pcm + * + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/io.h> +#include <asm/irq.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/pci.h> +#include <linux/dma-mapping.h> +#include <sound/core.h> +#include <sound/control.h> +#include "pmac.h" + +struct 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 */ + dma_addr_t addr; /* physical address of buffer */ + struct input_dev *dev; +}; + +/* + * stop beep if running + */ +void snd_pmac_beep_stop(struct snd_pmac *chip) +{ + struct pmac_beep *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 const 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) +{ + struct snd_pmac *chip; + struct pmac_beep *beep; + 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; break; + case SND_TONE: break; + default: return -1; + } + + chip = input_get_drvdata(dev); + if (!chip) + return -1; + beep = chip->beep; + if (!beep) + return -1; + + if (! hz) { + guard(spinlock_irqsave)(&chip->reg_lock); + if (beep->running) + snd_pmac_beep_stop(chip); + 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; + + scoped_guard(spinlock_irqsave, &chip->reg_lock) { + if (chip->playback.running || chip->capture.running || beep->running) + return 0; + beep->running = 1; + } + + 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; + } + + guard(spinlock_irqsave)(&chip->reg_lock); + snd_pmac_beep_dma_start(chip, beep->nsamples * 4, beep->addr, beep_speed); + return 0; +} + +/* + * beep volume mixer + */ + +static int snd_pmac_info_beep(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + if (snd_BUG_ON(!chip->beep)) + return -ENXIO; + ucontrol->value.integer.value[0] = chip->beep->volume; + return 0; +} + +static int snd_pmac_put_beep(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + unsigned int oval, nval; + if (snd_BUG_ON(!chip->beep)) + return -ENXIO; + oval = chip->beep->volume; + nval = ucontrol->value.integer.value[0]; + if (nval > 100) + return -EINVAL; + chip->beep->volume = nval; + return oval != chip->beep->volume; +} + +static const struct snd_kcontrol_new 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 snd_pmac_attach_beep(struct snd_pmac *chip) +{ + struct pmac_beep *beep; + struct input_dev *input_dev; + struct snd_kcontrol *beep_ctl; + void *dmabuf; + int err = -ENOMEM; + + beep = kzalloc_obj(*beep); + if (! beep) + return -ENOMEM; + dmabuf = dma_alloc_coherent(&chip->pdev->dev, BEEP_BUFLEN * 4, + &beep->addr, GFP_KERNEL); + input_dev = input_allocate_device(); + if (! dmabuf || ! input_dev) + goto fail1; + + /* FIXME: set more better values */ + input_dev->name = "PowerMac Beep"; + input_dev->phys = "powermac/beep"; + input_dev->id.bustype = BUS_ADB; + input_dev->id.vendor = 0x001f; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + + input_dev->evbit[0] = BIT_MASK(EV_SND); + input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE); + input_dev->event = snd_pmac_beep_event; + input_dev->dev.parent = &chip->pdev->dev; + input_set_drvdata(input_dev, chip); + + beep->dev = input_dev; + beep->buf = dmabuf; + beep->volume = BEEP_VOLUME; + beep->running = 0; + + beep_ctl = snd_ctl_new1(&snd_pmac_beep_mixer, chip); + err = snd_ctl_add(chip->card, beep_ctl); + if (err < 0) + goto fail1; + + chip->beep = beep; + + err = input_register_device(beep->dev); + if (err) + goto fail2; + + return 0; + + fail2: snd_ctl_remove(chip->card, beep_ctl); + fail1: input_free_device(input_dev); + if (dmabuf) + dma_free_coherent(&chip->pdev->dev, BEEP_BUFLEN * 4, + dmabuf, beep->addr); + kfree(beep); + return err; +} + +void snd_pmac_detach_beep(struct snd_pmac *chip) +{ + if (chip->beep) { + input_unregister_device(chip->beep->dev); + dma_free_coherent(&chip->pdev->dev, BEEP_BUFLEN * 4, + chip->beep->buf, chip->beep->addr); + kfree(chip->beep); + chip->beep = NULL; + } +} diff --git a/sound/ppc/burgundy.c b/sound/ppc/burgundy.c new file mode 100644 index 000000000..5d6accce3 --- /dev/null +++ b/sound/ppc/burgundy.c @@ -0,0 +1,714 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PMac Burgundy lowlevel functions + * + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * code based on dmasound.c. + */ + +#include <linux/io.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/of.h> +#include <sound/core.h> +#include "pmac.h" +#include "burgundy.h" + + +/* Waits for busy flag to clear */ +static inline void +snd_pmac_burgundy_busy_wait(struct snd_pmac *chip) +{ + int timeout = 50; + while ((in_le32(&chip->awacs->codec_ctrl) & MASK_NEWECMD) && timeout--) + udelay(1); + if (timeout < 0) + printk(KERN_DEBUG "burgundy_busy_wait: timeout\n"); +} + +static inline void +snd_pmac_burgundy_extend_wait(struct snd_pmac *chip) +{ + int timeout; + timeout = 50; + while (!(in_le32(&chip->awacs->codec_stat) & MASK_EXTEND) && timeout--) + udelay(1); + if (timeout < 0) + 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 < 0) + printk(KERN_DEBUG "burgundy_extend_wait: timeout #2\n"); +} + +static void +snd_pmac_burgundy_wcw(struct snd_pmac *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(struct snd_pmac *chip, unsigned addr) +{ + unsigned val = 0; + + guard(spinlock_irqsave)(&chip->reg_lock); + + 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; + + return val; +} + +static void +snd_pmac_burgundy_wcb(struct snd_pmac *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(struct snd_pmac *chip, unsigned int addr) +{ + unsigned val = 0; + + guard(spinlock_irqsave)(&chip->reg_lock); + + 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; + + return val; +} + +#define BASE2ADDR(base) ((base) << 12) +#define ADDR2BASE(addr) ((addr) >> 12) + +/* + * Burgundy volume: 0 - 100, stereo, word reg + */ +static void +snd_pmac_burgundy_write_volume(struct snd_pmac *chip, unsigned int address, + long *volume, int shift) +{ + int hardvolume, lvolume, rvolume; + + if (volume[0] < 0 || volume[0] > 100 || + volume[1] < 0 || volume[1] > 100) + return; /* -EINVAL */ + 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(struct snd_pmac *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; +} + +static int snd_pmac_burgundy_info_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *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_W(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)) } + +/* + * Burgundy volume: 0 - 100, stereo, 2-byte reg + */ +static void +snd_pmac_burgundy_write_volume_2b(struct snd_pmac *chip, unsigned int address, + long *volume, int off) +{ + int lvolume, rvolume; + + off |= off << 2; + lvolume = volume[0] ? volume[0] + BURGUNDY_VOLUME_OFFSET : 0; + rvolume = volume[1] ? volume[1] + BURGUNDY_VOLUME_OFFSET : 0; + + snd_pmac_burgundy_wcb(chip, address + off, lvolume); + snd_pmac_burgundy_wcb(chip, address + off + 0x500, rvolume); +} + +static void +snd_pmac_burgundy_read_volume_2b(struct snd_pmac *chip, unsigned int address, + long *volume, int off) +{ + volume[0] = snd_pmac_burgundy_rcb(chip, address + off); + if (volume[0] >= BURGUNDY_VOLUME_OFFSET) + volume[0] -= BURGUNDY_VOLUME_OFFSET; + else + volume[0] = 0; + volume[1] = snd_pmac_burgundy_rcb(chip, address + off + 0x100); + if (volume[1] >= BURGUNDY_VOLUME_OFFSET) + volume[1] -= BURGUNDY_VOLUME_OFFSET; + else + volume[1] = 0; +} + +static int snd_pmac_burgundy_info_volume_2b(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *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_2b(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff); + int off = kcontrol->private_value & 0x300; + snd_pmac_burgundy_read_volume_2b(chip, addr, + ucontrol->value.integer.value, off); + return 0; +} + +static int snd_pmac_burgundy_put_volume_2b(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff); + int off = kcontrol->private_value & 0x300; + long nvoices[2]; + + snd_pmac_burgundy_write_volume_2b(chip, addr, + ucontrol->value.integer.value, off); + snd_pmac_burgundy_read_volume_2b(chip, addr, nvoices, off); + return (nvoices[0] != ucontrol->value.integer.value[0] || + nvoices[1] != ucontrol->value.integer.value[1]); +} + +#define BURGUNDY_VOLUME_2B(xname, xindex, addr, off) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\ + .info = snd_pmac_burgundy_info_volume_2b,\ + .get = snd_pmac_burgundy_get_volume_2b,\ + .put = snd_pmac_burgundy_put_volume_2b,\ + .private_value = ((ADDR2BASE(addr) & 0xff) | ((off) << 8)) } + +/* + * Burgundy gain/attenuation: 0 - 15, mono/stereo, byte reg + */ +static int snd_pmac_burgundy_info_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *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_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff); + int stereo = (kcontrol->private_value >> 24) & 1; + int atten = (kcontrol->private_value >> 25) & 1; + int oval; + + oval = snd_pmac_burgundy_rcb(chip, addr); + if (atten) + oval = ~oval & 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_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff); + int stereo = (kcontrol->private_value >> 24) & 1; + int atten = (kcontrol->private_value >> 25) & 1; + int oval, val; + + oval = snd_pmac_burgundy_rcb(chip, addr); + if (atten) + oval = ~oval & 0xff; + val = ucontrol->value.integer.value[0]; + if (stereo) + val |= ucontrol->value.integer.value[1] << 4; + else + val |= ucontrol->value.integer.value[0] << 4; + if (atten) + val = ~val & 0xff; + snd_pmac_burgundy_wcb(chip, addr, val); + return val != oval; +} + +#define BURGUNDY_VOLUME_B(xname, xindex, addr, stereo, atten) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\ + .info = snd_pmac_burgundy_info_gain,\ + .get = snd_pmac_burgundy_get_gain,\ + .put = snd_pmac_burgundy_put_gain,\ + .private_value = (ADDR2BASE(addr) | ((stereo) << 24) | ((atten) << 25)) } + +/* + * Burgundy switch: 0/1, mono/stereo, word reg + */ +static int snd_pmac_burgundy_info_switch_w(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *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_w(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR((kcontrol->private_value >> 16) & 0xff); + int lmask = 1 << (kcontrol->private_value & 0xff); + int rmask = 1 << ((kcontrol->private_value >> 8) & 0xff); + int stereo = (kcontrol->private_value >> 24) & 1; + int val = snd_pmac_burgundy_rcw(chip, addr); + 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_w(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR((kcontrol->private_value >> 16) & 0xff); + int lmask = 1 << (kcontrol->private_value & 0xff); + int rmask = 1 << ((kcontrol->private_value >> 8) & 0xff); + int stereo = (kcontrol->private_value >> 24) & 1; + int val, oval; + oval = snd_pmac_burgundy_rcw(chip, addr); + val = oval & ~(lmask | (stereo ? rmask : 0)); + if (ucontrol->value.integer.value[0]) + val |= lmask; + if (stereo && ucontrol->value.integer.value[1]) + val |= rmask; + snd_pmac_burgundy_wcw(chip, addr, val); + return val != oval; +} + +#define BURGUNDY_SWITCH_W(xname, xindex, addr, lbit, rbit, stereo) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\ + .info = snd_pmac_burgundy_info_switch_w,\ + .get = snd_pmac_burgundy_get_switch_w,\ + .put = snd_pmac_burgundy_put_switch_w,\ + .private_value = ((lbit) | ((rbit) << 8)\ + | (ADDR2BASE(addr) << 16) | ((stereo) << 24)) } + +/* + * Burgundy switch: 0/1, mono/stereo, byte reg, bit mask + */ +static int snd_pmac_burgundy_info_switch_b(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *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_b(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR((kcontrol->private_value >> 16) & 0xff); + 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, addr); + 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_b(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR((kcontrol->private_value >> 16) & 0xff); + 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, addr); + 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, addr, val); + return val != oval; +} + +#define BURGUNDY_SWITCH_B(xname, xindex, addr, lmask, rmask, stereo) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\ + .info = snd_pmac_burgundy_info_switch_b,\ + .get = snd_pmac_burgundy_get_switch_b,\ + .put = snd_pmac_burgundy_put_switch_b,\ + .private_value = ((lmask) | ((rmask) << 8)\ + | (ADDR2BASE(addr) << 16) | ((stereo) << 24)) } + +/* + * Burgundy mixers + */ +static const struct snd_kcontrol_new snd_pmac_burgundy_mixers[] = { + BURGUNDY_VOLUME_W("Master Playback Volume", 0, + MASK_ADDR_BURGUNDY_MASTER_VOLUME, 8), + BURGUNDY_VOLUME_W("CD Capture Volume", 0, + MASK_ADDR_BURGUNDY_VOLCD, 16), + BURGUNDY_VOLUME_2B("Input Capture Volume", 0, + MASK_ADDR_BURGUNDY_VOLMIX01, 2), + BURGUNDY_VOLUME_2B("Mixer Playback Volume", 0, + MASK_ADDR_BURGUNDY_VOLMIX23, 0), + BURGUNDY_VOLUME_B("CD Gain Capture Volume", 0, + MASK_ADDR_BURGUNDY_GAINCD, 1, 0), + BURGUNDY_SWITCH_W("Master Capture Switch", 0, + MASK_ADDR_BURGUNDY_OUTPUTENABLES, 24, 0, 0), + BURGUNDY_SWITCH_W("CD Capture Switch", 0, + MASK_ADDR_BURGUNDY_CAPTURESELECTS, 0, 16, 1), + BURGUNDY_SWITCH_W("CD Playback Switch", 0, + MASK_ADDR_BURGUNDY_OUTPUTSELECTS, 0, 16, 1), +/* BURGUNDY_SWITCH_W("Loop Capture Switch", 0, + * MASK_ADDR_BURGUNDY_CAPTURESELECTS, 8, 24, 1), + * BURGUNDY_SWITCH_B("Mixer out Capture Switch", 0, + * MASK_ADDR_BURGUNDY_HOSTIFAD, 0x02, 0, 0), + * BURGUNDY_SWITCH_B("Mixer Capture Switch", 0, + * MASK_ADDR_BURGUNDY_HOSTIFAD, 0x01, 0, 0), + * BURGUNDY_SWITCH_B("PCM out Capture Switch", 0, + * MASK_ADDR_BURGUNDY_HOSTIFEH, 0x02, 0, 0), + */ BURGUNDY_SWITCH_B("PCM Capture Switch", 0, + MASK_ADDR_BURGUNDY_HOSTIFEH, 0x01, 0, 0) +}; +static const struct snd_kcontrol_new snd_pmac_burgundy_mixers_imac[] = { + BURGUNDY_VOLUME_W("Line in Capture Volume", 0, + MASK_ADDR_BURGUNDY_VOLLINE, 16), + BURGUNDY_VOLUME_W("Mic Capture Volume", 0, + MASK_ADDR_BURGUNDY_VOLMIC, 16), + BURGUNDY_VOLUME_B("Line in Gain Capture Volume", 0, + MASK_ADDR_BURGUNDY_GAINLINE, 1, 0), + BURGUNDY_VOLUME_B("Mic Gain Capture Volume", 0, + MASK_ADDR_BURGUNDY_GAINMIC, 1, 0), + BURGUNDY_VOLUME_B("Speaker Playback Volume", 0, + MASK_ADDR_BURGUNDY_ATTENSPEAKER, 1, 1), + BURGUNDY_VOLUME_B("Line out Playback Volume", 0, + MASK_ADDR_BURGUNDY_ATTENLINEOUT, 1, 1), + BURGUNDY_VOLUME_B("Headphone Playback Volume", 0, + MASK_ADDR_BURGUNDY_ATTENHP, 1, 1), + BURGUNDY_SWITCH_W("Line in Capture Switch", 0, + MASK_ADDR_BURGUNDY_CAPTURESELECTS, 1, 17, 1), + BURGUNDY_SWITCH_W("Mic Capture Switch", 0, + MASK_ADDR_BURGUNDY_CAPTURESELECTS, 2, 18, 1), + BURGUNDY_SWITCH_W("Line in Playback Switch", 0, + MASK_ADDR_BURGUNDY_OUTPUTSELECTS, 1, 17, 1), + BURGUNDY_SWITCH_W("Mic Playback Switch", 0, + MASK_ADDR_BURGUNDY_OUTPUTSELECTS, 2, 18, 1), + BURGUNDY_SWITCH_B("Mic Boost Capture Switch", 0, + MASK_ADDR_BURGUNDY_INPBOOST, 0x40, 0x80, 1) +}; +static const struct snd_kcontrol_new snd_pmac_burgundy_mixers_pmac[] = { + BURGUNDY_VOLUME_W("Line in Capture Volume", 0, + MASK_ADDR_BURGUNDY_VOLMIC, 16), + BURGUNDY_VOLUME_B("Line in Gain Capture Volume", 0, + MASK_ADDR_BURGUNDY_GAINMIC, 1, 0), + BURGUNDY_VOLUME_B("Speaker Playback Volume", 0, + MASK_ADDR_BURGUNDY_ATTENMONO, 0, 1), + BURGUNDY_VOLUME_B("Line out Playback Volume", 0, + MASK_ADDR_BURGUNDY_ATTENSPEAKER, 1, 1), + BURGUNDY_SWITCH_W("Line in Capture Switch", 0, + MASK_ADDR_BURGUNDY_CAPTURESELECTS, 2, 18, 1), + BURGUNDY_SWITCH_W("Line in Playback Switch", 0, + MASK_ADDR_BURGUNDY_OUTPUTSELECTS, 2, 18, 1), +/* BURGUNDY_SWITCH_B("Line in Boost Capture Switch", 0, + * MASK_ADDR_BURGUNDY_INPBOOST, 0x40, 0x80, 1) */ +}; +static const struct snd_kcontrol_new snd_pmac_burgundy_master_sw_imac = +BURGUNDY_SWITCH_B("Master Playback Switch", 0, + MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + BURGUNDY_OUTPUT_LEFT | BURGUNDY_LINEOUT_LEFT | BURGUNDY_HP_LEFT, + BURGUNDY_OUTPUT_RIGHT | BURGUNDY_LINEOUT_RIGHT | BURGUNDY_HP_RIGHT, 1); +static const struct snd_kcontrol_new snd_pmac_burgundy_master_sw_pmac = +BURGUNDY_SWITCH_B("Master Playback Switch", 0, + MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + BURGUNDY_OUTPUT_INTERN + | BURGUNDY_OUTPUT_LEFT, BURGUNDY_OUTPUT_RIGHT, 1); +static const struct snd_kcontrol_new snd_pmac_burgundy_speaker_sw_imac = +BURGUNDY_SWITCH_B("Speaker Playback Switch", 0, + MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + BURGUNDY_OUTPUT_LEFT, BURGUNDY_OUTPUT_RIGHT, 1); +static const struct snd_kcontrol_new snd_pmac_burgundy_speaker_sw_pmac = +BURGUNDY_SWITCH_B("Speaker Playback Switch", 0, + MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + BURGUNDY_OUTPUT_INTERN, 0, 0); +static const struct snd_kcontrol_new snd_pmac_burgundy_line_sw_imac = +BURGUNDY_SWITCH_B("Line out Playback Switch", 0, + MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + BURGUNDY_LINEOUT_LEFT, BURGUNDY_LINEOUT_RIGHT, 1); +static const struct snd_kcontrol_new snd_pmac_burgundy_line_sw_pmac = +BURGUNDY_SWITCH_B("Line out Playback Switch", 0, + MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + BURGUNDY_OUTPUT_LEFT, BURGUNDY_OUTPUT_RIGHT, 1); +static const struct snd_kcontrol_new snd_pmac_burgundy_hp_sw_imac = +BURGUNDY_SWITCH_B("Headphone Playback Switch", 0, + MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + BURGUNDY_HP_LEFT, BURGUNDY_HP_RIGHT, 1); + + +#ifdef PMAC_SUPPORT_AUTOMUTE +/* + * auto-mute stuffs + */ +static int snd_pmac_burgundy_detect_headphone(struct snd_pmac *chip) +{ + return (in_le32(&chip->awacs->codec_stat) & chip->hp_stat_mask) ? 1 : 0; +} + +static void snd_pmac_burgundy_update_automute(struct snd_pmac *chip, int do_notify) +{ + if (chip->auto_mute) { + int imac = of_machine_is_compatible("iMac"); + int reg, oreg; + reg = oreg = snd_pmac_burgundy_rcb(chip, + MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES); + reg &= imac ? ~(BURGUNDY_OUTPUT_LEFT | BURGUNDY_OUTPUT_RIGHT + | BURGUNDY_HP_LEFT | BURGUNDY_HP_RIGHT) + : ~(BURGUNDY_OUTPUT_LEFT | BURGUNDY_OUTPUT_RIGHT + | BURGUNDY_OUTPUT_INTERN); + if (snd_pmac_burgundy_detect_headphone(chip)) + reg |= imac ? (BURGUNDY_HP_LEFT | BURGUNDY_HP_RIGHT) + : (BURGUNDY_OUTPUT_LEFT + | BURGUNDY_OUTPUT_RIGHT); + else + reg |= imac ? (BURGUNDY_OUTPUT_LEFT + | BURGUNDY_OUTPUT_RIGHT) + : (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 snd_pmac_burgundy_init(struct snd_pmac *chip) +{ + int imac = of_machine_is_compatible("iMac"); + 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_wcw(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, + imac ? DEF_BURGUNDY_INPSEL3_IMAC + : DEF_BURGUNDY_INPSEL3_PMAC); + 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 */ + if (imac) + chip->hp_stat_mask = BURGUNDY_HPDETECT_IMAC_UPPER + | BURGUNDY_HPDETECT_IMAC_LOWER + | BURGUNDY_HPDETECT_IMAC_SIDE; + else + chip->hp_stat_mask = BURGUNDY_HPDETECT_PMAC_BACK; + } + /* + * build burgundy mixers + */ + strscpy(chip->card->mixername, "PowerMac Burgundy"); + + for (i = 0; i < ARRAY_SIZE(snd_pmac_burgundy_mixers); i++) { + err = snd_ctl_add(chip->card, + snd_ctl_new1(&snd_pmac_burgundy_mixers[i], chip)); + if (err < 0) + return err; + } + for (i = 0; i < (imac ? ARRAY_SIZE(snd_pmac_burgundy_mixers_imac) + : ARRAY_SIZE(snd_pmac_burgundy_mixers_pmac)); i++) { + err = snd_ctl_add(chip->card, + snd_ctl_new1(imac ? &snd_pmac_burgundy_mixers_imac[i] + : &snd_pmac_burgundy_mixers_pmac[i], chip)); + if (err < 0) + return err; + } + chip->master_sw_ctl = snd_ctl_new1(imac + ? &snd_pmac_burgundy_master_sw_imac + : &snd_pmac_burgundy_master_sw_pmac, chip); + err = snd_ctl_add(chip->card, chip->master_sw_ctl); + if (err < 0) + return err; + chip->master_sw_ctl = snd_ctl_new1(imac + ? &snd_pmac_burgundy_line_sw_imac + : &snd_pmac_burgundy_line_sw_pmac, chip); + err = snd_ctl_add(chip->card, chip->master_sw_ctl); + if (err < 0) + return err; + if (imac) { + chip->master_sw_ctl = snd_ctl_new1( + &snd_pmac_burgundy_hp_sw_imac, chip); + err = snd_ctl_add(chip->card, chip->master_sw_ctl); + if (err < 0) + return err; + } + chip->speaker_sw_ctl = snd_ctl_new1(imac + ? &snd_pmac_burgundy_speaker_sw_imac + : &snd_pmac_burgundy_speaker_sw_pmac, chip); + err = snd_ctl_add(chip->card, chip->speaker_sw_ctl); + if (err < 0) + return err; +#ifdef PMAC_SUPPORT_AUTOMUTE + err = snd_pmac_add_automute(chip); + if (err < 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 000000000..538add0e4 --- /dev/null +++ b/sound/ppc/burgundy.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for PowerMac Burgundy onboard soundchips + * Copyright (c) 2001 by Takashi Iwai <tiwai@suse.de> + * based on dmasound.c. + */ + + +#ifndef __BURGUNDY_H +#define __BURGUNDY_H + +#define MASK_ADDR_BURGUNDY_INPBOOST (0x10 << 12) +#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_CAPTURESELECTS (0x2A << 12) +#define MASK_ADDR_BURGUNDY_OUTPUTSELECTS (0x2B << 12) +#define MASK_ADDR_BURGUNDY_VOLMIX01 (0x2D << 12) +#define MASK_ADDR_BURGUNDY_VOLMIX23 (0x2E << 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_ATTENMONO (0x65 << 12) + +#define MASK_ADDR_BURGUNDY_HOSTIFAD (0x78 << 12) +#define MASK_ADDR_BURGUNDY_HOSTIFEH (0x79 << 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_IMAC (0x0A) +#define DEF_BURGUNDY_INPSEL3_PMAC (0x05) + +#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 (155) */ +#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 (0x0100000A) + +/* #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) + +/* MORE_OUTPUTENABLES bits */ +#define BURGUNDY_OUTPUT_LEFT 0x02 +#define BURGUNDY_OUTPUT_RIGHT 0x04 +#define BURGUNDY_LINEOUT_LEFT 0x08 +#define BURGUNDY_LINEOUT_RIGHT 0x10 +#define BURGUNDY_HP_LEFT 0x20 +#define BURGUNDY_HP_RIGHT 0x40 +#define BURGUNDY_OUTPUT_INTERN 0x80 + +/* Headphone detection bits */ +#define BURGUNDY_HPDETECT_PMAC_BACK 0x04 +#define BURGUNDY_HPDETECT_IMAC_SIDE 0x04 +#define BURGUNDY_HPDETECT_IMAC_UPPER 0x08 +#define BURGUNDY_HPDETECT_IMAC_LOWER 0x01 + +/* 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 000000000..f35cb870d --- /dev/null +++ b/sound/ppc/daca.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PMac DACA lowlevel functions + * + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + */ + + +#include <linux/init.h> +#include <linux/i2c.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 + + +struct pmac_daca { + struct pmac_keywest i2c; + int left_vol, right_vol; + unsigned int deemphasis : 1; + unsigned int amp_on : 1; +}; + + +/* + * initialize / detect DACA + */ +static int daca_init_client(struct pmac_keywest *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(struct pmac_daca *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) { + dev_err(&mix->i2c.client->dev, "failed to set volume\n"); + return -EINVAL; + } + return 0; +} + + +/* deemphasis switch */ +#define daca_info_deemphasis snd_ctl_boolean_mono_info + +static int daca_get_deemphasis(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_daca *mix; + mix = chip->mixer_data; + if (!mix) + return -ENODEV; + ucontrol->value.integer.value[0] = mix->deemphasis ? 1 : 0; + return 0; +} + +static int daca_put_deemphasis(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_daca *mix; + int change; + + mix = chip->mixer_data; + if (!mix) + 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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_daca *mix; + mix = chip->mixer_data; + if (!mix) + 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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_daca *mix; + unsigned int vol[2]; + int change; + + mix = chip->mixer_data; + if (!mix) + return -ENODEV; + vol[0] = ucontrol->value.integer.value[0]; + vol[1] = ucontrol->value.integer.value[1]; + if (vol[0] > DACA_VOL_MAX || vol[1] > DACA_VOL_MAX) + return -EINVAL; + change = mix->left_vol != vol[0] || + mix->right_vol != vol[1]; + if (change) { + mix->left_vol = vol[0]; + mix->right_vol = vol[1]; + daca_set_volume(mix); + } + return change; +} + +/* amplifier switch */ +#define daca_info_amp daca_info_deemphasis + +static int daca_get_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_daca *mix; + mix = chip->mixer_data; + if (!mix) + return -ENODEV; + ucontrol->value.integer.value[0] = mix->amp_on ? 1 : 0; + return 0; +} + +static int daca_put_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_daca *mix; + int change; + + mix = chip->mixer_data; + if (!mix) + 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 const struct snd_kcontrol_new 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_PM +static void daca_resume(struct snd_pmac *chip) +{ + struct pmac_daca *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_PM */ + + +static void daca_cleanup(struct snd_pmac *chip) +{ + struct pmac_daca *mix = chip->mixer_data; + if (! mix) + return; + snd_pmac_keywest_cleanup(&mix->i2c); + kfree(mix); + chip->mixer_data = NULL; +} + +/* exported */ +int snd_pmac_daca_init(struct snd_pmac *chip) +{ + int i, err; + struct pmac_daca *mix; + + request_module("i2c-powermac"); + + mix = kzalloc_obj(*mix); + if (! mix) + return -ENOMEM; + 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"; + err = snd_pmac_keywest_init(&mix->i2c); + if (err < 0) + return err; + + /* + * build mixers + */ + strscpy(chip->card->mixername, "PowerMac DACA"); + + for (i = 0; i < ARRAY_SIZE(daca_mixers); i++) { + err = snd_ctl_add(chip->card, snd_ctl_new1(&daca_mixers[i], chip)); + if (err < 0) + return err; + } + +#ifdef CONFIG_PM + chip->resume = daca_resume; +#endif + + return 0; +} diff --git a/sound/ppc/keywest.c b/sound/ppc/keywest.c new file mode 100644 index 000000000..4ce81ac7f --- /dev/null +++ b/sound/ppc/keywest.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * common keywest i2c layer + * + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + */ + + +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <sound/core.h> +#include "pmac.h" + +static struct pmac_keywest *keywest_ctx; +static bool keywest_probed; + +static int keywest_probe(struct i2c_client *client) +{ + keywest_probed = true; + /* If instantiated via i2c-powermac, we still need to set the client */ + if (!keywest_ctx->client) + keywest_ctx->client = client; + i2c_set_clientdata(client, keywest_ctx); + return 0; +} + +/* + * This is kind of a hack, best would be to turn powermac to fixed i2c + * bus numbers and declare the sound device as part of platform + * initialization + */ +static int keywest_attach_adapter(struct i2c_adapter *adapter) +{ + struct i2c_board_info info; + struct i2c_client *client; + + if (! keywest_ctx) + return -EINVAL; + + if (strncmp(adapter->name, "mac-io", 6)) + return -EINVAL; /* ignored */ + + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "keywest", I2C_NAME_SIZE); + info.addr = keywest_ctx->addr; + client = i2c_new_client_device(adapter, &info); + if (IS_ERR(client)) + return PTR_ERR(client); + keywest_ctx->client = client; + + /* + * We know the driver is already loaded, so the device should be + * already bound. If not it means binding failed, and then there + * is no point in keeping the device instantiated. + */ + if (!keywest_ctx->client->dev.driver) { + i2c_unregister_device(keywest_ctx->client); + keywest_ctx->client = NULL; + return -ENODEV; + } + + return 0; +} + +static void keywest_remove(struct i2c_client *client) +{ + if (! keywest_ctx) + return; + if (client == keywest_ctx->client) + keywest_ctx->client = NULL; +} + + +static const struct i2c_device_id keywest_i2c_id[] = { + { "MAC,tas3004" }, /* instantiated by i2c-powermac */ + { "keywest" }, /* instantiated by us if needed */ + { } +}; +MODULE_DEVICE_TABLE(i2c, keywest_i2c_id); + +static struct i2c_driver keywest_driver = { + .driver = { + .name = "PMac Keywest Audio", + }, + .probe = keywest_probe, + .remove = keywest_remove, + .id_table = keywest_i2c_id, +}; + +/* exported */ +void snd_pmac_keywest_cleanup(struct pmac_keywest *i2c) +{ + if (keywest_ctx && keywest_ctx == i2c) { + i2c_unregister_device(keywest_ctx->client); + i2c_del_driver(&keywest_driver); + keywest_ctx = NULL; + } +} + +int snd_pmac_tumbler_post_init(void) +{ + int err; + + if (!keywest_ctx || !keywest_ctx->client) + return -ENXIO; + + err = keywest_ctx->init_client(keywest_ctx); + if (err < 0) { + dev_err(&keywest_ctx->client->dev, + "tumbler: %i :cannot initialize the MCS\n", err); + return err; + } + return 0; +} + +/* exported */ +int snd_pmac_keywest_init(struct pmac_keywest *i2c) +{ + struct i2c_adapter *adap; + int err, i = 0; + + if (keywest_ctx) + return -EBUSY; + + adap = i2c_get_adapter(0); + if (!adap) + return -EPROBE_DEFER; + + keywest_ctx = i2c; + + err = i2c_add_driver(&keywest_driver); + if (err) { + dev_err(&i2c->client->dev, "cannot register keywest i2c driver\n"); + i2c_put_adapter(adap); + return err; + } + + /* There was already a device from i2c-powermac. Great, let's return */ + if (keywest_probed) + return 0; + + /* We assume Macs have consecutive I2C bus numbers starting at 0 */ + while (adap) { + /* Scan for devices to be bound to */ + err = keywest_attach_adapter(adap); + if (!err) + return 0; + i2c_put_adapter(adap); + adap = i2c_get_adapter(++i); + } + + return -ENODEV; +} diff --git a/sound/ppc/pmac.c b/sound/ppc/pmac.c new file mode 100644 index 000000000..ba2048ee6 --- /dev/null +++ b/sound/ppc/pmac.c @@ -0,0 +1,1363 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PMac DBDMA lowlevel functions + * + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * code based on dmasound.c. + */ + + +#include <linux/io.h> +#include <asm/irq.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/dma-mapping.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <sound/core.h> +#include "pmac.h" +#include <sound/pcm_params.h> +#include <asm/pmac_feature.h> + + +/* fixed frequency table for awacs, screamer, burgundy, DACA (44100 max) */ +static const int awacs_freqs[8] = { + 44100, 29400, 22050, 17640, 14700, 11025, 8820, 7350 +}; +/* fixed frequency table for tumbler */ +static const int tumbler_freqs[1] = { + 44100 +}; + + +/* + * we will allocate a single 'emergency' dbdma cmd block to use if the + * tx status comes up "DEAD". This happens on some PowerComputing Pmac + * clones, either owing to a bug in dbdma or some interaction between + * IDE and sound. However, this measure would deal with DEAD status if + * it appeared elsewhere. + */ +static struct pmac_dbdma emergency_dbdma; +static int emergency_in_use; + + +/* + * allocate DBDMA command arrays + */ +static int snd_pmac_dbdma_alloc(struct snd_pmac *chip, struct pmac_dbdma *rec, int size) +{ + unsigned int rsize = sizeof(struct dbdma_cmd) * (size + 1); + + rec->space = dma_alloc_coherent(&chip->pdev->dev, rsize, + &rec->dma_base, GFP_KERNEL); + if (rec->space == NULL) + return -ENOMEM; + rec->size = size; + memset(rec->space, 0, rsize); + rec->cmds = (void __iomem *)DBDMA_ALIGN(rec->space); + rec->addr = rec->dma_base + (unsigned long)((char *)rec->cmds - (char *)rec->space); + + return 0; +} + +static void snd_pmac_dbdma_free(struct snd_pmac *chip, struct pmac_dbdma *rec) +{ + if (rec->space) { + unsigned int rsize = sizeof(struct dbdma_cmd) * (rec->size + 1); + + dma_free_coherent(&chip->pdev->dev, rsize, rec->space, rec->dma_base); + } +} + + +/* + * pcm stuff + */ + +/* + * look up frequency table + */ + +unsigned int snd_pmac_rate_index(struct snd_pmac *chip, struct pmac_stream *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; +} + +/* + * get a stream of the opposite direction + */ +static struct pmac_stream *snd_pmac_get_stream(struct snd_pmac *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 + */ +static inline void +snd_pmac_wait_ack(struct pmac_stream *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(struct snd_pmac *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 + */ +static inline void snd_pmac_dma_stop(struct pmac_stream *rec) +{ + out_le32(&rec->dma->control, (RUN|WAKE|FLUSH|PAUSE) << 16); + snd_pmac_wait_ack(rec); +} + +/* + * set the command pointer address + */ +static inline void snd_pmac_dma_set_command(struct pmac_stream *rec, struct pmac_dbdma *cmd) +{ + out_le32(&rec->dma->cmdptr, cmd->addr); +} + +/* + * start the DMA + */ +static inline void snd_pmac_dma_run(struct pmac_stream *rec, int status) +{ + out_le32(&rec->dma->control, status | (status << 16)); +} + + +/* + * prepare playback/capture stream + */ +static int snd_pmac_pcm_prepare(struct snd_pmac *chip, struct pmac_stream *rec, struct snd_pcm_substream *subs) +{ + int i; + volatile struct dbdma_cmd __iomem *cp; + struct snd_pcm_runtime *runtime = subs->runtime; + int rate_index; + long offset; + struct pmac_stream *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)); + if (! 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 + */ + scoped_guard(spinlock_irq, &chip->reg_lock) { + snd_pmac_dma_stop(rec); + chip->extra_dma.cmds->command = cpu_to_le16(DBDMA_STOP); + snd_pmac_dma_set_command(rec, &chip->extra_dma); + snd_pmac_dma_run(rec, RUN); + } + mdelay(5); + scoped_guard(spinlock_irq, &chip->reg_lock) { + /* continuous DMA memory type doesn't provide the physical address, + * so we need to resolve the address here... + */ + offset = runtime->dma_addr; + for (i = 0, cp = rec->cmd.cmds; i < rec->nperiods; i++, cp++) { + cp->phy_addr = cpu_to_le32(offset); + cp->req_count = cpu_to_le16(rec->period_size); + /*cp->res_count = cpu_to_le16(0);*/ + cp->xfer_status = cpu_to_le16(0); + offset += rec->period_size; + } + /* make loop */ + cp->command = cpu_to_le16(DBDMA_NOP | BR_ALWAYS); + cp->cmd_dep = cpu_to_le32(rec->cmd.addr); + + snd_pmac_dma_stop(rec); + snd_pmac_dma_set_command(rec, &rec->cmd); + } + + return 0; +} + + +/* + * PCM trigger/stop + */ +static int snd_pmac_pcm_trigger(struct snd_pmac *chip, struct pmac_stream *rec, + struct snd_pcm_substream *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; + scoped_guard(spinlock, &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; + } + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + scoped_guard(spinlock, &chip->reg_lock) { + rec->running = 0; + snd_pmac_dma_stop(rec); + for (i = 0, cp = rec->cmd.cmds; i < rec->nperiods; i++, cp++) + out_le16(&cp->command, DBDMA_STOP); + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* + * return the current pointer + */ +inline +static snd_pcm_uframes_t snd_pmac_pcm_pointer(struct snd_pmac *chip, + struct pmac_stream *rec, + struct snd_pcm_substream *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 = le16_to_cpu(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; + return bytes_to_frames(subs->runtime, count); +} + +/* + * playback + */ + +static int snd_pmac_playback_prepare(struct snd_pcm_substream *subs) +{ + struct snd_pmac *chip = snd_pcm_substream_chip(subs); + return snd_pmac_pcm_prepare(chip, &chip->playback, subs); +} + +static int snd_pmac_playback_trigger(struct snd_pcm_substream *subs, + int cmd) +{ + struct snd_pmac *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(struct snd_pcm_substream *subs) +{ + struct snd_pmac *chip = snd_pcm_substream_chip(subs); + return snd_pmac_pcm_pointer(chip, &chip->playback, subs); +} + + +/* + * capture + */ + +static int snd_pmac_capture_prepare(struct snd_pcm_substream *subs) +{ + struct snd_pmac *chip = snd_pcm_substream_chip(subs); + return snd_pmac_pcm_prepare(chip, &chip->capture, subs); +} + +static int snd_pmac_capture_trigger(struct snd_pcm_substream *subs, + int cmd) +{ + struct snd_pmac *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(struct snd_pcm_substream *subs) +{ + struct snd_pmac *chip = snd_pcm_substream_chip(subs); + return snd_pmac_pcm_pointer(chip, &chip->capture, subs); +} + + +/* + * Handle DEAD DMA transfers: + * if the TX status comes up "DEAD" - reported on some Power Computing machines + * we need to re-start the dbdma - but from a different physical start address + * and with a different transfer length. It would get very messy to do this + * with the normal dbdma_cmd blocks - we would have to re-write the buffer start + * addresses each time. So, we will keep a single dbdma_cmd block which can be + * fiddled with. + * When DEAD status is first reported the content of the faulted dbdma block is + * copied into the emergency buffer and we note that the buffer is in use. + * we then bump the start physical address by the amount that was successfully + * output before it died. + * On any subsequent DEAD result we just do the bump-ups (we know that we are + * already using the emergency dbdma_cmd). + * CHECK: this just tries to "do it". It is possible that we should abandon + * xfers when the number of residual bytes gets below a certain value - I can + * see that this might cause a loop-forever if a too small transfer causes + * DEAD status. However this is a TODO for now - we'll see what gets reported. + * When we get a successful transfer result with the emergency buffer we just + * pretend that it completed using the original dmdma_cmd and carry on. The + * 'next_cmd' field will already point back to the original loop of blocks. + */ +static inline void snd_pmac_pcm_dead_xfer(struct pmac_stream *rec, + volatile struct dbdma_cmd __iomem *cp) +{ + unsigned short req, res ; + unsigned int phy ; + + /* to clear DEAD status we must first clear RUN + set it to quiescent to be on the safe side */ + (void)in_le32(&rec->dma->status); + out_le32(&rec->dma->control, (RUN|PAUSE|FLUSH|WAKE) << 16); + + if (!emergency_in_use) { /* new problem */ + memcpy((void *)emergency_dbdma.cmds, (void *)cp, + sizeof(struct dbdma_cmd)); + emergency_in_use = 1; + cp->xfer_status = cpu_to_le16(0); + cp->req_count = cpu_to_le16(rec->period_size); + cp = emergency_dbdma.cmds; + } + + /* now bump the values to reflect the amount + we haven't yet shifted */ + req = le16_to_cpu(cp->req_count); + res = le16_to_cpu(cp->res_count); + phy = le32_to_cpu(cp->phy_addr); + phy += (req - res); + cp->req_count = cpu_to_le16(res); + cp->res_count = cpu_to_le16(0); + cp->xfer_status = cpu_to_le16(0); + cp->phy_addr = cpu_to_le32(phy); + + cp->cmd_dep = cpu_to_le32(rec->cmd.addr + + sizeof(struct dbdma_cmd)*((rec->cur_period+1)%rec->nperiods)); + + cp->command = cpu_to_le16(OUTPUT_MORE | BR_ALWAYS | INTR_ALWAYS); + + /* point at our patched up command block */ + out_le32(&rec->dma->cmdptr, emergency_dbdma.addr); + + /* we must re-start the controller */ + (void)in_le32(&rec->dma->status); + /* should complete clearing the DEAD status */ + out_le32(&rec->dma->control, ((RUN|WAKE) << 16) + (RUN|WAKE)); +} + +/* + * update playback/capture pointer from interrupts + */ +static void snd_pmac_pcm_update(struct snd_pmac *chip, struct pmac_stream *rec) +{ + volatile struct dbdma_cmd __iomem *cp; + int c; + int stat; + + spin_lock(&chip->reg_lock); + if (rec->running) { + for (c = 0; c < rec->nperiods; c++) { /* at most all fragments */ + + if (emergency_in_use) /* already using DEAD xfer? */ + cp = emergency_dbdma.cmds; + else + cp = &rec->cmd.cmds[rec->cur_period]; + + stat = le16_to_cpu(cp->xfer_status); + + if (stat & DEAD) { + snd_pmac_pcm_dead_xfer(rec, cp); + break; /* this block is still going */ + } + + if (emergency_in_use) + emergency_in_use = 0 ; /* done that */ + + if (! (stat & ACTIVE)) + break; + + cp->xfer_status = cpu_to_le16(0); + cp->req_count = cpu_to_le16(rec->period_size); + /*cp->res_count = cpu_to_le16(0);*/ + rec->cur_period++; + if (rec->cur_period >= rec->nperiods) { + rec->cur_period = 0; + } + + spin_unlock(&chip->reg_lock); + snd_pcm_period_elapsed(rec->substream); + spin_lock(&chip->reg_lock); + } + } + spin_unlock(&chip->reg_lock); +} + + +/* + * hw info + */ + +static const struct snd_pcm_hardware 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 const struct snd_pcm_hardware 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(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_pmac *chip = rule->private; + struct pmac_stream *rec = snd_pmac_get_stream(chip, rule->deps[0]); + int i, freq_table[8], num_freqs; + + if (! 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(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_pmac *chip = rule->private; + struct pmac_stream *rec = snd_pmac_get_stream(chip, rule->deps[0]); + + if (! 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(struct snd_pmac *chip, struct pmac_stream *rec, + struct snd_pcm_substream *subs) +{ + struct snd_pcm_runtime *runtime = subs->runtime; + int i; + + /* look up frequency table and fill bit mask */ + runtime->hw.rates = 0; + for (i = 0; i < chip->num_freqs; i++) + if (chip->freqs_ok & (1 << i)) + runtime->hw.rates |= + snd_pcm_rate_to_rate_bit(chip->freq_table[i]); + + /* 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; + + /* constraints to fix choppy sound */ + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + return 0; +} + +static int snd_pmac_pcm_close(struct snd_pmac *chip, struct pmac_stream *rec, + struct snd_pcm_substream *subs) +{ + struct pmac_stream *astr; + + snd_pmac_dma_stop(rec); + + astr = snd_pmac_get_stream(chip, another_stream(rec->stream)); + if (! 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(struct snd_pcm_substream *subs) +{ + struct snd_pmac *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(struct snd_pcm_substream *subs) +{ + struct snd_pmac *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(struct snd_pcm_substream *subs) +{ + struct snd_pmac *chip = snd_pcm_substream_chip(subs); + + return snd_pmac_pcm_close(chip, &chip->playback, subs); +} + +static int snd_pmac_capture_close(struct snd_pcm_substream *subs) +{ + struct snd_pmac *chip = snd_pcm_substream_chip(subs); + + return snd_pmac_pcm_close(chip, &chip->capture, subs); +} + +/* + */ + +static const struct snd_pcm_ops snd_pmac_playback_ops = { + .open = snd_pmac_playback_open, + .close = snd_pmac_playback_close, + .prepare = snd_pmac_playback_prepare, + .trigger = snd_pmac_playback_trigger, + .pointer = snd_pmac_playback_pointer, +}; + +static const struct snd_pcm_ops snd_pmac_capture_ops = { + .open = snd_pmac_capture_open, + .close = snd_pmac_capture_close, + .prepare = snd_pmac_capture_prepare, + .trigger = snd_pmac_capture_trigger, + .pointer = snd_pmac_capture_pointer, +}; + +int snd_pmac_pcm_new(struct snd_pmac *chip) +{ + struct snd_pcm *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->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX; + strscpy(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_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pdev->dev, + 64 * 1024, 64 * 1024); + + return 0; +} + + +static void snd_pmac_dbdma_reset(struct snd_pmac *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(struct snd_pmac *chip, int bytes, unsigned long addr, int speed) +{ + struct pmac_stream *rec = &chip->playback; + + snd_pmac_dma_stop(rec); + chip->extra_dma.cmds->req_count = cpu_to_le16(bytes); + chip->extra_dma.cmds->xfer_status = cpu_to_le16(0); + chip->extra_dma.cmds->cmd_dep = cpu_to_le32(chip->extra_dma.addr); + chip->extra_dma.cmds->phy_addr = cpu_to_le32(addr); + chip->extra_dma.cmds->command = cpu_to_le16(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(struct snd_pmac *chip) +{ + snd_pmac_dma_stop(&chip->playback); + chip->extra_dma.cmds->command = cpu_to_le16(DBDMA_STOP); + snd_pmac_pcm_set_format(chip); /* reset format */ +} + + +/* + * interrupt handlers + */ +static irqreturn_t +snd_pmac_tx_intr(int irq, void *devid) +{ + struct snd_pmac *chip = devid; + snd_pmac_pcm_update(chip, &chip->playback); + return IRQ_HANDLED; +} + + +static irqreturn_t +snd_pmac_rx_intr(int irq, void *devid) +{ + struct snd_pmac *chip = devid; + snd_pmac_pcm_update(chip, &chip->capture); + return IRQ_HANDLED; +} + + +static irqreturn_t +snd_pmac_ctrl_intr(int irq, void *devid) +{ + struct snd_pmac *chip = devid; + int ctrl = in_le32(&chip->awacs->control); + + 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) + dev_dbg(chip->card->dev, "%s: error %x\n", __func__, 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 + */ +static void snd_pmac_sound_feature(struct snd_pmac *chip, int enable) +{ + if (ppc_md.feature_call) + ppc_md.feature_call(PMAC_FTR_SOUND_CHIP_ENABLE, chip->node, 0, enable); +} + +/* + * release resources + */ + +static int snd_pmac_free(struct snd_pmac *chip) +{ + /* 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); + } + + if (chip->node) + snd_pmac_sound_feature(chip, 0); + + /* 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, &chip->playback.cmd); + snd_pmac_dbdma_free(chip, &chip->capture.cmd); + snd_pmac_dbdma_free(chip, &chip->extra_dma); + snd_pmac_dbdma_free(chip, &emergency_dbdma); + iounmap(chip->macio_base); + iounmap(chip->latch_base); + iounmap(chip->awacs); + iounmap(chip->playback.dma); + iounmap(chip->capture.dma); + + if (chip->node) { + int i; + for (i = 0; i < 3; i++) { + if (chip->requested & (1 << i)) + release_mem_region(chip->rsrc[i].start, + resource_size(&chip->rsrc[i])); + } + } + + pci_dev_put(chip->pdev); + of_node_put(chip->node); + kfree(chip); + return 0; +} + + +/* + * free the device + */ +static int snd_pmac_dev_free(struct snd_device *device) +{ + struct snd_pmac *chip = device->device_data; + return snd_pmac_free(chip); +} + + +/* + * check the machine support byteswap (little-endian) + */ + +static void detect_byte_swap(struct snd_pmac *chip) +{ + struct device_node *mio; + + /* if seems that Keylargo can't byte-swap */ + for (mio = chip->node->parent; mio; mio = mio->parent) { + if (of_node_name_eq(mio, "mac-io")) { + if (of_device_is_compatible(mio, "Keylargo")) + chip->can_byte_swap = 0; + break; + } + } + + /* it seems the Pismo & iBook can't byte-swap in hardware. */ + if (of_machine_is_compatible("PowerBook3,1") || + of_machine_is_compatible("PowerBook2,1")) + chip->can_byte_swap = 0 ; + + if (of_machine_is_compatible("PowerBook2,1")) + chip->can_duplex = 0; +} + + +/* + * detect a sound chip + */ +static int snd_pmac_detect(struct snd_pmac *chip) +{ + struct device_node *sound; + struct device_node *dn; + const unsigned int *prop; + unsigned int l; + struct macio_chip* macio; + + if (!machine_is(powermac)) + 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->pdev = NULL; + + chip->control_mask = MASK_IEPC | MASK_IEE | 0x11; /* default */ + + /* check machine type */ + if (of_machine_is_compatible("AAPL,3400/2400") + || of_machine_is_compatible("AAPL,3500")) + chip->is_pbook_3400 = 1; + else if (of_machine_is_compatible("PowerBook1,1") + || of_machine_is_compatible("AAPL,PowerBook1998")) + chip->is_pbook_G3 = 1; + chip->node = of_find_node_by_name(NULL, "awacs"); + sound = of_node_get(chip->node); + + /* + * powermac G3 models have a node called "davbus" + * with a child called "sound". + */ + if (!chip->node) + chip->node = of_find_node_by_name(NULL, "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 = of_find_node_by_name(NULL, "i2s-a"); + if (chip->node && chip->node->parent && + chip->node->parent->parent) { + if (of_device_is_compatible(chip->node->parent->parent, + "K2-Keylargo")) + chip->is_k2 = 1; + } + } + if (! chip->node) + return -ENODEV; + + if (!sound) { + for_each_node_by_name(sound, "sound") + if (sound->parent == chip->node) + break; + } + if (! sound) { + of_node_put(chip->node); + chip->node = NULL; + return -ENODEV; + } + prop = of_get_property(sound, "sub-frame", NULL); + if (prop && *prop < 16) + chip->subframe = *prop; + prop = of_get_property(sound, "layout-id", NULL); + if (prop) { + /* partly deprecate snd-powermac, for those machines + * that have a layout-id property for now */ + dev_info(chip->card->dev, + "snd-powermac no longer handles any machines with a layout-id property in the device-tree, use snd-aoa.\n"); + of_node_put(sound); + of_node_put(chip->node); + chip->node = NULL; + return -ENODEV; + } + /* This should be verified on older screamers */ + if (of_device_is_compatible(sound, "screamer")) { + chip->model = PMAC_SCREAMER; + // chip->can_byte_swap = 0; /* FIXME: check this */ + } + if (of_device_is_compatible(sound, "burgundy")) { + chip->model = PMAC_BURGUNDY; + chip->control_mask = MASK_IEPC | 0x11; /* disable IEE */ + } + if (of_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 (of_device_is_compatible(sound, "tumbler")) { + chip->model = PMAC_TUMBLER; + chip->can_capture = of_machine_is_compatible("PowerMac4,2") + || of_machine_is_compatible("PowerBook3,2") + || of_machine_is_compatible("PowerBook3,3") + || of_machine_is_compatible("PowerBook4,1") + || of_machine_is_compatible("PowerBook4,2") + || of_machine_is_compatible("PowerBook4,3"); + 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 (of_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 */ + } + prop = of_get_property(sound, "device-id", NULL); + if (prop) + chip->device_id = *prop; + dn = of_find_node_by_name(NULL, "perch"); + chip->has_iic = (dn != NULL); + of_node_put(dn); + + /* We need the PCI device for DMA allocations, let's use a crude method + * for now ... + */ + macio = macio_find(chip->node, macio_unknown); + if (macio == NULL) + dev_warn(chip->card->dev, "snd-powermac: can't locate macio !\n"); + else { + struct pci_dev *pdev = NULL; + + for_each_pci_dev(pdev) { + struct device_node *np = pci_device_to_OF_node(pdev); + if (np && np == macio->of_node) { + chip->pdev = pdev; + break; + } + } + } + if (chip->pdev == NULL) + dev_warn(chip->card->dev, + "snd-powermac: can't locate macio PCI device !\n"); + + detect_byte_swap(chip); + + /* look for a property saying what sample rates + are available */ + prop = of_get_property(sound, "sample-rates", &l); + if (! prop) + prop = of_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; + } + + of_node_put(sound); + return 0; +} + +#ifdef PMAC_SUPPORT_AUTOMUTE +/* + * auto-mute + */ +static int pmac_auto_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = chip->auto_mute; + return 0; +} + +static int pmac_auto_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *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 const struct snd_kcontrol_new auto_mute_controls[] = { + { .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 snd_pmac_add_automute(struct snd_pmac *chip) +{ + int err; + chip->auto_mute = 1; + err = snd_ctl_add(chip->card, snd_ctl_new1(&auto_mute_controls[0], chip)); + if (err < 0) { + dev_err(chip->card->dev, + "snd-powermac: Failed to add automute control\n"); + 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 snd_pmac_new(struct snd_card *card, struct snd_pmac **chip_return) +{ + struct snd_pmac *chip; + struct device_node *np; + int i, err; + unsigned int irq; + unsigned long ctrl_addr, txdma_addr, rxdma_addr; + static const struct snd_device_ops ops = { + .dev_free = snd_pmac_dev_free, + }; + + *chip_return = NULL; + + chip = kzalloc_obj(*chip); + 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; + + err = snd_pmac_detect(chip); + if (err < 0) + goto __error; + + if (snd_pmac_dbdma_alloc(chip, &chip->playback.cmd, PMAC_MAX_FRAGS + 1) < 0 || + snd_pmac_dbdma_alloc(chip, &chip->capture.cmd, PMAC_MAX_FRAGS + 1) < 0 || + snd_pmac_dbdma_alloc(chip, &chip->extra_dma, 2) < 0 || + snd_pmac_dbdma_alloc(chip, &emergency_dbdma, 2) < 0) { + err = -ENOMEM; + goto __error; + } + + np = chip->node; + chip->requested = 0; + if (chip->is_k2) { + static const char * const rnames[] = { + "Sound Control", "Sound DMA" }; + for (i = 0; i < 2; i ++) { + if (of_address_to_resource(np->parent, i, + &chip->rsrc[i])) { + dev_err(chip->card->dev, + "snd: can't translate rsrc %d (%s)\n", + i, rnames[i]); + err = -ENODEV; + goto __error; + } + if (request_mem_region(chip->rsrc[i].start, + resource_size(&chip->rsrc[i]), + rnames[i]) == NULL) { + dev_err(chip->card->dev, + "snd: can't request rsrc %d (%s: %pR)\n", + i, rnames[i], &chip->rsrc[i]); + err = -ENODEV; + goto __error; + } + chip->requested |= (1 << i); + } + ctrl_addr = chip->rsrc[0].start; + txdma_addr = chip->rsrc[1].start; + rxdma_addr = txdma_addr + 0x100; + } else { + static const char * const rnames[] = { + "Sound Control", "Sound Tx DMA", "Sound Rx DMA" }; + for (i = 0; i < 3; i ++) { + if (of_address_to_resource(np, i, + &chip->rsrc[i])) { + dev_err(chip->card->dev, + "snd: can't translate rsrc %d (%s)\n", + i, rnames[i]); + err = -ENODEV; + goto __error; + } + if (request_mem_region(chip->rsrc[i].start, + resource_size(&chip->rsrc[i]), + rnames[i]) == NULL) { + dev_err(chip->card->dev, + "snd: can't request rsrc %d (%s: %pR)\n", + i, rnames[i], &chip->rsrc[i]); + err = -ENODEV; + goto __error; + } + chip->requested |= (1 << i); + } + ctrl_addr = chip->rsrc[0].start; + txdma_addr = chip->rsrc[1].start; + rxdma_addr = chip->rsrc[2].start; + } + + chip->awacs = ioremap(ctrl_addr, 0x1000); + chip->playback.dma = ioremap(txdma_addr, 0x100); + chip->capture.dma = ioremap(rxdma_addr, 0x100); + if (chip->model <= PMAC_BURGUNDY) { + irq = irq_of_parse_and_map(np, 0); + if (request_irq(irq, snd_pmac_ctrl_intr, 0, + "PMac", (void*)chip)) { + dev_err(chip->card->dev, + "pmac: unable to grab IRQ %d\n", irq); + err = -EBUSY; + goto __error; + } + chip->irq = irq; + } + irq = irq_of_parse_and_map(np, 1); + if (request_irq(irq, snd_pmac_tx_intr, 0, "PMac Output", (void*)chip)){ + dev_err(chip->card->dev, "pmac: unable to grab IRQ %d\n", irq); + err = -EBUSY; + goto __error; + } + chip->tx_irq = irq; + irq = irq_of_parse_and_map(np, 2); + if (request_irq(irq, snd_pmac_rx_intr, 0, "PMac Input", (void*)chip)) { + dev_err(chip->card->dev, "pmac: unable to grab IRQ %d\n", irq); + err = -EBUSY; + goto __error; + } + chip->rx_irq = irq; + + snd_pmac_sound_feature(chip, 1); + + /* reset & enable interrupts */ + if (chip->model <= PMAC_BURGUNDY) + out_le32(&chip->awacs->control, chip->control_mask); + + /* 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 (of_node_name_eq(mio, "mac-io")) { + struct resource r; + if (of_address_to_resource(mio, 0, &r) == 0) + chip->macio_base = + ioremap(r.start, 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); + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (err < 0) + goto __error; + + *chip_return = chip; + return 0; + + __error: + snd_pmac_free(chip); + return err; +} + + +/* + * sleep notify for powerbook + */ + +#ifdef CONFIG_PM + +/* + * Save state when going to sleep, restore it afterwards. + */ + +void snd_pmac_suspend(struct snd_pmac *chip) +{ + snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot); + if (chip->suspend) + chip->suspend(chip); + scoped_guard(spinlock_irqsave, &chip->reg_lock) { + snd_pmac_beep_stop(chip); + } + 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); +} + +void snd_pmac_resume(struct snd_pmac *chip) +{ + 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); + + snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); +} + +#endif /* CONFIG_PM */ + diff --git a/sound/ppc/pmac.h b/sound/ppc/pmac.h new file mode 100644 index 000000000..b6f454130 --- /dev/null +++ b/sound/ppc/pmac.h @@ -0,0 +1,198 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for PowerMac onboard soundchips + * Copyright (c) 2001 by Takashi Iwai <tiwai@suse.de> + * based on dmasound.c. + */ + + +#ifndef __PMAC_H +#define __PMAC_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> +#include <asm/pmac_feature.h> + +/* maximum number of fragments */ +#define PMAC_MAX_FRAGS 32 + + +#define PMAC_SUPPORT_AUTOMUTE + +/* + * DBDMA space + */ +struct pmac_dbdma { + dma_addr_t dma_base; + dma_addr_t addr; + struct dbdma_cmd __iomem *cmds; + void *space; + int size; +}; + +/* + * playback/capture stream + */ +struct 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; + + struct pmac_dbdma cmd; + volatile struct dbdma_regs __iomem *dma; + + struct snd_pcm_substream *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 { + struct snd_card *card; + + /* h/w info */ + struct device_node *node; + struct pci_dev *pdev; + 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 is_k2 : 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 requested; + struct resource rsrc[3]; + + int num_freqs; + const 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; + + struct pmac_stream playback; + struct pmac_stream capture; + + struct pmac_dbdma extra_dma; + + int irq, tx_irq, rx_irq; + + struct snd_pcm *pcm; + + struct pmac_beep *beep; + + unsigned int control_mask; /* control mask */ + + /* mixer stuffs */ + void *mixer_data; + void (*mixer_free)(struct snd_pmac *); + struct snd_kcontrol *master_sw_ctl; + struct snd_kcontrol *speaker_sw_ctl; + struct snd_kcontrol *drc_sw_ctl; /* only used for tumbler -ReneR */ + struct snd_kcontrol *hp_detect_ctl; + struct snd_kcontrol *lineout_sw_ctl; + + /* lowlevel callbacks */ + void (*set_format)(struct snd_pmac *chip); + void (*update_automute)(struct snd_pmac *chip, int do_notify); + int (*detect_headphone)(struct snd_pmac *chip); +#ifdef CONFIG_PM + void (*suspend)(struct snd_pmac *chip); + void (*resume)(struct snd_pmac *chip); +#endif + +}; + + +/* exported functions */ +int snd_pmac_new(struct snd_card *card, struct snd_pmac **chip_return); +int snd_pmac_pcm_new(struct snd_pmac *chip); +int snd_pmac_attach_beep(struct snd_pmac *chip); +void snd_pmac_detach_beep(struct snd_pmac *chip); +void snd_pmac_beep_stop(struct snd_pmac *chip); +unsigned int snd_pmac_rate_index(struct snd_pmac *chip, struct pmac_stream *rec, unsigned int rate); + +void snd_pmac_beep_dma_start(struct snd_pmac *chip, int bytes, unsigned long addr, int speed); +void snd_pmac_beep_dma_stop(struct snd_pmac *chip); + +#ifdef CONFIG_PM +void snd_pmac_suspend(struct snd_pmac *chip); +void snd_pmac_resume(struct snd_pmac *chip); +#endif + +/* initialize mixer */ +int snd_pmac_awacs_init(struct snd_pmac *chip); +int snd_pmac_burgundy_init(struct snd_pmac *chip); +int snd_pmac_daca_init(struct snd_pmac *chip); +int snd_pmac_tumbler_init(struct snd_pmac *chip); +int snd_pmac_tumbler_post_init(void); + +/* i2c functions */ +struct pmac_keywest { + int addr; + struct i2c_client *client; + int id; + int (*init_client)(struct pmac_keywest *i2c); + char *name; +}; + +int snd_pmac_keywest_init(struct pmac_keywest *i2c); +void snd_pmac_keywest_cleanup(struct pmac_keywest *i2c); + +/* misc */ +#define snd_pmac_boolean_stereo_info snd_ctl_boolean_stereo_info +#define snd_pmac_boolean_mono_info snd_ctl_boolean_mono_info + +int snd_pmac_add_automute(struct snd_pmac *chip); + +#endif /* __PMAC_H */ diff --git a/sound/ppc/powermac.c b/sound/ppc/powermac.c new file mode 100644 index 000000000..e685d2458 --- /dev/null +++ b/sound/ppc/powermac.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for PowerMac AWACS + * Copyright (c) 2001 by Takashi Iwai <tiwai@suse.de> + * based on dmasound.c. + */ + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/module.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_LICENSE("GPL"); + +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +static bool 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."); + +static struct platform_device *device; + + +/* + */ + +static int snd_pmac_probe(struct platform_device *devptr) +{ + struct snd_card *card; + struct snd_pmac *chip; + char *name_ext; + int err; + + err = snd_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card); + if (err < 0) + return err; + + err = snd_pmac_new(card, &chip); + if (err < 0) + goto __error; + card->private_data = chip; + + switch (chip->model) { + case PMAC_BURGUNDY: + strscpy(card->driver, "PMac Burgundy"); + strscpy(card->shortname, "PowerMac Burgundy"); + sprintf(card->longname, "%s (Dev %d) Sub-frame %d", + card->shortname, chip->device_id, chip->subframe); + err = snd_pmac_burgundy_init(chip); + if (err < 0) + goto __error; + break; + case PMAC_DACA: + strscpy(card->driver, "PMac DACA"); + strscpy(card->shortname, "PowerMac DACA"); + sprintf(card->longname, "%s (Dev %d) Sub-frame %d", + card->shortname, chip->device_id, chip->subframe); + err = snd_pmac_daca_init(chip); + if (err < 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); + err = snd_pmac_tumbler_init(chip); + if (err < 0) + goto __error; + err = snd_pmac_tumbler_post_init(); + if (err < 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); + err = snd_pmac_awacs_init(chip); + if (err < 0) + goto __error; + break; + default: + dev_err(&devptr->dev, "unsupported hardware %d\n", chip->model); + err = -EINVAL; + goto __error; + } + + err = snd_pmac_pcm_new(chip); + if (err < 0) + goto __error; + + chip->initialized = 1; + if (enable_beep) + snd_pmac_attach_beep(chip); + + err = snd_card_register(card); + if (err < 0) + goto __error; + + platform_set_drvdata(devptr, card); + return 0; + +__error: + snd_card_free(card); + return err; +} + + +static void snd_pmac_remove(struct platform_device *devptr) +{ + snd_card_free(platform_get_drvdata(devptr)); +} + +#ifdef CONFIG_PM_SLEEP +static int snd_pmac_driver_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + snd_pmac_suspend(card->private_data); + return 0; +} + +static int snd_pmac_driver_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + snd_pmac_resume(card->private_data); + return 0; +} + +static SIMPLE_DEV_PM_OPS(snd_pmac_pm, snd_pmac_driver_suspend, snd_pmac_driver_resume); +#define SND_PMAC_PM_OPS &snd_pmac_pm +#else +#define SND_PMAC_PM_OPS NULL +#endif + +#define SND_PMAC_DRIVER "snd_powermac" + +static struct platform_driver snd_pmac_driver = { + .probe = snd_pmac_probe, + .remove = snd_pmac_remove, + .driver = { + .name = SND_PMAC_DRIVER, + .pm = SND_PMAC_PM_OPS, + }, +}; + +static int __init alsa_card_pmac_init(void) +{ + int err; + + err = platform_driver_register(&snd_pmac_driver); + if (err < 0) + return err; + device = platform_device_register_simple(SND_PMAC_DRIVER, -1, NULL, 0); + return 0; + +} + +static void __exit alsa_card_pmac_exit(void) +{ + if (!IS_ERR(device)) + platform_device_unregister(device); + platform_driver_unregister(&snd_pmac_driver); +} + +module_init(alsa_card_pmac_init) +module_exit(alsa_card_pmac_exit) diff --git a/sound/ppc/snd_ps3.c b/sound/ppc/snd_ps3.c new file mode 100644 index 000000000..225b20f0b --- /dev/null +++ b/sound/ppc/snd_ps3.c @@ -0,0 +1,1102 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Audio support for PS3 + * Copyright (C) 2007 Sony Computer Entertainment Inc. + * All rights reserved. + * Copyright 2006, 2007 Sony Corporation + */ + +#include <linux/dma-mapping.h> +#include <linux/dmapool.h> +#include <linux/gfp.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> + +#include <sound/asound.h> +#include <sound/control.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/memalloc.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> + +#include <asm/dma.h> +#include <asm/firmware.h> +#include <asm/lv1call.h> +#include <asm/ps3.h> +#include <asm/ps3av.h> + +#include "snd_ps3.h" +#include "snd_ps3_reg.h" + + +/* + * global + */ +static struct snd_ps3_card_info the_card; + +static int snd_ps3_start_delay = CONFIG_SND_PS3_DEFAULT_START_DELAY; + +module_param_named(start_delay, snd_ps3_start_delay, uint, 0644); +MODULE_PARM_DESC(start_delay, "time to insert silent data in ms"); + +static int index = SNDRV_DEFAULT_IDX1; +static char *id = SNDRV_DEFAULT_STR1; + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for PS3 soundchip."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for PS3 soundchip."); + + +/* + * PS3 audio register access + */ +static inline u32 read_reg(unsigned int reg) +{ + return in_be32(the_card.mapped_mmio_vaddr + reg); +} +static inline void write_reg(unsigned int reg, u32 val) +{ + out_be32(the_card.mapped_mmio_vaddr + reg, val); +} +static inline void update_reg(unsigned int reg, u32 or_val) +{ + u32 newval = read_reg(reg) | or_val; + write_reg(reg, newval); +} +static inline void update_mask_reg(unsigned int reg, u32 mask, u32 or_val) +{ + u32 newval = (read_reg(reg) & mask) | or_val; + write_reg(reg, newval); +} + +/* + * ALSA defs + */ +static const struct snd_pcm_hardware snd_ps3_pcm_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_BE), + .rates = (SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000), + .rate_min = 44100, + .rate_max = 96000, + + .channels_min = 2, /* stereo only */ + .channels_max = 2, + + .buffer_bytes_max = PS3_AUDIO_FIFO_SIZE * 64, + + /* interrupt by four stages */ + .period_bytes_min = PS3_AUDIO_FIFO_STAGE_SIZE * 4, + .period_bytes_max = PS3_AUDIO_FIFO_STAGE_SIZE * 4, + + .periods_min = 16, + .periods_max = 32, /* buffer_size_max/ period_bytes_max */ + + .fifo_size = PS3_AUDIO_FIFO_SIZE +}; + +static int snd_ps3_verify_dma_stop(struct snd_ps3_card_info *card, + int count, int force_stop) +{ + int dma_ch, done, retries, stop_forced = 0; + uint32_t status; + + for (dma_ch = 0; dma_ch < 8; dma_ch++) { + retries = count; + do { + status = read_reg(PS3_AUDIO_KICK(dma_ch)) & + PS3_AUDIO_KICK_STATUS_MASK; + switch (status) { + case PS3_AUDIO_KICK_STATUS_DONE: + case PS3_AUDIO_KICK_STATUS_NOTIFY: + case PS3_AUDIO_KICK_STATUS_CLEAR: + case PS3_AUDIO_KICK_STATUS_ERROR: + done = 1; + break; + default: + done = 0; + udelay(10); + } + } while (!done && --retries); + if (!retries && force_stop) { + pr_info("%s: DMA ch %d is not stopped.", + __func__, dma_ch); + /* last resort. force to stop dma. + * NOTE: this cause DMA done interrupts + */ + update_reg(PS3_AUDIO_CONFIG, PS3_AUDIO_CONFIG_CLEAR); + stop_forced = 1; + } + } + return stop_forced; +} + +/* + * wait for all dma is done. + * NOTE: caller should reset card->running before call. + * If not, the interrupt handler will re-start DMA, + * then DMA is never stopped. + */ +static void snd_ps3_wait_for_dma_stop(struct snd_ps3_card_info *card) +{ + int stop_forced; + /* + * wait for the last dma is done + */ + + /* + * expected maximum DMA done time is 5.7ms + something (DMA itself). + * 5.7ms is from 16bit/sample 2ch 44.1Khz; the time next + * DMA kick event would occur. + */ + stop_forced = snd_ps3_verify_dma_stop(card, 700, 1); + + /* + * clear outstanding interrupts. + */ + update_reg(PS3_AUDIO_INTR_0, 0); + update_reg(PS3_AUDIO_AX_IS, 0); + + /* + *revert CLEAR bit since it will not reset automatically after DMA stop + */ + if (stop_forced) + update_mask_reg(PS3_AUDIO_CONFIG, ~PS3_AUDIO_CONFIG_CLEAR, 0); + /* ensure the hardware sees changes */ + wmb(); +} + +static void snd_ps3_kick_dma(struct snd_ps3_card_info *card) +{ + + update_reg(PS3_AUDIO_KICK(0), PS3_AUDIO_KICK_REQUEST); + /* ensure the hardware sees the change */ + wmb(); +} + +/* + * convert virtual addr to ioif bus addr. + */ +static dma_addr_t v_to_bus(struct snd_ps3_card_info *card, void *paddr, int ch) +{ + return card->dma_start_bus_addr[ch] + + (paddr - card->dma_start_vaddr[ch]); +}; + + +/* + * increment ring buffer pointer. + * NOTE: caller must hold write spinlock + */ +static void snd_ps3_bump_buffer(struct snd_ps3_card_info *card, + enum snd_ps3_ch ch, size_t byte_count, + int stage) +{ + if (!stage) + card->dma_last_transfer_vaddr[ch] = + card->dma_next_transfer_vaddr[ch]; + card->dma_next_transfer_vaddr[ch] += byte_count; + if ((card->dma_start_vaddr[ch] + (card->dma_buffer_size / 2)) <= + card->dma_next_transfer_vaddr[ch]) { + card->dma_next_transfer_vaddr[ch] = card->dma_start_vaddr[ch]; + } +} +/* + * setup dmac to send data to audio and attenuate samples on the ring buffer + */ +static int snd_ps3_program_dma(struct snd_ps3_card_info *card, + enum snd_ps3_dma_filltype filltype) +{ + /* this dmac does not support over 4G */ + uint32_t dma_addr; + int fill_stages, dma_ch, stage; + enum snd_ps3_ch ch; + uint32_t ch0_kick_event = 0; /* initialize to mute gcc */ + int silent = 0; + + switch (filltype) { + case SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL: + silent = 1; + fallthrough; + case SND_PS3_DMA_FILLTYPE_FIRSTFILL: + ch0_kick_event = PS3_AUDIO_KICK_EVENT_ALWAYS; + break; + + case SND_PS3_DMA_FILLTYPE_SILENT_RUNNING: + silent = 1; + fallthrough; + case SND_PS3_DMA_FILLTYPE_RUNNING: + ch0_kick_event = PS3_AUDIO_KICK_EVENT_SERIALOUT0_EMPTY; + break; + } + + snd_ps3_verify_dma_stop(card, 700, 0); + fill_stages = 4; + guard(spinlock_irqsave)(&card->dma_lock); + for (ch = 0; ch < 2; ch++) { + for (stage = 0; stage < fill_stages; stage++) { + dma_ch = stage * 2 + ch; + if (silent) + dma_addr = card->null_buffer_start_dma_addr; + else + dma_addr = + v_to_bus(card, + card->dma_next_transfer_vaddr[ch], + ch); + + write_reg(PS3_AUDIO_SOURCE(dma_ch), + (PS3_AUDIO_SOURCE_TARGET_SYSTEM_MEMORY | + dma_addr)); + + /* dst: fixed to 3wire#0 */ + if (ch == 0) + write_reg(PS3_AUDIO_DEST(dma_ch), + (PS3_AUDIO_DEST_TARGET_AUDIOFIFO | + PS3_AUDIO_AO_3W_LDATA(0))); + else + write_reg(PS3_AUDIO_DEST(dma_ch), + (PS3_AUDIO_DEST_TARGET_AUDIOFIFO | + PS3_AUDIO_AO_3W_RDATA(0))); + + /* count always 1 DMA block (1/2 stage = 128 bytes) */ + write_reg(PS3_AUDIO_DMASIZE(dma_ch), 0); + /* bump pointer if needed */ + if (!silent) + snd_ps3_bump_buffer(card, ch, + PS3_AUDIO_DMAC_BLOCK_SIZE, + stage); + + /* kick event */ + if (dma_ch == 0) + write_reg(PS3_AUDIO_KICK(dma_ch), + ch0_kick_event); + else + write_reg(PS3_AUDIO_KICK(dma_ch), + PS3_AUDIO_KICK_EVENT_AUDIO_DMA(dma_ch + - 1) | + PS3_AUDIO_KICK_REQUEST); + } + } + /* ensure the hardware sees the change */ + wmb(); + + return 0; +} + +/* + * Interrupt handler + */ +static irqreturn_t snd_ps3_interrupt(int irq, void *dev_id) +{ + + uint32_t port_intr; + int underflow_occured = 0; + struct snd_ps3_card_info *card = dev_id; + + if (!card->running) { + update_reg(PS3_AUDIO_AX_IS, 0); + update_reg(PS3_AUDIO_INTR_0, 0); + return IRQ_HANDLED; + } + + port_intr = read_reg(PS3_AUDIO_AX_IS); + /* + *serial buffer empty detected (every 4 times), + *program next dma and kick it + */ + if (port_intr & PS3_AUDIO_AX_IE_ASOBEIE(0)) { + write_reg(PS3_AUDIO_AX_IS, PS3_AUDIO_AX_IE_ASOBEIE(0)); + if (port_intr & PS3_AUDIO_AX_IE_ASOBUIE(0)) { + write_reg(PS3_AUDIO_AX_IS, port_intr); + underflow_occured = 1; + } + if (card->silent) { + /* we are still in silent time */ + snd_ps3_program_dma(card, + (underflow_occured) ? + SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL : + SND_PS3_DMA_FILLTYPE_SILENT_RUNNING); + snd_ps3_kick_dma(card); + card->silent--; + } else { + snd_ps3_program_dma(card, + (underflow_occured) ? + SND_PS3_DMA_FILLTYPE_FIRSTFILL : + SND_PS3_DMA_FILLTYPE_RUNNING); + snd_ps3_kick_dma(card); + snd_pcm_period_elapsed(card->substream); + } + } else if (port_intr & PS3_AUDIO_AX_IE_ASOBUIE(0)) { + write_reg(PS3_AUDIO_AX_IS, PS3_AUDIO_AX_IE_ASOBUIE(0)); + /* + * serial out underflow, but buffer empty not detected. + * in this case, fill fifo with 0 to recover. After + * filling dummy data, serial automatically start to + * consume them and then will generate normal buffer + * empty interrupts. + * If both buffer underflow and buffer empty are occurred, + * it is better to do nomal data transfer than empty one + */ + snd_ps3_program_dma(card, + SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL); + snd_ps3_kick_dma(card); + snd_ps3_program_dma(card, + SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL); + snd_ps3_kick_dma(card); + } + /* clear interrupt cause */ + return IRQ_HANDLED; +}; + +/* + * audio mute on/off + * mute_on : 0 output enabled + * 1 mute + */ +static int snd_ps3_mute(int mute_on) +{ + return ps3av_audio_mute(mute_on); +} + +/* + * av setting + * NOTE: calling this function may generate audio interrupt. + */ +static int snd_ps3_change_avsetting(struct snd_ps3_card_info *card) +{ + int ret, retries, i; + pr_debug("%s: start\n", __func__); + + ret = ps3av_set_audio_mode(card->avs.avs_audio_ch, + card->avs.avs_audio_rate, + card->avs.avs_audio_width, + card->avs.avs_audio_format, + card->avs.avs_audio_source); + /* + * Reset the following unwanted settings: + */ + + /* disable all 3wire buffers */ + update_mask_reg(PS3_AUDIO_AO_3WMCTRL, + ~(PS3_AUDIO_AO_3WMCTRL_ASOEN(0) | + PS3_AUDIO_AO_3WMCTRL_ASOEN(1) | + PS3_AUDIO_AO_3WMCTRL_ASOEN(2) | + PS3_AUDIO_AO_3WMCTRL_ASOEN(3)), + 0); + wmb(); /* ensure the hardware sees the change */ + /* wait for actually stopped */ + retries = 1000; + while ((read_reg(PS3_AUDIO_AO_3WMCTRL) & + (PS3_AUDIO_AO_3WMCTRL_ASORUN(0) | + PS3_AUDIO_AO_3WMCTRL_ASORUN(1) | + PS3_AUDIO_AO_3WMCTRL_ASORUN(2) | + PS3_AUDIO_AO_3WMCTRL_ASORUN(3))) && + --retries) { + udelay(1); + } + + /* reset buffer pointer */ + for (i = 0; i < 4; i++) { + update_reg(PS3_AUDIO_AO_3WCTRL(i), + PS3_AUDIO_AO_3WCTRL_ASOBRST_RESET); + udelay(10); + } + wmb(); /* ensure the hardware actually start resetting */ + + /* enable 3wire#0 buffer */ + update_reg(PS3_AUDIO_AO_3WMCTRL, PS3_AUDIO_AO_3WMCTRL_ASOEN(0)); + + + /* In 24bit mode,ALSA inserts a zero byte at first byte of per sample */ + update_mask_reg(PS3_AUDIO_AO_3WCTRL(0), + ~PS3_AUDIO_AO_3WCTRL_ASODF, + PS3_AUDIO_AO_3WCTRL_ASODF_LSB); + update_mask_reg(PS3_AUDIO_AO_SPDCTRL(0), + ~PS3_AUDIO_AO_SPDCTRL_SPODF, + PS3_AUDIO_AO_SPDCTRL_SPODF_LSB); + /* ensure all the setting above is written back to register */ + wmb(); + /* avsetting driver altered AX_IE, caller must reset it if you want */ + pr_debug("%s: end\n", __func__); + return ret; +} + +/* + * set sampling rate according to the substream + */ +static int snd_ps3_set_avsetting(struct snd_pcm_substream *substream) +{ + struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream); + struct snd_ps3_avsetting_info avs; + int ret; + + avs = card->avs; + + pr_debug("%s: called freq=%d width=%d\n", __func__, + substream->runtime->rate, + snd_pcm_format_width(substream->runtime->format)); + + pr_debug("%s: before freq=%d width=%d\n", __func__, + card->avs.avs_audio_rate, card->avs.avs_audio_width); + + /* sample rate */ + switch (substream->runtime->rate) { + case 44100: + avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_44K; + break; + case 48000: + avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_48K; + break; + case 88200: + avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_88K; + break; + case 96000: + avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_96K; + break; + default: + pr_info("%s: invalid rate %d\n", __func__, + substream->runtime->rate); + return 1; + } + + /* width */ + switch (snd_pcm_format_width(substream->runtime->format)) { + case 16: + avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_16; + break; + case 24: + avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_24; + break; + default: + pr_info("%s: invalid width %d\n", __func__, + snd_pcm_format_width(substream->runtime->format)); + return 1; + } + + memcpy(avs.avs_cs_info, ps3av_mode_cs_info, 8); + + if (memcmp(&card->avs, &avs, sizeof(avs))) { + pr_debug("%s: after freq=%d width=%d\n", __func__, + card->avs.avs_audio_rate, card->avs.avs_audio_width); + + card->avs = avs; + snd_ps3_change_avsetting(card); + ret = 0; + } else + ret = 1; + + /* check CS non-audio bit and mute accordingly */ + if (avs.avs_cs_info[0] & 0x02) + ps3av_audio_mute_analog(1); /* mute if non-audio */ + else + ps3av_audio_mute_analog(0); + + return ret; +} + +/* + * PCM operators + */ +static int snd_ps3_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream); + + /* to retrieve substream/runtime in interrupt handler */ + card->substream = substream; + + runtime->hw = snd_ps3_pcm_hw; + + card->start_delay = snd_ps3_start_delay; + + /* mute off */ + snd_ps3_mute(0); /* this function sleep */ + + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + PS3_AUDIO_FIFO_STAGE_SIZE * 4 * 2); + return 0; +}; + +static int snd_ps3_pcm_close(struct snd_pcm_substream *substream) +{ + /* mute on */ + snd_ps3_mute(1); + return 0; +}; + +static int snd_ps3_delay_to_bytes(struct snd_pcm_substream *substream, + unsigned int delay_ms) +{ + int ret; + int rate ; + + rate = substream->runtime->rate; + ret = snd_pcm_format_size(substream->runtime->format, + rate * delay_ms / 1000) + * substream->runtime->channels; + + pr_debug("%s: time=%d rate=%d bytes=%ld, frames=%d, ret=%d\n", + __func__, + delay_ms, + rate, + snd_pcm_format_size(substream->runtime->format, rate), + rate * delay_ms / 1000, + ret); + + return ret; +}; + +static int snd_ps3_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream); + + if (!snd_ps3_set_avsetting(substream)) { + /* some parameter changed */ + write_reg(PS3_AUDIO_AX_IE, + PS3_AUDIO_AX_IE_ASOBEIE(0) | + PS3_AUDIO_AX_IE_ASOBUIE(0)); + /* + * let SPDIF device re-lock with SPDIF signal, + * start with some silence + */ + card->silent = snd_ps3_delay_to_bytes(substream, + card->start_delay) / + (PS3_AUDIO_FIFO_STAGE_SIZE * 4); /* every 4 times */ + } + + /* restart ring buffer pointer */ + scoped_guard(spinlock_irqsave, &card->dma_lock) { + card->dma_buffer_size = runtime->dma_bytes; + + card->dma_last_transfer_vaddr[SND_PS3_CH_L] = + card->dma_next_transfer_vaddr[SND_PS3_CH_L] = + card->dma_start_vaddr[SND_PS3_CH_L] = + runtime->dma_area; + card->dma_start_bus_addr[SND_PS3_CH_L] = runtime->dma_addr; + + card->dma_last_transfer_vaddr[SND_PS3_CH_R] = + card->dma_next_transfer_vaddr[SND_PS3_CH_R] = + card->dma_start_vaddr[SND_PS3_CH_R] = + runtime->dma_area + (runtime->dma_bytes / 2); + card->dma_start_bus_addr[SND_PS3_CH_R] = + runtime->dma_addr + (runtime->dma_bytes / 2); + + pr_debug("%s: vaddr=%p bus=%#llx\n", __func__, + card->dma_start_vaddr[SND_PS3_CH_L], + card->dma_start_bus_addr[SND_PS3_CH_L]); + + } + + /* ensure the hardware sees the change */ + mb(); + + return 0; +}; + +static int snd_ps3_pcm_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* clear outstanding interrupts */ + update_reg(PS3_AUDIO_AX_IS, 0); + + scoped_guard(spinlock, &card->dma_lock) { + card->running = 1; + } + + snd_ps3_program_dma(card, + SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL); + snd_ps3_kick_dma(card); + while (read_reg(PS3_AUDIO_KICK(7)) & + PS3_AUDIO_KICK_STATUS_MASK) { + udelay(1); + } + snd_ps3_program_dma(card, SND_PS3_DMA_FILLTYPE_SILENT_RUNNING); + snd_ps3_kick_dma(card); + break; + + case SNDRV_PCM_TRIGGER_STOP: + scoped_guard(spinlock, &card->dma_lock) { + card->running = 0; + } + snd_ps3_wait_for_dma_stop(card); + break; + default: + break; + + } + + return 0; +}; + +/* + * report current pointer + */ +static snd_pcm_uframes_t snd_ps3_pcm_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream); + size_t bytes; + snd_pcm_uframes_t ret; + + scoped_guard(spinlock, &card->dma_lock) { + bytes = (size_t)(card->dma_last_transfer_vaddr[SND_PS3_CH_L] - + card->dma_start_vaddr[SND_PS3_CH_L]); + } + + ret = bytes_to_frames(substream->runtime, bytes * 2); + + return ret; +}; + +/* + * SPDIF status bits controls + */ +static int snd_ps3_spdif_mask_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +/* FIXME: ps3av_set_audio_mode() assumes only consumer mode */ +static int snd_ps3_spdif_cmask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + memset(ucontrol->value.iec958.status, 0xff, 8); + return 0; +} + +static int snd_ps3_spdif_pmask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return 0; +} + +static int snd_ps3_spdif_default_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + memcpy(ucontrol->value.iec958.status, ps3av_mode_cs_info, 8); + return 0; +} + +static int snd_ps3_spdif_default_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (memcmp(ps3av_mode_cs_info, ucontrol->value.iec958.status, 8)) { + memcpy(ps3av_mode_cs_info, ucontrol->value.iec958.status, 8); + return 1; + } + return 0; +} + +static const struct snd_kcontrol_new spdif_ctls[] = { + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK), + .info = snd_ps3_spdif_mask_info, + .get = snd_ps3_spdif_cmask_get, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PRO_MASK), + .info = snd_ps3_spdif_mask_info, + .get = snd_ps3_spdif_pmask_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = snd_ps3_spdif_mask_info, + .get = snd_ps3_spdif_default_get, + .put = snd_ps3_spdif_default_put, + }, +}; + +static const struct snd_pcm_ops snd_ps3_pcm_spdif_ops = { + .open = snd_ps3_pcm_open, + .close = snd_ps3_pcm_close, + .prepare = snd_ps3_pcm_prepare, + .trigger = snd_ps3_pcm_trigger, + .pointer = snd_ps3_pcm_pointer, +}; + + +static int snd_ps3_map_mmio(void) +{ + the_card.mapped_mmio_vaddr = + ioremap(the_card.ps3_dev->m_region->bus_addr, + the_card.ps3_dev->m_region->len); + + if (!the_card.mapped_mmio_vaddr) { + pr_info("%s: ioremap 0 failed p=%#lx l=%#lx \n", + __func__, the_card.ps3_dev->m_region->lpar_addr, + the_card.ps3_dev->m_region->len); + return -ENXIO; + } + + return 0; +}; + +static void snd_ps3_unmap_mmio(void) +{ + iounmap(the_card.mapped_mmio_vaddr); + the_card.mapped_mmio_vaddr = NULL; +} + +static int snd_ps3_allocate_irq(void) +{ + int ret; + u64 lpar_addr, lpar_size; + u64 __iomem *mapped; + + /* FIXME: move this to device_init (H/W probe) */ + + /* get irq outlet */ + ret = lv1_gpu_device_map(1, &lpar_addr, &lpar_size); + if (ret) { + pr_info("%s: device map 1 failed %d\n", __func__, + ret); + return -ENXIO; + } + + mapped = ioremap(lpar_addr, lpar_size); + if (!mapped) { + pr_info("%s: ioremap 1 failed \n", __func__); + return -ENXIO; + } + + the_card.audio_irq_outlet = in_be64(mapped); + + iounmap(mapped); + ret = lv1_gpu_device_unmap(1); + if (ret) + pr_info("%s: unmap 1 failed\n", __func__); + + /* irq */ + ret = ps3_irq_plug_setup(PS3_BINDING_CPU_ANY, + the_card.audio_irq_outlet, + &the_card.irq_no); + if (ret) { + pr_info("%s:ps3_alloc_irq failed (%d)\n", __func__, ret); + return ret; + } + + ret = request_irq(the_card.irq_no, snd_ps3_interrupt, 0, + SND_PS3_DRIVER_NAME, &the_card); + if (ret) { + pr_info("%s: request_irq failed (%d)\n", __func__, ret); + goto cleanup_irq; + } + + return 0; + + cleanup_irq: + ps3_irq_plug_destroy(the_card.irq_no); + return ret; +}; + +static void snd_ps3_free_irq(void) +{ + free_irq(the_card.irq_no, &the_card); + ps3_irq_plug_destroy(the_card.irq_no); +} + +static void snd_ps3_audio_set_base_addr(uint64_t ioaddr_start) +{ + uint64_t val; + int ret; + + val = (ioaddr_start & (0x0fUL << 32)) >> (32 - 20) | + (0x03UL << 24) | + (0x0fUL << 12) | + (PS3_AUDIO_IOID); + + ret = lv1_gpu_attribute(0x100, 0x007, val); + if (ret) + pr_info("%s: gpu_attribute failed %d\n", __func__, + ret); +} + +static void snd_ps3_audio_fixup(struct snd_ps3_card_info *card) +{ + /* + * avsetting driver seems to never change the following + * so, init them here once + */ + + /* no dma interrupt needed */ + write_reg(PS3_AUDIO_INTR_EN_0, 0); + + /* use every 4 buffer empty interrupt */ + update_mask_reg(PS3_AUDIO_AX_IC, + PS3_AUDIO_AX_IC_AASOIMD_MASK, + PS3_AUDIO_AX_IC_AASOIMD_EVERY4); + + /* enable 3wire clocks */ + update_mask_reg(PS3_AUDIO_AO_3WMCTRL, + ~(PS3_AUDIO_AO_3WMCTRL_ASOBCLKD_DISABLED | + PS3_AUDIO_AO_3WMCTRL_ASOLRCKD_DISABLED), + 0); + update_reg(PS3_AUDIO_AO_3WMCTRL, + PS3_AUDIO_AO_3WMCTRL_ASOPLRCK_DEFAULT); +} + +static int snd_ps3_init_avsetting(struct snd_ps3_card_info *card) +{ + int ret; + pr_debug("%s: start\n", __func__); + card->avs.avs_audio_ch = PS3AV_CMD_AUDIO_NUM_OF_CH_2; + card->avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_48K; + card->avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_16; + card->avs.avs_audio_format = PS3AV_CMD_AUDIO_FORMAT_PCM; + card->avs.avs_audio_source = PS3AV_CMD_AUDIO_SOURCE_SERIAL; + memcpy(card->avs.avs_cs_info, ps3av_mode_cs_info, 8); + + ret = snd_ps3_change_avsetting(card); + + snd_ps3_audio_fixup(card); + + /* to start to generate SPDIF signal, fill data */ + snd_ps3_program_dma(card, SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL); + snd_ps3_kick_dma(card); + pr_debug("%s: end\n", __func__); + return ret; +} + +static int snd_ps3_driver_probe(struct ps3_system_bus_device *dev) +{ + int i, ret; + u64 lpar_addr, lpar_size; + static u64 dummy_mask; + + the_card.ps3_dev = dev; + + ret = ps3_open_hv_device(dev); + + if (ret) + return -ENXIO; + + /* setup MMIO */ + ret = lv1_gpu_device_map(2, &lpar_addr, &lpar_size); + if (ret) { + pr_info("%s: device map 2 failed %d\n", __func__, ret); + goto clean_open; + } + ps3_mmio_region_init(dev, dev->m_region, lpar_addr, lpar_size, + PAGE_SHIFT); + + ret = snd_ps3_map_mmio(); + if (ret) + goto clean_dev_map; + + /* setup DMA area */ + ps3_dma_region_init(dev, dev->d_region, + PAGE_SHIFT, /* use system page size */ + 0, /* dma type; not used */ + NULL, + ALIGN(SND_PS3_DMA_REGION_SIZE, PAGE_SIZE)); + dev->d_region->ioid = PS3_AUDIO_IOID; + + ret = ps3_dma_region_create(dev->d_region); + if (ret) { + pr_info("%s: region_create\n", __func__); + goto clean_mmio; + } + + dummy_mask = DMA_BIT_MASK(32); + dev->core.dma_mask = &dummy_mask; + dma_set_coherent_mask(&dev->core, dummy_mask); + + snd_ps3_audio_set_base_addr(dev->d_region->bus_addr); + + /* CONFIG_SND_PS3_DEFAULT_START_DELAY */ + the_card.start_delay = snd_ps3_start_delay; + + /* irq */ + if (snd_ps3_allocate_irq()) { + ret = -ENXIO; + goto clean_dma_region; + } + + /* create card instance */ + ret = snd_card_new(&dev->core, index, id, THIS_MODULE, + 0, &the_card.card); + if (ret < 0) + goto clean_irq; + + strscpy(the_card.card->driver, "PS3"); + strscpy(the_card.card->shortname, "PS3"); + strscpy(the_card.card->longname, "PS3 sound"); + + /* create control elements */ + for (i = 0; i < ARRAY_SIZE(spdif_ctls); i++) { + ret = snd_ctl_add(the_card.card, + snd_ctl_new1(&spdif_ctls[i], &the_card)); + if (ret < 0) + goto clean_card; + } + + /* create PCM devices instance */ + /* NOTE:this driver works assuming pcm:substream = 1:1 */ + ret = snd_pcm_new(the_card.card, + "SPDIF", + 0, /* instance index, will be stored pcm.device*/ + 1, /* output substream */ + 0, /* input substream */ + &(the_card.pcm)); + if (ret) + goto clean_card; + + the_card.pcm->private_data = &the_card; + strscpy(the_card.pcm->name, "SPDIF"); + + /* set pcm ops */ + snd_pcm_set_ops(the_card.pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_ps3_pcm_spdif_ops); + + the_card.pcm->info_flags = SNDRV_PCM_INFO_NONINTERLEAVED; + /* pre-alloc PCM DMA buffer*/ + snd_pcm_set_managed_buffer_all(the_card.pcm, + SNDRV_DMA_TYPE_DEV, + &dev->core, + SND_PS3_PCM_PREALLOC_SIZE, + SND_PS3_PCM_PREALLOC_SIZE); + + /* + * allocate null buffer + * its size should be lager than PS3_AUDIO_FIFO_STAGE_SIZE * 2 + * PAGE_SIZE is enogh + */ + the_card.null_buffer_start_vaddr = + dma_alloc_coherent(&the_card.ps3_dev->core, + PAGE_SIZE, + &the_card.null_buffer_start_dma_addr, + GFP_KERNEL); + if (!the_card.null_buffer_start_vaddr) { + pr_info("%s: nullbuffer alloc failed\n", __func__); + ret = -ENOMEM; + goto clean_card; + } + pr_debug("%s: null vaddr=%p dma=%#llx\n", __func__, + the_card.null_buffer_start_vaddr, + the_card.null_buffer_start_dma_addr); + /* set default sample rate/word width */ + snd_ps3_init_avsetting(&the_card); + + /* register the card */ + ret = snd_card_register(the_card.card); + if (ret < 0) + goto clean_dma_map; + + pr_info("%s started. start_delay=%dms\n", + the_card.card->longname, the_card.start_delay); + return 0; + +clean_dma_map: + dma_free_coherent(&the_card.ps3_dev->core, + PAGE_SIZE, + the_card.null_buffer_start_vaddr, + the_card.null_buffer_start_dma_addr); +clean_card: + snd_card_free(the_card.card); +clean_irq: + snd_ps3_free_irq(); +clean_dma_region: + ps3_dma_region_free(dev->d_region); +clean_mmio: + snd_ps3_unmap_mmio(); +clean_dev_map: + lv1_gpu_device_unmap(2); +clean_open: + ps3_close_hv_device(dev); + /* + * there is no destructor function to pcm. + * midlayer automatically releases if the card removed + */ + return ret; +}; /* snd_ps3_probe */ + +/* called when module removal */ +static void snd_ps3_driver_remove(struct ps3_system_bus_device *dev) +{ + pr_info("%s:start id=%d\n", __func__, dev->match_id); + + /* + * ctl and preallocate buffer will be freed in + * snd_card_free + */ + snd_card_free(the_card.card); + + dma_free_coherent(&dev->core, + PAGE_SIZE, + the_card.null_buffer_start_vaddr, + the_card.null_buffer_start_dma_addr); + + ps3_dma_region_free(dev->d_region); + + snd_ps3_free_irq(); + snd_ps3_unmap_mmio(); + + lv1_gpu_device_unmap(2); + ps3_close_hv_device(dev); + pr_info("%s:end id=%d\n", __func__, dev->match_id); +} /* snd_ps3_remove */ + +static struct ps3_system_bus_driver snd_ps3_bus_driver_info = { + .match_id = PS3_MATCH_ID_SOUND, + .probe = snd_ps3_driver_probe, + .remove = snd_ps3_driver_remove, + .shutdown = snd_ps3_driver_remove, + .core = { + .name = SND_PS3_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + + +/* + * module/subsystem initialize/terminate + */ +static int __init snd_ps3_init(void) +{ + int ret; + + if (!firmware_has_feature(FW_FEATURE_PS3_LV1)) + return -ENXIO; + + memset(&the_card, 0, sizeof(the_card)); + spin_lock_init(&the_card.dma_lock); + + /* register systembus DRIVER, this calls our probe() func */ + ret = ps3_system_bus_driver_register(&snd_ps3_bus_driver_info); + + return ret; +} +module_init(snd_ps3_init); + +static void __exit snd_ps3_exit(void) +{ + ps3_system_bus_driver_unregister(&snd_ps3_bus_driver_info); +} +module_exit(snd_ps3_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PS3 sound driver"); +MODULE_AUTHOR("Sony Computer Entertainment Inc."); +MODULE_ALIAS(PS3_MODULE_ALIAS_SOUND); diff --git a/sound/ppc/snd_ps3.h b/sound/ppc/snd_ps3.h new file mode 100644 index 000000000..8b554a79b --- /dev/null +++ b/sound/ppc/snd_ps3.h @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Audio support for PS3 + * Copyright (C) 2007 Sony Computer Entertainment Inc. + * All rights reserved. + * Copyright 2006, 2007 Sony Corporation + */ + +#if !defined(_SND_PS3_H_) +#define _SND_PS3_H_ + +#include <linux/irqreturn.h> + +#define SND_PS3_DRIVER_NAME "snd_ps3" + +enum snd_ps3_out_channel { + SND_PS3_OUT_SPDIF_0, + SND_PS3_OUT_SPDIF_1, + SND_PS3_OUT_SERIAL_0, + SND_PS3_OUT_DEVS +}; + +enum snd_ps3_dma_filltype { + SND_PS3_DMA_FILLTYPE_FIRSTFILL, + SND_PS3_DMA_FILLTYPE_RUNNING, + SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL, + SND_PS3_DMA_FILLTYPE_SILENT_RUNNING +}; + +enum snd_ps3_ch { + SND_PS3_CH_L = 0, + SND_PS3_CH_R = 1, + SND_PS3_CH_MAX = 2 +}; + +struct snd_ps3_avsetting_info { + uint32_t avs_audio_ch; /* fixed */ + uint32_t avs_audio_rate; + uint32_t avs_audio_width; + uint32_t avs_audio_format; /* fixed */ + uint32_t avs_audio_source; /* fixed */ + unsigned char avs_cs_info[8]; +}; +/* + * PS3 audio 'card' instance + * there should be only ONE hardware. + */ +struct snd_ps3_card_info { + struct ps3_system_bus_device *ps3_dev; + struct snd_card *card; + + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + + /* hvc info */ + u64 audio_lpar_addr; + u64 audio_lpar_size; + + /* registers */ + void __iomem *mapped_mmio_vaddr; + + /* irq */ + u64 audio_irq_outlet; + unsigned int irq_no; + + /* remember avsetting */ + struct snd_ps3_avsetting_info avs; + + /* dma buffer management */ + spinlock_t dma_lock; + /* dma_lock start */ + void * dma_start_vaddr[2]; /* 0 for L, 1 for R */ + dma_addr_t dma_start_bus_addr[2]; + size_t dma_buffer_size; + void * dma_last_transfer_vaddr[2]; + void * dma_next_transfer_vaddr[2]; + int silent; + /* dma_lock end */ + + int running; + + /* null buffer */ + void *null_buffer_start_vaddr; + dma_addr_t null_buffer_start_dma_addr; + + /* start delay */ + unsigned int start_delay; + +}; + + +/* PS3 audio DMAC block size in bytes */ +#define PS3_AUDIO_DMAC_BLOCK_SIZE (128) +/* one stage (stereo) of audio FIFO in bytes */ +#define PS3_AUDIO_FIFO_STAGE_SIZE (256) +/* how many stages the fifo have */ +#define PS3_AUDIO_FIFO_STAGE_COUNT (8) +/* fifo size 128 bytes * 8 stages * stereo (2ch) */ +#define PS3_AUDIO_FIFO_SIZE \ + (PS3_AUDIO_FIFO_STAGE_SIZE * PS3_AUDIO_FIFO_STAGE_COUNT) + +/* PS3 audio DMAC max block count in one dma shot = 128 (0x80) blocks*/ +#define PS3_AUDIO_DMAC_MAX_BLOCKS (PS3_AUDIO_DMASIZE_BLOCKS_MASK + 1) + +#define PS3_AUDIO_NORMAL_DMA_START_CH (0) +#define PS3_AUDIO_NORMAL_DMA_COUNT (8) +#define PS3_AUDIO_NULL_DMA_START_CH \ + (PS3_AUDIO_NORMAL_DMA_START_CH + PS3_AUDIO_NORMAL_DMA_COUNT) +#define PS3_AUDIO_NULL_DMA_COUNT (2) + +#define SND_PS3_MAX_VOL (0x0F) +#define SND_PS3_MIN_VOL (0x00) +#define SND_PS3_MIN_ATT SND_PS3_MIN_VOL +#define SND_PS3_MAX_ATT SND_PS3_MAX_VOL + +#define SND_PS3_PCM_PREALLOC_SIZE \ + (PS3_AUDIO_DMAC_BLOCK_SIZE * PS3_AUDIO_DMAC_MAX_BLOCKS * 4) + +#define SND_PS3_DMA_REGION_SIZE \ + (SND_PS3_PCM_PREALLOC_SIZE + PAGE_SIZE) + +#define PS3_AUDIO_IOID (1UL) + +#endif /* _SND_PS3_H_ */ diff --git a/sound/ppc/snd_ps3_reg.h b/sound/ppc/snd_ps3_reg.h new file mode 100644 index 000000000..e2212b793 --- /dev/null +++ b/sound/ppc/snd_ps3_reg.h @@ -0,0 +1,879 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Audio support for PS3 + * Copyright (C) 2007 Sony Computer Entertainment Inc. + * Copyright 2006, 2007 Sony Corporation + * All rights reserved. + */ + +/* + * interrupt / configure registers + */ + +#define PS3_AUDIO_INTR_0 (0x00000100) +#define PS3_AUDIO_INTR_EN_0 (0x00000140) +#define PS3_AUDIO_CONFIG (0x00000200) + +/* + * DMAC registers + * n:0..9 + */ +#define PS3_AUDIO_DMAC_REGBASE(x) (0x0000210 + 0x20 * (x)) + +#define PS3_AUDIO_KICK(n) (PS3_AUDIO_DMAC_REGBASE(n) + 0x00) +#define PS3_AUDIO_SOURCE(n) (PS3_AUDIO_DMAC_REGBASE(n) + 0x04) +#define PS3_AUDIO_DEST(n) (PS3_AUDIO_DMAC_REGBASE(n) + 0x08) +#define PS3_AUDIO_DMASIZE(n) (PS3_AUDIO_DMAC_REGBASE(n) + 0x0C) + +/* + * mute control + */ +#define PS3_AUDIO_AX_MCTRL (0x00004000) +#define PS3_AUDIO_AX_ISBP (0x00004004) +#define PS3_AUDIO_AX_AOBP (0x00004008) +#define PS3_AUDIO_AX_IC (0x00004010) +#define PS3_AUDIO_AX_IE (0x00004014) +#define PS3_AUDIO_AX_IS (0x00004018) + +/* + * three wire serial + * n:0..3 + */ +#define PS3_AUDIO_AO_MCTRL (0x00006000) +#define PS3_AUDIO_AO_3WMCTRL (0x00006004) + +#define PS3_AUDIO_AO_3WCTRL(n) (0x00006200 + 0x200 * (n)) + +/* + * S/PDIF + * n:0..1 + * x:0..11 + * y:0..5 + */ +#define PS3_AUDIO_AO_SPD_REGBASE(n) (0x00007200 + 0x200 * (n)) + +#define PS3_AUDIO_AO_SPDCTRL(n) \ + (PS3_AUDIO_AO_SPD_REGBASE(n) + 0x00) +#define PS3_AUDIO_AO_SPDUB(n, x) \ + (PS3_AUDIO_AO_SPD_REGBASE(n) + 0x04 + 0x04 * (x)) +#define PS3_AUDIO_AO_SPDCS(n, y) \ + (PS3_AUDIO_AO_SPD_REGBASE(n) + 0x34 + 0x04 * (y)) + + +/* + PS3_AUDIO_INTR_0 register tells an interrupt handler which audio + DMA channel triggered the interrupt. The interrupt status for a channel + can be cleared by writing a '1' to the corresponding bit. A new interrupt + cannot be generated until the previous interrupt has been cleared. + + Note that the status reported by PS3_AUDIO_INTR_0 is independent of the + value of PS3_AUDIO_INTR_EN_0. + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0 0 0 0 0 0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C| INTR_0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ +#define PS3_AUDIO_INTR_0_CHAN(n) (1 << ((n) * 2)) +#define PS3_AUDIO_INTR_0_CHAN9 PS3_AUDIO_INTR_0_CHAN(9) +#define PS3_AUDIO_INTR_0_CHAN8 PS3_AUDIO_INTR_0_CHAN(8) +#define PS3_AUDIO_INTR_0_CHAN7 PS3_AUDIO_INTR_0_CHAN(7) +#define PS3_AUDIO_INTR_0_CHAN6 PS3_AUDIO_INTR_0_CHAN(6) +#define PS3_AUDIO_INTR_0_CHAN5 PS3_AUDIO_INTR_0_CHAN(5) +#define PS3_AUDIO_INTR_0_CHAN4 PS3_AUDIO_INTR_0_CHAN(4) +#define PS3_AUDIO_INTR_0_CHAN3 PS3_AUDIO_INTR_0_CHAN(3) +#define PS3_AUDIO_INTR_0_CHAN2 PS3_AUDIO_INTR_0_CHAN(2) +#define PS3_AUDIO_INTR_0_CHAN1 PS3_AUDIO_INTR_0_CHAN(1) +#define PS3_AUDIO_INTR_0_CHAN0 PS3_AUDIO_INTR_0_CHAN(0) + +/* + The PS3_AUDIO_INTR_EN_0 register specifies which DMA channels can generate + an interrupt to the PU. Each bit of PS3_AUDIO_INTR_EN_0 is ANDed with the + corresponding bit in PS3_AUDIO_INTR_0. The resulting bits are OR'd together + to generate the Audio interrupt. + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0 0 0 0 0 0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C| INTR_EN_0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + + Bit assignments are same as PS3_AUDIO_INTR_0 +*/ + +/* + PS3_AUDIO_CONFIG + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 C|0 0 0 0 0 0 0 0| CONFIG + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + +*/ + +/* The CLEAR field cancels all pending transfers, and stops any running DMA + transfers. Any interrupts associated with the canceled transfers + will occur as if the transfer had finished. + Since this bit is designed to recover from DMA related issues + which are caused by unpredictable situations, it is preferred to wait + for normal DMA transfer end without using this bit. +*/ +#define PS3_AUDIO_CONFIG_CLEAR (1 << 8) /* RWIVF */ + +/* + PS3_AUDIO_AX_MCTRL: Audio Port Mute Control Register + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|A|A|A|0 0 0 0 0 0 0|S|S|A|A|A|A| AX_MCTRL + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + +/* 3 Wire Audio Serial Output Channel Mutes (0..3) */ +#define PS3_AUDIO_AX_MCTRL_ASOMT(n) (1 << (3 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_MCTRL_ASO3MT (1 << 0) /* RWIVF */ +#define PS3_AUDIO_AX_MCTRL_ASO2MT (1 << 1) /* RWIVF */ +#define PS3_AUDIO_AX_MCTRL_ASO1MT (1 << 2) /* RWIVF */ +#define PS3_AUDIO_AX_MCTRL_ASO0MT (1 << 3) /* RWIVF */ + +/* S/PDIF mutes (0,1)*/ +#define PS3_AUDIO_AX_MCTRL_SPOMT(n) (1 << (5 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_MCTRL_SPO1MT (1 << 4) /* RWIVF */ +#define PS3_AUDIO_AX_MCTRL_SPO0MT (1 << 5) /* RWIVF */ + +/* All 3 Wire Serial Outputs Mute */ +#define PS3_AUDIO_AX_MCTRL_AASOMT (1 << 13) /* RWIVF */ + +/* All S/PDIF Mute */ +#define PS3_AUDIO_AX_MCTRL_ASPOMT (1 << 14) /* RWIVF */ + +/* All Audio Outputs Mute */ +#define PS3_AUDIO_AX_MCTRL_AAOMT (1 << 15) /* RWIVF */ + +/* + S/PDIF Outputs Buffer Read/Write Pointer Register + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0|0|SPO0B|0|SPO1B|0 0 0 0 0 0 0 0|0|SPO0B|0|SPO1B| AX_ISBP + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + +*/ +/* + S/PDIF Output Channel Read Buffer Numbers + Buffer number is value of field. + Indicates current read access buffer ID from Audio Data + Transfer controller of S/PDIF Output +*/ + +#define PS3_AUDIO_AX_ISBP_SPOBRN_MASK(n) (0x7 << 4 * (1 - (n))) /* R-IUF */ +#define PS3_AUDIO_AX_ISBP_SPO1BRN_MASK (0x7 << 0) /* R-IUF */ +#define PS3_AUDIO_AX_ISBP_SPO0BRN_MASK (0x7 << 4) /* R-IUF */ + +/* +S/PDIF Output Channel Buffer Write Numbers +Indicates current write access buffer ID from bus master. +*/ +#define PS3_AUDIO_AX_ISBP_SPOBWN_MASK(n) (0x7 << 4 * (5 - (n))) /* R-IUF */ +#define PS3_AUDIO_AX_ISBP_SPO1BWN_MASK (0x7 << 16) /* R-IUF */ +#define PS3_AUDIO_AX_ISBP_SPO0BWN_MASK (0x7 << 20) /* R-IUF */ + +/* + 3 Wire Audio Serial Outputs Buffer Read/Write + Pointer Register + Buffer number is value of field + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0|ASO0B|0|ASO1B|0|ASO2B|0|ASO3B|0|ASO0B|0|ASO1B|0|ASO2B|0|ASO3B| AX_AOBP + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + +/* +3 Wire Audio Serial Output Channel Buffer Read Numbers +Indicates current read access buffer Id from Audio Data Transfer +Controller of 3 Wire Audio Serial Output Channels +*/ +#define PS3_AUDIO_AX_AOBP_ASOBRN_MASK(n) (0x7 << 4 * (3 - (n))) /* R-IUF */ + +#define PS3_AUDIO_AX_AOBP_ASO3BRN_MASK (0x7 << 0) /* R-IUF */ +#define PS3_AUDIO_AX_AOBP_ASO2BRN_MASK (0x7 << 4) /* R-IUF */ +#define PS3_AUDIO_AX_AOBP_ASO1BRN_MASK (0x7 << 8) /* R-IUF */ +#define PS3_AUDIO_AX_AOBP_ASO0BRN_MASK (0x7 << 12) /* R-IUF */ + +/* +3 Wire Audio Serial Output Channel Buffer Write Numbers +Indicates current write access buffer ID from bus master. +*/ +#define PS3_AUDIO_AX_AOBP_ASOBWN_MASK(n) (0x7 << 4 * (7 - (n))) /* R-IUF */ + +#define PS3_AUDIO_AX_AOBP_ASO3BWN_MASK (0x7 << 16) /* R-IUF */ +#define PS3_AUDIO_AX_AOBP_ASO2BWN_MASK (0x7 << 20) /* R-IUF */ +#define PS3_AUDIO_AX_AOBP_ASO1BWN_MASK (0x7 << 24) /* R-IUF */ +#define PS3_AUDIO_AX_AOBP_ASO0BWN_MASK (0x7 << 28) /* R-IUF */ + + + +/* +Audio Port Interrupt Condition Register +For the fields in this register, the following values apply: +0 = Interrupt is generated every interrupt event. +1 = Interrupt is generated every 2 interrupt events. +2 = Interrupt is generated every 4 interrupt events. +3 = Reserved + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0|0 0|SPO|0 0|SPO|0 0|AAS|0 0 0 0 0 0 0 0 0 0 0 0| AX_IC + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ +/* +All 3-Wire Audio Serial Outputs Interrupt Mode +Configures the Interrupt and Signal Notification +condition of all 3-wire Audio Serial Outputs. +*/ +#define PS3_AUDIO_AX_IC_AASOIMD_MASK (0x3 << 12) /* RWIVF */ +#define PS3_AUDIO_AX_IC_AASOIMD_EVERY1 (0x0 << 12) /* RWI-V */ +#define PS3_AUDIO_AX_IC_AASOIMD_EVERY2 (0x1 << 12) /* RW--V */ +#define PS3_AUDIO_AX_IC_AASOIMD_EVERY4 (0x2 << 12) /* RW--V */ + +/* +S/PDIF Output Channel Interrupt Modes +Configures the Interrupt and signal Notification +conditions of S/PDIF output channels. +*/ +#define PS3_AUDIO_AX_IC_SPO1IMD_MASK (0x3 << 16) /* RWIVF */ +#define PS3_AUDIO_AX_IC_SPO1IMD_EVERY1 (0x0 << 16) /* RWI-V */ +#define PS3_AUDIO_AX_IC_SPO1IMD_EVERY2 (0x1 << 16) /* RW--V */ +#define PS3_AUDIO_AX_IC_SPO1IMD_EVERY4 (0x2 << 16) /* RW--V */ + +#define PS3_AUDIO_AX_IC_SPO0IMD_MASK (0x3 << 20) /* RWIVF */ +#define PS3_AUDIO_AX_IC_SPO0IMD_EVERY1 (0x0 << 20) /* RWI-V */ +#define PS3_AUDIO_AX_IC_SPO0IMD_EVERY2 (0x1 << 20) /* RW--V */ +#define PS3_AUDIO_AX_IC_SPO0IMD_EVERY4 (0x2 << 20) /* RW--V */ + +/* +Audio Port interrupt Enable Register +Configures whether to enable or disable each Interrupt Generation. + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0|S|S|0 0|A|A|A|A|0 0 0 0|S|S|0 0|S|S|0 0|A|A|A|A| AX_IE + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + +*/ + +/* +3 Wire Audio Serial Output Channel Buffer Underflow +Interrupt Enables +Select enable/disable of Buffer Underflow Interrupts for +3-Wire Audio Serial Output Channels +DISABLED=Interrupt generation disabled. +*/ +#define PS3_AUDIO_AX_IE_ASOBUIE(n) (1 << (3 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO3BUIE (1 << 0) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO2BUIE (1 << 1) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO1BUIE (1 << 2) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO0BUIE (1 << 3) /* RWIVF */ + +/* S/PDIF Output Channel Buffer Underflow Interrupt Enables */ + +#define PS3_AUDIO_AX_IE_SPOBUIE(n) (1 << (7 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_IE_SPO1BUIE (1 << 6) /* RWIVF */ +#define PS3_AUDIO_AX_IE_SPO0BUIE (1 << 7) /* RWIVF */ + +/* S/PDIF Output Channel One Block Transfer Completion Interrupt Enables */ + +#define PS3_AUDIO_AX_IE_SPOBTCIE(n) (1 << (11 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_IE_SPO1BTCIE (1 << 10) /* RWIVF */ +#define PS3_AUDIO_AX_IE_SPO0BTCIE (1 << 11) /* RWIVF */ + +/* 3-Wire Audio Serial Output Channel Buffer Empty Interrupt Enables */ + +#define PS3_AUDIO_AX_IE_ASOBEIE(n) (1 << (19 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO3BEIE (1 << 16) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO2BEIE (1 << 17) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO1BEIE (1 << 18) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO0BEIE (1 << 19) /* RWIVF */ + +/* S/PDIF Output Channel Buffer Empty Interrupt Enables */ + +#define PS3_AUDIO_AX_IE_SPOBEIE(n) (1 << (23 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_IE_SPO1BEIE (1 << 22) /* RWIVF */ +#define PS3_AUDIO_AX_IE_SPO0BEIE (1 << 23) /* RWIVF */ + +/* +Audio Port Interrupt Status Register +Indicates Interrupt status, which interrupt has occurred, and can clear +each interrupt in this register. +Writing 1b to a field containing 1b clears field and de-asserts interrupt. +Writing 0b to a field has no effect. +Field values are the following: +0 - Interrupt hasn't occurred. +1 - Interrupt has occurred. + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0|S|S|0 0|A|A|A|A|0 0 0 0|S|S|0 0|S|S|0 0|A|A|A|A| AX_IS + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + + Bit assignment are same as AX_IE +*/ + +/* +Audio Output Master Control Register +Configures Master Clock and other master Audio Output Settings + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0|SCKSE|0|SCKSE| MR0 | MR1 |MCL|MCL|0 0 0 0|0 0 0 0 0 0 0 0| AO_MCTRL + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + +/* +MCLK Output Control +Controls mclko[1] output. +0 - Disable output (fixed at High) +1 - Output clock produced by clock selected +with scksel1 by mr1 +2 - Reserved +3 - Reserved +*/ + +#define PS3_AUDIO_AO_MCTRL_MCLKC1_MASK (0x3 << 12) /* RWIVF */ +#define PS3_AUDIO_AO_MCTRL_MCLKC1_DISABLED (0x0 << 12) /* RWI-V */ +#define PS3_AUDIO_AO_MCTRL_MCLKC1_ENABLED (0x1 << 12) /* RW--V */ +#define PS3_AUDIO_AO_MCTRL_MCLKC1_RESVD2 (0x2 << 12) /* RW--V */ +#define PS3_AUDIO_AO_MCTRL_MCLKC1_RESVD3 (0x3 << 12) /* RW--V */ + +/* +MCLK Output Control +Controls mclko[0] output. +0 - Disable output (fixed at High) +1 - Output clock produced by clock selected +with SCKSEL0 by MR0 +2 - Reserved +3 - Reserved +*/ +#define PS3_AUDIO_AO_MCTRL_MCLKC0_MASK (0x3 << 14) /* RWIVF */ +#define PS3_AUDIO_AO_MCTRL_MCLKC0_DISABLED (0x0 << 14) /* RWI-V */ +#define PS3_AUDIO_AO_MCTRL_MCLKC0_ENABLED (0x1 << 14) /* RW--V */ +#define PS3_AUDIO_AO_MCTRL_MCLKC0_RESVD2 (0x2 << 14) /* RW--V */ +#define PS3_AUDIO_AO_MCTRL_MCLKC0_RESVD3 (0x3 << 14) /* RW--V */ +/* +Master Clock Rate 1 +Sets the divide ration of Master Clock1 (clock output from +mclko[1] for the input clock selected by scksel1. +*/ +#define PS3_AUDIO_AO_MCTRL_MR1_MASK (0xf << 16) +#define PS3_AUDIO_AO_MCTRL_MR1_DEFAULT (0x0 << 16) /* RWI-V */ +/* +Master Clock Rate 0 +Sets the divide ratio of Master Clock0 (clock output from +mclko[0] for the input clock selected by scksel0). +*/ +#define PS3_AUDIO_AO_MCTRL_MR0_MASK (0xf << 20) /* RWIVF */ +#define PS3_AUDIO_AO_MCTRL_MR0_DEFAULT (0x0 << 20) /* RWI-V */ +/* +System Clock Select 0/1 +Selects the system clock to be used as Master Clock 0/1 +Input the system clock that is appropriate for the sampling +rate. +*/ +#define PS3_AUDIO_AO_MCTRL_SCKSEL1_MASK (0x7 << 24) /* RWIVF */ +#define PS3_AUDIO_AO_MCTRL_SCKSEL1_DEFAULT (0x2 << 24) /* RWI-V */ + +#define PS3_AUDIO_AO_MCTRL_SCKSEL0_MASK (0x7 << 28) /* RWIVF */ +#define PS3_AUDIO_AO_MCTRL_SCKSEL0_DEFAULT (0x2 << 28) /* RWI-V */ + + +/* +3-Wire Audio Output Master Control Register +Configures clock, 3-Wire Audio Serial Output Enable, and +other 3-Wire Audio Serial Output Master Settings + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |A|A|A|A|0 0 0|A| ASOSR |0 0 0 0|A|A|A|A|A|A|0|1|0 0 0 0 0 0 0 0| AO_3WMCTRL + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + + +/* +LRCKO Polarity +0 - Reserved +1 - default +*/ +#define PS3_AUDIO_AO_3WMCTRL_ASOPLRCK (1 << 8) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOPLRCK_DEFAULT (1 << 8) /* RW--V */ + +/* LRCK Output Disable */ + +#define PS3_AUDIO_AO_3WMCTRL_ASOLRCKD (1 << 10) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOLRCKD_ENABLED (0 << 10) /* RW--V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOLRCKD_DISABLED (1 << 10) /* RWI-V */ + +/* Bit Clock Output Disable */ + +#define PS3_AUDIO_AO_3WMCTRL_ASOBCLKD (1 << 11) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOBCLKD_ENABLED (0 << 11) /* RW--V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOBCLKD_DISABLED (1 << 11) /* RWI-V */ + +/* +3-Wire Audio Serial Output Channel 0-3 Operational +Status. Each bit becomes 1 after each 3-Wire Audio +Serial Output Channel N is in action by setting 1 to +asoen. +Each bit becomes 0 after each 3-Wire Audio Serial Output +Channel N is out of action by setting 0 to asoen. +*/ +#define PS3_AUDIO_AO_3WMCTRL_ASORUN(n) (1 << (15 - (n))) /* R-IVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASORUN_STOPPED(n) (0 << (15 - (n))) /* R-I-V */ +#define PS3_AUDIO_AO_3WMCTRL_ASORUN_RUNNING(n) (1 << (15 - (n))) /* R---V */ +#define PS3_AUDIO_AO_3WMCTRL_ASORUN0 \ + PS3_AUDIO_AO_3WMCTRL_ASORUN(0) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN0_STOPPED \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_STOPPED(0) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN0_RUNNING \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_RUNNING(0) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN1 \ + PS3_AUDIO_AO_3WMCTRL_ASORUN(1) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN1_STOPPED \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_STOPPED(1) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN1_RUNNING \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_RUNNING(1) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN2 \ + PS3_AUDIO_AO_3WMCTRL_ASORUN(2) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN2_STOPPED \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_STOPPED(2) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN2_RUNNING \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_RUNNING(2) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN3 \ + PS3_AUDIO_AO_3WMCTRL_ASORUN(3) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN3_STOPPED \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_STOPPED(3) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN3_RUNNING \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_RUNNING(3) + +/* +Sampling Rate +Specifies the divide ratio of the bit clock (clock output +from bclko) used by the 3-wire Audio Output Clock, which +is applied to the master clock selected by mcksel. +Data output is synchronized with this clock. +*/ +#define PS3_AUDIO_AO_3WMCTRL_ASOSR_MASK (0xf << 20) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOSR_DIV2 (0x1 << 20) /* RWI-V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOSR_DIV4 (0x2 << 20) /* RW--V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOSR_DIV8 (0x4 << 20) /* RW--V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOSR_DIV12 (0x6 << 20) /* RW--V */ + +/* +Master Clock Select +0 - Master Clock 0 +1 - Master Clock 1 +*/ +#define PS3_AUDIO_AO_3WMCTRL_ASOMCKSEL (1 << 24) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOMCKSEL_CLK0 (0 << 24) /* RWI-V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOMCKSEL_CLK1 (1 << 24) /* RW--V */ + +/* +Enables and disables 4ch 3-Wire Audio Serial Output +operation. Each Bit from 0 to 3 corresponds to an +output channel, which means that each output channel +can be enabled or disabled individually. When +multiple channels are enabled at the same time, output +operations are performed in synchronization. +Bit 0 - Output Channel 0 (SDOUT[0]) +Bit 1 - Output Channel 1 (SDOUT[1]) +Bit 2 - Output Channel 2 (SDOUT[2]) +Bit 3 - Output Channel 3 (SDOUT[3]) +*/ +#define PS3_AUDIO_AO_3WMCTRL_ASOEN(n) (1 << (31 - (n))) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOEN_DISABLED(n) (0 << (31 - (n))) /* RWI-V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOEN_ENABLED(n) (1 << (31 - (n))) /* RW--V */ + +#define PS3_AUDIO_AO_3WMCTRL_ASOEN0 \ + PS3_AUDIO_AO_3WMCTRL_ASOEN(0) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOEN0_DISABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_DISABLED(0) /* RWI-V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOEN0_ENABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_ENABLED(0) /* RW--V */ +#define PS3_AUDIO_A1_3WMCTRL_ASOEN0 \ + PS3_AUDIO_AO_3WMCTRL_ASOEN(1) /* RWIVF */ +#define PS3_AUDIO_A1_3WMCTRL_ASOEN0_DISABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_DISABLED(1) /* RWI-V */ +#define PS3_AUDIO_A1_3WMCTRL_ASOEN0_ENABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_ENABLED(1) /* RW--V */ +#define PS3_AUDIO_A2_3WMCTRL_ASOEN0 \ + PS3_AUDIO_AO_3WMCTRL_ASOEN(2) /* RWIVF */ +#define PS3_AUDIO_A2_3WMCTRL_ASOEN0_DISABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_DISABLED(2) /* RWI-V */ +#define PS3_AUDIO_A2_3WMCTRL_ASOEN0_ENABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_ENABLED(2) /* RW--V */ +#define PS3_AUDIO_A3_3WMCTRL_ASOEN0 \ + PS3_AUDIO_AO_3WMCTRL_ASOEN(3) /* RWIVF */ +#define PS3_AUDIO_A3_3WMCTRL_ASOEN0_DISABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_DISABLED(3) /* RWI-V */ +#define PS3_AUDIO_A3_3WMCTRL_ASOEN0_ENABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_ENABLED(3) /* RW--V */ + +/* +3-Wire Audio Serial output Channel 0-3 Control Register +Configures settings for 3-Wire Serial Audio Output Channel 0-3 + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|A|0 0 0 0|A|0|ASO|0 0 0|0|0|0|0|0| AO_3WCTRL + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + +*/ +/* +Data Bit Mode +Specifies the number of data bits +0 - 16 bits +1 - reserved +2 - 20 bits +3 - 24 bits +*/ +#define PS3_AUDIO_AO_3WCTRL_ASODB_MASK (0x3 << 8) /* RWIVF */ +#define PS3_AUDIO_AO_3WCTRL_ASODB_16BIT (0x0 << 8) /* RWI-V */ +#define PS3_AUDIO_AO_3WCTRL_ASODB_RESVD (0x1 << 8) /* RWI-V */ +#define PS3_AUDIO_AO_3WCTRL_ASODB_20BIT (0x2 << 8) /* RW--V */ +#define PS3_AUDIO_AO_3WCTRL_ASODB_24BIT (0x3 << 8) /* RW--V */ +/* +Data Format Mode +Specifies the data format where (LSB side or MSB) the data(in 20 bit +or 24 bit resolution mode) is put in a 32 bit field. +0 - Data put on LSB side +1 - Data put on MSB side +*/ +#define PS3_AUDIO_AO_3WCTRL_ASODF (1 << 11) /* RWIVF */ +#define PS3_AUDIO_AO_3WCTRL_ASODF_LSB (0 << 11) /* RWI-V */ +#define PS3_AUDIO_AO_3WCTRL_ASODF_MSB (1 << 11) /* RW--V */ +/* +Buffer Reset +Performs buffer reset. Writing 1 to this bit initializes the +corresponding 3-Wire Audio Output buffers(both L and R). +*/ +#define PS3_AUDIO_AO_3WCTRL_ASOBRST (1 << 16) /* CWIVF */ +#define PS3_AUDIO_AO_3WCTRL_ASOBRST_IDLE (0 << 16) /* -WI-V */ +#define PS3_AUDIO_AO_3WCTRL_ASOBRST_RESET (1 << 16) /* -W--T */ + +/* +S/PDIF Audio Output Channel 0/1 Control Register +Configures settings for S/PDIF Audio Output Channel 0/1. + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |S|0 0 0|S|0 0|S| SPOSR |0 0|SPO|0 0 0 0|S|0|SPO|0 0 0 0 0 0 0|S| AO_SPDCTRL + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ +/* +Buffer reset. Writing 1 to this bit initializes the +corresponding S/PDIF output buffer pointer. +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPOBRST (1 << 0) /* CWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPOBRST_IDLE (0 << 0) /* -WI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOBRST_RESET (1 << 0) /* -W--T */ + +/* +Data Bit Mode +Specifies number of data bits +0 - 16 bits +1 - Reserved +2 - 20 bits +3 - 24 bits +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPODB_MASK (0x3 << 8) /* RWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPODB_16BIT (0x0 << 8) /* RWI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPODB_RESVD (0x1 << 8) /* RW--V */ +#define PS3_AUDIO_AO_SPDCTRL_SPODB_20BIT (0x2 << 8) /* RW--V */ +#define PS3_AUDIO_AO_SPDCTRL_SPODB_24BIT (0x3 << 8) /* RW--V */ +/* +Data format Mode +Specifies the data format, where (LSB side or MSB) +the data(in 20 or 24 bit resolution) is put in the +32 bit field. +0 - LSB Side +1 - MSB Side +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPODF (1 << 11) /* RWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPODF_LSB (0 << 11) /* RWI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPODF_MSB (1 << 11) /* RW--V */ +/* +Source Select +Specifies the source of the S/PDIF output. When 0, output +operation is controlled by 3wen[0] of AO_3WMCTRL register. +The SR must have the same setting as the a0_3wmctrl reg. +0 - 3-Wire Audio OUT Ch0 Buffer +1 - S/PDIF buffer +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPOSS_MASK (0x3 << 16) /* RWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPOSS_3WEN (0x0 << 16) /* RWI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOSS_SPDIF (0x1 << 16) /* RW--V */ +/* +Sampling Rate +Specifies the divide ratio of the bit clock (clock output +from bclko) used by the S/PDIF Output Clock, which +is applied to the master clock selected by mcksel. +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPOSR (0xf << 20) /* RWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPOSR_DIV2 (0x1 << 20) /* RWI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOSR_DIV4 (0x2 << 20) /* RW--V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOSR_DIV8 (0x4 << 20) /* RW--V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOSR_DIV12 (0x6 << 20) /* RW--V */ +/* +Master Clock Select +0 - Master Clock 0 +1 - Master Clock 1 +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPOMCKSEL (1 << 24) /* RWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPOMCKSEL_CLK0 (0 << 24) /* RWI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOMCKSEL_CLK1 (1 << 24) /* RW--V */ + +/* +S/PDIF Output Channel Operational Status +This bit becomes 1 after S/PDIF Output Channel is in +action by setting 1 to spoen. This bit becomes 0 +after S/PDIF Output Channel is out of action by setting +0 to spoen. +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPORUN (1 << 27) /* R-IVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPORUN_STOPPED (0 << 27) /* R-I-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPORUN_RUNNING (1 << 27) /* R---V */ + +/* +S/PDIF Audio Output Channel Output Enable +Enables and disables output operation. This bit is used +only when sposs = 1 +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPOEN (1 << 31) /* RWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPOEN_DISABLED (0 << 31) /* RWI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOEN_ENABLED (1 << 31) /* RW--V */ + +/* +S/PDIF Audio Output Channel Channel Status +Setting Registers. +Configures channel status bit settings for each block +(192 bits). +Output is performed from the MSB(AO_SPDCS0 register bit 31). +The same value is added for subframes within the same frame. + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + | SPOCS | AO_SPDCS + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + +S/PDIF Audio Output Channel User Bit Setting +Configures user bit settings for each block (384 bits). +Output is performed from the MSB(ao_spdub0 register bit 31). + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + | SPOUB | AO_SPDUB + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ +/***************************************************************************** + * + * DMAC register + * + *****************************************************************************/ +/* +The PS3_AUDIO_KICK register is used to initiate a DMA transfer and monitor +its status + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0|STATU|0 0 0| EVENT |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|R| KICK + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ +/* +The REQUEST field is written to ACTIVE to initiate a DMA request when EVENT +occurs. +It will return to the DONE state when the request is completed. +The registers for a DMA channel should only be written if REQUEST is IDLE. +*/ + +#define PS3_AUDIO_KICK_REQUEST (1 << 0) /* RWIVF */ +#define PS3_AUDIO_KICK_REQUEST_IDLE (0 << 0) /* RWI-V */ +#define PS3_AUDIO_KICK_REQUEST_ACTIVE (1 << 0) /* -W--T */ + +/* + *The EVENT field is used to set the event in which + *the DMA request becomes active. + */ +#define PS3_AUDIO_KICK_EVENT_MASK (0x1f << 16) /* RWIVF */ +#define PS3_AUDIO_KICK_EVENT_ALWAYS (0x00 << 16) /* RWI-V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT0_EMPTY (0x01 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT0_UNDERFLOW (0x02 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT1_EMPTY (0x03 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT1_UNDERFLOW (0x04 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT2_EMPTY (0x05 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT2_UNDERFLOW (0x06 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT3_EMPTY (0x07 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT3_UNDERFLOW (0x08 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SPDIF0_BLOCKTRANSFERCOMPLETE \ + (0x09 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SPDIF0_UNDERFLOW (0x0A << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SPDIF0_EMPTY (0x0B << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SPDIF1_BLOCKTRANSFERCOMPLETE \ + (0x0C << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SPDIF1_UNDERFLOW (0x0D << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SPDIF1_EMPTY (0x0E << 16) /* RW--V */ + +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA(n) \ + ((0x13 + (n)) << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA0 (0x13 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA1 (0x14 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA2 (0x15 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA3 (0x16 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA4 (0x17 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA5 (0x18 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA6 (0x19 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA7 (0x1A << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA8 (0x1B << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA9 (0x1C << 16) /* RW--V */ + +/* +The STATUS field can be used to monitor the progress of a DMA request. +DONE indicates the previous request has completed. +EVENT indicates that the DMA engine is waiting for the EVENT to occur. +PENDING indicates that the DMA engine has not started processing this +request, but the EVENT has occurred. +DMA indicates that the data transfer is in progress. +NOTIFY indicates that the notifier signalling end of transfer is being written. +CLEAR indicated that the previous transfer was cleared. +ERROR indicates the previous transfer requested an unsupported +source/destination combination. +*/ + +#define PS3_AUDIO_KICK_STATUS_MASK (0x7 << 24) /* R-IVF */ +#define PS3_AUDIO_KICK_STATUS_DONE (0x0 << 24) /* R-I-V */ +#define PS3_AUDIO_KICK_STATUS_EVENT (0x1 << 24) /* R---V */ +#define PS3_AUDIO_KICK_STATUS_PENDING (0x2 << 24) /* R---V */ +#define PS3_AUDIO_KICK_STATUS_DMA (0x3 << 24) /* R---V */ +#define PS3_AUDIO_KICK_STATUS_NOTIFY (0x4 << 24) /* R---V */ +#define PS3_AUDIO_KICK_STATUS_CLEAR (0x5 << 24) /* R---V */ +#define PS3_AUDIO_KICK_STATUS_ERROR (0x6 << 24) /* R---V */ + +/* +The PS3_AUDIO_SOURCE register specifies the source address for transfers. + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + | START |0 0 0 0 0|TAR| SOURCE + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + +/* +The Audio DMA engine uses 128-byte transfers, thus the address must be aligned +to a 128 byte boundary. The low seven bits are assumed to be 0. +*/ + +#define PS3_AUDIO_SOURCE_START_MASK (0x01FFFFFF << 7) /* RWIUF */ + +/* +The TARGET field specifies the memory space containing the source address. +*/ + +#define PS3_AUDIO_SOURCE_TARGET_MASK (3 << 0) /* RWIVF */ +#define PS3_AUDIO_SOURCE_TARGET_SYSTEM_MEMORY (2 << 0) /* RW--V */ + +/* +The PS3_AUDIO_DEST register specifies the destination address for transfers. + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + | START |0 0 0 0 0|TAR| DEST + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + +/* +The Audio DMA engine uses 128-byte transfers, thus the address must be aligned +to a 128 byte boundary. The low seven bits are assumed to be 0. +*/ + +#define PS3_AUDIO_DEST_START_MASK (0x01FFFFFF << 7) /* RWIUF */ + +/* +The TARGET field specifies the memory space containing the destination address +AUDIOFIFO = Audio WriteData FIFO, +*/ + +#define PS3_AUDIO_DEST_TARGET_MASK (3 << 0) /* RWIVF */ +#define PS3_AUDIO_DEST_TARGET_AUDIOFIFO (1 << 0) /* RW--V */ + +/* +PS3_AUDIO_DMASIZE specifies the number of 128-byte blocks + 1 to transfer. +So a value of 0 means 128-bytes will get transferred. + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0| BLOCKS | DMASIZE + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + + +#define PS3_AUDIO_DMASIZE_BLOCKS_MASK (0x7f << 0) /* RWIUF */ + +/* + * source/destination address for internal fifos + */ +#define PS3_AUDIO_AO_3W_LDATA(n) (0x1000 + (0x100 * (n))) +#define PS3_AUDIO_AO_3W_RDATA(n) (0x1080 + (0x100 * (n))) + +#define PS3_AUDIO_AO_SPD_DATA(n) (0x2000 + (0x400 * (n))) + + +/* + * field attiribute + * + * Read + * ' ' = Other Information + * '-' = Field is part of a write-only register + * 'C' = Value read is always the same, constant value line follows (C) + * 'R' = Value is read + * + * Write + * ' ' = Other Information + * '-' = Must not be written (D), value ignored when written (R,A,F) + * 'W' = Can be written + * + * Internal State + * ' ' = Other Information + * '-' = No internal state + * 'X' = Internal state, initial value is unknown + * 'I' = Internal state, initial value is known and follows (I) + * + * Declaration/Size + * ' ' = Other Information + * '-' = Does Not Apply + * 'V' = Type is void + * 'U' = Type is unsigned integer + * 'S' = Type is signed integer + * 'F' = Type is IEEE floating point + * '1' = Byte size (008) + * '2' = Short size (016) + * '3' = Three byte size (024) + * '4' = Word size (032) + * '8' = Double size (064) + * + * Define Indicator + * ' ' = Other Information + * 'D' = Device + * 'M' = Memory + * 'R' = Register + * 'A' = Array of Registers + * 'F' = Field + * 'V' = Value + * 'T' = Task + */ + diff --git a/sound/ppc/tumbler.c b/sound/ppc/tumbler.c new file mode 100644 index 000000000..139c7b259 --- /dev/null +++ b/sound/ppc/tumbler.c @@ -0,0 +1,1497 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PMac Tumbler/Snapper lowlevel functions + * + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * + * Rene Rebe <rene.rebe@gmx.net>: + * * update from shadow registers on wakeup and headphone plug + * * automatically toggle DRC on headphone plug + */ + + +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/kmod.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/string.h> +#include <linux/of_irq.h> +#include <linux/io.h> +#include <sound/core.h> +#include <asm/irq.h> +#include <asm/machdep.h> +#include <asm/pmac_feature.h> +#include "pmac.h" +#include "tumbler_volume.h" + +#undef DEBUG + +#ifdef DEBUG +#define DBG(fmt...) pr_debug(fmt) +#else +#define DBG(fmt...) +#endif + +#define IS_G4DA (of_machine_is_compatible("PowerMac3,4")) + +/* 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 +}; + +struct pmac_gpio { + unsigned int addr; + u8 active_val; + u8 inactive_val; + u8 active_state; +}; + +struct pmac_tumbler { + struct pmac_keywest i2c; + struct pmac_gpio audio_reset; + struct pmac_gpio amp_mute; + struct pmac_gpio line_mute; + struct pmac_gpio line_detect; + struct pmac_gpio hp_mute; + struct pmac_gpio hp_detect; + int headphone_irq; + int lineout_irq; + unsigned int save_master_vol[2]; + unsigned int master_vol[2]; + unsigned int save_master_switch[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; + int anded_reset; + int auto_mute_notify; + int reset_on_sleep; + u8 acs; +}; + + +/* + */ + +static int send_init_client(struct pmac_keywest *i2c, const 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; + DBG("(W) i2c error %d\n", err); + mdelay(10); + } while (count--); + if (err < 0) + return -ENXIO; + regs += 2; + } + return 0; +} + + +static int tumbler_init_client(struct pmac_keywest *i2c) +{ + static const 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 */ + }; + DBG("(I) tumbler init client\n"); + return send_init_client(i2c, regs); +} + +static int snapper_init_client(struct pmac_keywest *i2c) +{ + static const 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 */ + }; + DBG("(I) snapper init client\n"); + return send_init_client(i2c, regs); +} + +/* + * gpio access + */ +#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 */ + +static void write_audio_gpio(struct pmac_gpio *gp, int active) +{ + if (! gp->addr) + return; + active = active ? gp->active_val : gp->inactive_val; + do_gpio_write(gp, active); + DBG("(I) gpio %x write %d\n", gp->addr, active); +} + +static int check_audio_gpio(struct pmac_gpio *gp) +{ + int ret; + + if (! gp->addr) + return 0; + + ret = do_gpio_read(gp); + + return (ret & 0x1) == (gp->active_val & 0x1); +} + +static int read_audio_gpio(struct pmac_gpio *gp) +{ + int ret; + if (! gp->addr) + return 0; + ret = do_gpio_read(gp); + ret = (ret & 0x02) !=0; + return ret == gp->active_state; +} + +/* + * update master volume + */ +static int tumbler_set_master_volume(struct pmac_tumbler *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_i2c_block_data(mix->i2c.client, TAS_REG_VOL, 6, + block) < 0) { + dev_err(&mix->i2c.client->dev, "failed to set volume\n"); + return -EINVAL; + } + DBG("(I) succeeded to set volume (%u, %u)\n", left_vol, right_vol); + return 0; +} + + +/* output volume */ +static int tumbler_info_master_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix = chip->mixer_data; + + 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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix = chip->mixer_data; + unsigned int vol[2]; + int change; + + vol[0] = ucontrol->value.integer.value[0]; + vol[1] = ucontrol->value.integer.value[1]; + if (vol[0] >= ARRAY_SIZE(master_volume_table) || + vol[1] >= ARRAY_SIZE(master_volume_table)) + return -EINVAL; + change = mix->master_vol[0] != vol[0] || + mix->master_vol[1] != vol[1]; + if (change) { + mix->master_vol[0] = vol[0]; + mix->master_vol[1] = vol[1]; + tumbler_set_master_volume(mix); + } + return change; +} + +/* output switch */ +static int tumbler_get_master_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix = chip->mixer_data; + + 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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix = chip->mixer_data; + int change; + + 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(struct pmac_tumbler *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_i2c_block_data(mix->i2c.client, TAS_REG_DRC, + 2, val) < 0) { + dev_err(&mix->i2c.client->dev, "failed to set DRC\n"); + return -EINVAL; + } + DBG("(I) succeeded to set DRC (%u, %u)\n", val[0], val[1]); + return 0; +} + +/* + * TAS3004 + */ + +#define TAS3004_DRC_MAX 0xef + +static int snapper_set_drc(struct pmac_tumbler *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_i2c_block_data(mix->i2c.client, TAS_REG_DRC, + 6, val) < 0) { + dev_err(&mix->i2c.client->dev, "failed to set DRC\n"); + return -EINVAL; + } + DBG("(I) succeeded to set DRC (%u, %u)\n", val[0], val[1]); + return 0; +} + +static int tumbler_info_drc_value(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_pmac *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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix; + mix = chip->mixer_data; + if (!mix) + return -ENODEV; + ucontrol->value.integer.value[0] = mix->drc_range; + return 0; +} + +static int tumbler_put_drc_value(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix; + unsigned int val; + int change; + + mix = chip->mixer_data; + if (!mix) + return -ENODEV; + val = ucontrol->value.integer.value[0]; + if (chip->model == PMAC_TUMBLER) { + if (val > TAS3001_DRC_MAX) + return -EINVAL; + } else { + if (val > TAS3004_DRC_MAX) + return -EINVAL; + } + change = mix->drc_range != val; + if (change) { + mix->drc_range = val; + if (chip->model == PMAC_TUMBLER) + tumbler_set_drc(mix); + else + snapper_set_drc(mix); + } + return change; +} + +static int tumbler_get_drc_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix; + mix = chip->mixer_data; + if (!mix) + return -ENODEV; + ucontrol->value.integer.value[0] = mix->drc_enable; + return 0; +} + +static int tumbler_put_drc_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix; + int change; + + mix = chip->mixer_data; + if (!mix) + 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; + const unsigned int *table; +}; + +static int tumbler_set_mono_volume(struct pmac_tumbler *mix, + const 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_i2c_block_data(mix->i2c.client, info->reg, + info->bytes, block) < 0) { + dev_err(&mix->i2c.client->dev, "failed to set mono volume %d\n", + info->index); + return -EINVAL; + } + return 0; +} + +static int tumbler_info_mono(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tumbler_mono_vol *info = (struct tumbler_mono_vol *)kcontrol->private_value; + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix; + mix = chip->mixer_data; + if (!mix) + return -ENODEV; + ucontrol->value.integer.value[0] = mix->mono_vol[info->index]; + return 0; +} + +static int tumbler_put_mono(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tumbler_mono_vol *info = (struct tumbler_mono_vol *)kcontrol->private_value; + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix; + unsigned int vol; + int change; + + mix = chip->mixer_data; + if (!mix) + return -ENODEV; + vol = ucontrol->value.integer.value[0]; + if (vol >= info->max) + return -EINVAL; + change = mix->mono_vol[info->index] != vol; + if (change) { + mix->mono_vol[info->index] = vol; + tumbler_set_mono_volume(mix, info); + } + return change; +} + +/* TAS3001c mono volumes */ +static const 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 const 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 const 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 const 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 const 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(struct pmac_tumbler *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_i2c_block_data(mix->i2c.client, reg, + 9, block) < 0) { + dev_err(&mix->i2c.client->dev, + "failed to set mono volume %d\n", reg); + return -EINVAL; + } + return 0; +} + +static int snapper_set_mix_vol(struct pmac_tumbler *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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int idx = (int)kcontrol->private_value; + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix; + mix = chip->mixer_data; + if (!mix) + 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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int idx = (int)kcontrol->private_value; + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix; + unsigned int vol[2]; + int change; + + mix = chip->mixer_data; + if (!mix) + return -ENODEV; + vol[0] = ucontrol->value.integer.value[0]; + vol[1] = ucontrol->value.integer.value[1]; + if (vol[0] >= ARRAY_SIZE(mixer_volume_table) || + vol[1] >= ARRAY_SIZE(mixer_volume_table)) + return -EINVAL; + change = mix->mix_vol[idx][0] != vol[0] || + mix->mix_vol[idx][1] != vol[1]; + if (change) { + mix->mix_vol[idx][0] = vol[0]; + mix->mix_vol[idx][1] = vol[1]; + snapper_set_mix_vol(mix, idx); + } + return change; +} + + +/* + * mute switches. FIXME: Turn that into software mute when both outputs are muted + * to avoid codec reset on ibook M7 + */ + +enum { TUMBLER_MUTE_HP, TUMBLER_MUTE_AMP, TUMBLER_MUTE_LINE }; + +static int tumbler_get_mute_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix; + struct pmac_gpio *gp; + mix = chip->mixer_data; + if (!mix) + return -ENODEV; + switch(kcontrol->private_value) { + case TUMBLER_MUTE_HP: + gp = &mix->hp_mute; break; + case TUMBLER_MUTE_AMP: + gp = &mix->amp_mute; break; + case TUMBLER_MUTE_LINE: + gp = &mix->line_mute; break; + default: + gp = NULL; + } + if (gp == NULL) + return -EINVAL; + ucontrol->value.integer.value[0] = !check_audio_gpio(gp); + return 0; +} + +static int tumbler_put_mute_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix; + struct pmac_gpio *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 + mix = chip->mixer_data; + if (!mix) + return -ENODEV; + switch(kcontrol->private_value) { + case TUMBLER_MUTE_HP: + gp = &mix->hp_mute; break; + case TUMBLER_MUTE_AMP: + gp = &mix->amp_mute; break; + case TUMBLER_MUTE_LINE: + gp = &mix->line_mute; break; + default: + gp = NULL; + } + if (gp == NULL) + return -EINVAL; + val = ! check_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(struct pmac_tumbler *mix) +{ + if (! mix->i2c.client) + return -ENODEV; + if (mix->capture_source) + mix->acs |= 2; + else + mix->acs &= ~2; + return i2c_smbus_write_byte_data(mix->i2c.client, TAS_REG_ACS, mix->acs); +} + +static int snapper_info_capture_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[2] = { + "Line", "Mic" + }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int snapper_get_capture_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix = chip->mixer_data; + + ucontrol->value.enumerated.item[0] = mix->capture_source; + return 0; +} + +static int snapper_put_capture_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix = chip->mixer_data; + int change; + + change = ucontrol->value.enumerated.item[0] != mix->capture_source; + if (change) { + mix->capture_source = !!ucontrol->value.enumerated.item[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 const struct snd_kcontrol_new tumbler_mixers[] = { + { .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 const struct snd_kcontrol_new snapper_mixers[] = { + { .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), + /* Alternative PCM is assigned to Mic analog loopback on iBook G4 */ + DEFINE_SNAPPER_MIX("Mic Playback Volume", 0, 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 const struct snd_kcontrol_new tumbler_hp_sw = { + .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 const struct snd_kcontrol_new tumbler_speaker_sw = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "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 const struct snd_kcontrol_new tumbler_lineout_sw = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line Out Playback Switch", + .info = snd_pmac_boolean_mono_info, + .get = tumbler_get_mute_switch, + .put = tumbler_put_mute_switch, + .private_value = TUMBLER_MUTE_LINE, +}; +static const struct snd_kcontrol_new tumbler_drc_sw = { + .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(struct snd_pmac *chip) +{ + struct pmac_tumbler *mix = chip->mixer_data; + int detect = 0; + + if (mix->hp_detect.addr) + detect |= read_audio_gpio(&mix->hp_detect); + return detect; +} + +static int tumbler_detect_lineout(struct snd_pmac *chip) +{ + struct pmac_tumbler *mix = chip->mixer_data; + int detect = 0; + + if (mix->line_detect.addr) + detect |= read_audio_gpio(&mix->line_detect); + return detect; +} + +static void check_mute(struct snd_pmac *chip, struct pmac_gpio *gp, int val, int do_notify, + struct snd_kcontrol *sw) +{ + if (check_audio_gpio(gp) != val) { + 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 struct snd_pmac *device_change_chip; + +static void device_change_handler(struct work_struct *work) +{ + struct snd_pmac *chip = device_change_chip; + struct pmac_tumbler *mix; + int headphone, lineout; + + if (!chip) + return; + + mix = chip->mixer_data; + if (snd_BUG_ON(!mix)) + return; + + headphone = tumbler_detect_headphone(chip); + lineout = tumbler_detect_lineout(chip); + + DBG("headphone: %d, lineout: %d\n", headphone, lineout); + + if (headphone || lineout) { + /* unmute headphone/lineout & mute speaker */ + if (headphone) + check_mute(chip, &mix->hp_mute, 0, mix->auto_mute_notify, + chip->master_sw_ctl); + if (lineout && mix->line_mute.addr != 0) + check_mute(chip, &mix->line_mute, 0, mix->auto_mute_notify, + chip->lineout_sw_ctl); + if (mix->anded_reset) + msleep(10); + check_mute(chip, &mix->amp_mute, !IS_G4DA, mix->auto_mute_notify, + chip->speaker_sw_ctl); + } else { + /* unmute speaker, mute others */ + check_mute(chip, &mix->amp_mute, 0, mix->auto_mute_notify, + chip->speaker_sw_ctl); + if (mix->anded_reset) + msleep(10); + check_mute(chip, &mix->hp_mute, 1, mix->auto_mute_notify, + chip->master_sw_ctl); + if (mix->line_mute.addr != 0) + check_mute(chip, &mix->line_mute, 1, mix->auto_mute_notify, + chip->lineout_sw_ctl); + } + if (mix->auto_mute_notify) + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->hp_detect_ctl->id); + +#ifdef CONFIG_SND_POWERMAC_AUTO_DRC + mix->drc_enable = ! (headphone || lineout); + if (mix->auto_mute_notify) + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->drc_sw_ctl->id); + if (chip->model == PMAC_TUMBLER) + tumbler_set_drc(mix); + else + snapper_set_drc(mix); +#endif + + /* reset the master volume so the correct amplification is applied */ + tumbler_set_master_volume(mix); +} + +static void tumbler_update_automute(struct snd_pmac *chip, int do_notify) +{ + if (chip->auto_mute) { + struct pmac_tumbler *mix; + mix = chip->mixer_data; + if (snd_BUG_ON(!mix)) + return; + mix->auto_mute_notify = do_notify; + schedule_work(&device_change); + } +} +#endif /* PMAC_SUPPORT_AUTOMUTE */ + + +/* interrupt - headphone plug changed */ +static irqreturn_t headphone_intr(int irq, void *devid) +{ + struct snd_pmac *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 *gpiop; + struct device_node *np; + + gpiop = of_find_node_by_name(NULL, "gpio"); + if (! gpiop) + return NULL; + + for_each_child_of_node(gpiop, np) { + const char *property = of_get_property(np, "audio-gpio", NULL); + if (property && strcmp(property, name) == 0) + break; + } + of_node_put(gpiop); + return np; +} + +/* look for audio-gpio device */ +static struct device_node *find_compatible_audio_device(const char *name) +{ + struct device_node *gpiop; + struct device_node *np; + + gpiop = of_find_node_by_name(NULL, "gpio"); + if (!gpiop) + return NULL; + + for_each_child_of_node(gpiop, np) { + if (of_device_is_compatible(np, name)) + break; + } + of_node_put(gpiop); + return np; +} + +/* find an audio device and get its address */ +static long tumbler_find_device(const char *device, const char *platform, + struct pmac_gpio *gp, int is_compatible) +{ + struct device_node *node; + const u32 *base; + u32 addr; + long ret; + + if (is_compatible) + node = find_compatible_audio_device(device); + else + node = find_audio_device(device); + if (! node) { + DBG("(W) cannot find audio device %s !\n", device); + return -ENODEV; + } + + base = of_get_property(node, "AAPL,address", NULL); + if (! base) { + base = of_get_property(node, "reg", NULL); + if (!base) { + DBG("(E) cannot find address for device %s !\n", device); + of_node_put(node); + return -ENODEV; + } + addr = *base; + if (addr < 0x50) + addr += 0x50; + } else + addr = *base; + + gp->addr = addr & 0x0000ffff; + /* Try to find the active state, default to 0 ! */ + base = of_get_property(node, "audio-gpio-active-state", NULL); + if (base) { + gp->active_state = *base; + gp->active_val = (*base) ? 0x5 : 0x4; + gp->inactive_val = (*base) ? 0x4 : 0x5; + } else { + const u32 *prop = NULL; + gp->active_state = IS_G4DA + && !strncmp(device, "keywest-gpio1", 13); + gp->active_val = 0x4; + gp->inactive_val = 0x5; + /* Here are some crude hacks to extract the GPIO polarity and + * open collector informations out of the do-platform script + * as we don't yet have an interpreter for these things + */ + if (platform) + prop = of_get_property(node, platform, NULL); + if (prop) { + if (prop[3] == 0x9 && prop[4] == 0x9) { + gp->active_val = 0xd; + gp->inactive_val = 0xc; + } + if (prop[3] == 0x1 && prop[4] == 0x1) { + gp->active_val = 0x5; + gp->inactive_val = 0x4; + } + } + } + + DBG("(I) GPIO device %s found, offset: %x, active state: %d !\n", + device, gp->addr, gp->active_state); + + ret = irq_of_parse_and_map(node, 0); + of_node_put(node); + return ret; +} + +/* reset audio */ +static void tumbler_reset_audio(struct snd_pmac *chip) +{ + struct pmac_tumbler *mix = chip->mixer_data; + + if (mix->anded_reset) { + DBG("(I) codec anded reset !\n"); + write_audio_gpio(&mix->hp_mute, 0); + write_audio_gpio(&mix->amp_mute, 0); + msleep(200); + write_audio_gpio(&mix->hp_mute, 1); + write_audio_gpio(&mix->amp_mute, 1); + msleep(100); + write_audio_gpio(&mix->hp_mute, 0); + write_audio_gpio(&mix->amp_mute, 0); + msleep(100); + } else { + DBG("(I) codec normal reset !\n"); + + write_audio_gpio(&mix->audio_reset, 0); + msleep(200); + write_audio_gpio(&mix->audio_reset, 1); + msleep(100); + write_audio_gpio(&mix->audio_reset, 0); + msleep(100); + } +} + +#ifdef CONFIG_PM +/* suspend mixer */ +static void tumbler_suspend(struct snd_pmac *chip) +{ + struct pmac_tumbler *mix = chip->mixer_data; + + if (mix->headphone_irq >= 0) + disable_irq(mix->headphone_irq); + if (mix->lineout_irq >= 0) + disable_irq(mix->lineout_irq); + mix->save_master_switch[0] = mix->master_switch[0]; + mix->save_master_switch[1] = mix->master_switch[1]; + mix->save_master_vol[0] = mix->master_vol[0]; + mix->save_master_vol[1] = mix->master_vol[1]; + mix->master_switch[0] = mix->master_switch[1] = 0; + tumbler_set_master_volume(mix); + if (!mix->anded_reset) { + write_audio_gpio(&mix->amp_mute, 1); + write_audio_gpio(&mix->hp_mute, 1); + } + if (chip->model == PMAC_SNAPPER) { + mix->acs |= 1; + i2c_smbus_write_byte_data(mix->i2c.client, TAS_REG_ACS, mix->acs); + } + if (mix->anded_reset) { + write_audio_gpio(&mix->amp_mute, 1); + write_audio_gpio(&mix->hp_mute, 1); + } else + write_audio_gpio(&mix->audio_reset, 1); +} + +/* resume mixer */ +static void tumbler_resume(struct snd_pmac *chip) +{ + struct pmac_tumbler *mix = chip->mixer_data; + + mix->acs &= ~1; + mix->master_switch[0] = mix->save_master_switch[0]; + mix->master_switch[1] = mix->save_master_switch[1]; + mix->master_vol[0] = mix->save_master_vol[0]; + mix->master_vol[1] = mix->save_master_vol[1]; + tumbler_reset_audio(chip); + if (mix->i2c.client && mix->i2c.init_client) { + if (mix->i2c.init_client(&mix->i2c) < 0) + dev_err(chip->card->dev, "tumbler_init_client error\n"); + } else + dev_err(chip->card->dev, "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); + if (mix->headphone_irq >= 0) { + unsigned char val; + + enable_irq(mix->headphone_irq); + /* activate headphone status interrupts */ + val = do_gpio_read(&mix->hp_detect); + do_gpio_write(&mix->hp_detect, val | 0x80); + } + if (mix->lineout_irq >= 0) + enable_irq(mix->lineout_irq); +} +#endif + +/* initialize tumbler */ +static int tumbler_init(struct snd_pmac *chip) +{ + int irq; + struct pmac_tumbler *mix = chip->mixer_data; + + if (tumbler_find_device("audio-hw-reset", + "platform-do-hw-reset", + &mix->audio_reset, 0) < 0) + tumbler_find_device("hw-reset", + "platform-do-hw-reset", + &mix->audio_reset, 1); + if (tumbler_find_device("amp-mute", + "platform-do-amp-mute", + &mix->amp_mute, 0) < 0) + tumbler_find_device("amp-mute", + "platform-do-amp-mute", + &mix->amp_mute, 1); + if (tumbler_find_device("headphone-mute", + "platform-do-headphone-mute", + &mix->hp_mute, 0) < 0) + tumbler_find_device("headphone-mute", + "platform-do-headphone-mute", + &mix->hp_mute, 1); + if (tumbler_find_device("line-output-mute", + "platform-do-lineout-mute", + &mix->line_mute, 0) < 0) + tumbler_find_device("line-output-mute", + "platform-do-lineout-mute", + &mix->line_mute, 1); + irq = tumbler_find_device("headphone-detect", + NULL, &mix->hp_detect, 0); + if (irq <= 0) + irq = tumbler_find_device("headphone-detect", + NULL, &mix->hp_detect, 1); + if (irq <= 0) + irq = tumbler_find_device("keywest-gpio15", + NULL, &mix->hp_detect, 1); + mix->headphone_irq = irq; + irq = tumbler_find_device("line-output-detect", + NULL, &mix->line_detect, 0); + if (irq <= 0) + irq = tumbler_find_device("line-output-detect", + NULL, &mix->line_detect, 1); + if (IS_G4DA && irq <= 0) + irq = tumbler_find_device("keywest-gpio16", + NULL, &mix->line_detect, 1); + mix->lineout_irq = irq; + + tumbler_reset_audio(chip); + + return 0; +} + +static void tumbler_cleanup(struct snd_pmac *chip) +{ + struct pmac_tumbler *mix = chip->mixer_data; + if (! mix) + return; + + if (mix->headphone_irq >= 0) + free_irq(mix->headphone_irq, chip); + if (mix->lineout_irq >= 0) + free_irq(mix->lineout_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 snd_pmac_tumbler_init(struct snd_pmac *chip) +{ + int i, err; + struct pmac_tumbler *mix; + const u32 *paddr; + struct device_node *tas_node, *np; + char *chipname; + + request_module("i2c-powermac"); + + mix = kzalloc_obj(*mix); + if (! mix) + return -ENOMEM; + mix->headphone_irq = -1; + + chip->mixer_data = mix; + chip->mixer_free = tumbler_cleanup; + mix->anded_reset = 0; + mix->reset_on_sleep = 1; + + for_each_child_of_node(chip->node, np) { + if (of_node_name_eq(np, "sound")) { + if (of_property_read_bool(np, "has-anded-reset")) + mix->anded_reset = 1; + if (of_property_present(np, "layout-id")) + mix->reset_on_sleep = 0; + of_node_put(np); + break; + } + } + err = tumbler_init(chip); + if (err < 0) + return err; + + /* set up TAS */ + tas_node = of_find_node_by_name(NULL, "deq"); + if (tas_node == NULL) + tas_node = of_find_node_by_name(NULL, "codec"); + if (tas_node == NULL) + return -ENODEV; + + paddr = of_get_property(tas_node, "i2c-address", NULL); + if (paddr == NULL) + paddr = of_get_property(tas_node, "reg", NULL); + if (paddr) + mix->i2c.addr = (*paddr) >> 1; + else + mix->i2c.addr = TAS_I2C_ADDR; + of_node_put(tas_node); + + DBG("(I) TAS i2c address is: %x\n", mix->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"; + } + + err = snd_pmac_keywest_init(&mix->i2c); + if (err < 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++) { + err = snd_ctl_add(chip->card, snd_ctl_new1(&tumbler_mixers[i], chip)); + if (err < 0) + return err; + } + } else { + for (i = 0; i < ARRAY_SIZE(snapper_mixers); i++) { + err = snd_ctl_add(chip->card, snd_ctl_new1(&snapper_mixers[i], chip)); + if (err < 0) + return err; + } + } + chip->master_sw_ctl = snd_ctl_new1(&tumbler_hp_sw, chip); + err = snd_ctl_add(chip->card, chip->master_sw_ctl); + if (err < 0) + return err; + chip->speaker_sw_ctl = snd_ctl_new1(&tumbler_speaker_sw, chip); + err = snd_ctl_add(chip->card, chip->speaker_sw_ctl); + if (err < 0) + return err; + if (mix->line_mute.addr != 0) { + chip->lineout_sw_ctl = snd_ctl_new1(&tumbler_lineout_sw, chip); + err = snd_ctl_add(chip->card, chip->lineout_sw_ctl); + if (err < 0) + return err; + } + chip->drc_sw_ctl = snd_ctl_new1(&tumbler_drc_sw, chip); + err = snd_ctl_add(chip->card, chip->drc_sw_ctl); + if (err < 0) + return err; + + /* set initial DRC range to 60% */ + if (chip->model == PMAC_TUMBLER) + mix->drc_range = (TAS3001_DRC_MAX * 6) / 10; + else + mix->drc_range = (TAS3004_DRC_MAX * 6) / 10; + mix->drc_enable = 1; /* will be changed later if AUTO_DRC is set */ + if (chip->model == PMAC_TUMBLER) + tumbler_set_drc(mix); + else + snapper_set_drc(mix); + +#ifdef CONFIG_PM + chip->suspend = tumbler_suspend; + chip->resume = tumbler_resume; +#endif + + INIT_WORK(&device_change, device_change_handler); + device_change_chip = chip; + +#ifdef PMAC_SUPPORT_AUTOMUTE + if (mix->headphone_irq >= 0 || mix->lineout_irq >= 0) { + err = snd_pmac_add_automute(chip); + if (err < 0) + return err; + } + chip->detect_headphone = tumbler_detect_headphone; + chip->update_automute = tumbler_update_automute; + tumbler_update_automute(chip, 0); /* update the status only */ + + /* activate headphone status interrupts */ + if (mix->headphone_irq >= 0) { + unsigned char val; + err = request_irq(mix->headphone_irq, headphone_intr, 0, + "Sound Headphone Detection", chip); + if (err < 0) + return 0; + /* activate headphone status interrupts */ + val = do_gpio_read(&mix->hp_detect); + do_gpio_write(&mix->hp_detect, val | 0x80); + } + if (mix->lineout_irq >= 0) { + unsigned char val; + err = request_irq(mix->lineout_irq, headphone_intr, 0, + "Sound Lineout Detection", chip); + if (err < 0) + return 0; + /* activate headphone status interrupts */ + val = do_gpio_read(&mix->line_detect); + do_gpio_write(&mix->line_detect, val | 0x80); + } +#endif + + return 0; +} diff --git a/sound/ppc/tumbler_volume.h b/sound/ppc/tumbler_volume.h new file mode 100644 index 000000000..16d548019 --- /dev/null +++ b/sound/ppc/tumbler_volume.h @@ -0,0 +1,251 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* volume tables, taken from TAS3001c data manual */ +/* volume gain values */ +/* 0 = -70 dB, 175 = 18.0 dB in 0.5 dB step */ +static const 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 const 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 const 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 const 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 const 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 const 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, +}; + |
