blob: 40f95f549d2bb5739a7e777e1b27c3acdda3a3f0 [file] [log] [blame]
Stas Sergeev9ab4d072008-03-03 10:53:54 +01001/*
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>
Takashi Iwai96c7d472008-08-11 10:18:39 +020011#include <linux/interrupt.h>
Stas Sergeev9ab4d072008-03-03 10:53:54 +010012#include <sound/pcm.h>
Stas Sergeev9ab4d072008-03-03 10:53:54 +010013#include <asm/io.h>
Stas Sergeev9ab4d072008-03-03 10:53:54 +010014#include "pcsp.h"
15
16static int nforce_wa;
17module_param(nforce_wa, bool, 0444);
18MODULE_PARM_DESC(nforce_wa, "Apply NForce chipset workaround "
19 "(expect bad sound)");
20
Stas Sergeev4dfd7952008-05-17 08:44:41 +020021#define DMIX_WANTS_S16 1
22
Takashi Iwai96c7d472008-08-11 10:18:39 +020023/*
24 * Call snd_pcm_period_elapsed in a tasklet
25 * This avoids spinlock messes and long-running irq contexts
26 */
27static void pcsp_call_pcm_elapsed(unsigned long priv)
28{
29 if (atomic_read(&pcsp_chip.timer_active)) {
30 struct snd_pcm_substream *substream;
31 substream = pcsp_chip.playback_substream;
32 if (substream)
33 snd_pcm_period_elapsed(substream);
34 }
35}
36
37static DECLARE_TASKLET(pcsp_pcm_tasklet, pcsp_call_pcm_elapsed, 0);
38
Stas Sergeev9ab4d072008-03-03 10:53:54 +010039enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle)
40{
Stas Sergeev9ab4d072008-03-03 10:53:54 +010041 unsigned char timer_cnt, val;
Stas Sergeev4dfd7952008-05-17 08:44:41 +020042 int fmt_size, periods_elapsed;
Stas Sergeev9ab4d072008-03-03 10:53:54 +010043 u64 ns;
44 size_t period_bytes, buffer_bytes;
45 struct snd_pcm_substream *substream;
46 struct snd_pcm_runtime *runtime;
47 struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer);
Takashi Iwai96c7d472008-08-11 10:18:39 +020048 unsigned long flags;
Stas Sergeev9ab4d072008-03-03 10:53:54 +010049
50 if (chip->thalf) {
51 outb(chip->val61, 0x61);
52 chip->thalf = 0;
53 if (!atomic_read(&chip->timer_active))
Takashi Iwai96c7d472008-08-11 10:18:39 +020054 goto stop;
Stas Sergeev9ab4d072008-03-03 10:53:54 +010055 hrtimer_forward(&chip->timer, chip->timer.expires,
56 ktime_set(0, chip->ns_rem));
57 return HRTIMER_RESTART;
58 }
59
Stas Sergeev9ab4d072008-03-03 10:53:54 +010060 if (!atomic_read(&chip->timer_active))
Takashi Iwai96c7d472008-08-11 10:18:39 +020061 goto stop;
62 substream = chip->playback_substream;
63 if (!substream)
64 goto stop;
Stas Sergeev9ab4d072008-03-03 10:53:54 +010065
66 runtime = substream->runtime;
Stas Sergeev4dfd7952008-05-17 08:44:41 +020067 fmt_size = snd_pcm_format_physical_width(runtime->format) >> 3;
68 /* assume it is mono! */
69 val = runtime->dma_area[chip->playback_ptr + fmt_size - 1];
70 if (snd_pcm_format_signed(runtime->format))
71 val ^= 0x80;
Stas Sergeev9ab4d072008-03-03 10:53:54 +010072 timer_cnt = val * CUR_DIV() / 256;
73
74 if (timer_cnt && chip->enable) {
75 spin_lock(&i8253_lock);
76 if (!nforce_wa) {
77 outb_p(chip->val61, 0x61);
78 outb_p(timer_cnt, 0x42);
79 outb(chip->val61 ^ 1, 0x61);
80 } else {
81 outb(chip->val61 ^ 2, 0x61);
82 chip->thalf = 1;
83 }
84 spin_unlock(&i8253_lock);
85 }
86
87 period_bytes = snd_pcm_lib_period_bytes(substream);
88 buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
Takashi Iwai96c7d472008-08-11 10:18:39 +020089
90 spin_lock_irqsave(&chip->substream_lock, flags);
Stas Sergeev4dfd7952008-05-17 08:44:41 +020091 chip->playback_ptr += PCSP_INDEX_INC() * fmt_size;
Stas Sergeev9ab4d072008-03-03 10:53:54 +010092 periods_elapsed = chip->playback_ptr - chip->period_ptr;
93 if (periods_elapsed < 0) {
Stas Sergeev42ece6c2008-05-18 18:30:03 +020094#if PCSP_DEBUG
95 printk(KERN_INFO "PCSP: buffer_bytes mod period_bytes != 0 ? "
Stas Sergeev9ab4d072008-03-03 10:53:54 +010096 "(%zi %zi %zi)\n",
97 chip->playback_ptr, period_bytes, buffer_bytes);
Stas Sergeev42ece6c2008-05-18 18:30:03 +020098#endif
Stas Sergeev9ab4d072008-03-03 10:53:54 +010099 periods_elapsed += buffer_bytes;
100 }
101 periods_elapsed /= period_bytes;
102 /* wrap the pointer _before_ calling snd_pcm_period_elapsed(),
103 * or ALSA will BUG on us. */
104 chip->playback_ptr %= buffer_bytes;
105
Stas Sergeev9ab4d072008-03-03 10:53:54 +0100106 if (periods_elapsed) {
Stas Sergeev9ab4d072008-03-03 10:53:54 +0100107 chip->period_ptr += periods_elapsed * period_bytes;
108 chip->period_ptr %= buffer_bytes;
Takashi Iwai96c7d472008-08-11 10:18:39 +0200109 tasklet_schedule(&pcsp_pcm_tasklet);
Stas Sergeev9ab4d072008-03-03 10:53:54 +0100110 }
Takashi Iwai96c7d472008-08-11 10:18:39 +0200111 spin_unlock_irqrestore(&chip->substream_lock, flags);
Stas Sergeev9ab4d072008-03-03 10:53:54 +0100112
113 if (!atomic_read(&chip->timer_active))
Takashi Iwai96c7d472008-08-11 10:18:39 +0200114 goto stop;
Stas Sergeev9ab4d072008-03-03 10:53:54 +0100115
116 chip->ns_rem = PCSP_PERIOD_NS();
117 ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem);
118 chip->ns_rem -= ns;
119 hrtimer_forward(&chip->timer, chip->timer.expires, ktime_set(0, ns));
120 return HRTIMER_RESTART;
121
Takashi Iwai96c7d472008-08-11 10:18:39 +0200122 stop:
Stas Sergeev9ab4d072008-03-03 10:53:54 +0100123 return HRTIMER_NORESTART;
124}
125
126static void pcsp_start_playing(struct snd_pcsp *chip)
127{
128#if PCSP_DEBUG
129 printk(KERN_INFO "PCSP: start_playing called\n");
130#endif
131 if (atomic_read(&chip->timer_active)) {
132 printk(KERN_ERR "PCSP: Timer already active\n");
133 return;
134 }
135
136 spin_lock(&i8253_lock);
137 chip->val61 = inb(0x61) | 0x03;
138 outb_p(0x92, 0x43); /* binary, mode 1, LSB only, ch 2 */
139 spin_unlock(&i8253_lock);
140 atomic_set(&chip->timer_active, 1);
141 chip->thalf = 0;
142
Stas Sergeev4b7afb02008-05-20 11:47:29 +0200143 hrtimer_start(&pcsp_chip.timer, ktime_set(0, 0), HRTIMER_MODE_REL);
Stas Sergeev9ab4d072008-03-03 10:53:54 +0100144}
145
146static void pcsp_stop_playing(struct snd_pcsp *chip)
147{
148#if PCSP_DEBUG
149 printk(KERN_INFO "PCSP: stop_playing called\n");
150#endif
151 if (!atomic_read(&chip->timer_active))
152 return;
153
154 atomic_set(&chip->timer_active, 0);
155 spin_lock(&i8253_lock);
156 /* restore the timer */
157 outb_p(0xb6, 0x43); /* binary, mode 3, LSB/MSB, ch 2 */
158 outb(chip->val61 & 0xFC, 0x61);
159 spin_unlock(&i8253_lock);
160}
161
Takashi Iwai96c7d472008-08-11 10:18:39 +0200162/*
163 * Force to stop and sync the stream
164 */
165void pcsp_sync_stop(struct snd_pcsp *chip)
166{
167 local_irq_disable();
168 pcsp_stop_playing(chip);
169 local_irq_enable();
170 hrtimer_cancel(&chip->timer);
171 tasklet_kill(&pcsp_pcm_tasklet);
172}
173
Stas Sergeev9ab4d072008-03-03 10:53:54 +0100174static int snd_pcsp_playback_close(struct snd_pcm_substream *substream)
175{
176 struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
177#if PCSP_DEBUG
178 printk(KERN_INFO "PCSP: close called\n");
179#endif
Takashi Iwai96c7d472008-08-11 10:18:39 +0200180 pcsp_sync_stop(chip);
Stas Sergeev9ab4d072008-03-03 10:53:54 +0100181 chip->playback_substream = NULL;
Stas Sergeev9ab4d072008-03-03 10:53:54 +0100182 return 0;
183}
184
185static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream,
186 struct snd_pcm_hw_params *hw_params)
187{
Takashi Iwai96c7d472008-08-11 10:18:39 +0200188 struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
Stas Sergeev9ab4d072008-03-03 10:53:54 +0100189 int err;
Takashi Iwai96c7d472008-08-11 10:18:39 +0200190 pcsp_sync_stop(chip);
Stas Sergeev9ab4d072008-03-03 10:53:54 +0100191 err = snd_pcm_lib_malloc_pages(substream,
192 params_buffer_bytes(hw_params));
193 if (err < 0)
194 return err;
195 return 0;
196}
197
198static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream)
199{
Takashi Iwai96c7d472008-08-11 10:18:39 +0200200 struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
Stas Sergeev9ab4d072008-03-03 10:53:54 +0100201#if PCSP_DEBUG
202 printk(KERN_INFO "PCSP: hw_free called\n");
203#endif
Takashi Iwai96c7d472008-08-11 10:18:39 +0200204 pcsp_sync_stop(chip);
Stas Sergeev9ab4d072008-03-03 10:53:54 +0100205 return snd_pcm_lib_free_pages(substream);
206}
207
208static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream)
209{
210 struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
211#if PCSP_DEBUG
212 printk(KERN_INFO "PCSP: prepare called, "
213 "size=%zi psize=%zi f=%zi f1=%i\n",
214 snd_pcm_lib_buffer_bytes(substream),
215 snd_pcm_lib_period_bytes(substream),
216 snd_pcm_lib_buffer_bytes(substream) /
217 snd_pcm_lib_period_bytes(substream),
218 substream->runtime->periods);
219#endif
Takashi Iwai96c7d472008-08-11 10:18:39 +0200220 pcsp_sync_stop(chip);
Stas Sergeev9ab4d072008-03-03 10:53:54 +0100221 chip->playback_ptr = 0;
222 chip->period_ptr = 0;
223 return 0;
224}
225
226static int snd_pcsp_trigger(struct snd_pcm_substream *substream, int cmd)
227{
228 struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
229#if PCSP_DEBUG
230 printk(KERN_INFO "PCSP: trigger called\n");
231#endif
232 switch (cmd) {
233 case SNDRV_PCM_TRIGGER_START:
234 case SNDRV_PCM_TRIGGER_RESUME:
235 pcsp_start_playing(chip);
236 break;
237 case SNDRV_PCM_TRIGGER_STOP:
238 case SNDRV_PCM_TRIGGER_SUSPEND:
239 pcsp_stop_playing(chip);
240 break;
241 default:
242 return -EINVAL;
243 }
244 return 0;
245}
246
247static snd_pcm_uframes_t snd_pcsp_playback_pointer(struct snd_pcm_substream
248 *substream)
249{
250 struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
Takashi Iwai96c7d472008-08-11 10:18:39 +0200251 unsigned int pos;
252 spin_lock(&chip->substream_lock);
253 pos = chip->playback_ptr;
254 spin_unlock(&chip->substream_lock);
255 return bytes_to_frames(substream->runtime, pos);
Stas Sergeev9ab4d072008-03-03 10:53:54 +0100256}
257
258static struct snd_pcm_hardware snd_pcsp_playback = {
259 .info = (SNDRV_PCM_INFO_INTERLEAVED |
260 SNDRV_PCM_INFO_HALF_DUPLEX |
261 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
Stas Sergeev4dfd7952008-05-17 08:44:41 +0200262 .formats = (SNDRV_PCM_FMTBIT_U8
263#if DMIX_WANTS_S16
264 | SNDRV_PCM_FMTBIT_S16_LE
265#endif
266 ),
Stas Sergeev9ab4d072008-03-03 10:53:54 +0100267 .rates = SNDRV_PCM_RATE_KNOT,
268 .rate_min = PCSP_DEFAULT_SRATE,
269 .rate_max = PCSP_DEFAULT_SRATE,
270 .channels_min = 1,
271 .channels_max = 1,
272 .buffer_bytes_max = PCSP_BUFFER_SIZE,
273 .period_bytes_min = 64,
274 .period_bytes_max = PCSP_MAX_PERIOD_SIZE,
275 .periods_min = 2,
276 .periods_max = PCSP_MAX_PERIODS,
277 .fifo_size = 0,
278};
279
280static int snd_pcsp_playback_open(struct snd_pcm_substream *substream)
281{
282 struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
283 struct snd_pcm_runtime *runtime = substream->runtime;
284#if PCSP_DEBUG
285 printk(KERN_INFO "PCSP: open called\n");
286#endif
287 if (atomic_read(&chip->timer_active)) {
288 printk(KERN_ERR "PCSP: still active!!\n");
289 return -EBUSY;
290 }
291 runtime->hw = snd_pcsp_playback;
292 chip->playback_substream = substream;
Stas Sergeev9ab4d072008-03-03 10:53:54 +0100293 return 0;
294}
295
296static struct snd_pcm_ops snd_pcsp_playback_ops = {
297 .open = snd_pcsp_playback_open,
298 .close = snd_pcsp_playback_close,
299 .ioctl = snd_pcm_lib_ioctl,
300 .hw_params = snd_pcsp_playback_hw_params,
301 .hw_free = snd_pcsp_playback_hw_free,
302 .prepare = snd_pcsp_playback_prepare,
303 .trigger = snd_pcsp_trigger,
304 .pointer = snd_pcsp_playback_pointer,
305};
306
307int __devinit snd_pcsp_new_pcm(struct snd_pcsp *chip)
308{
309 int err;
310
311 err = snd_pcm_new(chip->card, "pcspeaker", 0, 1, 0, &chip->pcm);
312 if (err < 0)
313 return err;
314
315 snd_pcm_set_ops(chip->pcm, SNDRV_PCM_STREAM_PLAYBACK,
316 &snd_pcsp_playback_ops);
317
318 chip->pcm->private_data = chip;
319 chip->pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
320 strcpy(chip->pcm->name, "pcsp");
321
322 snd_pcm_lib_preallocate_pages_for_all(chip->pcm,
323 SNDRV_DMA_TYPE_CONTINUOUS,
324 snd_dma_continuous_data
325 (GFP_KERNEL), PCSP_BUFFER_SIZE,
326 PCSP_BUFFER_SIZE);
327
328 return 0;
329}