1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (c) by Jaroslav Kysela <perex@perex.cz> |
4 | * Creative Labs, Inc. |
5 | * Routines for IRQ control of EMU10K1 chips |
6 | */ |
7 | |
8 | #include <linux/time.h> |
9 | #include <sound/core.h> |
10 | #include <sound/emu10k1.h> |
11 | |
12 | irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id) |
13 | { |
14 | struct snd_emu10k1 *emu = dev_id; |
15 | unsigned int status, orig_status; |
16 | int handled = 0; |
17 | int timeout = 0; |
18 | |
19 | while ((status = inl(port: emu->port + IPR)) != 0) { |
20 | handled = 1; |
21 | if ((status & 0xffffffff) == 0xffffffff) { |
22 | dev_info(emu->card->dev, |
23 | "Suspected sound card removal\n" ); |
24 | break; |
25 | } |
26 | if (++timeout == 1000) { |
27 | dev_info(emu->card->dev, "emu10k1 irq routine failure\n" ); |
28 | break; |
29 | } |
30 | orig_status = status; |
31 | if (status & IPR_PCIERROR) { |
32 | dev_err(emu->card->dev, "interrupt: PCI error\n" ); |
33 | snd_emu10k1_intr_disable(emu, INTE_PCIERRORENABLE); |
34 | status &= ~IPR_PCIERROR; |
35 | } |
36 | if (status & (IPR_VOLINCR|IPR_VOLDECR|IPR_MUTE)) { |
37 | if (emu->hwvol_interrupt) |
38 | emu->hwvol_interrupt(emu, status); |
39 | else |
40 | snd_emu10k1_intr_disable(emu, INTE_VOLINCRENABLE|INTE_VOLDECRENABLE|INTE_MUTEENABLE); |
41 | status &= ~(IPR_VOLINCR|IPR_VOLDECR|IPR_MUTE); |
42 | } |
43 | if (status & IPR_CHANNELLOOP) { |
44 | struct snd_emu10k1_voice *pvoice; |
45 | int voice; |
46 | int voice_max = status & IPR_CHANNELNUMBERMASK; |
47 | u32 val; |
48 | |
49 | val = snd_emu10k1_ptr_read(emu, CLIPL, chn: 0); |
50 | pvoice = emu->voices; |
51 | for (voice = 0; voice <= voice_max; voice++) { |
52 | if (voice == 0x20) |
53 | val = snd_emu10k1_ptr_read(emu, CLIPH, chn: 0); |
54 | if (val & 1) { |
55 | if (pvoice->use && pvoice->interrupt != NULL) { |
56 | pvoice->interrupt(emu, pvoice); |
57 | snd_emu10k1_voice_intr_ack(emu, voicenum: voice); |
58 | } else { |
59 | snd_emu10k1_voice_intr_disable(emu, voicenum: voice); |
60 | } |
61 | } |
62 | val >>= 1; |
63 | pvoice++; |
64 | } |
65 | val = snd_emu10k1_ptr_read(emu, HLIPL, chn: 0); |
66 | pvoice = emu->voices; |
67 | for (voice = 0; voice <= voice_max; voice++) { |
68 | if (voice == 0x20) |
69 | val = snd_emu10k1_ptr_read(emu, HLIPH, chn: 0); |
70 | if (val & 1) { |
71 | if (pvoice->use && pvoice->interrupt != NULL) { |
72 | pvoice->interrupt(emu, pvoice); |
73 | snd_emu10k1_voice_half_loop_intr_ack(emu, voicenum: voice); |
74 | } else { |
75 | snd_emu10k1_voice_half_loop_intr_disable(emu, voicenum: voice); |
76 | } |
77 | } |
78 | val >>= 1; |
79 | pvoice++; |
80 | } |
81 | status &= ~(IPR_CHANNELLOOP | IPR_CHANNELNUMBERMASK); |
82 | } |
83 | if (status & (IPR_ADCBUFFULL|IPR_ADCBUFHALFFULL)) { |
84 | if (emu->capture_interrupt) |
85 | emu->capture_interrupt(emu, status); |
86 | else |
87 | snd_emu10k1_intr_disable(emu, INTE_ADCBUFENABLE); |
88 | status &= ~(IPR_ADCBUFFULL|IPR_ADCBUFHALFFULL); |
89 | } |
90 | if (status & (IPR_MICBUFFULL|IPR_MICBUFHALFFULL)) { |
91 | if (emu->capture_mic_interrupt) |
92 | emu->capture_mic_interrupt(emu, status); |
93 | else |
94 | snd_emu10k1_intr_disable(emu, INTE_MICBUFENABLE); |
95 | status &= ~(IPR_MICBUFFULL|IPR_MICBUFHALFFULL); |
96 | } |
97 | if (status & (IPR_EFXBUFFULL|IPR_EFXBUFHALFFULL)) { |
98 | if (emu->capture_efx_interrupt) |
99 | emu->capture_efx_interrupt(emu, status); |
100 | else |
101 | snd_emu10k1_intr_disable(emu, INTE_EFXBUFENABLE); |
102 | status &= ~(IPR_EFXBUFFULL|IPR_EFXBUFHALFFULL); |
103 | } |
104 | if (status & (IPR_MIDITRANSBUFEMPTY|IPR_MIDIRECVBUFEMPTY)) { |
105 | if (emu->midi.interrupt) |
106 | emu->midi.interrupt(emu, status); |
107 | else |
108 | snd_emu10k1_intr_disable(emu, INTE_MIDITXENABLE|INTE_MIDIRXENABLE); |
109 | status &= ~(IPR_MIDITRANSBUFEMPTY|IPR_MIDIRECVBUFEMPTY); |
110 | } |
111 | if (status & (IPR_A_MIDITRANSBUFEMPTY2|IPR_A_MIDIRECVBUFEMPTY2)) { |
112 | if (emu->midi2.interrupt) |
113 | emu->midi2.interrupt(emu, status); |
114 | else |
115 | snd_emu10k1_intr_disable(emu, INTE_A_MIDITXENABLE2|INTE_A_MIDIRXENABLE2); |
116 | status &= ~(IPR_A_MIDITRANSBUFEMPTY2|IPR_A_MIDIRECVBUFEMPTY2); |
117 | } |
118 | if (status & IPR_INTERVALTIMER) { |
119 | if (emu->timer) |
120 | snd_timer_interrupt(timer: emu->timer, ticks_left: emu->timer->sticks); |
121 | else |
122 | snd_emu10k1_intr_disable(emu, INTE_INTERVALTIMERENB); |
123 | status &= ~IPR_INTERVALTIMER; |
124 | } |
125 | if (status & (IPR_GPSPDIFSTATUSCHANGE|IPR_CDROMSTATUSCHANGE)) { |
126 | if (emu->spdif_interrupt) |
127 | emu->spdif_interrupt(emu, status); |
128 | else |
129 | snd_emu10k1_intr_disable(emu, INTE_GPSPDIFENABLE|INTE_CDSPDIFENABLE); |
130 | status &= ~(IPR_GPSPDIFSTATUSCHANGE|IPR_CDROMSTATUSCHANGE); |
131 | } |
132 | if (status & IPR_FXDSP) { |
133 | if (emu->dsp_interrupt) |
134 | emu->dsp_interrupt(emu); |
135 | else |
136 | snd_emu10k1_intr_disable(emu, INTE_FXDSPENABLE); |
137 | status &= ~IPR_FXDSP; |
138 | } |
139 | if (status & IPR_P16V) { |
140 | if (emu->p16v_interrupt) |
141 | emu->p16v_interrupt(emu); |
142 | else |
143 | outl(value: 0, port: emu->port + INTE2); |
144 | status &= ~IPR_P16V; |
145 | } |
146 | if (status & IPR_A_GPIO) { |
147 | if (emu->gpio_interrupt) |
148 | emu->gpio_interrupt(emu); |
149 | else |
150 | snd_emu10k1_intr_disable(emu, INTE_A_GPIOENABLE); |
151 | status &= ~IPR_A_GPIO; |
152 | } |
153 | |
154 | if (status) { |
155 | dev_err(emu->card->dev, |
156 | "unhandled interrupt: 0x%08x\n" , status); |
157 | } |
158 | outl(value: orig_status, port: emu->port + IPR); /* ack all */ |
159 | } |
160 | |
161 | return IRQ_RETVAL(handled); |
162 | } |
163 | |