18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
48c2ecf20Sopenharmony_ci *                   Creative Labs, Inc.
58c2ecf20Sopenharmony_ci *  Routines for IRQ control of EMU10K1 chips
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci *  BUGS:
88c2ecf20Sopenharmony_ci *    --
98c2ecf20Sopenharmony_ci *
108c2ecf20Sopenharmony_ci *  TODO:
118c2ecf20Sopenharmony_ci *    --
128c2ecf20Sopenharmony_ci */
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include <linux/time.h>
158c2ecf20Sopenharmony_ci#include <sound/core.h>
168c2ecf20Sopenharmony_ci#include <sound/emu10k1.h>
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ciirqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id)
198c2ecf20Sopenharmony_ci{
208c2ecf20Sopenharmony_ci	struct snd_emu10k1 *emu = dev_id;
218c2ecf20Sopenharmony_ci	unsigned int status, status2, orig_status, orig_status2;
228c2ecf20Sopenharmony_ci	int handled = 0;
238c2ecf20Sopenharmony_ci	int timeout = 0;
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci	while (((status = inl(emu->port + IPR)) != 0) && (timeout < 1000)) {
268c2ecf20Sopenharmony_ci		timeout++;
278c2ecf20Sopenharmony_ci		orig_status = status;
288c2ecf20Sopenharmony_ci		handled = 1;
298c2ecf20Sopenharmony_ci		if ((status & 0xffffffff) == 0xffffffff) {
308c2ecf20Sopenharmony_ci			dev_info(emu->card->dev,
318c2ecf20Sopenharmony_ci				 "Suspected sound card removal\n");
328c2ecf20Sopenharmony_ci			break;
338c2ecf20Sopenharmony_ci		}
348c2ecf20Sopenharmony_ci		if (status & IPR_PCIERROR) {
358c2ecf20Sopenharmony_ci			dev_err(emu->card->dev, "interrupt: PCI error\n");
368c2ecf20Sopenharmony_ci			snd_emu10k1_intr_disable(emu, INTE_PCIERRORENABLE);
378c2ecf20Sopenharmony_ci			status &= ~IPR_PCIERROR;
388c2ecf20Sopenharmony_ci		}
398c2ecf20Sopenharmony_ci		if (status & (IPR_VOLINCR|IPR_VOLDECR|IPR_MUTE)) {
408c2ecf20Sopenharmony_ci			if (emu->hwvol_interrupt)
418c2ecf20Sopenharmony_ci				emu->hwvol_interrupt(emu, status);
428c2ecf20Sopenharmony_ci			else
438c2ecf20Sopenharmony_ci				snd_emu10k1_intr_disable(emu, INTE_VOLINCRENABLE|INTE_VOLDECRENABLE|INTE_MUTEENABLE);
448c2ecf20Sopenharmony_ci			status &= ~(IPR_VOLINCR|IPR_VOLDECR|IPR_MUTE);
458c2ecf20Sopenharmony_ci		}
468c2ecf20Sopenharmony_ci		if (status & IPR_CHANNELLOOP) {
478c2ecf20Sopenharmony_ci			int voice;
488c2ecf20Sopenharmony_ci			int voice_max = status & IPR_CHANNELNUMBERMASK;
498c2ecf20Sopenharmony_ci			u32 val;
508c2ecf20Sopenharmony_ci			struct snd_emu10k1_voice *pvoice = emu->voices;
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci			val = snd_emu10k1_ptr_read(emu, CLIPL, 0);
538c2ecf20Sopenharmony_ci			for (voice = 0; voice <= voice_max; voice++) {
548c2ecf20Sopenharmony_ci				if (voice == 0x20)
558c2ecf20Sopenharmony_ci					val = snd_emu10k1_ptr_read(emu, CLIPH, 0);
568c2ecf20Sopenharmony_ci				if (val & 1) {
578c2ecf20Sopenharmony_ci					if (pvoice->use && pvoice->interrupt != NULL) {
588c2ecf20Sopenharmony_ci						pvoice->interrupt(emu, pvoice);
598c2ecf20Sopenharmony_ci						snd_emu10k1_voice_intr_ack(emu, voice);
608c2ecf20Sopenharmony_ci					} else {
618c2ecf20Sopenharmony_ci						snd_emu10k1_voice_intr_disable(emu, voice);
628c2ecf20Sopenharmony_ci					}
638c2ecf20Sopenharmony_ci				}
648c2ecf20Sopenharmony_ci				val >>= 1;
658c2ecf20Sopenharmony_ci				pvoice++;
668c2ecf20Sopenharmony_ci			}
678c2ecf20Sopenharmony_ci			val = snd_emu10k1_ptr_read(emu, HLIPL, 0);
688c2ecf20Sopenharmony_ci			for (voice = 0; voice <= voice_max; voice++) {
698c2ecf20Sopenharmony_ci				if (voice == 0x20)
708c2ecf20Sopenharmony_ci					val = snd_emu10k1_ptr_read(emu, HLIPH, 0);
718c2ecf20Sopenharmony_ci				if (val & 1) {
728c2ecf20Sopenharmony_ci					if (pvoice->use && pvoice->interrupt != NULL) {
738c2ecf20Sopenharmony_ci						pvoice->interrupt(emu, pvoice);
748c2ecf20Sopenharmony_ci						snd_emu10k1_voice_half_loop_intr_ack(emu, voice);
758c2ecf20Sopenharmony_ci					} else {
768c2ecf20Sopenharmony_ci						snd_emu10k1_voice_half_loop_intr_disable(emu, voice);
778c2ecf20Sopenharmony_ci					}
788c2ecf20Sopenharmony_ci				}
798c2ecf20Sopenharmony_ci				val >>= 1;
808c2ecf20Sopenharmony_ci				pvoice++;
818c2ecf20Sopenharmony_ci			}
828c2ecf20Sopenharmony_ci			status &= ~IPR_CHANNELLOOP;
838c2ecf20Sopenharmony_ci		}
848c2ecf20Sopenharmony_ci		status &= ~IPR_CHANNELNUMBERMASK;
858c2ecf20Sopenharmony_ci		if (status & (IPR_ADCBUFFULL|IPR_ADCBUFHALFFULL)) {
868c2ecf20Sopenharmony_ci			if (emu->capture_interrupt)
878c2ecf20Sopenharmony_ci				emu->capture_interrupt(emu, status);
888c2ecf20Sopenharmony_ci			else
898c2ecf20Sopenharmony_ci				snd_emu10k1_intr_disable(emu, INTE_ADCBUFENABLE);
908c2ecf20Sopenharmony_ci			status &= ~(IPR_ADCBUFFULL|IPR_ADCBUFHALFFULL);
918c2ecf20Sopenharmony_ci		}
928c2ecf20Sopenharmony_ci		if (status & (IPR_MICBUFFULL|IPR_MICBUFHALFFULL)) {
938c2ecf20Sopenharmony_ci			if (emu->capture_mic_interrupt)
948c2ecf20Sopenharmony_ci				emu->capture_mic_interrupt(emu, status);
958c2ecf20Sopenharmony_ci			else
968c2ecf20Sopenharmony_ci				snd_emu10k1_intr_disable(emu, INTE_MICBUFENABLE);
978c2ecf20Sopenharmony_ci			status &= ~(IPR_MICBUFFULL|IPR_MICBUFHALFFULL);
988c2ecf20Sopenharmony_ci		}
998c2ecf20Sopenharmony_ci		if (status & (IPR_EFXBUFFULL|IPR_EFXBUFHALFFULL)) {
1008c2ecf20Sopenharmony_ci			if (emu->capture_efx_interrupt)
1018c2ecf20Sopenharmony_ci				emu->capture_efx_interrupt(emu, status);
1028c2ecf20Sopenharmony_ci			else
1038c2ecf20Sopenharmony_ci				snd_emu10k1_intr_disable(emu, INTE_EFXBUFENABLE);
1048c2ecf20Sopenharmony_ci			status &= ~(IPR_EFXBUFFULL|IPR_EFXBUFHALFFULL);
1058c2ecf20Sopenharmony_ci		}
1068c2ecf20Sopenharmony_ci		if (status & (IPR_MIDITRANSBUFEMPTY|IPR_MIDIRECVBUFEMPTY)) {
1078c2ecf20Sopenharmony_ci			if (emu->midi.interrupt)
1088c2ecf20Sopenharmony_ci				emu->midi.interrupt(emu, status);
1098c2ecf20Sopenharmony_ci			else
1108c2ecf20Sopenharmony_ci				snd_emu10k1_intr_disable(emu, INTE_MIDITXENABLE|INTE_MIDIRXENABLE);
1118c2ecf20Sopenharmony_ci			status &= ~(IPR_MIDITRANSBUFEMPTY|IPR_MIDIRECVBUFEMPTY);
1128c2ecf20Sopenharmony_ci		}
1138c2ecf20Sopenharmony_ci		if (status & (IPR_A_MIDITRANSBUFEMPTY2|IPR_A_MIDIRECVBUFEMPTY2)) {
1148c2ecf20Sopenharmony_ci			if (emu->midi2.interrupt)
1158c2ecf20Sopenharmony_ci				emu->midi2.interrupt(emu, status);
1168c2ecf20Sopenharmony_ci			else
1178c2ecf20Sopenharmony_ci				snd_emu10k1_intr_disable(emu, INTE_A_MIDITXENABLE2|INTE_A_MIDIRXENABLE2);
1188c2ecf20Sopenharmony_ci			status &= ~(IPR_A_MIDITRANSBUFEMPTY2|IPR_A_MIDIRECVBUFEMPTY2);
1198c2ecf20Sopenharmony_ci		}
1208c2ecf20Sopenharmony_ci		if (status & IPR_INTERVALTIMER) {
1218c2ecf20Sopenharmony_ci			if (emu->timer)
1228c2ecf20Sopenharmony_ci				snd_timer_interrupt(emu->timer, emu->timer->sticks);
1238c2ecf20Sopenharmony_ci			else
1248c2ecf20Sopenharmony_ci				snd_emu10k1_intr_disable(emu, INTE_INTERVALTIMERENB);
1258c2ecf20Sopenharmony_ci			status &= ~IPR_INTERVALTIMER;
1268c2ecf20Sopenharmony_ci		}
1278c2ecf20Sopenharmony_ci		if (status & (IPR_GPSPDIFSTATUSCHANGE|IPR_CDROMSTATUSCHANGE)) {
1288c2ecf20Sopenharmony_ci			if (emu->spdif_interrupt)
1298c2ecf20Sopenharmony_ci				emu->spdif_interrupt(emu, status);
1308c2ecf20Sopenharmony_ci			else
1318c2ecf20Sopenharmony_ci				snd_emu10k1_intr_disable(emu, INTE_GPSPDIFENABLE|INTE_CDSPDIFENABLE);
1328c2ecf20Sopenharmony_ci			status &= ~(IPR_GPSPDIFSTATUSCHANGE|IPR_CDROMSTATUSCHANGE);
1338c2ecf20Sopenharmony_ci		}
1348c2ecf20Sopenharmony_ci		if (status & IPR_FXDSP) {
1358c2ecf20Sopenharmony_ci			if (emu->dsp_interrupt)
1368c2ecf20Sopenharmony_ci				emu->dsp_interrupt(emu);
1378c2ecf20Sopenharmony_ci			else
1388c2ecf20Sopenharmony_ci				snd_emu10k1_intr_disable(emu, INTE_FXDSPENABLE);
1398c2ecf20Sopenharmony_ci			status &= ~IPR_FXDSP;
1408c2ecf20Sopenharmony_ci		}
1418c2ecf20Sopenharmony_ci		if (status & IPR_P16V) {
1428c2ecf20Sopenharmony_ci			while ((status2 = inl(emu->port + IPR2)) != 0) {
1438c2ecf20Sopenharmony_ci				u32 mask = INTE2_PLAYBACK_CH_0_LOOP;  /* Full Loop */
1448c2ecf20Sopenharmony_ci				struct snd_emu10k1_voice *pvoice = &(emu->p16v_voices[0]);
1458c2ecf20Sopenharmony_ci				struct snd_emu10k1_voice *cvoice = &(emu->p16v_capture_voice);
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci				/* dev_dbg(emu->card->dev, "status2=0x%x\n", status2); */
1488c2ecf20Sopenharmony_ci				orig_status2 = status2;
1498c2ecf20Sopenharmony_ci				if(status2 & mask) {
1508c2ecf20Sopenharmony_ci					if(pvoice->use) {
1518c2ecf20Sopenharmony_ci						snd_pcm_period_elapsed(pvoice->epcm->substream);
1528c2ecf20Sopenharmony_ci					} else {
1538c2ecf20Sopenharmony_ci						dev_err(emu->card->dev,
1548c2ecf20Sopenharmony_ci							"p16v: status: 0x%08x, mask=0x%08x, pvoice=%p, use=%d\n",
1558c2ecf20Sopenharmony_ci							status2, mask, pvoice,
1568c2ecf20Sopenharmony_ci							pvoice->use);
1578c2ecf20Sopenharmony_ci					}
1588c2ecf20Sopenharmony_ci				}
1598c2ecf20Sopenharmony_ci				if(status2 & 0x110000) {
1608c2ecf20Sopenharmony_ci					/* dev_info(emu->card->dev, "capture int found\n"); */
1618c2ecf20Sopenharmony_ci					if(cvoice->use) {
1628c2ecf20Sopenharmony_ci						/* dev_info(emu->card->dev, "capture period_elapsed\n"); */
1638c2ecf20Sopenharmony_ci						snd_pcm_period_elapsed(cvoice->epcm->substream);
1648c2ecf20Sopenharmony_ci					}
1658c2ecf20Sopenharmony_ci				}
1668c2ecf20Sopenharmony_ci				outl(orig_status2, emu->port + IPR2); /* ack all */
1678c2ecf20Sopenharmony_ci			}
1688c2ecf20Sopenharmony_ci			status &= ~IPR_P16V;
1698c2ecf20Sopenharmony_ci		}
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci		if (status) {
1728c2ecf20Sopenharmony_ci			unsigned int bits;
1738c2ecf20Sopenharmony_ci			dev_err(emu->card->dev,
1748c2ecf20Sopenharmony_ci				"unhandled interrupt: 0x%08x\n", status);
1758c2ecf20Sopenharmony_ci			//make sure any interrupts we don't handle are disabled:
1768c2ecf20Sopenharmony_ci			bits = INTE_FXDSPENABLE |
1778c2ecf20Sopenharmony_ci				INTE_PCIERRORENABLE |
1788c2ecf20Sopenharmony_ci				INTE_VOLINCRENABLE |
1798c2ecf20Sopenharmony_ci				INTE_VOLDECRENABLE |
1808c2ecf20Sopenharmony_ci				INTE_MUTEENABLE |
1818c2ecf20Sopenharmony_ci				INTE_MICBUFENABLE |
1828c2ecf20Sopenharmony_ci				INTE_ADCBUFENABLE |
1838c2ecf20Sopenharmony_ci				INTE_EFXBUFENABLE |
1848c2ecf20Sopenharmony_ci				INTE_GPSPDIFENABLE |
1858c2ecf20Sopenharmony_ci				INTE_CDSPDIFENABLE |
1868c2ecf20Sopenharmony_ci				INTE_INTERVALTIMERENB |
1878c2ecf20Sopenharmony_ci				INTE_MIDITXENABLE |
1888c2ecf20Sopenharmony_ci				INTE_MIDIRXENABLE;
1898c2ecf20Sopenharmony_ci			if (emu->audigy)
1908c2ecf20Sopenharmony_ci				bits |= INTE_A_MIDITXENABLE2 | INTE_A_MIDIRXENABLE2;
1918c2ecf20Sopenharmony_ci			snd_emu10k1_intr_disable(emu, bits);
1928c2ecf20Sopenharmony_ci		}
1938c2ecf20Sopenharmony_ci		outl(orig_status, emu->port + IPR); /* ack all */
1948c2ecf20Sopenharmony_ci	}
1958c2ecf20Sopenharmony_ci	if (timeout == 1000)
1968c2ecf20Sopenharmony_ci		dev_info(emu->card->dev, "emu10k1 irq routine failure\n");
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	return IRQ_RETVAL(handled);
1998c2ecf20Sopenharmony_ci}
200