Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
awilliam
GitHub Repository: awilliam/linux-vfio
Path: blob/master/sound/drivers/pcsp/pcsp_lib.c
10817 views
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/gfp.h>
11
#include <linux/moduleparam.h>
12
#include <linux/interrupt.h>
13
#include <sound/pcm.h>
14
#include <asm/io.h>
15
#include "pcsp.h"
16
17
static int nforce_wa;
18
module_param(nforce_wa, bool, 0444);
19
MODULE_PARM_DESC(nforce_wa, "Apply NForce chipset workaround "
20
"(expect bad sound)");
21
22
#define DMIX_WANTS_S16 1
23
24
/*
25
* Call snd_pcm_period_elapsed in a tasklet
26
* This avoids spinlock messes and long-running irq contexts
27
*/
28
static void pcsp_call_pcm_elapsed(unsigned long priv)
29
{
30
if (atomic_read(&pcsp_chip.timer_active)) {
31
struct snd_pcm_substream *substream;
32
substream = pcsp_chip.playback_substream;
33
if (substream)
34
snd_pcm_period_elapsed(substream);
35
}
36
}
37
38
static DECLARE_TASKLET(pcsp_pcm_tasklet, pcsp_call_pcm_elapsed, 0);
39
40
/* write the port and returns the next expire time in ns;
41
* called at the trigger-start and in hrtimer callback
42
*/
43
static u64 pcsp_timer_update(struct snd_pcsp *chip)
44
{
45
unsigned char timer_cnt, val;
46
u64 ns;
47
struct snd_pcm_substream *substream;
48
struct snd_pcm_runtime *runtime;
49
unsigned long flags;
50
51
if (chip->thalf) {
52
outb(chip->val61, 0x61);
53
chip->thalf = 0;
54
return chip->ns_rem;
55
}
56
57
substream = chip->playback_substream;
58
if (!substream)
59
return 0;
60
61
runtime = substream->runtime;
62
/* assume it is mono! */
63
val = runtime->dma_area[chip->playback_ptr + chip->fmt_size - 1];
64
if (chip->is_signed)
65
val ^= 0x80;
66
timer_cnt = val * CUR_DIV() / 256;
67
68
if (timer_cnt && chip->enable) {
69
raw_spin_lock_irqsave(&i8253_lock, flags);
70
if (!nforce_wa) {
71
outb_p(chip->val61, 0x61);
72
outb_p(timer_cnt, 0x42);
73
outb(chip->val61 ^ 1, 0x61);
74
} else {
75
outb(chip->val61 ^ 2, 0x61);
76
chip->thalf = 1;
77
}
78
raw_spin_unlock_irqrestore(&i8253_lock, flags);
79
}
80
81
chip->ns_rem = PCSP_PERIOD_NS();
82
ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem);
83
chip->ns_rem -= ns;
84
return ns;
85
}
86
87
static void pcsp_pointer_update(struct snd_pcsp *chip)
88
{
89
struct snd_pcm_substream *substream;
90
size_t period_bytes, buffer_bytes;
91
int periods_elapsed;
92
unsigned long flags;
93
94
/* update the playback position */
95
substream = chip->playback_substream;
96
if (!substream)
97
return;
98
99
period_bytes = snd_pcm_lib_period_bytes(substream);
100
buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
101
102
spin_lock_irqsave(&chip->substream_lock, flags);
103
chip->playback_ptr += PCSP_INDEX_INC() * chip->fmt_size;
104
periods_elapsed = chip->playback_ptr - chip->period_ptr;
105
if (periods_elapsed < 0) {
106
#if PCSP_DEBUG
107
printk(KERN_INFO "PCSP: buffer_bytes mod period_bytes != 0 ? "
108
"(%zi %zi %zi)\n",
109
chip->playback_ptr, period_bytes, buffer_bytes);
110
#endif
111
periods_elapsed += buffer_bytes;
112
}
113
periods_elapsed /= period_bytes;
114
/* wrap the pointer _before_ calling snd_pcm_period_elapsed(),
115
* or ALSA will BUG on us. */
116
chip->playback_ptr %= buffer_bytes;
117
118
if (periods_elapsed) {
119
chip->period_ptr += periods_elapsed * period_bytes;
120
chip->period_ptr %= buffer_bytes;
121
}
122
spin_unlock_irqrestore(&chip->substream_lock, flags);
123
124
if (periods_elapsed)
125
tasklet_schedule(&pcsp_pcm_tasklet);
126
}
127
128
enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle)
129
{
130
struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer);
131
int pointer_update;
132
u64 ns;
133
134
if (!atomic_read(&chip->timer_active) || !chip->playback_substream)
135
return HRTIMER_NORESTART;
136
137
pointer_update = !chip->thalf;
138
ns = pcsp_timer_update(chip);
139
if (!ns) {
140
printk(KERN_WARNING "PCSP: unexpected stop\n");
141
return HRTIMER_NORESTART;
142
}
143
144
if (pointer_update)
145
pcsp_pointer_update(chip);
146
147
hrtimer_forward(handle, hrtimer_get_expires(handle), ns_to_ktime(ns));
148
149
return HRTIMER_RESTART;
150
}
151
152
static int pcsp_start_playing(struct snd_pcsp *chip)
153
{
154
#if PCSP_DEBUG
155
printk(KERN_INFO "PCSP: start_playing called\n");
156
#endif
157
if (atomic_read(&chip->timer_active)) {
158
printk(KERN_ERR "PCSP: Timer already active\n");
159
return -EIO;
160
}
161
162
raw_spin_lock(&i8253_lock);
163
chip->val61 = inb(0x61) | 0x03;
164
outb_p(0x92, 0x43); /* binary, mode 1, LSB only, ch 2 */
165
raw_spin_unlock(&i8253_lock);
166
atomic_set(&chip->timer_active, 1);
167
chip->thalf = 0;
168
169
hrtimer_start(&pcsp_chip.timer, ktime_set(0, 0), HRTIMER_MODE_REL);
170
return 0;
171
}
172
173
static void pcsp_stop_playing(struct snd_pcsp *chip)
174
{
175
#if PCSP_DEBUG
176
printk(KERN_INFO "PCSP: stop_playing called\n");
177
#endif
178
if (!atomic_read(&chip->timer_active))
179
return;
180
181
atomic_set(&chip->timer_active, 0);
182
raw_spin_lock(&i8253_lock);
183
/* restore the timer */
184
outb_p(0xb6, 0x43); /* binary, mode 3, LSB/MSB, ch 2 */
185
outb(chip->val61 & 0xFC, 0x61);
186
raw_spin_unlock(&i8253_lock);
187
}
188
189
/*
190
* Force to stop and sync the stream
191
*/
192
void pcsp_sync_stop(struct snd_pcsp *chip)
193
{
194
local_irq_disable();
195
pcsp_stop_playing(chip);
196
local_irq_enable();
197
hrtimer_cancel(&chip->timer);
198
tasklet_kill(&pcsp_pcm_tasklet);
199
}
200
201
static int snd_pcsp_playback_close(struct snd_pcm_substream *substream)
202
{
203
struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
204
#if PCSP_DEBUG
205
printk(KERN_INFO "PCSP: close called\n");
206
#endif
207
pcsp_sync_stop(chip);
208
chip->playback_substream = NULL;
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
struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
216
int err;
217
pcsp_sync_stop(chip);
218
err = snd_pcm_lib_malloc_pages(substream,
219
params_buffer_bytes(hw_params));
220
if (err < 0)
221
return err;
222
return 0;
223
}
224
225
static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream)
226
{
227
struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
228
#if PCSP_DEBUG
229
printk(KERN_INFO "PCSP: hw_free called\n");
230
#endif
231
pcsp_sync_stop(chip);
232
return snd_pcm_lib_free_pages(substream);
233
}
234
235
static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream)
236
{
237
struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
238
pcsp_sync_stop(chip);
239
chip->playback_ptr = 0;
240
chip->period_ptr = 0;
241
chip->fmt_size =
242
snd_pcm_format_physical_width(substream->runtime->format) >> 3;
243
chip->is_signed = snd_pcm_format_signed(substream->runtime->format);
244
#if PCSP_DEBUG
245
printk(KERN_INFO "PCSP: prepare called, "
246
"size=%zi psize=%zi f=%zi f1=%i fsize=%i\n",
247
snd_pcm_lib_buffer_bytes(substream),
248
snd_pcm_lib_period_bytes(substream),
249
snd_pcm_lib_buffer_bytes(substream) /
250
snd_pcm_lib_period_bytes(substream),
251
substream->runtime->periods,
252
chip->fmt_size);
253
#endif
254
return 0;
255
}
256
257
static int snd_pcsp_trigger(struct snd_pcm_substream *substream, int cmd)
258
{
259
struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
260
#if PCSP_DEBUG
261
printk(KERN_INFO "PCSP: trigger called\n");
262
#endif
263
switch (cmd) {
264
case SNDRV_PCM_TRIGGER_START:
265
case SNDRV_PCM_TRIGGER_RESUME:
266
return pcsp_start_playing(chip);
267
case SNDRV_PCM_TRIGGER_STOP:
268
case SNDRV_PCM_TRIGGER_SUSPEND:
269
pcsp_stop_playing(chip);
270
break;
271
default:
272
return -EINVAL;
273
}
274
return 0;
275
}
276
277
static snd_pcm_uframes_t snd_pcsp_playback_pointer(struct snd_pcm_substream
278
*substream)
279
{
280
struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
281
unsigned int pos;
282
spin_lock(&chip->substream_lock);
283
pos = chip->playback_ptr;
284
spin_unlock(&chip->substream_lock);
285
return bytes_to_frames(substream->runtime, pos);
286
}
287
288
static struct snd_pcm_hardware snd_pcsp_playback = {
289
.info = (SNDRV_PCM_INFO_INTERLEAVED |
290
SNDRV_PCM_INFO_HALF_DUPLEX |
291
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
292
.formats = (SNDRV_PCM_FMTBIT_U8
293
#if DMIX_WANTS_S16
294
| SNDRV_PCM_FMTBIT_S16_LE
295
#endif
296
),
297
.rates = SNDRV_PCM_RATE_KNOT,
298
.rate_min = PCSP_DEFAULT_SRATE,
299
.rate_max = PCSP_DEFAULT_SRATE,
300
.channels_min = 1,
301
.channels_max = 1,
302
.buffer_bytes_max = PCSP_BUFFER_SIZE,
303
.period_bytes_min = 64,
304
.period_bytes_max = PCSP_MAX_PERIOD_SIZE,
305
.periods_min = 2,
306
.periods_max = PCSP_MAX_PERIODS,
307
.fifo_size = 0,
308
};
309
310
static int snd_pcsp_playback_open(struct snd_pcm_substream *substream)
311
{
312
struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
313
struct snd_pcm_runtime *runtime = substream->runtime;
314
#if PCSP_DEBUG
315
printk(KERN_INFO "PCSP: open called\n");
316
#endif
317
if (atomic_read(&chip->timer_active)) {
318
printk(KERN_ERR "PCSP: still active!!\n");
319
return -EBUSY;
320
}
321
runtime->hw = snd_pcsp_playback;
322
chip->playback_substream = substream;
323
return 0;
324
}
325
326
static struct snd_pcm_ops snd_pcsp_playback_ops = {
327
.open = snd_pcsp_playback_open,
328
.close = snd_pcsp_playback_close,
329
.ioctl = snd_pcm_lib_ioctl,
330
.hw_params = snd_pcsp_playback_hw_params,
331
.hw_free = snd_pcsp_playback_hw_free,
332
.prepare = snd_pcsp_playback_prepare,
333
.trigger = snd_pcsp_trigger,
334
.pointer = snd_pcsp_playback_pointer,
335
};
336
337
int __devinit snd_pcsp_new_pcm(struct snd_pcsp *chip)
338
{
339
int err;
340
341
err = snd_pcm_new(chip->card, "pcspeaker", 0, 1, 0, &chip->pcm);
342
if (err < 0)
343
return err;
344
345
snd_pcm_set_ops(chip->pcm, SNDRV_PCM_STREAM_PLAYBACK,
346
&snd_pcsp_playback_ops);
347
348
chip->pcm->private_data = chip;
349
chip->pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
350
strcpy(chip->pcm->name, "pcsp");
351
352
snd_pcm_lib_preallocate_pages_for_all(chip->pcm,
353
SNDRV_DMA_TYPE_CONTINUOUS,
354
snd_dma_continuous_data
355
(GFP_KERNEL), PCSP_BUFFER_SIZE,
356
PCSP_BUFFER_SIZE);
357
358
return 0;
359
}
360
361