mirror_ubuntu-kernels/sound/pci/emu10k1/io.c
Oswald Buddenhagen e73b597e63 ALSA: emu10k1: query rate of external clock sources on E-MU cards
The value isn't used yet; the subsequent commits will do that.

This ignores the existence of rates above 48 kHz, which is fine, as the
hardware will just switch to the fallback clock source when fed with a
rate which is incompatible with the base clock multiplier, which
currently is always x1.

The sample rate display in /proc spdif-in is adjusted to reflect our
understanding of the input rates.

This is tested only with an 0404b card without sync card, so there is a
lot of room for improvement.

Signed-off-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>

Link: https://lore.kernel.org/r/20230612191325.1315854-4-oswald.buddenhagen@gmx.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2023-06-13 07:41:07 +02:00

726 lines
19 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Creative Labs, Inc.
* Routines for control of EMU10K1 chips
*
* BUGS:
* --
*
* TODO:
* --
*/
#include <linux/time.h>
#include <sound/core.h>
#include <sound/emu10k1.h>
#include <linux/delay.h>
#include <linux/export.h>
#include "p17v.h"
static inline bool check_ptr_reg(struct snd_emu10k1 *emu, unsigned int reg)
{
if (snd_BUG_ON(!emu))
return false;
if (snd_BUG_ON(reg & (emu->audigy ? (0xffff0000 & ~A_PTR_ADDRESS_MASK)
: (0xffff0000 & ~PTR_ADDRESS_MASK))))
return false;
if (snd_BUG_ON(reg & 0x0000ffff & ~PTR_CHANNELNUM_MASK))
return false;
return true;
}
unsigned int snd_emu10k1_ptr_read(struct snd_emu10k1 * emu, unsigned int reg, unsigned int chn)
{
unsigned long flags;
unsigned int regptr, val;
unsigned int mask;
regptr = (reg << 16) | chn;
if (!check_ptr_reg(emu, regptr))
return 0;
spin_lock_irqsave(&emu->emu_lock, flags);
outl(regptr, emu->port + PTR);
val = inl(emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
if (reg & 0xff000000) {
unsigned char size, offset;
size = (reg >> 24) & 0x3f;
offset = (reg >> 16) & 0x1f;
mask = (1 << size) - 1;
return (val >> offset) & mask;
} else {
return val;
}
}
EXPORT_SYMBOL(snd_emu10k1_ptr_read);
void snd_emu10k1_ptr_write(struct snd_emu10k1 *emu, unsigned int reg, unsigned int chn, unsigned int data)
{
unsigned int regptr;
unsigned long flags;
unsigned int mask;
regptr = (reg << 16) | chn;
if (!check_ptr_reg(emu, regptr))
return;
if (reg & 0xff000000) {
unsigned char size, offset;
size = (reg >> 24) & 0x3f;
offset = (reg >> 16) & 0x1f;
mask = (1 << size) - 1;
if (snd_BUG_ON(data & ~mask))
return;
mask <<= offset;
data <<= offset;
spin_lock_irqsave(&emu->emu_lock, flags);
outl(regptr, emu->port + PTR);
data |= inl(emu->port + DATA) & ~mask;
} else {
spin_lock_irqsave(&emu->emu_lock, flags);
outl(regptr, emu->port + PTR);
}
outl(data, emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
EXPORT_SYMBOL(snd_emu10k1_ptr_write);
void snd_emu10k1_ptr_write_multiple(struct snd_emu10k1 *emu, unsigned int chn, ...)
{
va_list va;
u32 addr_mask;
unsigned long flags;
if (snd_BUG_ON(!emu))
return;
if (snd_BUG_ON(chn & ~PTR_CHANNELNUM_MASK))
return;
addr_mask = ~((emu->audigy ? A_PTR_ADDRESS_MASK : PTR_ADDRESS_MASK) >> 16);
va_start(va, chn);
spin_lock_irqsave(&emu->emu_lock, flags);
for (;;) {
u32 data;
u32 reg = va_arg(va, u32);
if (reg == REGLIST_END)
break;
data = va_arg(va, u32);
if (snd_BUG_ON(reg & addr_mask)) // Only raw registers supported here
continue;
outl((reg << 16) | chn, emu->port + PTR);
outl(data, emu->port + DATA);
}
spin_unlock_irqrestore(&emu->emu_lock, flags);
va_end(va);
}
EXPORT_SYMBOL(snd_emu10k1_ptr_write_multiple);
unsigned int snd_emu10k1_ptr20_read(struct snd_emu10k1 * emu,
unsigned int reg,
unsigned int chn)
{
unsigned long flags;
unsigned int regptr, val;
regptr = (reg << 16) | chn;
spin_lock_irqsave(&emu->emu_lock, flags);
outl(regptr, emu->port + PTR2);
val = inl(emu->port + DATA2);
spin_unlock_irqrestore(&emu->emu_lock, flags);
return val;
}
void snd_emu10k1_ptr20_write(struct snd_emu10k1 *emu,
unsigned int reg,
unsigned int chn,
unsigned int data)
{
unsigned int regptr;
unsigned long flags;
regptr = (reg << 16) | chn;
spin_lock_irqsave(&emu->emu_lock, flags);
outl(regptr, emu->port + PTR2);
outl(data, emu->port + DATA2);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
int snd_emu10k1_spi_write(struct snd_emu10k1 * emu,
unsigned int data)
{
unsigned int reset, set;
unsigned int reg, tmp;
int n, result;
int err = 0;
/* This function is not re-entrant, so protect against it. */
spin_lock(&emu->spi_lock);
if (emu->card_capabilities->ca0108_chip)
reg = P17V_SPI;
else {
/* For other chip types the SPI register
* is currently unknown. */
err = 1;
goto spi_write_exit;
}
if (data > 0xffff) {
/* Only 16bit values allowed */
err = 1;
goto spi_write_exit;
}
tmp = snd_emu10k1_ptr20_read(emu, reg, 0);
reset = (tmp & ~0x3ffff) | 0x20000; /* Set xxx20000 */
set = reset | 0x10000; /* Set xxx1xxxx */
snd_emu10k1_ptr20_write(emu, reg, 0, reset | data);
tmp = snd_emu10k1_ptr20_read(emu, reg, 0); /* write post */
snd_emu10k1_ptr20_write(emu, reg, 0, set | data);
result = 1;
/* Wait for status bit to return to 0 */
for (n = 0; n < 100; n++) {
udelay(10);
tmp = snd_emu10k1_ptr20_read(emu, reg, 0);
if (!(tmp & 0x10000)) {
result = 0;
break;
}
}
if (result) {
/* Timed out */
err = 1;
goto spi_write_exit;
}
snd_emu10k1_ptr20_write(emu, reg, 0, reset | data);
tmp = snd_emu10k1_ptr20_read(emu, reg, 0); /* Write post */
err = 0;
spi_write_exit:
spin_unlock(&emu->spi_lock);
return err;
}
/* The ADC does not support i2c read, so only write is implemented */
int snd_emu10k1_i2c_write(struct snd_emu10k1 *emu,
u32 reg,
u32 value)
{
u32 tmp;
int timeout = 0;
int status;
int retry;
int err = 0;
if ((reg > 0x7f) || (value > 0x1ff)) {
dev_err(emu->card->dev, "i2c_write: invalid values.\n");
return -EINVAL;
}
/* This function is not re-entrant, so protect against it. */
spin_lock(&emu->i2c_lock);
tmp = reg << 25 | value << 16;
/* This controls the I2C connected to the WM8775 ADC Codec */
snd_emu10k1_ptr20_write(emu, P17V_I2C_1, 0, tmp);
tmp = snd_emu10k1_ptr20_read(emu, P17V_I2C_1, 0); /* write post */
for (retry = 0; retry < 10; retry++) {
/* Send the data to i2c */
tmp = 0;
tmp = tmp | (I2C_A_ADC_LAST|I2C_A_ADC_START|I2C_A_ADC_ADD);
snd_emu10k1_ptr20_write(emu, P17V_I2C_ADDR, 0, tmp);
/* Wait till the transaction ends */
while (1) {
mdelay(1);
status = snd_emu10k1_ptr20_read(emu, P17V_I2C_ADDR, 0);
timeout++;
if ((status & I2C_A_ADC_START) == 0)
break;
if (timeout > 1000) {
dev_warn(emu->card->dev,
"emu10k1:I2C:timeout status=0x%x\n",
status);
break;
}
}
//Read back and see if the transaction is successful
if ((status & I2C_A_ADC_ABORT) == 0)
break;
}
if (retry == 10) {
dev_err(emu->card->dev, "Writing to ADC failed!\n");
dev_err(emu->card->dev, "status=0x%x, reg=%d, value=%d\n",
status, reg, value);
/* dump_stack(); */
err = -EINVAL;
}
spin_unlock(&emu->i2c_lock);
return err;
}
static void snd_emu1010_fpga_write_locked(struct snd_emu10k1 *emu, u32 reg, u32 value)
{
if (snd_BUG_ON(reg > 0x3f))
return;
reg += 0x40; /* 0x40 upwards are registers. */
if (snd_BUG_ON(value > 0x3f)) /* 0 to 0x3f are values */
return;
outw(reg, emu->port + A_GPIO);
udelay(10);
outw(reg | 0x80, emu->port + A_GPIO); /* High bit clocks the value into the fpga. */
udelay(10);
outw(value, emu->port + A_GPIO);
udelay(10);
outw(value | 0x80 , emu->port + A_GPIO); /* High bit clocks the value into the fpga. */
}
void snd_emu1010_fpga_write(struct snd_emu10k1 *emu, u32 reg, u32 value)
{
unsigned long flags;
spin_lock_irqsave(&emu->emu_lock, flags);
snd_emu1010_fpga_write_locked(emu, reg, value);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
static void snd_emu1010_fpga_read_locked(struct snd_emu10k1 *emu, u32 reg, u32 *value)
{
// The higest input pin is used as the designated interrupt trigger,
// so it needs to be masked out.
u32 mask = emu->card_capabilities->ca0108_chip ? 0x1f : 0x7f;
if (snd_BUG_ON(reg > 0x3f))
return;
reg += 0x40; /* 0x40 upwards are registers. */
outw(reg, emu->port + A_GPIO);
udelay(10);
outw(reg | 0x80, emu->port + A_GPIO); /* High bit clocks the value into the fpga. */
udelay(10);
*value = ((inw(emu->port + A_GPIO) >> 8) & mask);
}
void snd_emu1010_fpga_read(struct snd_emu10k1 *emu, u32 reg, u32 *value)
{
unsigned long flags;
spin_lock_irqsave(&emu->emu_lock, flags);
snd_emu1010_fpga_read_locked(emu, reg, value);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
/* Each Destination has one and only one Source,
* but one Source can feed any number of Destinations simultaneously.
*/
void snd_emu1010_fpga_link_dst_src_write(struct snd_emu10k1 *emu, u32 dst, u32 src)
{
unsigned long flags;
if (snd_BUG_ON(dst & ~0x71f))
return;
if (snd_BUG_ON(src & ~0x71f))
return;
spin_lock_irqsave(&emu->emu_lock, flags);
snd_emu1010_fpga_write_locked(emu, EMU_HANA_DESTHI, dst >> 8);
snd_emu1010_fpga_write_locked(emu, EMU_HANA_DESTLO, dst & 0x1f);
snd_emu1010_fpga_write_locked(emu, EMU_HANA_SRCHI, src >> 8);
snd_emu1010_fpga_write_locked(emu, EMU_HANA_SRCLO, src & 0x1f);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
u32 snd_emu1010_fpga_link_dst_src_read(struct snd_emu10k1 *emu, u32 dst)
{
unsigned long flags;
u32 hi, lo;
if (snd_BUG_ON(dst & ~0x71f))
return 0;
spin_lock_irqsave(&emu->emu_lock, flags);
snd_emu1010_fpga_write_locked(emu, EMU_HANA_DESTHI, dst >> 8);
snd_emu1010_fpga_write_locked(emu, EMU_HANA_DESTLO, dst & 0x1f);
snd_emu1010_fpga_read_locked(emu, EMU_HANA_SRCHI, &hi);
snd_emu1010_fpga_read_locked(emu, EMU_HANA_SRCLO, &lo);
spin_unlock_irqrestore(&emu->emu_lock, flags);
return (hi << 8) | lo;
}
int snd_emu1010_get_raw_rate(struct snd_emu10k1 *emu, u8 src)
{
u32 reg_lo, reg_hi, value, value2;
switch (src) {
case EMU_HANA_WCLOCK_HANA_SPDIF_IN:
snd_emu1010_fpga_read(emu, EMU_HANA_SPDIF_MODE, &value);
if (value & EMU_HANA_SPDIF_MODE_RX_INVALID)
return 0;
reg_lo = EMU_HANA_WC_SPDIF_LO;
reg_hi = EMU_HANA_WC_SPDIF_HI;
break;
case EMU_HANA_WCLOCK_HANA_ADAT_IN:
reg_lo = EMU_HANA_WC_ADAT_LO;
reg_hi = EMU_HANA_WC_ADAT_HI;
break;
case EMU_HANA_WCLOCK_SYNC_BNC:
reg_lo = EMU_HANA_WC_BNC_LO;
reg_hi = EMU_HANA_WC_BNC_HI;
break;
case EMU_HANA_WCLOCK_2ND_HANA:
reg_lo = EMU_HANA2_WC_SPDIF_LO;
reg_hi = EMU_HANA2_WC_SPDIF_HI;
break;
default:
return 0;
}
snd_emu1010_fpga_read(emu, reg_hi, &value);
snd_emu1010_fpga_read(emu, reg_lo, &value2);
// FIXME: The /4 is valid for 0404b, but contradicts all other info.
return 0x1770000 / 4 / (((value << 5) | value2) + 1);
}
void snd_emu1010_update_clock(struct snd_emu10k1 *emu)
{
int clock;
u32 leds;
switch (emu->emu1010.wclock) {
case EMU_HANA_WCLOCK_INT_44_1K | EMU_HANA_WCLOCK_1X:
clock = 44100;
leds = EMU_HANA_DOCK_LEDS_2_44K;
break;
case EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_1X:
clock = 48000;
leds = EMU_HANA_DOCK_LEDS_2_48K;
break;
default:
clock = snd_emu1010_get_raw_rate(
emu, emu->emu1010.wclock & EMU_HANA_WCLOCK_SRC_MASK);
// The raw rate reading is rather coarse (it cannot accurately
// represent 44.1 kHz) and fluctuates slightly. Luckily, the
// clock comes from digital inputs, which use standardized rates.
// So we round to the closest standard rate and ignore discrepancies.
if (clock < 46000) {
clock = 44100;
leds = EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_44K;
} else {
clock = 48000;
leds = EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_48K;
}
break;
}
emu->emu1010.word_clock = clock;
// FIXME: this should probably represent the AND of all currently
// used sources' lock status. But we don't know how to get that ...
leds |= EMU_HANA_DOCK_LEDS_2_LOCK;
snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, leds);
}
void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb)
{
unsigned long flags;
unsigned int enable;
spin_lock_irqsave(&emu->emu_lock, flags);
enable = inl(emu->port + INTE) | intrenb;
outl(enable, emu->port + INTE);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
void snd_emu10k1_intr_disable(struct snd_emu10k1 *emu, unsigned int intrenb)
{
unsigned long flags;
unsigned int enable;
spin_lock_irqsave(&emu->emu_lock, flags);
enable = inl(emu->port + INTE) & ~intrenb;
outl(enable, emu->port + INTE);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
void snd_emu10k1_voice_intr_enable(struct snd_emu10k1 *emu, unsigned int voicenum)
{
unsigned long flags;
unsigned int val;
spin_lock_irqsave(&emu->emu_lock, flags);
if (voicenum >= 32) {
outl(CLIEH << 16, emu->port + PTR);
val = inl(emu->port + DATA);
val |= 1 << (voicenum - 32);
} else {
outl(CLIEL << 16, emu->port + PTR);
val = inl(emu->port + DATA);
val |= 1 << voicenum;
}
outl(val, emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
void snd_emu10k1_voice_intr_disable(struct snd_emu10k1 *emu, unsigned int voicenum)
{
unsigned long flags;
unsigned int val;
spin_lock_irqsave(&emu->emu_lock, flags);
if (voicenum >= 32) {
outl(CLIEH << 16, emu->port + PTR);
val = inl(emu->port + DATA);
val &= ~(1 << (voicenum - 32));
} else {
outl(CLIEL << 16, emu->port + PTR);
val = inl(emu->port + DATA);
val &= ~(1 << voicenum);
}
outl(val, emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
void snd_emu10k1_voice_intr_ack(struct snd_emu10k1 *emu, unsigned int voicenum)
{
unsigned long flags;
spin_lock_irqsave(&emu->emu_lock, flags);
if (voicenum >= 32) {
outl(CLIPH << 16, emu->port + PTR);
voicenum = 1 << (voicenum - 32);
} else {
outl(CLIPL << 16, emu->port + PTR);
voicenum = 1 << voicenum;
}
outl(voicenum, emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
void snd_emu10k1_voice_half_loop_intr_enable(struct snd_emu10k1 *emu, unsigned int voicenum)
{
unsigned long flags;
unsigned int val;
spin_lock_irqsave(&emu->emu_lock, flags);
if (voicenum >= 32) {
outl(HLIEH << 16, emu->port + PTR);
val = inl(emu->port + DATA);
val |= 1 << (voicenum - 32);
} else {
outl(HLIEL << 16, emu->port + PTR);
val = inl(emu->port + DATA);
val |= 1 << voicenum;
}
outl(val, emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
void snd_emu10k1_voice_half_loop_intr_disable(struct snd_emu10k1 *emu, unsigned int voicenum)
{
unsigned long flags;
unsigned int val;
spin_lock_irqsave(&emu->emu_lock, flags);
if (voicenum >= 32) {
outl(HLIEH << 16, emu->port + PTR);
val = inl(emu->port + DATA);
val &= ~(1 << (voicenum - 32));
} else {
outl(HLIEL << 16, emu->port + PTR);
val = inl(emu->port + DATA);
val &= ~(1 << voicenum);
}
outl(val, emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
void snd_emu10k1_voice_half_loop_intr_ack(struct snd_emu10k1 *emu, unsigned int voicenum)
{
unsigned long flags;
spin_lock_irqsave(&emu->emu_lock, flags);
if (voicenum >= 32) {
outl(HLIPH << 16, emu->port + PTR);
voicenum = 1 << (voicenum - 32);
} else {
outl(HLIPL << 16, emu->port + PTR);
voicenum = 1 << voicenum;
}
outl(voicenum, emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
#if 0
void snd_emu10k1_voice_set_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum)
{
unsigned long flags;
unsigned int sol;
spin_lock_irqsave(&emu->emu_lock, flags);
if (voicenum >= 32) {
outl(SOLEH << 16, emu->port + PTR);
sol = inl(emu->port + DATA);
sol |= 1 << (voicenum - 32);
} else {
outl(SOLEL << 16, emu->port + PTR);
sol = inl(emu->port + DATA);
sol |= 1 << voicenum;
}
outl(sol, emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
void snd_emu10k1_voice_clear_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum)
{
unsigned long flags;
unsigned int sol;
spin_lock_irqsave(&emu->emu_lock, flags);
if (voicenum >= 32) {
outl(SOLEH << 16, emu->port + PTR);
sol = inl(emu->port + DATA);
sol &= ~(1 << (voicenum - 32));
} else {
outl(SOLEL << 16, emu->port + PTR);
sol = inl(emu->port + DATA);
sol &= ~(1 << voicenum);
}
outl(sol, emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
#endif
void snd_emu10k1_voice_set_loop_stop_multiple(struct snd_emu10k1 *emu, u64 voices)
{
unsigned long flags;
spin_lock_irqsave(&emu->emu_lock, flags);
outl(SOLEL << 16, emu->port + PTR);
outl(inl(emu->port + DATA) | (u32)voices, emu->port + DATA);
outl(SOLEH << 16, emu->port + PTR);
outl(inl(emu->port + DATA) | (u32)(voices >> 32), emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
void snd_emu10k1_voice_clear_loop_stop_multiple(struct snd_emu10k1 *emu, u64 voices)
{
unsigned long flags;
spin_lock_irqsave(&emu->emu_lock, flags);
outl(SOLEL << 16, emu->port + PTR);
outl(inl(emu->port + DATA) & (u32)~voices, emu->port + DATA);
outl(SOLEH << 16, emu->port + PTR);
outl(inl(emu->port + DATA) & (u32)(~voices >> 32), emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
int snd_emu10k1_voice_clear_loop_stop_multiple_atomic(struct snd_emu10k1 *emu, u64 voices)
{
unsigned long flags;
u32 soll, solh;
int ret = -EIO;
spin_lock_irqsave(&emu->emu_lock, flags);
outl(SOLEL << 16, emu->port + PTR);
soll = inl(emu->port + DATA);
outl(SOLEH << 16, emu->port + PTR);
solh = inl(emu->port + DATA);
soll &= (u32)~voices;
solh &= (u32)(~voices >> 32);
for (int tries = 0; tries < 1000; tries++) {
const u32 quart = 1U << (REG_SIZE(WC_CURRENTCHANNEL) - 2);
// First we wait for the third quarter of the sample cycle ...
u32 wc = inl(emu->port + WC);
u32 cc = REG_VAL_GET(WC_CURRENTCHANNEL, wc);
if (cc >= quart * 2 && cc < quart * 3) {
// ... and release the low voices, while the high ones are serviced.
outl(SOLEL << 16, emu->port + PTR);
outl(soll, emu->port + DATA);
// Then we wait for the first quarter of the next sample cycle ...
for (; tries < 1000; tries++) {
cc = REG_VAL_GET(WC_CURRENTCHANNEL, inl(emu->port + WC));
if (cc < quart)
goto good;
// We will block for 10+ us with interrupts disabled. This is
// not nice at all, but necessary for reasonable reliability.
udelay(1);
}
break;
good:
// ... and release the high voices, while the low ones are serviced.
outl(SOLEH << 16, emu->port + PTR);
outl(solh, emu->port + DATA);
// Finally we verify that nothing interfered in fact.
if (REG_VAL_GET(WC_SAMPLECOUNTER, inl(emu->port + WC)) ==
((REG_VAL_GET(WC_SAMPLECOUNTER, wc) + 1) & REG_MASK0(WC_SAMPLECOUNTER))) {
ret = 0;
} else {
ret = -EAGAIN;
}
break;
}
// Don't block for too long
spin_unlock_irqrestore(&emu->emu_lock, flags);
udelay(1);
spin_lock_irqsave(&emu->emu_lock, flags);
}
spin_unlock_irqrestore(&emu->emu_lock, flags);
return ret;
}
void snd_emu10k1_wait(struct snd_emu10k1 *emu, unsigned int wait)
{
volatile unsigned count;
unsigned int newtime = 0, curtime;
curtime = inl(emu->port + WC) >> 6;
while (wait-- > 0) {
count = 0;
while (count++ < 16384) {
newtime = inl(emu->port + WC) >> 6;
if (newtime != curtime)
break;
}
if (count > 16384)
break;
curtime = newtime;
}
}
unsigned short snd_emu10k1_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
{
struct snd_emu10k1 *emu = ac97->private_data;
unsigned long flags;
unsigned short val;
spin_lock_irqsave(&emu->emu_lock, flags);
outb(reg, emu->port + AC97ADDRESS);
val = inw(emu->port + AC97DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
return val;
}
void snd_emu10k1_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short data)
{
struct snd_emu10k1 *emu = ac97->private_data;
unsigned long flags;
spin_lock_irqsave(&emu->emu_lock, flags);
outb(reg, emu->port + AC97ADDRESS);
outw(data, emu->port + AC97DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}