162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
462306a36Sopenharmony_ci *                   Creative Labs, Inc.
562306a36Sopenharmony_ci *  Routines for IRQ control of EMU10K1 chips
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/time.h>
962306a36Sopenharmony_ci#include <sound/core.h>
1062306a36Sopenharmony_ci#include <sound/emu10k1.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ciirqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id)
1362306a36Sopenharmony_ci{
1462306a36Sopenharmony_ci	struct snd_emu10k1 *emu = dev_id;
1562306a36Sopenharmony_ci	unsigned int status, orig_status;
1662306a36Sopenharmony_ci	int handled = 0;
1762306a36Sopenharmony_ci	int timeout = 0;
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci	while ((status = inl(emu->port + IPR)) != 0) {
2062306a36Sopenharmony_ci		handled = 1;
2162306a36Sopenharmony_ci		if ((status & 0xffffffff) == 0xffffffff) {
2262306a36Sopenharmony_ci			dev_info(emu->card->dev,
2362306a36Sopenharmony_ci				 "Suspected sound card removal\n");
2462306a36Sopenharmony_ci			break;
2562306a36Sopenharmony_ci		}
2662306a36Sopenharmony_ci		if (++timeout == 1000) {
2762306a36Sopenharmony_ci			dev_info(emu->card->dev, "emu10k1 irq routine failure\n");
2862306a36Sopenharmony_ci			break;
2962306a36Sopenharmony_ci		}
3062306a36Sopenharmony_ci		orig_status = status;
3162306a36Sopenharmony_ci		if (status & IPR_PCIERROR) {
3262306a36Sopenharmony_ci			dev_err(emu->card->dev, "interrupt: PCI error\n");
3362306a36Sopenharmony_ci			snd_emu10k1_intr_disable(emu, INTE_PCIERRORENABLE);
3462306a36Sopenharmony_ci			status &= ~IPR_PCIERROR;
3562306a36Sopenharmony_ci		}
3662306a36Sopenharmony_ci		if (status & (IPR_VOLINCR|IPR_VOLDECR|IPR_MUTE)) {
3762306a36Sopenharmony_ci			if (emu->hwvol_interrupt)
3862306a36Sopenharmony_ci				emu->hwvol_interrupt(emu, status);
3962306a36Sopenharmony_ci			else
4062306a36Sopenharmony_ci				snd_emu10k1_intr_disable(emu, INTE_VOLINCRENABLE|INTE_VOLDECRENABLE|INTE_MUTEENABLE);
4162306a36Sopenharmony_ci			status &= ~(IPR_VOLINCR|IPR_VOLDECR|IPR_MUTE);
4262306a36Sopenharmony_ci		}
4362306a36Sopenharmony_ci		if (status & IPR_CHANNELLOOP) {
4462306a36Sopenharmony_ci			struct snd_emu10k1_voice *pvoice;
4562306a36Sopenharmony_ci			int voice;
4662306a36Sopenharmony_ci			int voice_max = status & IPR_CHANNELNUMBERMASK;
4762306a36Sopenharmony_ci			u32 val;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci			val = snd_emu10k1_ptr_read(emu, CLIPL, 0);
5062306a36Sopenharmony_ci			pvoice = emu->voices;
5162306a36Sopenharmony_ci			for (voice = 0; voice <= voice_max; voice++) {
5262306a36Sopenharmony_ci				if (voice == 0x20)
5362306a36Sopenharmony_ci					val = snd_emu10k1_ptr_read(emu, CLIPH, 0);
5462306a36Sopenharmony_ci				if (val & 1) {
5562306a36Sopenharmony_ci					if (pvoice->use && pvoice->interrupt != NULL) {
5662306a36Sopenharmony_ci						pvoice->interrupt(emu, pvoice);
5762306a36Sopenharmony_ci						snd_emu10k1_voice_intr_ack(emu, voice);
5862306a36Sopenharmony_ci					} else {
5962306a36Sopenharmony_ci						snd_emu10k1_voice_intr_disable(emu, voice);
6062306a36Sopenharmony_ci					}
6162306a36Sopenharmony_ci				}
6262306a36Sopenharmony_ci				val >>= 1;
6362306a36Sopenharmony_ci				pvoice++;
6462306a36Sopenharmony_ci			}
6562306a36Sopenharmony_ci			val = snd_emu10k1_ptr_read(emu, HLIPL, 0);
6662306a36Sopenharmony_ci			pvoice = emu->voices;
6762306a36Sopenharmony_ci			for (voice = 0; voice <= voice_max; voice++) {
6862306a36Sopenharmony_ci				if (voice == 0x20)
6962306a36Sopenharmony_ci					val = snd_emu10k1_ptr_read(emu, HLIPH, 0);
7062306a36Sopenharmony_ci				if (val & 1) {
7162306a36Sopenharmony_ci					if (pvoice->use && pvoice->interrupt != NULL) {
7262306a36Sopenharmony_ci						pvoice->interrupt(emu, pvoice);
7362306a36Sopenharmony_ci						snd_emu10k1_voice_half_loop_intr_ack(emu, voice);
7462306a36Sopenharmony_ci					} else {
7562306a36Sopenharmony_ci						snd_emu10k1_voice_half_loop_intr_disable(emu, voice);
7662306a36Sopenharmony_ci					}
7762306a36Sopenharmony_ci				}
7862306a36Sopenharmony_ci				val >>= 1;
7962306a36Sopenharmony_ci				pvoice++;
8062306a36Sopenharmony_ci			}
8162306a36Sopenharmony_ci			status &= ~(IPR_CHANNELLOOP | IPR_CHANNELNUMBERMASK);
8262306a36Sopenharmony_ci		}
8362306a36Sopenharmony_ci		if (status & (IPR_ADCBUFFULL|IPR_ADCBUFHALFFULL)) {
8462306a36Sopenharmony_ci			if (emu->capture_interrupt)
8562306a36Sopenharmony_ci				emu->capture_interrupt(emu, status);
8662306a36Sopenharmony_ci			else
8762306a36Sopenharmony_ci				snd_emu10k1_intr_disable(emu, INTE_ADCBUFENABLE);
8862306a36Sopenharmony_ci			status &= ~(IPR_ADCBUFFULL|IPR_ADCBUFHALFFULL);
8962306a36Sopenharmony_ci		}
9062306a36Sopenharmony_ci		if (status & (IPR_MICBUFFULL|IPR_MICBUFHALFFULL)) {
9162306a36Sopenharmony_ci			if (emu->capture_mic_interrupt)
9262306a36Sopenharmony_ci				emu->capture_mic_interrupt(emu, status);
9362306a36Sopenharmony_ci			else
9462306a36Sopenharmony_ci				snd_emu10k1_intr_disable(emu, INTE_MICBUFENABLE);
9562306a36Sopenharmony_ci			status &= ~(IPR_MICBUFFULL|IPR_MICBUFHALFFULL);
9662306a36Sopenharmony_ci		}
9762306a36Sopenharmony_ci		if (status & (IPR_EFXBUFFULL|IPR_EFXBUFHALFFULL)) {
9862306a36Sopenharmony_ci			if (emu->capture_efx_interrupt)
9962306a36Sopenharmony_ci				emu->capture_efx_interrupt(emu, status);
10062306a36Sopenharmony_ci			else
10162306a36Sopenharmony_ci				snd_emu10k1_intr_disable(emu, INTE_EFXBUFENABLE);
10262306a36Sopenharmony_ci			status &= ~(IPR_EFXBUFFULL|IPR_EFXBUFHALFFULL);
10362306a36Sopenharmony_ci		}
10462306a36Sopenharmony_ci		if (status & (IPR_MIDITRANSBUFEMPTY|IPR_MIDIRECVBUFEMPTY)) {
10562306a36Sopenharmony_ci			if (emu->midi.interrupt)
10662306a36Sopenharmony_ci				emu->midi.interrupt(emu, status);
10762306a36Sopenharmony_ci			else
10862306a36Sopenharmony_ci				snd_emu10k1_intr_disable(emu, INTE_MIDITXENABLE|INTE_MIDIRXENABLE);
10962306a36Sopenharmony_ci			status &= ~(IPR_MIDITRANSBUFEMPTY|IPR_MIDIRECVBUFEMPTY);
11062306a36Sopenharmony_ci		}
11162306a36Sopenharmony_ci		if (status & (IPR_A_MIDITRANSBUFEMPTY2|IPR_A_MIDIRECVBUFEMPTY2)) {
11262306a36Sopenharmony_ci			if (emu->midi2.interrupt)
11362306a36Sopenharmony_ci				emu->midi2.interrupt(emu, status);
11462306a36Sopenharmony_ci			else
11562306a36Sopenharmony_ci				snd_emu10k1_intr_disable(emu, INTE_A_MIDITXENABLE2|INTE_A_MIDIRXENABLE2);
11662306a36Sopenharmony_ci			status &= ~(IPR_A_MIDITRANSBUFEMPTY2|IPR_A_MIDIRECVBUFEMPTY2);
11762306a36Sopenharmony_ci		}
11862306a36Sopenharmony_ci		if (status & IPR_INTERVALTIMER) {
11962306a36Sopenharmony_ci			if (emu->timer)
12062306a36Sopenharmony_ci				snd_timer_interrupt(emu->timer, emu->timer->sticks);
12162306a36Sopenharmony_ci			else
12262306a36Sopenharmony_ci				snd_emu10k1_intr_disable(emu, INTE_INTERVALTIMERENB);
12362306a36Sopenharmony_ci			status &= ~IPR_INTERVALTIMER;
12462306a36Sopenharmony_ci		}
12562306a36Sopenharmony_ci		if (status & (IPR_GPSPDIFSTATUSCHANGE|IPR_CDROMSTATUSCHANGE)) {
12662306a36Sopenharmony_ci			if (emu->spdif_interrupt)
12762306a36Sopenharmony_ci				emu->spdif_interrupt(emu, status);
12862306a36Sopenharmony_ci			else
12962306a36Sopenharmony_ci				snd_emu10k1_intr_disable(emu, INTE_GPSPDIFENABLE|INTE_CDSPDIFENABLE);
13062306a36Sopenharmony_ci			status &= ~(IPR_GPSPDIFSTATUSCHANGE|IPR_CDROMSTATUSCHANGE);
13162306a36Sopenharmony_ci		}
13262306a36Sopenharmony_ci		if (status & IPR_FXDSP) {
13362306a36Sopenharmony_ci			if (emu->dsp_interrupt)
13462306a36Sopenharmony_ci				emu->dsp_interrupt(emu);
13562306a36Sopenharmony_ci			else
13662306a36Sopenharmony_ci				snd_emu10k1_intr_disable(emu, INTE_FXDSPENABLE);
13762306a36Sopenharmony_ci			status &= ~IPR_FXDSP;
13862306a36Sopenharmony_ci		}
13962306a36Sopenharmony_ci		if (status & IPR_P16V) {
14062306a36Sopenharmony_ci			if (emu->p16v_interrupt)
14162306a36Sopenharmony_ci				emu->p16v_interrupt(emu);
14262306a36Sopenharmony_ci			else
14362306a36Sopenharmony_ci				outl(0, emu->port + INTE2);
14462306a36Sopenharmony_ci			status &= ~IPR_P16V;
14562306a36Sopenharmony_ci		}
14662306a36Sopenharmony_ci		if (status & IPR_A_GPIO) {
14762306a36Sopenharmony_ci			if (emu->gpio_interrupt)
14862306a36Sopenharmony_ci				emu->gpio_interrupt(emu);
14962306a36Sopenharmony_ci			else
15062306a36Sopenharmony_ci				snd_emu10k1_intr_disable(emu, INTE_A_GPIOENABLE);
15162306a36Sopenharmony_ci			status &= ~IPR_A_GPIO;
15262306a36Sopenharmony_ci		}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci		if (status) {
15562306a36Sopenharmony_ci			dev_err(emu->card->dev,
15662306a36Sopenharmony_ci				"unhandled interrupt: 0x%08x\n", status);
15762306a36Sopenharmony_ci		}
15862306a36Sopenharmony_ci		outl(orig_status, emu->port + IPR); /* ack all */
15962306a36Sopenharmony_ci	}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	return IRQ_RETVAL(handled);
16262306a36Sopenharmony_ci}
163