diff options
Diffstat (limited to 'sound/drivers/pcsp/pcsp_lib.c')
-rw-r--r-- | sound/drivers/pcsp/pcsp_lib.c | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/sound/drivers/pcsp/pcsp_lib.c b/sound/drivers/pcsp/pcsp_lib.c new file mode 100644 index 000000000000..6bdcb89129d8 --- /dev/null +++ b/sound/drivers/pcsp/pcsp_lib.c | |||
@@ -0,0 +1,347 @@ | |||
1 | /* | ||
2 | * PC-Speaker driver for Linux | ||
3 | * | ||
4 | * Copyright (C) 1993-1997 Michael Beck | ||
5 | * Copyright (C) 1997-2001 David Woodhouse | ||
6 | * Copyright (C) 2001-2008 Stas Sergeev | ||
7 | */ | ||
8 | |||
9 | #include <linux/module.h> | ||
10 | #include <linux/moduleparam.h> | ||
11 | #include <sound/pcm.h> | ||
12 | #include <sound/pcm_params.h> | ||
13 | #include <linux/interrupt.h> | ||
14 | #include <asm/io.h> | ||
15 | #include <asm/i8253.h> | ||
16 | #include "pcsp.h" | ||
17 | |||
18 | static int nforce_wa; | ||
19 | module_param(nforce_wa, bool, 0444); | ||
20 | MODULE_PARM_DESC(nforce_wa, "Apply NForce chipset workaround " | ||
21 | "(expect bad sound)"); | ||
22 | |||
23 | #define DMIX_WANTS_S16 1 | ||
24 | |||
25 | static void pcsp_start_timer(unsigned long dummy) | ||
26 | { | ||
27 | hrtimer_start(&pcsp_chip.timer, ktime_set(0, 0), HRTIMER_MODE_REL); | ||
28 | } | ||
29 | |||
30 | /* | ||
31 | * We need the hrtimer_start as a tasklet to avoid | ||
32 | * the nasty locking problem. :( | ||
33 | * The problem: | ||
34 | * - The timer handler is called with the cpu_base->lock | ||
35 | * already held by hrtimer code. | ||
36 | * - snd_pcm_period_elapsed() takes the | ||
37 | * substream->self_group.lock. | ||
38 | * So far so good. | ||
39 | * But the snd_pcsp_trigger() is called with the | ||
40 | * substream->self_group.lock held, and it calls | ||
41 | * hrtimer_start(), which takes the cpu_base->lock. | ||
42 | * You see the problem. We have the code pathes | ||
43 | * which take two locks in a reverse order. This | ||
44 | * can deadlock and the lock validator complains. | ||
45 | * The only solution I could find was to move the | ||
46 | * hrtimer_start() into a tasklet. -stsp | ||
47 | */ | ||
48 | DECLARE_TASKLET(pcsp_start_timer_tasklet, pcsp_start_timer, 0); | ||
49 | |||
50 | enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) | ||
51 | { | ||
52 | unsigned long flags; | ||
53 | unsigned char timer_cnt, val; | ||
54 | int fmt_size, periods_elapsed; | ||
55 | u64 ns; | ||
56 | size_t period_bytes, buffer_bytes; | ||
57 | struct snd_pcm_substream *substream; | ||
58 | struct snd_pcm_runtime *runtime; | ||
59 | struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer); | ||
60 | |||
61 | if (chip->thalf) { | ||
62 | outb(chip->val61, 0x61); | ||
63 | chip->thalf = 0; | ||
64 | if (!atomic_read(&chip->timer_active)) | ||
65 | return HRTIMER_NORESTART; | ||
66 | hrtimer_forward(&chip->timer, chip->timer.expires, | ||
67 | ktime_set(0, chip->ns_rem)); | ||
68 | return HRTIMER_RESTART; | ||
69 | } | ||
70 | |||
71 | /* hrtimer calls us from both hardirq and softirq contexts, | ||
72 | * so irqsave :( */ | ||
73 | spin_lock_irqsave(&chip->substream_lock, flags); | ||
74 | /* Takashi Iwai says regarding this extra lock: | ||
75 | |||
76 | If the irq handler handles some data on the DMA buffer, it should | ||
77 | do snd_pcm_stream_lock(). | ||
78 | That protects basically against all races among PCM callbacks, yes. | ||
79 | However, there are two remaining issues: | ||
80 | 1. The substream pointer you try to lock isn't protected _before_ | ||
81 | this lock yet. | ||
82 | 2. snd_pcm_period_elapsed() itself acquires the lock. | ||
83 | The requirement of another lock is because of 1. When you get | ||
84 | chip->playback_substream, it's not protected. | ||
85 | Keeping this lock while snd_pcm_period_elapsed() assures the substream | ||
86 | is still protected (at least, not released). And the other status is | ||
87 | handled properly inside snd_pcm_stream_lock() in | ||
88 | snd_pcm_period_elapsed(). | ||
89 | |||
90 | */ | ||
91 | if (!chip->playback_substream) | ||
92 | goto exit_nr_unlock1; | ||
93 | substream = chip->playback_substream; | ||
94 | snd_pcm_stream_lock(substream); | ||
95 | if (!atomic_read(&chip->timer_active)) | ||
96 | goto exit_nr_unlock2; | ||
97 | |||
98 | runtime = substream->runtime; | ||
99 | fmt_size = snd_pcm_format_physical_width(runtime->format) >> 3; | ||
100 | /* assume it is mono! */ | ||
101 | val = runtime->dma_area[chip->playback_ptr + fmt_size - 1]; | ||
102 | if (snd_pcm_format_signed(runtime->format)) | ||
103 | val ^= 0x80; | ||
104 | timer_cnt = val * CUR_DIV() / 256; | ||
105 | |||
106 | if (timer_cnt && chip->enable) { | ||
107 | spin_lock(&i8253_lock); | ||
108 | if (!nforce_wa) { | ||
109 | outb_p(chip->val61, 0x61); | ||
110 | outb_p(timer_cnt, 0x42); | ||
111 | outb(chip->val61 ^ 1, 0x61); | ||
112 | } else { | ||
113 | outb(chip->val61 ^ 2, 0x61); | ||
114 | chip->thalf = 1; | ||
115 | } | ||
116 | spin_unlock(&i8253_lock); | ||
117 | } | ||
118 | |||
119 | period_bytes = snd_pcm_lib_period_bytes(substream); | ||
120 | buffer_bytes = snd_pcm_lib_buffer_bytes(substream); | ||
121 | chip->playback_ptr += PCSP_INDEX_INC() * fmt_size; | ||
122 | periods_elapsed = chip->playback_ptr - chip->period_ptr; | ||
123 | if (periods_elapsed < 0) { | ||
124 | printk(KERN_WARNING "PCSP: playback_ptr inconsistent " | ||
125 | "(%zi %zi %zi)\n", | ||
126 | chip->playback_ptr, period_bytes, buffer_bytes); | ||
127 | periods_elapsed += buffer_bytes; | ||
128 | } | ||
129 | periods_elapsed /= period_bytes; | ||
130 | /* wrap the pointer _before_ calling snd_pcm_period_elapsed(), | ||
131 | * or ALSA will BUG on us. */ | ||
132 | chip->playback_ptr %= buffer_bytes; | ||
133 | |||
134 | snd_pcm_stream_unlock(substream); | ||
135 | |||
136 | if (periods_elapsed) { | ||
137 | snd_pcm_period_elapsed(substream); | ||
138 | chip->period_ptr += periods_elapsed * period_bytes; | ||
139 | chip->period_ptr %= buffer_bytes; | ||
140 | } | ||
141 | |||
142 | spin_unlock_irqrestore(&chip->substream_lock, flags); | ||
143 | |||
144 | if (!atomic_read(&chip->timer_active)) | ||
145 | return HRTIMER_NORESTART; | ||
146 | |||
147 | chip->ns_rem = PCSP_PERIOD_NS(); | ||
148 | ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem); | ||
149 | chip->ns_rem -= ns; | ||
150 | hrtimer_forward(&chip->timer, chip->timer.expires, ktime_set(0, ns)); | ||
151 | return HRTIMER_RESTART; | ||
152 | |||
153 | exit_nr_unlock2: | ||
154 | snd_pcm_stream_unlock(substream); | ||
155 | exit_nr_unlock1: | ||
156 | spin_unlock_irqrestore(&chip->substream_lock, flags); | ||
157 | return HRTIMER_NORESTART; | ||
158 | } | ||
159 | |||
160 | static void pcsp_start_playing(struct snd_pcsp *chip) | ||
161 | { | ||
162 | #if PCSP_DEBUG | ||
163 | printk(KERN_INFO "PCSP: start_playing called\n"); | ||
164 | #endif | ||
165 | if (atomic_read(&chip->timer_active)) { | ||
166 | printk(KERN_ERR "PCSP: Timer already active\n"); | ||
167 | return; | ||
168 | } | ||
169 | |||
170 | spin_lock(&i8253_lock); | ||
171 | chip->val61 = inb(0x61) | 0x03; | ||
172 | outb_p(0x92, 0x43); /* binary, mode 1, LSB only, ch 2 */ | ||
173 | spin_unlock(&i8253_lock); | ||
174 | atomic_set(&chip->timer_active, 1); | ||
175 | chip->thalf = 0; | ||
176 | |||
177 | tasklet_schedule(&pcsp_start_timer_tasklet); | ||
178 | } | ||
179 | |||
180 | static void pcsp_stop_playing(struct snd_pcsp *chip) | ||
181 | { | ||
182 | #if PCSP_DEBUG | ||
183 | printk(KERN_INFO "PCSP: stop_playing called\n"); | ||
184 | #endif | ||
185 | if (!atomic_read(&chip->timer_active)) | ||
186 | return; | ||
187 | |||
188 | atomic_set(&chip->timer_active, 0); | ||
189 | spin_lock(&i8253_lock); | ||
190 | /* restore the timer */ | ||
191 | outb_p(0xb6, 0x43); /* binary, mode 3, LSB/MSB, ch 2 */ | ||
192 | outb(chip->val61 & 0xFC, 0x61); | ||
193 | spin_unlock(&i8253_lock); | ||
194 | } | ||
195 | |||
196 | static int snd_pcsp_playback_close(struct snd_pcm_substream *substream) | ||
197 | { | ||
198 | struct snd_pcsp *chip = snd_pcm_substream_chip(substream); | ||
199 | #if PCSP_DEBUG | ||
200 | printk(KERN_INFO "PCSP: close called\n"); | ||
201 | #endif | ||
202 | if (atomic_read(&chip->timer_active)) { | ||
203 | printk(KERN_ERR "PCSP: timer still active\n"); | ||
204 | pcsp_stop_playing(chip); | ||
205 | } | ||
206 | spin_lock_irq(&chip->substream_lock); | ||
207 | chip->playback_substream = NULL; | ||
208 | spin_unlock_irq(&chip->substream_lock); | ||
209 | return 0; | ||
210 | } | ||
211 | |||
212 | static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream, | ||
213 | struct snd_pcm_hw_params *hw_params) | ||
214 | { | ||
215 | int err; | ||
216 | err = snd_pcm_lib_malloc_pages(substream, | ||
217 | params_buffer_bytes(hw_params)); | ||
218 | if (err < 0) | ||
219 | return err; | ||
220 | return 0; | ||
221 | } | ||
222 | |||
223 | static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream) | ||
224 | { | ||
225 | #if PCSP_DEBUG | ||
226 | printk(KERN_INFO "PCSP: hw_free called\n"); | ||
227 | #endif | ||
228 | return snd_pcm_lib_free_pages(substream); | ||
229 | } | ||
230 | |||
231 | static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream) | ||
232 | { | ||
233 | struct snd_pcsp *chip = snd_pcm_substream_chip(substream); | ||
234 | #if PCSP_DEBUG | ||
235 | printk(KERN_INFO "PCSP: prepare called, " | ||
236 | "size=%zi psize=%zi f=%zi f1=%i\n", | ||
237 | snd_pcm_lib_buffer_bytes(substream), | ||
238 | snd_pcm_lib_period_bytes(substream), | ||
239 | snd_pcm_lib_buffer_bytes(substream) / | ||
240 | snd_pcm_lib_period_bytes(substream), | ||
241 | substream->runtime->periods); | ||
242 | #endif | ||
243 | chip->playback_ptr = 0; | ||
244 | chip->period_ptr = 0; | ||
245 | return 0; | ||
246 | } | ||
247 | |||
248 | static int snd_pcsp_trigger(struct snd_pcm_substream *substream, int cmd) | ||
249 | { | ||
250 | struct snd_pcsp *chip = snd_pcm_substream_chip(substream); | ||
251 | #if PCSP_DEBUG | ||
252 | printk(KERN_INFO "PCSP: trigger called\n"); | ||
253 | #endif | ||
254 | switch (cmd) { | ||
255 | case SNDRV_PCM_TRIGGER_START: | ||
256 | case SNDRV_PCM_TRIGGER_RESUME: | ||
257 | pcsp_start_playing(chip); | ||
258 | break; | ||
259 | case SNDRV_PCM_TRIGGER_STOP: | ||
260 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
261 | pcsp_stop_playing(chip); | ||
262 | break; | ||
263 | default: | ||
264 | return -EINVAL; | ||
265 | } | ||
266 | return 0; | ||
267 | } | ||
268 | |||
269 | static snd_pcm_uframes_t snd_pcsp_playback_pointer(struct snd_pcm_substream | ||
270 | *substream) | ||
271 | { | ||
272 | struct snd_pcsp *chip = snd_pcm_substream_chip(substream); | ||
273 | return bytes_to_frames(substream->runtime, chip->playback_ptr); | ||
274 | } | ||
275 | |||
276 | static struct snd_pcm_hardware snd_pcsp_playback = { | ||
277 | .info = (SNDRV_PCM_INFO_INTERLEAVED | | ||
278 | SNDRV_PCM_INFO_HALF_DUPLEX | | ||
279 | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID), | ||
280 | .formats = (SNDRV_PCM_FMTBIT_U8 | ||
281 | #if DMIX_WANTS_S16 | ||
282 | | SNDRV_PCM_FMTBIT_S16_LE | ||
283 | #endif | ||
284 | ), | ||
285 | .rates = SNDRV_PCM_RATE_KNOT, | ||
286 | .rate_min = PCSP_DEFAULT_SRATE, | ||
287 | .rate_max = PCSP_DEFAULT_SRATE, | ||
288 | .channels_min = 1, | ||
289 | .channels_max = 1, | ||
290 | .buffer_bytes_max = PCSP_BUFFER_SIZE, | ||
291 | .period_bytes_min = 64, | ||
292 | .period_bytes_max = PCSP_MAX_PERIOD_SIZE, | ||
293 | .periods_min = 2, | ||
294 | .periods_max = PCSP_MAX_PERIODS, | ||
295 | .fifo_size = 0, | ||
296 | }; | ||
297 | |||
298 | static int snd_pcsp_playback_open(struct snd_pcm_substream *substream) | ||
299 | { | ||
300 | struct snd_pcsp *chip = snd_pcm_substream_chip(substream); | ||
301 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
302 | #if PCSP_DEBUG | ||
303 | printk(KERN_INFO "PCSP: open called\n"); | ||
304 | #endif | ||
305 | if (atomic_read(&chip->timer_active)) { | ||
306 | printk(KERN_ERR "PCSP: still active!!\n"); | ||
307 | return -EBUSY; | ||
308 | } | ||
309 | runtime->hw = snd_pcsp_playback; | ||
310 | chip->playback_substream = substream; | ||
311 | return 0; | ||
312 | } | ||
313 | |||
314 | static struct snd_pcm_ops snd_pcsp_playback_ops = { | ||
315 | .open = snd_pcsp_playback_open, | ||
316 | .close = snd_pcsp_playback_close, | ||
317 | .ioctl = snd_pcm_lib_ioctl, | ||
318 | .hw_params = snd_pcsp_playback_hw_params, | ||
319 | .hw_free = snd_pcsp_playback_hw_free, | ||
320 | .prepare = snd_pcsp_playback_prepare, | ||
321 | .trigger = snd_pcsp_trigger, | ||
322 | .pointer = snd_pcsp_playback_pointer, | ||
323 | }; | ||
324 | |||
325 | int __devinit snd_pcsp_new_pcm(struct snd_pcsp *chip) | ||
326 | { | ||
327 | int err; | ||
328 | |||
329 | err = snd_pcm_new(chip->card, "pcspeaker", 0, 1, 0, &chip->pcm); | ||
330 | if (err < 0) | ||
331 | return err; | ||
332 | |||
333 | snd_pcm_set_ops(chip->pcm, SNDRV_PCM_STREAM_PLAYBACK, | ||
334 | &snd_pcsp_playback_ops); | ||
335 | |||
336 | chip->pcm->private_data = chip; | ||
337 | chip->pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX; | ||
338 | strcpy(chip->pcm->name, "pcsp"); | ||
339 | |||
340 | snd_pcm_lib_preallocate_pages_for_all(chip->pcm, | ||
341 | SNDRV_DMA_TYPE_CONTINUOUS, | ||
342 | snd_dma_continuous_data | ||
343 | (GFP_KERNEL), PCSP_BUFFER_SIZE, | ||
344 | PCSP_BUFFER_SIZE); | ||
345 | |||
346 | return 0; | ||
347 | } | ||