| // 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 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. */ |
| 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) |
| { |
| snd_emu1010_fpga_lock(emu); |
| snd_emu1010_fpga_write_locked(emu, reg, value); |
| snd_emu1010_fpga_unlock(emu); |
| } |
| |
| 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 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); |
| } |