root/sound/pci/emu10k1/io.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
 *                   Lee Revell <rlrevell@joe-job.com>
 *                   James Courtier-Dutton <James@superbug.co.uk>
 *                   Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
 *                   Creative Labs, Inc.
 *
 *  Routines for control of EMU10K1 chips
 */

#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 int regptr, val;
        unsigned int mask;

        regptr = (reg << 16) | chn;
        if (!check_ptr_reg(emu, regptr))
                return 0;

        scoped_guard(spinlock_irqsave, &emu->emu_lock) {
                outl(regptr, emu->port + PTR);
                val = inl(emu->port + DATA);
        }

        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 int mask;

        regptr = (reg << 16) | chn;
        if (!check_ptr_reg(emu, regptr))
                return;

        guard(spinlock_irqsave)(&emu->emu_lock);
        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;

                outl(regptr, emu->port + PTR);
                data |= inl(emu->port + DATA) & ~mask;
        } else {
                outl(regptr, emu->port + PTR);
        }
        outl(data, emu->port + DATA);
}

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;

        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);
        guard(spinlock_irqsave)(&emu->emu_lock);
        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);
        }
        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 int regptr;
  
        regptr = (reg << 16) | chn;

        guard(spinlock_irqsave)(&emu->emu_lock);
        outl(regptr, emu->port + PTR2);
        return inl(emu->port + DATA2);
}

void snd_emu10k1_ptr20_write(struct snd_emu10k1 *emu, 
                                   unsigned int reg, 
                                   unsigned int chn, 
                                   unsigned int data)
{
        unsigned int regptr;

        regptr = (reg << 16) | chn;

        guard(spinlock_irqsave)(&emu->emu_lock);
        outl(regptr, emu->port + PTR2);
        outl(data, emu->port + DATA2);
}

int snd_emu10k1_spi_write(struct snd_emu10k1 * emu,
                                   unsigned int data)
{
        unsigned int reset, set;
        unsigned int reg, tmp;
        int n, result;

        /* This function is not re-entrant, so protect against it. */
        guard(spinlock)(&emu->spi_lock);
        if (emu->card_capabilities->ca0108_chip)
                reg = P17V_SPI;
        else {
                /* For other chip types the SPI register
                 * is currently unknown. */
                return 1;
        }
        if (data > 0xffff) {
                /* Only 16bit values allowed */
                return 1;
        }

        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 */
                return 1;
        }
        snd_emu10k1_ptr20_write(emu, reg, 0, reset | data);
        tmp = snd_emu10k1_ptr20_read(emu, reg, 0); /* Write post */
        return 0;
}

/* 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;

        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. */
        guard(spinlock)(&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(); */
                return -EINVAL;
        }
    
        return 0;
}

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. */
        udelay(10);
}

void snd_emu1010_fpga_write(struct snd_emu10k1 *emu, u32 reg, u32 value)
{
        if (snd_BUG_ON(!mutex_is_locked(&emu->emu1010.lock)))
                return;
        snd_emu1010_fpga_write_locked(emu, reg, value);
}

void snd_emu1010_fpga_write_lock(struct snd_emu10k1 *emu, u32 reg, u32 value)
{
        guard(snd_emu1010_fpga_lock)(emu);
        snd_emu1010_fpga_write_locked(emu, reg, value);
}

void snd_emu1010_fpga_read(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.
        // But note that any other input pin change will also cause an IRQ,
        // so using this function often causes an IRQ as a side effect.
        u32 mask = emu->card_capabilities->ca0108_chip ? 0x1f : 0x7f;

        if (snd_BUG_ON(!mutex_is_locked(&emu->emu1010.lock)))
                return;
        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);
}

/* 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)
{
        if (snd_BUG_ON(dst & ~0x71f))
                return;
        if (snd_BUG_ON(src & ~0x71f))
                return;
        snd_emu1010_fpga_write(emu, EMU_HANA_DESTHI, dst >> 8);
        snd_emu1010_fpga_write(emu, EMU_HANA_DESTLO, dst & 0x1f);
        snd_emu1010_fpga_write(emu, EMU_HANA_SRCHI, src >> 8);
        snd_emu1010_fpga_write(emu, EMU_HANA_SRCLO, src & 0x1f);
}

u32 snd_emu1010_fpga_link_dst_src_read(struct snd_emu10k1 *emu, u32 dst)
{
        u32 hi, lo;

        if (snd_BUG_ON(dst & ~0x71f))
                return 0;
        snd_emu1010_fpga_write(emu, EMU_HANA_DESTHI, dst >> 8);
        snd_emu1010_fpga_write(emu, EMU_HANA_DESTLO, dst & 0x1f);
        snd_emu1010_fpga_read(emu, EMU_HANA_SRCHI, &hi);
        snd_emu1010_fpga_read(emu, EMU_HANA_SRCLO, &lo);
        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_emu1010_load_firmware_entry(struct snd_emu10k1 *emu, int dock,
                                     const struct firmware *fw_entry)
{
        __always_unused u16 write_post;

        // On E-MU 1010 rev1 the FPGA is a Xilinx Spartan IIE XC2S50E.
        // On E-MU 0404b it is a Xilinx Spartan III XC3S50.
        // The wiring is as follows:
        // GPO7 -> FPGA input & 1K resistor -> FPGA /PGMN <- FPGA output
        //   In normal operation, the active low reset line is held up by
        //   an FPGA output, while the GPO pin performs its duty as control
        //   register access strobe signal. Writing the respective bit to
        //   EMU_HANA_FPGA_CONFIG puts the FPGA output into high-Z mode, at
        //   which point the GPO pin can control the reset line through the
        //   resistor.
        // GPO6 -> FPGA CCLK & FPGA input
        // GPO5 -> FPGA DIN (dual function)

        // If the FPGA is already programmed, return it to programming mode
        snd_emu1010_fpga_write(emu, EMU_HANA_FPGA_CONFIG,
                               dock ? EMU_HANA_FPGA_CONFIG_AUDIODOCK :
                                      EMU_HANA_FPGA_CONFIG_HANA);

        // Assert reset line for 100uS
        outw(0x00, emu->port + A_GPIO);
        write_post = inw(emu->port + A_GPIO);
        udelay(100);
        outw(0x80, emu->port + A_GPIO);
        write_post = inw(emu->port + A_GPIO);
        udelay(100);  // Allow FPGA memory to clean

        // Upload the netlist. Keep reset line high!
        for (int n = 0; n < fw_entry->size; n++) {
                u8 value = fw_entry->data[n];
                for (int i = 0; i < 8; i++) {
                        u16 reg = 0x80;
                        if (value & 1)
                                reg |= 0x20;
                        value >>= 1;
                        outw(reg, emu->port + A_GPIO);
                        write_post = inw(emu->port + A_GPIO);
                        outw(reg | 0x40, emu->port + A_GPIO);
                        write_post = inw(emu->port + A_GPIO);
                }
        }

        // After programming, set GPIO bit 4 high again.
        // This appears to be a config word that the rev1 Hana
        // firmware reads; weird things happen without this.
        outw(0x10, emu->port + A_GPIO);
        write_post = inw(emu->port + A_GPIO);
}

void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb)
{
        unsigned int enable;

        guard(spinlock_irqsave)(&emu->emu_lock);
        enable = inl(emu->port + INTE) | intrenb;
        outl(enable, emu->port + INTE);
}

void snd_emu10k1_intr_disable(struct snd_emu10k1 *emu, unsigned int intrenb)
{
        unsigned int enable;

        guard(spinlock_irqsave)(&emu->emu_lock);
        enable = inl(emu->port + INTE) & ~intrenb;
        outl(enable, emu->port + INTE);
}

void snd_emu10k1_voice_intr_enable(struct snd_emu10k1 *emu, unsigned int voicenum)
{
        unsigned int val;

        guard(spinlock_irqsave)(&emu->emu_lock);
        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);
}

void snd_emu10k1_voice_intr_disable(struct snd_emu10k1 *emu, unsigned int voicenum)
{
        unsigned int val;

        guard(spinlock_irqsave)(&emu->emu_lock);
        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);
}

void snd_emu10k1_voice_intr_ack(struct snd_emu10k1 *emu, unsigned int voicenum)
{
        guard(spinlock_irqsave)(&emu->emu_lock);
        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);
}

void snd_emu10k1_voice_half_loop_intr_enable(struct snd_emu10k1 *emu, unsigned int voicenum)
{
        unsigned int val;

        guard(spinlock_irqsave)(&emu->emu_lock);
        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);
}

void snd_emu10k1_voice_half_loop_intr_disable(struct snd_emu10k1 *emu, unsigned int voicenum)
{
        unsigned int val;

        guard(spinlock_irqsave)(&emu->emu_lock);
        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);
}

void snd_emu10k1_voice_half_loop_intr_ack(struct snd_emu10k1 *emu, unsigned int voicenum)
{
        guard(spinlock_irqsave)(&emu->emu_lock);
        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);
}

#if 0
void snd_emu10k1_voice_set_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum)
{
        unsigned int sol;

        guard(spinlock_irqsave)(&emu->emu_lock);
        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);
}

void snd_emu10k1_voice_clear_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum)
{
        unsigned int sol;

        guard(spinlock_irqsave)(&emu->emu_lock);
        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);
}
#endif

void snd_emu10k1_voice_set_loop_stop_multiple(struct snd_emu10k1 *emu, u64 voices)
{
        guard(spinlock_irqsave)(&emu->emu_lock);
        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);
}

void snd_emu10k1_voice_clear_loop_stop_multiple(struct snd_emu10k1 *emu, u64 voices)
{
        guard(spinlock_irqsave)(&emu->emu_lock);
        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);
}

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;

        guard(spinlock_irqsave)(&emu->emu_lock);
        outb(reg, emu->port + AC97ADDRESS);
        return inw(emu->port + AC97DATA);
}

void snd_emu10k1_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short data)
{
        struct snd_emu10k1 *emu = ac97->private_data;

        guard(spinlock_irqsave)(&emu->emu_lock);
        outb(reg, emu->port + AC97ADDRESS);
        outw(data, emu->port + AC97DATA);
}