xref: /openbmc/linux/sound/drivers/pcsp/pcsp_lib.c (revision 96c7d478efad594e483ee8a826395b1342404885)
19ab4d072SStas Sergeev /*
29ab4d072SStas Sergeev  * PC-Speaker driver for Linux
39ab4d072SStas Sergeev  *
49ab4d072SStas Sergeev  * Copyright (C) 1993-1997  Michael Beck
59ab4d072SStas Sergeev  * Copyright (C) 1997-2001  David Woodhouse
69ab4d072SStas Sergeev  * Copyright (C) 2001-2008  Stas Sergeev
79ab4d072SStas Sergeev  */
89ab4d072SStas Sergeev 
99ab4d072SStas Sergeev #include <linux/module.h>
109ab4d072SStas Sergeev #include <linux/moduleparam.h>
11*96c7d478STakashi Iwai #include <linux/interrupt.h>
129ab4d072SStas Sergeev #include <sound/pcm.h>
139ab4d072SStas Sergeev #include <asm/io.h>
149ab4d072SStas Sergeev #include "pcsp.h"
159ab4d072SStas Sergeev 
169ab4d072SStas Sergeev static int nforce_wa;
179ab4d072SStas Sergeev module_param(nforce_wa, bool, 0444);
189ab4d072SStas Sergeev MODULE_PARM_DESC(nforce_wa, "Apply NForce chipset workaround "
199ab4d072SStas Sergeev 		"(expect bad sound)");
209ab4d072SStas Sergeev 
214dfd7954SStas Sergeev #define DMIX_WANTS_S16	1
224dfd7954SStas Sergeev 
23*96c7d478STakashi Iwai /*
24*96c7d478STakashi Iwai  * Call snd_pcm_period_elapsed in a tasklet
25*96c7d478STakashi Iwai  * This avoids spinlock messes and long-running irq contexts
26*96c7d478STakashi Iwai  */
27*96c7d478STakashi Iwai static void pcsp_call_pcm_elapsed(unsigned long priv)
28*96c7d478STakashi Iwai {
29*96c7d478STakashi Iwai 	if (atomic_read(&pcsp_chip.timer_active)) {
30*96c7d478STakashi Iwai 		struct snd_pcm_substream *substream;
31*96c7d478STakashi Iwai 		substream = pcsp_chip.playback_substream;
32*96c7d478STakashi Iwai 		if (substream)
33*96c7d478STakashi Iwai 			snd_pcm_period_elapsed(substream);
34*96c7d478STakashi Iwai 	}
35*96c7d478STakashi Iwai }
36*96c7d478STakashi Iwai 
37*96c7d478STakashi Iwai static DECLARE_TASKLET(pcsp_pcm_tasklet, pcsp_call_pcm_elapsed, 0);
38*96c7d478STakashi Iwai 
399ab4d072SStas Sergeev enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle)
409ab4d072SStas Sergeev {
419ab4d072SStas Sergeev 	unsigned char timer_cnt, val;
424dfd7954SStas Sergeev 	int fmt_size, periods_elapsed;
439ab4d072SStas Sergeev 	u64 ns;
449ab4d072SStas Sergeev 	size_t period_bytes, buffer_bytes;
459ab4d072SStas Sergeev 	struct snd_pcm_substream *substream;
469ab4d072SStas Sergeev 	struct snd_pcm_runtime *runtime;
479ab4d072SStas Sergeev 	struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer);
48*96c7d478STakashi Iwai 	unsigned long flags;
499ab4d072SStas Sergeev 
509ab4d072SStas Sergeev 	if (chip->thalf) {
519ab4d072SStas Sergeev 		outb(chip->val61, 0x61);
529ab4d072SStas Sergeev 		chip->thalf = 0;
539ab4d072SStas Sergeev 		if (!atomic_read(&chip->timer_active))
54*96c7d478STakashi Iwai 			goto stop;
559ab4d072SStas Sergeev 		hrtimer_forward(&chip->timer, chip->timer.expires,
569ab4d072SStas Sergeev 				ktime_set(0, chip->ns_rem));
579ab4d072SStas Sergeev 		return HRTIMER_RESTART;
589ab4d072SStas Sergeev 	}
599ab4d072SStas Sergeev 
609ab4d072SStas Sergeev 	if (!atomic_read(&chip->timer_active))
61*96c7d478STakashi Iwai 		goto stop;
62*96c7d478STakashi Iwai 	substream = chip->playback_substream;
63*96c7d478STakashi Iwai 	if (!substream)
64*96c7d478STakashi Iwai 		goto stop;
659ab4d072SStas Sergeev 
669ab4d072SStas Sergeev 	runtime = substream->runtime;
674dfd7954SStas Sergeev 	fmt_size = snd_pcm_format_physical_width(runtime->format) >> 3;
684dfd7954SStas Sergeev 	/* assume it is mono! */
694dfd7954SStas Sergeev 	val = runtime->dma_area[chip->playback_ptr + fmt_size - 1];
704dfd7954SStas Sergeev 	if (snd_pcm_format_signed(runtime->format))
714dfd7954SStas Sergeev 		val ^= 0x80;
729ab4d072SStas Sergeev 	timer_cnt = val * CUR_DIV() / 256;
739ab4d072SStas Sergeev 
749ab4d072SStas Sergeev 	if (timer_cnt && chip->enable) {
759ab4d072SStas Sergeev 		spin_lock(&i8253_lock);
769ab4d072SStas Sergeev 		if (!nforce_wa) {
779ab4d072SStas Sergeev 			outb_p(chip->val61, 0x61);
789ab4d072SStas Sergeev 			outb_p(timer_cnt, 0x42);
799ab4d072SStas Sergeev 			outb(chip->val61 ^ 1, 0x61);
809ab4d072SStas Sergeev 		} else {
819ab4d072SStas Sergeev 			outb(chip->val61 ^ 2, 0x61);
829ab4d072SStas Sergeev 			chip->thalf = 1;
839ab4d072SStas Sergeev 		}
849ab4d072SStas Sergeev 		spin_unlock(&i8253_lock);
859ab4d072SStas Sergeev 	}
869ab4d072SStas Sergeev 
879ab4d072SStas Sergeev 	period_bytes = snd_pcm_lib_period_bytes(substream);
889ab4d072SStas Sergeev 	buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
89*96c7d478STakashi Iwai 
90*96c7d478STakashi Iwai 	spin_lock_irqsave(&chip->substream_lock, flags);
914dfd7954SStas Sergeev 	chip->playback_ptr += PCSP_INDEX_INC() * fmt_size;
929ab4d072SStas Sergeev 	periods_elapsed = chip->playback_ptr - chip->period_ptr;
939ab4d072SStas Sergeev 	if (periods_elapsed < 0) {
9442ece6c1SStas Sergeev #if PCSP_DEBUG
9542ece6c1SStas Sergeev 		printk(KERN_INFO "PCSP: buffer_bytes mod period_bytes != 0 ? "
969ab4d072SStas Sergeev 			"(%zi %zi %zi)\n",
979ab4d072SStas Sergeev 			chip->playback_ptr, period_bytes, buffer_bytes);
9842ece6c1SStas Sergeev #endif
999ab4d072SStas Sergeev 		periods_elapsed += buffer_bytes;
1009ab4d072SStas Sergeev 	}
1019ab4d072SStas Sergeev 	periods_elapsed /= period_bytes;
1029ab4d072SStas Sergeev 	/* wrap the pointer _before_ calling snd_pcm_period_elapsed(),
1039ab4d072SStas Sergeev 	 * or ALSA will BUG on us. */
1049ab4d072SStas Sergeev 	chip->playback_ptr %= buffer_bytes;
1059ab4d072SStas Sergeev 
1069ab4d072SStas Sergeev 	if (periods_elapsed) {
1079ab4d072SStas Sergeev 		chip->period_ptr += periods_elapsed * period_bytes;
1089ab4d072SStas Sergeev 		chip->period_ptr %= buffer_bytes;
109*96c7d478STakashi Iwai 		tasklet_schedule(&pcsp_pcm_tasklet);
1109ab4d072SStas Sergeev 	}
111*96c7d478STakashi Iwai 	spin_unlock_irqrestore(&chip->substream_lock, flags);
1129ab4d072SStas Sergeev 
1139ab4d072SStas Sergeev 	if (!atomic_read(&chip->timer_active))
114*96c7d478STakashi Iwai 		goto stop;
1159ab4d072SStas Sergeev 
1169ab4d072SStas Sergeev 	chip->ns_rem = PCSP_PERIOD_NS();
1179ab4d072SStas Sergeev 	ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem);
1189ab4d072SStas Sergeev 	chip->ns_rem -= ns;
1199ab4d072SStas Sergeev 	hrtimer_forward(&chip->timer, chip->timer.expires, ktime_set(0, ns));
1209ab4d072SStas Sergeev 	return HRTIMER_RESTART;
1219ab4d072SStas Sergeev 
122*96c7d478STakashi Iwai  stop:
1239ab4d072SStas Sergeev 	return HRTIMER_NORESTART;
1249ab4d072SStas Sergeev }
1259ab4d072SStas Sergeev 
1269ab4d072SStas Sergeev static void pcsp_start_playing(struct snd_pcsp *chip)
1279ab4d072SStas Sergeev {
1289ab4d072SStas Sergeev #if PCSP_DEBUG
1299ab4d072SStas Sergeev 	printk(KERN_INFO "PCSP: start_playing called\n");
1309ab4d072SStas Sergeev #endif
1319ab4d072SStas Sergeev 	if (atomic_read(&chip->timer_active)) {
1329ab4d072SStas Sergeev 		printk(KERN_ERR "PCSP: Timer already active\n");
1339ab4d072SStas Sergeev 		return;
1349ab4d072SStas Sergeev 	}
1359ab4d072SStas Sergeev 
1369ab4d072SStas Sergeev 	spin_lock(&i8253_lock);
1379ab4d072SStas Sergeev 	chip->val61 = inb(0x61) | 0x03;
1389ab4d072SStas Sergeev 	outb_p(0x92, 0x43);	/* binary, mode 1, LSB only, ch 2 */
1399ab4d072SStas Sergeev 	spin_unlock(&i8253_lock);
1409ab4d072SStas Sergeev 	atomic_set(&chip->timer_active, 1);
1419ab4d072SStas Sergeev 	chip->thalf = 0;
1429ab4d072SStas Sergeev 
1434b7afb0dSStas Sergeev 	hrtimer_start(&pcsp_chip.timer, ktime_set(0, 0), HRTIMER_MODE_REL);
1449ab4d072SStas Sergeev }
1459ab4d072SStas Sergeev 
1469ab4d072SStas Sergeev static void pcsp_stop_playing(struct snd_pcsp *chip)
1479ab4d072SStas Sergeev {
1489ab4d072SStas Sergeev #if PCSP_DEBUG
1499ab4d072SStas Sergeev 	printk(KERN_INFO "PCSP: stop_playing called\n");
1509ab4d072SStas Sergeev #endif
1519ab4d072SStas Sergeev 	if (!atomic_read(&chip->timer_active))
1529ab4d072SStas Sergeev 		return;
1539ab4d072SStas Sergeev 
1549ab4d072SStas Sergeev 	atomic_set(&chip->timer_active, 0);
1559ab4d072SStas Sergeev 	spin_lock(&i8253_lock);
1569ab4d072SStas Sergeev 	/* restore the timer */
1579ab4d072SStas Sergeev 	outb_p(0xb6, 0x43);	/* binary, mode 3, LSB/MSB, ch 2 */
1589ab4d072SStas Sergeev 	outb(chip->val61 & 0xFC, 0x61);
1599ab4d072SStas Sergeev 	spin_unlock(&i8253_lock);
1609ab4d072SStas Sergeev }
1619ab4d072SStas Sergeev 
162*96c7d478STakashi Iwai /*
163*96c7d478STakashi Iwai  * Force to stop and sync the stream
164*96c7d478STakashi Iwai  */
165*96c7d478STakashi Iwai void pcsp_sync_stop(struct snd_pcsp *chip)
166*96c7d478STakashi Iwai {
167*96c7d478STakashi Iwai 	local_irq_disable();
168*96c7d478STakashi Iwai 	pcsp_stop_playing(chip);
169*96c7d478STakashi Iwai 	local_irq_enable();
170*96c7d478STakashi Iwai 	hrtimer_cancel(&chip->timer);
171*96c7d478STakashi Iwai 	tasklet_kill(&pcsp_pcm_tasklet);
172*96c7d478STakashi Iwai }
173*96c7d478STakashi Iwai 
1749ab4d072SStas Sergeev static int snd_pcsp_playback_close(struct snd_pcm_substream *substream)
1759ab4d072SStas Sergeev {
1769ab4d072SStas Sergeev 	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
1779ab4d072SStas Sergeev #if PCSP_DEBUG
1789ab4d072SStas Sergeev 	printk(KERN_INFO "PCSP: close called\n");
1799ab4d072SStas Sergeev #endif
180*96c7d478STakashi Iwai 	pcsp_sync_stop(chip);
1819ab4d072SStas Sergeev 	chip->playback_substream = NULL;
1829ab4d072SStas Sergeev 	return 0;
1839ab4d072SStas Sergeev }
1849ab4d072SStas Sergeev 
1859ab4d072SStas Sergeev static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream,
1869ab4d072SStas Sergeev 				       struct snd_pcm_hw_params *hw_params)
1879ab4d072SStas Sergeev {
188*96c7d478STakashi Iwai 	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
1899ab4d072SStas Sergeev 	int err;
190*96c7d478STakashi Iwai 	pcsp_sync_stop(chip);
1919ab4d072SStas Sergeev 	err = snd_pcm_lib_malloc_pages(substream,
1929ab4d072SStas Sergeev 				      params_buffer_bytes(hw_params));
1939ab4d072SStas Sergeev 	if (err < 0)
1949ab4d072SStas Sergeev 		return err;
1959ab4d072SStas Sergeev 	return 0;
1969ab4d072SStas Sergeev }
1979ab4d072SStas Sergeev 
1989ab4d072SStas Sergeev static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream)
1999ab4d072SStas Sergeev {
200*96c7d478STakashi Iwai 	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
2019ab4d072SStas Sergeev #if PCSP_DEBUG
2029ab4d072SStas Sergeev 	printk(KERN_INFO "PCSP: hw_free called\n");
2039ab4d072SStas Sergeev #endif
204*96c7d478STakashi Iwai 	pcsp_sync_stop(chip);
2059ab4d072SStas Sergeev 	return snd_pcm_lib_free_pages(substream);
2069ab4d072SStas Sergeev }
2079ab4d072SStas Sergeev 
2089ab4d072SStas Sergeev static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream)
2099ab4d072SStas Sergeev {
2109ab4d072SStas Sergeev 	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
2119ab4d072SStas Sergeev #if PCSP_DEBUG
2129ab4d072SStas Sergeev 	printk(KERN_INFO "PCSP: prepare called, "
2139ab4d072SStas Sergeev 			"size=%zi psize=%zi f=%zi f1=%i\n",
2149ab4d072SStas Sergeev 			snd_pcm_lib_buffer_bytes(substream),
2159ab4d072SStas Sergeev 			snd_pcm_lib_period_bytes(substream),
2169ab4d072SStas Sergeev 			snd_pcm_lib_buffer_bytes(substream) /
2179ab4d072SStas Sergeev 			snd_pcm_lib_period_bytes(substream),
2189ab4d072SStas Sergeev 			substream->runtime->periods);
2199ab4d072SStas Sergeev #endif
220*96c7d478STakashi Iwai 	pcsp_sync_stop(chip);
2219ab4d072SStas Sergeev 	chip->playback_ptr = 0;
2229ab4d072SStas Sergeev 	chip->period_ptr = 0;
2239ab4d072SStas Sergeev 	return 0;
2249ab4d072SStas Sergeev }
2259ab4d072SStas Sergeev 
2269ab4d072SStas Sergeev static int snd_pcsp_trigger(struct snd_pcm_substream *substream, int cmd)
2279ab4d072SStas Sergeev {
2289ab4d072SStas Sergeev 	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
2299ab4d072SStas Sergeev #if PCSP_DEBUG
2309ab4d072SStas Sergeev 	printk(KERN_INFO "PCSP: trigger called\n");
2319ab4d072SStas Sergeev #endif
2329ab4d072SStas Sergeev 	switch (cmd) {
2339ab4d072SStas Sergeev 	case SNDRV_PCM_TRIGGER_START:
2349ab4d072SStas Sergeev 	case SNDRV_PCM_TRIGGER_RESUME:
2359ab4d072SStas Sergeev 		pcsp_start_playing(chip);
2369ab4d072SStas Sergeev 		break;
2379ab4d072SStas Sergeev 	case SNDRV_PCM_TRIGGER_STOP:
2389ab4d072SStas Sergeev 	case SNDRV_PCM_TRIGGER_SUSPEND:
2399ab4d072SStas Sergeev 		pcsp_stop_playing(chip);
2409ab4d072SStas Sergeev 		break;
2419ab4d072SStas Sergeev 	default:
2429ab4d072SStas Sergeev 		return -EINVAL;
2439ab4d072SStas Sergeev 	}
2449ab4d072SStas Sergeev 	return 0;
2459ab4d072SStas Sergeev }
2469ab4d072SStas Sergeev 
2479ab4d072SStas Sergeev static snd_pcm_uframes_t snd_pcsp_playback_pointer(struct snd_pcm_substream
2489ab4d072SStas Sergeev 						   *substream)
2499ab4d072SStas Sergeev {
2509ab4d072SStas Sergeev 	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
251*96c7d478STakashi Iwai 	unsigned int pos;
252*96c7d478STakashi Iwai 	spin_lock(&chip->substream_lock);
253*96c7d478STakashi Iwai 	pos = chip->playback_ptr;
254*96c7d478STakashi Iwai 	spin_unlock(&chip->substream_lock);
255*96c7d478STakashi Iwai 	return bytes_to_frames(substream->runtime, pos);
2569ab4d072SStas Sergeev }
2579ab4d072SStas Sergeev 
2589ab4d072SStas Sergeev static struct snd_pcm_hardware snd_pcsp_playback = {
2599ab4d072SStas Sergeev 	.info = (SNDRV_PCM_INFO_INTERLEAVED |
2609ab4d072SStas Sergeev 		 SNDRV_PCM_INFO_HALF_DUPLEX |
2619ab4d072SStas Sergeev 		 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
2624dfd7954SStas Sergeev 	.formats = (SNDRV_PCM_FMTBIT_U8
2634dfd7954SStas Sergeev #if DMIX_WANTS_S16
2644dfd7954SStas Sergeev 		    | SNDRV_PCM_FMTBIT_S16_LE
2654dfd7954SStas Sergeev #endif
2664dfd7954SStas Sergeev 	    ),
2679ab4d072SStas Sergeev 	.rates = SNDRV_PCM_RATE_KNOT,
2689ab4d072SStas Sergeev 	.rate_min = PCSP_DEFAULT_SRATE,
2699ab4d072SStas Sergeev 	.rate_max = PCSP_DEFAULT_SRATE,
2709ab4d072SStas Sergeev 	.channels_min = 1,
2719ab4d072SStas Sergeev 	.channels_max = 1,
2729ab4d072SStas Sergeev 	.buffer_bytes_max = PCSP_BUFFER_SIZE,
2739ab4d072SStas Sergeev 	.period_bytes_min = 64,
2749ab4d072SStas Sergeev 	.period_bytes_max = PCSP_MAX_PERIOD_SIZE,
2759ab4d072SStas Sergeev 	.periods_min = 2,
2769ab4d072SStas Sergeev 	.periods_max = PCSP_MAX_PERIODS,
2779ab4d072SStas Sergeev 	.fifo_size = 0,
2789ab4d072SStas Sergeev };
2799ab4d072SStas Sergeev 
2809ab4d072SStas Sergeev static int snd_pcsp_playback_open(struct snd_pcm_substream *substream)
2819ab4d072SStas Sergeev {
2829ab4d072SStas Sergeev 	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
2839ab4d072SStas Sergeev 	struct snd_pcm_runtime *runtime = substream->runtime;
2849ab4d072SStas Sergeev #if PCSP_DEBUG
2859ab4d072SStas Sergeev 	printk(KERN_INFO "PCSP: open called\n");
2869ab4d072SStas Sergeev #endif
2879ab4d072SStas Sergeev 	if (atomic_read(&chip->timer_active)) {
2889ab4d072SStas Sergeev 		printk(KERN_ERR "PCSP: still active!!\n");
2899ab4d072SStas Sergeev 		return -EBUSY;
2909ab4d072SStas Sergeev 	}
2919ab4d072SStas Sergeev 	runtime->hw = snd_pcsp_playback;
2929ab4d072SStas Sergeev 	chip->playback_substream = substream;
2939ab4d072SStas Sergeev 	return 0;
2949ab4d072SStas Sergeev }
2959ab4d072SStas Sergeev 
2969ab4d072SStas Sergeev static struct snd_pcm_ops snd_pcsp_playback_ops = {
2979ab4d072SStas Sergeev 	.open = snd_pcsp_playback_open,
2989ab4d072SStas Sergeev 	.close = snd_pcsp_playback_close,
2999ab4d072SStas Sergeev 	.ioctl = snd_pcm_lib_ioctl,
3009ab4d072SStas Sergeev 	.hw_params = snd_pcsp_playback_hw_params,
3019ab4d072SStas Sergeev 	.hw_free = snd_pcsp_playback_hw_free,
3029ab4d072SStas Sergeev 	.prepare = snd_pcsp_playback_prepare,
3039ab4d072SStas Sergeev 	.trigger = snd_pcsp_trigger,
3049ab4d072SStas Sergeev 	.pointer = snd_pcsp_playback_pointer,
3059ab4d072SStas Sergeev };
3069ab4d072SStas Sergeev 
3079ab4d072SStas Sergeev int __devinit snd_pcsp_new_pcm(struct snd_pcsp *chip)
3089ab4d072SStas Sergeev {
3099ab4d072SStas Sergeev 	int err;
3109ab4d072SStas Sergeev 
3119ab4d072SStas Sergeev 	err = snd_pcm_new(chip->card, "pcspeaker", 0, 1, 0, &chip->pcm);
3129ab4d072SStas Sergeev 	if (err < 0)
3139ab4d072SStas Sergeev 		return err;
3149ab4d072SStas Sergeev 
3159ab4d072SStas Sergeev 	snd_pcm_set_ops(chip->pcm, SNDRV_PCM_STREAM_PLAYBACK,
3169ab4d072SStas Sergeev 			&snd_pcsp_playback_ops);
3179ab4d072SStas Sergeev 
3189ab4d072SStas Sergeev 	chip->pcm->private_data = chip;
3199ab4d072SStas Sergeev 	chip->pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
3209ab4d072SStas Sergeev 	strcpy(chip->pcm->name, "pcsp");
3219ab4d072SStas Sergeev 
3229ab4d072SStas Sergeev 	snd_pcm_lib_preallocate_pages_for_all(chip->pcm,
3239ab4d072SStas Sergeev 					      SNDRV_DMA_TYPE_CONTINUOUS,
3249ab4d072SStas Sergeev 					      snd_dma_continuous_data
3259ab4d072SStas Sergeev 					      (GFP_KERNEL), PCSP_BUFFER_SIZE,
3269ab4d072SStas Sergeev 					      PCSP_BUFFER_SIZE);
3279ab4d072SStas Sergeev 
3289ab4d072SStas Sergeev 	return 0;
3299ab4d072SStas Sergeev }
330