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