diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
commit | 76cb841cb886eef6b3bee341a2266c76578724ad (patch) | |
tree | f5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /sound/pci/ac97/ac97_pcm.c | |
parent | Initial commit. (diff) | |
download | linux-76cb841cb886eef6b3bee341a2266c76578724ad.tar.xz linux-76cb841cb886eef6b3bee341a2266c76578724ad.zip |
Adding upstream version 4.19.249.upstream/4.19.249upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sound/pci/ac97/ac97_pcm.c')
-rw-r--r-- | sound/pci/ac97/ac97_pcm.c | 752 |
1 files changed, 752 insertions, 0 deletions
diff --git a/sound/pci/ac97/ac97_pcm.c b/sound/pci/ac97/ac97_pcm.c new file mode 100644 index 000000000..d15297a68 --- /dev/null +++ b/sound/pci/ac97/ac97_pcm.c @@ -0,0 +1,752 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + * Universal interface for Audio Codec '97 + * + * For more details look to AC '97 component specification revision 2.2 + * by Intel Corporation (http://developer.intel.com) and to datasheets + * for specific codecs. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/export.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/control.h> +#include <sound/ac97_codec.h> +#include <sound/asoundef.h> +#include "ac97_id.h" +#include "ac97_local.h" + +/* + * PCM support + */ + +static unsigned char rate_reg_tables[2][4][9] = { +{ + /* standard rates */ + { + /* 3&4 front, 7&8 rear, 6&9 center/lfe */ + AC97_PCM_FRONT_DAC_RATE, /* slot 3 */ + AC97_PCM_FRONT_DAC_RATE, /* slot 4 */ + 0xff, /* slot 5 */ + AC97_PCM_LFE_DAC_RATE, /* slot 6 */ + AC97_PCM_SURR_DAC_RATE, /* slot 7 */ + AC97_PCM_SURR_DAC_RATE, /* slot 8 */ + AC97_PCM_LFE_DAC_RATE, /* slot 9 */ + 0xff, /* slot 10 */ + 0xff, /* slot 11 */ + }, + { + /* 7&8 front, 6&9 rear, 10&11 center/lfe */ + 0xff, /* slot 3 */ + 0xff, /* slot 4 */ + 0xff, /* slot 5 */ + AC97_PCM_SURR_DAC_RATE, /* slot 6 */ + AC97_PCM_FRONT_DAC_RATE, /* slot 7 */ + AC97_PCM_FRONT_DAC_RATE, /* slot 8 */ + AC97_PCM_SURR_DAC_RATE, /* slot 9 */ + AC97_PCM_LFE_DAC_RATE, /* slot 10 */ + AC97_PCM_LFE_DAC_RATE, /* slot 11 */ + }, + { + /* 6&9 front, 10&11 rear, 3&4 center/lfe */ + AC97_PCM_LFE_DAC_RATE, /* slot 3 */ + AC97_PCM_LFE_DAC_RATE, /* slot 4 */ + 0xff, /* slot 5 */ + AC97_PCM_FRONT_DAC_RATE, /* slot 6 */ + 0xff, /* slot 7 */ + 0xff, /* slot 8 */ + AC97_PCM_FRONT_DAC_RATE, /* slot 9 */ + AC97_PCM_SURR_DAC_RATE, /* slot 10 */ + AC97_PCM_SURR_DAC_RATE, /* slot 11 */ + }, + { + /* 10&11 front, 3&4 rear, 7&8 center/lfe */ + AC97_PCM_SURR_DAC_RATE, /* slot 3 */ + AC97_PCM_SURR_DAC_RATE, /* slot 4 */ + 0xff, /* slot 5 */ + 0xff, /* slot 6 */ + AC97_PCM_LFE_DAC_RATE, /* slot 7 */ + AC97_PCM_LFE_DAC_RATE, /* slot 8 */ + 0xff, /* slot 9 */ + AC97_PCM_FRONT_DAC_RATE, /* slot 10 */ + AC97_PCM_FRONT_DAC_RATE, /* slot 11 */ + }, +}, +{ + /* double rates */ + { + /* 3&4 front, 7&8 front (t+1) */ + AC97_PCM_FRONT_DAC_RATE, /* slot 3 */ + AC97_PCM_FRONT_DAC_RATE, /* slot 4 */ + 0xff, /* slot 5 */ + 0xff, /* slot 6 */ + AC97_PCM_FRONT_DAC_RATE, /* slot 7 */ + AC97_PCM_FRONT_DAC_RATE, /* slot 8 */ + 0xff, /* slot 9 */ + 0xff, /* slot 10 */ + 0xff, /* slot 11 */ + }, + { + /* not specified in the specification */ + 0xff, /* slot 3 */ + 0xff, /* slot 4 */ + 0xff, /* slot 5 */ + 0xff, /* slot 6 */ + 0xff, /* slot 7 */ + 0xff, /* slot 8 */ + 0xff, /* slot 9 */ + 0xff, /* slot 10 */ + 0xff, /* slot 11 */ + }, + { + 0xff, /* slot 3 */ + 0xff, /* slot 4 */ + 0xff, /* slot 5 */ + 0xff, /* slot 6 */ + 0xff, /* slot 7 */ + 0xff, /* slot 8 */ + 0xff, /* slot 9 */ + 0xff, /* slot 10 */ + 0xff, /* slot 11 */ + }, + { + 0xff, /* slot 3 */ + 0xff, /* slot 4 */ + 0xff, /* slot 5 */ + 0xff, /* slot 6 */ + 0xff, /* slot 7 */ + 0xff, /* slot 8 */ + 0xff, /* slot 9 */ + 0xff, /* slot 10 */ + 0xff, /* slot 11 */ + } +}}; + +/* FIXME: more various mappings for ADC? */ +static unsigned char rate_cregs[9] = { + AC97_PCM_LR_ADC_RATE, /* 3 */ + AC97_PCM_LR_ADC_RATE, /* 4 */ + 0xff, /* 5 */ + AC97_PCM_MIC_ADC_RATE, /* 6 */ + 0xff, /* 7 */ + 0xff, /* 8 */ + 0xff, /* 9 */ + 0xff, /* 10 */ + 0xff, /* 11 */ +}; + +static unsigned char get_slot_reg(struct ac97_pcm *pcm, unsigned short cidx, + unsigned short slot, int dbl) +{ + if (slot < 3) + return 0xff; + if (slot > 11) + return 0xff; + if (pcm->spdif) + return AC97_SPDIF; /* pseudo register */ + if (pcm->stream == SNDRV_PCM_STREAM_PLAYBACK) + return rate_reg_tables[dbl][pcm->r[dbl].rate_table[cidx]][slot - 3]; + else + return rate_cregs[slot - 3]; +} + +static int set_spdif_rate(struct snd_ac97 *ac97, unsigned short rate) +{ + unsigned short old, bits, reg, mask; + unsigned int sbits; + + if (! (ac97->ext_id & AC97_EI_SPDIF)) + return -ENODEV; + + /* TODO: double rate support */ + if (ac97->flags & AC97_CS_SPDIF) { + switch (rate) { + case 48000: bits = 0; break; + case 44100: bits = 1 << AC97_SC_SPSR_SHIFT; break; + default: /* invalid - disable output */ + snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0); + return -EINVAL; + } + reg = AC97_CSR_SPDIF; + mask = 1 << AC97_SC_SPSR_SHIFT; + } else { + if (ac97->id == AC97_ID_CM9739 && rate != 48000) { + snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0); + return -EINVAL; + } + switch (rate) { + case 44100: bits = AC97_SC_SPSR_44K; break; + case 48000: bits = AC97_SC_SPSR_48K; break; + case 32000: bits = AC97_SC_SPSR_32K; break; + default: /* invalid - disable output */ + snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0); + return -EINVAL; + } + reg = AC97_SPDIF; + mask = AC97_SC_SPSR_MASK; + } + + mutex_lock(&ac97->reg_mutex); + old = snd_ac97_read(ac97, reg) & mask; + if (old != bits) { + snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0); + snd_ac97_update_bits_nolock(ac97, reg, mask, bits); + /* update the internal spdif bits */ + sbits = ac97->spdif_status; + if (sbits & IEC958_AES0_PROFESSIONAL) { + sbits &= ~IEC958_AES0_PRO_FS; + switch (rate) { + case 44100: sbits |= IEC958_AES0_PRO_FS_44100; break; + case 48000: sbits |= IEC958_AES0_PRO_FS_48000; break; + case 32000: sbits |= IEC958_AES0_PRO_FS_32000; break; + } + } else { + sbits &= ~(IEC958_AES3_CON_FS << 24); + switch (rate) { + case 44100: sbits |= IEC958_AES3_CON_FS_44100<<24; break; + case 48000: sbits |= IEC958_AES3_CON_FS_48000<<24; break; + case 32000: sbits |= IEC958_AES3_CON_FS_32000<<24; break; + } + } + ac97->spdif_status = sbits; + } + snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, AC97_EA_SPDIF); + mutex_unlock(&ac97->reg_mutex); + return 0; +} + +/** + * snd_ac97_set_rate - change the rate of the given input/output. + * @ac97: the ac97 instance + * @reg: the register to change + * @rate: the sample rate to set + * + * Changes the rate of the given input/output on the codec. + * If the codec doesn't support VAR, the rate must be 48000 (except + * for SPDIF). + * + * The valid registers are AC97_PMC_MIC_ADC_RATE, + * AC97_PCM_FRONT_DAC_RATE, AC97_PCM_LR_ADC_RATE. + * AC97_PCM_SURR_DAC_RATE and AC97_PCM_LFE_DAC_RATE are accepted + * if the codec supports them. + * AC97_SPDIF is accepted as a pseudo register to modify the SPDIF + * status bits. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_ac97_set_rate(struct snd_ac97 *ac97, int reg, unsigned int rate) +{ + int dbl; + unsigned int tmp; + + dbl = rate > 48000; + if (dbl) { + if (!(ac97->flags & AC97_DOUBLE_RATE)) + return -EINVAL; + if (reg != AC97_PCM_FRONT_DAC_RATE) + return -EINVAL; + } + + snd_ac97_update_power(ac97, reg, 1); + switch (reg) { + case AC97_PCM_MIC_ADC_RATE: + if ((ac97->regs[AC97_EXTENDED_STATUS] & AC97_EA_VRM) == 0) /* MIC VRA */ + if (rate != 48000) + return -EINVAL; + break; + case AC97_PCM_FRONT_DAC_RATE: + case AC97_PCM_LR_ADC_RATE: + if ((ac97->regs[AC97_EXTENDED_STATUS] & AC97_EA_VRA) == 0) /* VRA */ + if (rate != 48000 && rate != 96000) + return -EINVAL; + break; + case AC97_PCM_SURR_DAC_RATE: + if (! (ac97->scaps & AC97_SCAP_SURROUND_DAC)) + return -EINVAL; + break; + case AC97_PCM_LFE_DAC_RATE: + if (! (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC)) + return -EINVAL; + break; + case AC97_SPDIF: + /* special case */ + return set_spdif_rate(ac97, rate); + default: + return -EINVAL; + } + if (dbl) + rate /= 2; + tmp = (rate * ac97->bus->clock) / 48000; + if (tmp > 65535) + return -EINVAL; + if ((ac97->ext_id & AC97_EI_DRA) && reg == AC97_PCM_FRONT_DAC_RATE) + snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, + AC97_EA_DRA, dbl ? AC97_EA_DRA : 0); + snd_ac97_update(ac97, reg, tmp & 0xffff); + snd_ac97_read(ac97, reg); + if ((ac97->ext_id & AC97_EI_DRA) && reg == AC97_PCM_FRONT_DAC_RATE) { + /* Intel controllers require double rate data to be put in + * slots 7+8 + */ + snd_ac97_update_bits(ac97, AC97_GENERAL_PURPOSE, + AC97_GP_DRSS_MASK, + dbl ? AC97_GP_DRSS_78 : 0); + snd_ac97_read(ac97, AC97_GENERAL_PURPOSE); + } + return 0; +} + +EXPORT_SYMBOL(snd_ac97_set_rate); + +static unsigned short get_pslots(struct snd_ac97 *ac97, unsigned char *rate_table, unsigned short *spdif_slots) +{ + if (!ac97_is_audio(ac97)) + return 0; + if (ac97_is_rev22(ac97) || ac97_can_amap(ac97)) { + unsigned short slots = 0; + if (ac97_is_rev22(ac97)) { + /* Note: it's simply emulation of AMAP behaviour */ + u16 es; + es = ac97->regs[AC97_EXTENDED_ID] &= ~AC97_EI_DACS_SLOT_MASK; + switch (ac97->addr) { + case 1: + case 2: es |= (1<<AC97_EI_DACS_SLOT_SHIFT); break; + case 3: es |= (2<<AC97_EI_DACS_SLOT_SHIFT); break; + } + snd_ac97_write_cache(ac97, AC97_EXTENDED_ID, es); + } + switch (ac97->addr) { + case 0: + slots |= (1<<AC97_SLOT_PCM_LEFT)|(1<<AC97_SLOT_PCM_RIGHT); + if (ac97->scaps & AC97_SCAP_SURROUND_DAC) + slots |= (1<<AC97_SLOT_PCM_SLEFT)|(1<<AC97_SLOT_PCM_SRIGHT); + if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC) + slots |= (1<<AC97_SLOT_PCM_CENTER)|(1<<AC97_SLOT_LFE); + if (ac97->ext_id & AC97_EI_SPDIF) { + if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC)) + *spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT)|(1<<AC97_SLOT_SPDIF_RIGHT); + else if (!(ac97->scaps & AC97_SCAP_CENTER_LFE_DAC)) + *spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT1)|(1<<AC97_SLOT_SPDIF_RIGHT1); + else + *spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT2)|(1<<AC97_SLOT_SPDIF_RIGHT2); + } + *rate_table = 0; + break; + case 1: + case 2: + slots |= (1<<AC97_SLOT_PCM_SLEFT)|(1<<AC97_SLOT_PCM_SRIGHT); + if (ac97->scaps & AC97_SCAP_SURROUND_DAC) + slots |= (1<<AC97_SLOT_PCM_CENTER)|(1<<AC97_SLOT_LFE); + if (ac97->ext_id & AC97_EI_SPDIF) { + if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC)) + *spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT1)|(1<<AC97_SLOT_SPDIF_RIGHT1); + else + *spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT2)|(1<<AC97_SLOT_SPDIF_RIGHT2); + } + *rate_table = 1; + break; + case 3: + slots |= (1<<AC97_SLOT_PCM_CENTER)|(1<<AC97_SLOT_LFE); + if (ac97->ext_id & AC97_EI_SPDIF) + *spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT2)|(1<<AC97_SLOT_SPDIF_RIGHT2); + *rate_table = 2; + break; + } + return slots; + } else { + unsigned short slots; + slots = (1<<AC97_SLOT_PCM_LEFT)|(1<<AC97_SLOT_PCM_RIGHT); + if (ac97->scaps & AC97_SCAP_SURROUND_DAC) + slots |= (1<<AC97_SLOT_PCM_SLEFT)|(1<<AC97_SLOT_PCM_SRIGHT); + if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC) + slots |= (1<<AC97_SLOT_PCM_CENTER)|(1<<AC97_SLOT_LFE); + if (ac97->ext_id & AC97_EI_SPDIF) { + if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC)) + *spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT)|(1<<AC97_SLOT_SPDIF_RIGHT); + else if (!(ac97->scaps & AC97_SCAP_CENTER_LFE_DAC)) + *spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT1)|(1<<AC97_SLOT_SPDIF_RIGHT1); + else + *spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT2)|(1<<AC97_SLOT_SPDIF_RIGHT2); + } + *rate_table = 0; + return slots; + } +} + +static unsigned short get_cslots(struct snd_ac97 *ac97) +{ + unsigned short slots; + + if (!ac97_is_audio(ac97)) + return 0; + slots = (1<<AC97_SLOT_PCM_LEFT)|(1<<AC97_SLOT_PCM_RIGHT); + slots |= (1<<AC97_SLOT_MIC); + return slots; +} + +static unsigned int get_rates(struct ac97_pcm *pcm, unsigned int cidx, unsigned short slots, int dbl) +{ + int i, idx; + unsigned int rates = ~0; + unsigned char reg; + + for (i = 3; i < 12; i++) { + if (!(slots & (1 << i))) + continue; + reg = get_slot_reg(pcm, cidx, i, dbl); + switch (reg) { + case AC97_PCM_FRONT_DAC_RATE: idx = AC97_RATES_FRONT_DAC; break; + case AC97_PCM_SURR_DAC_RATE: idx = AC97_RATES_SURR_DAC; break; + case AC97_PCM_LFE_DAC_RATE: idx = AC97_RATES_LFE_DAC; break; + case AC97_PCM_LR_ADC_RATE: idx = AC97_RATES_ADC; break; + case AC97_PCM_MIC_ADC_RATE: idx = AC97_RATES_MIC_ADC; break; + default: idx = AC97_RATES_SPDIF; break; + } + rates &= pcm->r[dbl].codec[cidx]->rates[idx]; + } + if (!dbl) + rates &= ~(SNDRV_PCM_RATE_64000 | SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000); + return rates; +} + +/** + * snd_ac97_pcm_assign - assign AC97 slots to given PCM streams + * @bus: the ac97 bus instance + * @pcms_count: count of PCMs to be assigned + * @pcms: PCMs to be assigned + * + * It assigns available AC97 slots for given PCMs. If none or only + * some slots are available, pcm->xxx.slots and pcm->xxx.rslots[] members + * are reduced and might be zero. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_ac97_pcm_assign(struct snd_ac97_bus *bus, + unsigned short pcms_count, + const struct ac97_pcm *pcms) +{ + int i, j, k; + const struct ac97_pcm *pcm; + struct ac97_pcm *rpcms, *rpcm; + unsigned short avail_slots[2][4]; + unsigned char rate_table[2][4]; + unsigned short tmp, slots; + unsigned short spdif_slots[4]; + unsigned int rates; + struct snd_ac97 *codec; + + rpcms = kcalloc(pcms_count, sizeof(struct ac97_pcm), GFP_KERNEL); + if (rpcms == NULL) + return -ENOMEM; + memset(avail_slots, 0, sizeof(avail_slots)); + memset(rate_table, 0, sizeof(rate_table)); + memset(spdif_slots, 0, sizeof(spdif_slots)); + for (i = 0; i < 4; i++) { + codec = bus->codec[i]; + if (!codec) + continue; + avail_slots[0][i] = get_pslots(codec, &rate_table[0][i], &spdif_slots[i]); + avail_slots[1][i] = get_cslots(codec); + if (!(codec->scaps & AC97_SCAP_INDEP_SDIN)) { + for (j = 0; j < i; j++) { + if (bus->codec[j]) + avail_slots[1][i] &= ~avail_slots[1][j]; + } + } + } + /* first step - exclusive devices */ + for (i = 0; i < pcms_count; i++) { + pcm = &pcms[i]; + rpcm = &rpcms[i]; + /* low-level driver thinks that it's more clever */ + if (pcm->copy_flag) { + *rpcm = *pcm; + continue; + } + rpcm->stream = pcm->stream; + rpcm->exclusive = pcm->exclusive; + rpcm->spdif = pcm->spdif; + rpcm->private_value = pcm->private_value; + rpcm->bus = bus; + rpcm->rates = ~0; + slots = pcm->r[0].slots; + for (j = 0; j < 4 && slots; j++) { + if (!bus->codec[j]) + continue; + rates = ~0; + if (pcm->spdif && pcm->stream == 0) + tmp = spdif_slots[j]; + else + tmp = avail_slots[pcm->stream][j]; + if (pcm->exclusive) { + /* exclusive access */ + tmp &= slots; + for (k = 0; k < i; k++) { + if (rpcm->stream == rpcms[k].stream) + tmp &= ~rpcms[k].r[0].rslots[j]; + } + } else { + /* non-exclusive access */ + tmp &= pcm->r[0].slots; + } + if (tmp) { + rpcm->r[0].rslots[j] = tmp; + rpcm->r[0].codec[j] = bus->codec[j]; + rpcm->r[0].rate_table[j] = rate_table[pcm->stream][j]; + if (bus->no_vra) + rates = SNDRV_PCM_RATE_48000; + else + rates = get_rates(rpcm, j, tmp, 0); + if (pcm->exclusive) + avail_slots[pcm->stream][j] &= ~tmp; + } + slots &= ~tmp; + rpcm->r[0].slots |= tmp; + rpcm->rates &= rates; + } + /* for double rate, we check the first codec only */ + if (pcm->stream == SNDRV_PCM_STREAM_PLAYBACK && + bus->codec[0] && (bus->codec[0]->flags & AC97_DOUBLE_RATE) && + rate_table[pcm->stream][0] == 0) { + tmp = (1<<AC97_SLOT_PCM_LEFT) | (1<<AC97_SLOT_PCM_RIGHT) | + (1<<AC97_SLOT_PCM_LEFT_0) | (1<<AC97_SLOT_PCM_RIGHT_0); + if ((tmp & pcm->r[1].slots) == tmp) { + rpcm->r[1].slots = tmp; + rpcm->r[1].rslots[0] = tmp; + rpcm->r[1].rate_table[0] = 0; + rpcm->r[1].codec[0] = bus->codec[0]; + if (pcm->exclusive) + avail_slots[pcm->stream][0] &= ~tmp; + if (bus->no_vra) + rates = SNDRV_PCM_RATE_96000; + else + rates = get_rates(rpcm, 0, tmp, 1); + rpcm->rates |= rates; + } + } + if (rpcm->rates == ~0) + rpcm->rates = 0; /* not used */ + } + bus->pcms_count = pcms_count; + bus->pcms = rpcms; + return 0; +} + +EXPORT_SYMBOL(snd_ac97_pcm_assign); + +/** + * snd_ac97_pcm_open - opens the given AC97 pcm + * @pcm: the ac97 pcm instance + * @rate: rate in Hz, if codec does not support VRA, this value must be 48000Hz + * @cfg: output stream characteristics + * @slots: a subset of allocated slots (snd_ac97_pcm_assign) for this pcm + * + * It locks the specified slots and sets the given rate to AC97 registers. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_ac97_pcm_open(struct ac97_pcm *pcm, unsigned int rate, + enum ac97_pcm_cfg cfg, unsigned short slots) +{ + struct snd_ac97_bus *bus; + int i, cidx, r, ok_flag; + unsigned int reg_ok[4] = {0,0,0,0}; + unsigned char reg; + int err = 0; + + r = rate > 48000; + bus = pcm->bus; + if (cfg == AC97_PCM_CFG_SPDIF) { + for (cidx = 0; cidx < 4; cidx++) + if (bus->codec[cidx] && (bus->codec[cidx]->ext_id & AC97_EI_SPDIF)) { + err = set_spdif_rate(bus->codec[cidx], rate); + if (err < 0) + return err; + } + } + spin_lock_irq(&pcm->bus->bus_lock); + for (i = 3; i < 12; i++) { + if (!(slots & (1 << i))) + continue; + ok_flag = 0; + for (cidx = 0; cidx < 4; cidx++) { + if (bus->used_slots[pcm->stream][cidx] & (1 << i)) { + spin_unlock_irq(&pcm->bus->bus_lock); + err = -EBUSY; + goto error; + } + if (pcm->r[r].rslots[cidx] & (1 << i)) { + bus->used_slots[pcm->stream][cidx] |= (1 << i); + ok_flag++; + } + } + if (!ok_flag) { + spin_unlock_irq(&pcm->bus->bus_lock); + dev_err(bus->card->dev, + "cannot find configuration for AC97 slot %i\n", + i); + err = -EAGAIN; + goto error; + } + } + pcm->cur_dbl = r; + spin_unlock_irq(&pcm->bus->bus_lock); + for (i = 3; i < 12; i++) { + if (!(slots & (1 << i))) + continue; + for (cidx = 0; cidx < 4; cidx++) { + if (pcm->r[r].rslots[cidx] & (1 << i)) { + reg = get_slot_reg(pcm, cidx, i, r); + if (reg == 0xff) { + dev_err(bus->card->dev, + "invalid AC97 slot %i?\n", i); + continue; + } + if (reg_ok[cidx] & (1 << (reg - AC97_PCM_FRONT_DAC_RATE))) + continue; + dev_dbg(bus->card->dev, + "setting ac97 reg 0x%x to rate %d\n", + reg, rate); + err = snd_ac97_set_rate(pcm->r[r].codec[cidx], reg, rate); + if (err < 0) + dev_err(bus->card->dev, + "error in snd_ac97_set_rate: cidx=%d, reg=0x%x, rate=%d, err=%d\n", + cidx, reg, rate, err); + else + reg_ok[cidx] |= (1 << (reg - AC97_PCM_FRONT_DAC_RATE)); + } + } + } + pcm->aslots = slots; + return 0; + + error: + pcm->aslots = slots; + snd_ac97_pcm_close(pcm); + return err; +} + +EXPORT_SYMBOL(snd_ac97_pcm_open); + +/** + * snd_ac97_pcm_close - closes the given AC97 pcm + * @pcm: the ac97 pcm instance + * + * It frees the locked AC97 slots. + * + * Return: Zero. + */ +int snd_ac97_pcm_close(struct ac97_pcm *pcm) +{ + struct snd_ac97_bus *bus; + unsigned short slots = pcm->aslots; + int i, cidx; + +#ifdef CONFIG_SND_AC97_POWER_SAVE + int r = pcm->cur_dbl; + for (i = 3; i < 12; i++) { + if (!(slots & (1 << i))) + continue; + for (cidx = 0; cidx < 4; cidx++) { + if (pcm->r[r].rslots[cidx] & (1 << i)) { + int reg = get_slot_reg(pcm, cidx, i, r); + snd_ac97_update_power(pcm->r[r].codec[cidx], + reg, 0); + } + } + } +#endif + + bus = pcm->bus; + spin_lock_irq(&pcm->bus->bus_lock); + for (i = 3; i < 12; i++) { + if (!(slots & (1 << i))) + continue; + for (cidx = 0; cidx < 4; cidx++) + bus->used_slots[pcm->stream][cidx] &= ~(1 << i); + } + pcm->aslots = 0; + pcm->cur_dbl = 0; + spin_unlock_irq(&pcm->bus->bus_lock); + return 0; +} + +EXPORT_SYMBOL(snd_ac97_pcm_close); + +static int double_rate_hw_constraint_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + if (channels->min > 2) { + static const struct snd_interval single_rates = { + .min = 1, + .max = 48000, + }; + struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + return snd_interval_refine(rate, &single_rates); + } + return 0; +} + +static int double_rate_hw_constraint_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + if (rate->min > 48000) { + static const struct snd_interval double_rate_channels = { + .min = 2, + .max = 2, + }; + struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + return snd_interval_refine(channels, &double_rate_channels); + } + return 0; +} + +/** + * snd_ac97_pcm_double_rate_rules - set double rate constraints + * @runtime: the runtime of the ac97 front playback pcm + * + * Installs the hardware constraint rules to prevent using double rates and + * more than two channels at the same time. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_ac97_pcm_double_rate_rules(struct snd_pcm_runtime *runtime) +{ + int err; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + double_rate_hw_constraint_rate, NULL, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + double_rate_hw_constraint_channels, NULL, + SNDRV_PCM_HW_PARAM_RATE, -1); + return err; +} + +EXPORT_SYMBOL(snd_ac97_pcm_double_rate_rules); |