xref: /openbmc/linux/sound/drivers/pcsp/pcsp_lib.c (revision f2ff7147)
1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
29ab4d072SStas Sergeev /*
39ab4d072SStas Sergeev  * PC-Speaker driver for Linux
49ab4d072SStas Sergeev  *
59ab4d072SStas Sergeev  * Copyright (C) 1993-1997  Michael Beck
69ab4d072SStas Sergeev  * Copyright (C) 1997-2001  David Woodhouse
79ab4d072SStas Sergeev  * Copyright (C) 2001-2008  Stas Sergeev
89ab4d072SStas Sergeev  */
99ab4d072SStas Sergeev 
109ab4d072SStas Sergeev #include <linux/module.h>
115a0e3ad6STejun Heo #include <linux/gfp.h>
129ab4d072SStas Sergeev #include <linux/moduleparam.h>
1396c7d478STakashi Iwai #include <linux/interrupt.h>
146cbbfe1cSTakashi Iwai #include <linux/io.h>
159ab4d072SStas Sergeev #include <sound/pcm.h>
169ab4d072SStas Sergeev #include "pcsp.h"
179ab4d072SStas Sergeev 
18a67ff6a5SRusty Russell static bool nforce_wa;
199ab4d072SStas Sergeev module_param(nforce_wa, bool, 0444);
209ab4d072SStas Sergeev MODULE_PARM_DESC(nforce_wa, "Apply NForce chipset workaround "
219ab4d072SStas Sergeev 		"(expect bad sound)");
229ab4d072SStas Sergeev 
234dfd7954SStas Sergeev #define DMIX_WANTS_S16	1
244dfd7954SStas Sergeev 
2596c7d478STakashi Iwai /*
2668f86a90STakashi Iwai  * Call snd_pcm_period_elapsed in a work
2796c7d478STakashi Iwai  * This avoids spinlock messes and long-running irq contexts
2896c7d478STakashi Iwai  */
pcsp_call_pcm_elapsed(struct work_struct * work)2968f86a90STakashi Iwai static void pcsp_call_pcm_elapsed(struct work_struct *work)
3096c7d478STakashi Iwai {
3196c7d478STakashi Iwai 	if (atomic_read(&pcsp_chip.timer_active)) {
3296c7d478STakashi Iwai 		struct snd_pcm_substream *substream;
3396c7d478STakashi Iwai 		substream = pcsp_chip.playback_substream;
3496c7d478STakashi Iwai 		if (substream)
3596c7d478STakashi Iwai 			snd_pcm_period_elapsed(substream);
3696c7d478STakashi Iwai 	}
3796c7d478STakashi Iwai }
3896c7d478STakashi Iwai 
3968f86a90STakashi Iwai static DECLARE_WORK(pcsp_pcm_work, pcsp_call_pcm_elapsed);
4096c7d478STakashi Iwai 
41eea0579fSTakashi Iwai /* write the port and returns the next expire time in ns;
42eea0579fSTakashi Iwai  * called at the trigger-start and in hrtimer callback
43eea0579fSTakashi Iwai  */
pcsp_timer_update(struct snd_pcsp * chip)44b71207e9SStas Sergeev static u64 pcsp_timer_update(struct snd_pcsp *chip)
459ab4d072SStas Sergeev {
469ab4d072SStas Sergeev 	unsigned char timer_cnt, val;
479ab4d072SStas Sergeev 	u64 ns;
489ab4d072SStas Sergeev 	struct snd_pcm_substream *substream;
499ab4d072SStas Sergeev 	struct snd_pcm_runtime *runtime;
5096c7d478STakashi Iwai 	unsigned long flags;
519ab4d072SStas Sergeev 
529ab4d072SStas Sergeev 	if (chip->thalf) {
539ab4d072SStas Sergeev 		outb(chip->val61, 0x61);
549ab4d072SStas Sergeev 		chip->thalf = 0;
55eea0579fSTakashi Iwai 		return chip->ns_rem;
569ab4d072SStas Sergeev 	}
579ab4d072SStas Sergeev 
5896c7d478STakashi Iwai 	substream = chip->playback_substream;
5996c7d478STakashi Iwai 	if (!substream)
60eea0579fSTakashi Iwai 		return 0;
619ab4d072SStas Sergeev 
629ab4d072SStas Sergeev 	runtime = substream->runtime;
634dfd7954SStas Sergeev 	/* assume it is mono! */
64eea0579fSTakashi Iwai 	val = runtime->dma_area[chip->playback_ptr + chip->fmt_size - 1];
65eea0579fSTakashi Iwai 	if (chip->is_signed)
664dfd7954SStas Sergeev 		val ^= 0x80;
679ab4d072SStas Sergeev 	timer_cnt = val * CUR_DIV() / 256;
689ab4d072SStas Sergeev 
699ab4d072SStas Sergeev 	if (timer_cnt && chip->enable) {
70ced918ebSThomas Gleixner 		raw_spin_lock_irqsave(&i8253_lock, flags);
719ab4d072SStas Sergeev 		if (!nforce_wa) {
729ab4d072SStas Sergeev 			outb_p(chip->val61, 0x61);
739ab4d072SStas Sergeev 			outb_p(timer_cnt, 0x42);
749ab4d072SStas Sergeev 			outb(chip->val61 ^ 1, 0x61);
759ab4d072SStas Sergeev 		} else {
769ab4d072SStas Sergeev 			outb(chip->val61 ^ 2, 0x61);
779ab4d072SStas Sergeev 			chip->thalf = 1;
789ab4d072SStas Sergeev 		}
79ced918ebSThomas Gleixner 		raw_spin_unlock_irqrestore(&i8253_lock, flags);
809ab4d072SStas Sergeev 	}
819ab4d072SStas Sergeev 
82eea0579fSTakashi Iwai 	chip->ns_rem = PCSP_PERIOD_NS();
83eea0579fSTakashi Iwai 	ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem);
84eea0579fSTakashi Iwai 	chip->ns_rem -= ns;
85eea0579fSTakashi Iwai 	return ns;
86eea0579fSTakashi Iwai }
87eea0579fSTakashi Iwai 
pcsp_pointer_update(struct snd_pcsp * chip)88b71207e9SStas Sergeev static void pcsp_pointer_update(struct snd_pcsp *chip)
89eea0579fSTakashi Iwai {
90eea0579fSTakashi Iwai 	struct snd_pcm_substream *substream;
91eea0579fSTakashi Iwai 	size_t period_bytes, buffer_bytes;
92b71207e9SStas Sergeev 	int periods_elapsed;
93eea0579fSTakashi Iwai 	unsigned long flags;
94eea0579fSTakashi Iwai 
95eea0579fSTakashi Iwai 	/* update the playback position */
96eea0579fSTakashi Iwai 	substream = chip->playback_substream;
97eea0579fSTakashi Iwai 	if (!substream)
98b71207e9SStas Sergeev 		return;
99eea0579fSTakashi Iwai 
1009ab4d072SStas Sergeev 	period_bytes = snd_pcm_lib_period_bytes(substream);
1019ab4d072SStas Sergeev 	buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
10296c7d478STakashi Iwai 
10396c7d478STakashi Iwai 	spin_lock_irqsave(&chip->substream_lock, flags);
104eea0579fSTakashi Iwai 	chip->playback_ptr += PCSP_INDEX_INC() * chip->fmt_size;
1059ab4d072SStas Sergeev 	periods_elapsed = chip->playback_ptr - chip->period_ptr;
1069ab4d072SStas Sergeev 	if (periods_elapsed < 0) {
10742ece6c1SStas Sergeev #if PCSP_DEBUG
10842ece6c1SStas Sergeev 		printk(KERN_INFO "PCSP: buffer_bytes mod period_bytes != 0 ? "
1099ab4d072SStas Sergeev 			"(%zi %zi %zi)\n",
1109ab4d072SStas Sergeev 			chip->playback_ptr, period_bytes, buffer_bytes);
11142ece6c1SStas Sergeev #endif
1129ab4d072SStas Sergeev 		periods_elapsed += buffer_bytes;
1139ab4d072SStas Sergeev 	}
1149ab4d072SStas Sergeev 	periods_elapsed /= period_bytes;
1159ab4d072SStas Sergeev 	/* wrap the pointer _before_ calling snd_pcm_period_elapsed(),
1169ab4d072SStas Sergeev 	 * or ALSA will BUG on us. */
1179ab4d072SStas Sergeev 	chip->playback_ptr %= buffer_bytes;
1189ab4d072SStas Sergeev 
1199ab4d072SStas Sergeev 	if (periods_elapsed) {
1209ab4d072SStas Sergeev 		chip->period_ptr += periods_elapsed * period_bytes;
1219ab4d072SStas Sergeev 		chip->period_ptr %= buffer_bytes;
12268f86a90STakashi Iwai 		queue_work(system_highpri_wq, &pcsp_pcm_work);
1239ab4d072SStas Sergeev 	}
12496c7d478STakashi Iwai 	spin_unlock_irqrestore(&chip->substream_lock, flags);
125b71207e9SStas Sergeev }
126b71207e9SStas Sergeev 
pcsp_do_timer(struct hrtimer * handle)127b71207e9SStas Sergeev enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle)
128b71207e9SStas Sergeev {
129b71207e9SStas Sergeev 	struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer);
130b71207e9SStas Sergeev 	int pointer_update;
131b71207e9SStas Sergeev 	u64 ns;
132b71207e9SStas Sergeev 
133b71207e9SStas Sergeev 	if (!atomic_read(&chip->timer_active) || !chip->playback_substream)
134b71207e9SStas Sergeev 		return HRTIMER_NORESTART;
135b71207e9SStas Sergeev 
136b71207e9SStas Sergeev 	pointer_update = !chip->thalf;
137b71207e9SStas Sergeev 	ns = pcsp_timer_update(chip);
138b71207e9SStas Sergeev 	if (!ns) {
139b71207e9SStas Sergeev 		printk(KERN_WARNING "PCSP: unexpected stop\n");
140b71207e9SStas Sergeev 		return HRTIMER_NORESTART;
141b71207e9SStas Sergeev 	}
142b71207e9SStas Sergeev 
143b71207e9SStas Sergeev 	if (pointer_update)
144b71207e9SStas Sergeev 		pcsp_pointer_update(chip);
1459ab4d072SStas Sergeev 
146*f2ff7147SThomas Gleixner 	hrtimer_forward_now(handle, ns_to_ktime(ns));
147eea0579fSTakashi Iwai 
1489ab4d072SStas Sergeev 	return HRTIMER_RESTART;
1499ab4d072SStas Sergeev }
1509ab4d072SStas Sergeev 
pcsp_start_playing(struct snd_pcsp * chip)151eea0579fSTakashi Iwai static int pcsp_start_playing(struct snd_pcsp *chip)
1529ab4d072SStas Sergeev {
1539ab4d072SStas Sergeev #if PCSP_DEBUG
1549ab4d072SStas Sergeev 	printk(KERN_INFO "PCSP: start_playing called\n");
1559ab4d072SStas Sergeev #endif
1569ab4d072SStas Sergeev 	if (atomic_read(&chip->timer_active)) {
1579ab4d072SStas Sergeev 		printk(KERN_ERR "PCSP: Timer already active\n");
158eea0579fSTakashi Iwai 		return -EIO;
1599ab4d072SStas Sergeev 	}
1609ab4d072SStas Sergeev 
161ced918ebSThomas Gleixner 	raw_spin_lock(&i8253_lock);
1629ab4d072SStas Sergeev 	chip->val61 = inb(0x61) | 0x03;
1639ab4d072SStas Sergeev 	outb_p(0x92, 0x43);	/* binary, mode 1, LSB only, ch 2 */
164ced918ebSThomas Gleixner 	raw_spin_unlock(&i8253_lock);
1659ab4d072SStas Sergeev 	atomic_set(&chip->timer_active, 1);
1669ab4d072SStas Sergeev 	chip->thalf = 0;
1679ab4d072SStas Sergeev 
1688b0e1953SThomas Gleixner 	hrtimer_start(&pcsp_chip.timer, 0, HRTIMER_MODE_REL);
169eea0579fSTakashi Iwai 	return 0;
1709ab4d072SStas Sergeev }
1719ab4d072SStas Sergeev 
pcsp_stop_playing(struct snd_pcsp * chip)1729ab4d072SStas Sergeev static void pcsp_stop_playing(struct snd_pcsp *chip)
1739ab4d072SStas Sergeev {
1749ab4d072SStas Sergeev #if PCSP_DEBUG
1759ab4d072SStas Sergeev 	printk(KERN_INFO "PCSP: stop_playing called\n");
1769ab4d072SStas Sergeev #endif
1779ab4d072SStas Sergeev 	if (!atomic_read(&chip->timer_active))
1789ab4d072SStas Sergeev 		return;
1799ab4d072SStas Sergeev 
1809ab4d072SStas Sergeev 	atomic_set(&chip->timer_active, 0);
181ced918ebSThomas Gleixner 	raw_spin_lock(&i8253_lock);
1829ab4d072SStas Sergeev 	/* restore the timer */
1839ab4d072SStas Sergeev 	outb_p(0xb6, 0x43);	/* binary, mode 3, LSB/MSB, ch 2 */
1849ab4d072SStas Sergeev 	outb(chip->val61 & 0xFC, 0x61);
185ced918ebSThomas Gleixner 	raw_spin_unlock(&i8253_lock);
1869ab4d072SStas Sergeev }
1879ab4d072SStas Sergeev 
18896c7d478STakashi Iwai /*
18996c7d478STakashi Iwai  * Force to stop and sync the stream
19096c7d478STakashi Iwai  */
pcsp_sync_stop(struct snd_pcsp * chip)19196c7d478STakashi Iwai void pcsp_sync_stop(struct snd_pcsp *chip)
19296c7d478STakashi Iwai {
19396c7d478STakashi Iwai 	local_irq_disable();
19496c7d478STakashi Iwai 	pcsp_stop_playing(chip);
19596c7d478STakashi Iwai 	local_irq_enable();
19696c7d478STakashi Iwai 	hrtimer_cancel(&chip->timer);
19768f86a90STakashi Iwai 	cancel_work_sync(&pcsp_pcm_work);
19896c7d478STakashi Iwai }
19996c7d478STakashi Iwai 
snd_pcsp_playback_close(struct snd_pcm_substream * substream)2009ab4d072SStas Sergeev static int snd_pcsp_playback_close(struct snd_pcm_substream *substream)
2019ab4d072SStas Sergeev {
2029ab4d072SStas Sergeev 	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
2039ab4d072SStas Sergeev #if PCSP_DEBUG
2049ab4d072SStas Sergeev 	printk(KERN_INFO "PCSP: close called\n");
2059ab4d072SStas Sergeev #endif
20696c7d478STakashi Iwai 	pcsp_sync_stop(chip);
2079ab4d072SStas Sergeev 	chip->playback_substream = NULL;
2089ab4d072SStas Sergeev 	return 0;
2099ab4d072SStas Sergeev }
2109ab4d072SStas Sergeev 
snd_pcsp_playback_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * hw_params)2119ab4d072SStas Sergeev static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream,
2129ab4d072SStas Sergeev 				       struct snd_pcm_hw_params *hw_params)
2139ab4d072SStas Sergeev {
21496c7d478STakashi Iwai 	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
21596c7d478STakashi Iwai 	pcsp_sync_stop(chip);
2169ab4d072SStas Sergeev 	return 0;
2179ab4d072SStas Sergeev }
2189ab4d072SStas Sergeev 
snd_pcsp_playback_hw_free(struct snd_pcm_substream * substream)2199ab4d072SStas Sergeev static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream)
2209ab4d072SStas Sergeev {
22196c7d478STakashi Iwai 	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
2229ab4d072SStas Sergeev #if PCSP_DEBUG
2239ab4d072SStas Sergeev 	printk(KERN_INFO "PCSP: hw_free called\n");
2249ab4d072SStas Sergeev #endif
22596c7d478STakashi Iwai 	pcsp_sync_stop(chip);
226d635f09eSTakashi Iwai 	return 0;
2279ab4d072SStas Sergeev }
2289ab4d072SStas Sergeev 
snd_pcsp_playback_prepare(struct snd_pcm_substream * substream)2299ab4d072SStas Sergeev static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream)
2309ab4d072SStas Sergeev {
2319ab4d072SStas Sergeev 	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
23296c7d478STakashi Iwai 	pcsp_sync_stop(chip);
2339ab4d072SStas Sergeev 	chip->playback_ptr = 0;
2349ab4d072SStas Sergeev 	chip->period_ptr = 0;
235eea0579fSTakashi Iwai 	chip->fmt_size =
236eea0579fSTakashi Iwai 		snd_pcm_format_physical_width(substream->runtime->format) >> 3;
237eea0579fSTakashi Iwai 	chip->is_signed = snd_pcm_format_signed(substream->runtime->format);
238b71207e9SStas Sergeev #if PCSP_DEBUG
239b71207e9SStas Sergeev 	printk(KERN_INFO "PCSP: prepare called, "
240b71207e9SStas Sergeev 			"size=%zi psize=%zi f=%zi f1=%i fsize=%i\n",
241b71207e9SStas Sergeev 			snd_pcm_lib_buffer_bytes(substream),
242b71207e9SStas Sergeev 			snd_pcm_lib_period_bytes(substream),
243b71207e9SStas Sergeev 			snd_pcm_lib_buffer_bytes(substream) /
244b71207e9SStas Sergeev 			snd_pcm_lib_period_bytes(substream),
245b71207e9SStas Sergeev 			substream->runtime->periods,
246b71207e9SStas Sergeev 			chip->fmt_size);
247b71207e9SStas Sergeev #endif
2489ab4d072SStas Sergeev 	return 0;
2499ab4d072SStas Sergeev }
2509ab4d072SStas Sergeev 
snd_pcsp_trigger(struct snd_pcm_substream * substream,int cmd)2519ab4d072SStas Sergeev static int snd_pcsp_trigger(struct snd_pcm_substream *substream, int cmd)
2529ab4d072SStas Sergeev {
2539ab4d072SStas Sergeev 	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
2549ab4d072SStas Sergeev #if PCSP_DEBUG
2559ab4d072SStas Sergeev 	printk(KERN_INFO "PCSP: trigger called\n");
2569ab4d072SStas Sergeev #endif
2579ab4d072SStas Sergeev 	switch (cmd) {
2589ab4d072SStas Sergeev 	case SNDRV_PCM_TRIGGER_START:
2599ab4d072SStas Sergeev 	case SNDRV_PCM_TRIGGER_RESUME:
260eea0579fSTakashi Iwai 		return pcsp_start_playing(chip);
2619ab4d072SStas Sergeev 	case SNDRV_PCM_TRIGGER_STOP:
2629ab4d072SStas Sergeev 	case SNDRV_PCM_TRIGGER_SUSPEND:
2639ab4d072SStas Sergeev 		pcsp_stop_playing(chip);
2649ab4d072SStas Sergeev 		break;
2659ab4d072SStas Sergeev 	default:
2669ab4d072SStas Sergeev 		return -EINVAL;
2679ab4d072SStas Sergeev 	}
2689ab4d072SStas Sergeev 	return 0;
2699ab4d072SStas Sergeev }
2709ab4d072SStas Sergeev 
snd_pcsp_playback_pointer(struct snd_pcm_substream * substream)2719ab4d072SStas Sergeev static snd_pcm_uframes_t snd_pcsp_playback_pointer(struct snd_pcm_substream
2729ab4d072SStas Sergeev 						   *substream)
2739ab4d072SStas Sergeev {
2749ab4d072SStas Sergeev 	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
27596c7d478STakashi Iwai 	unsigned int pos;
27696c7d478STakashi Iwai 	spin_lock(&chip->substream_lock);
27796c7d478STakashi Iwai 	pos = chip->playback_ptr;
27896c7d478STakashi Iwai 	spin_unlock(&chip->substream_lock);
27996c7d478STakashi Iwai 	return bytes_to_frames(substream->runtime, pos);
2809ab4d072SStas Sergeev }
2819ab4d072SStas Sergeev 
282b6c0b715SBhumika Goyal static const struct snd_pcm_hardware snd_pcsp_playback = {
2839ab4d072SStas Sergeev 	.info = (SNDRV_PCM_INFO_INTERLEAVED |
2849ab4d072SStas Sergeev 		 SNDRV_PCM_INFO_HALF_DUPLEX |
2859ab4d072SStas Sergeev 		 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
2864dfd7954SStas Sergeev 	.formats = (SNDRV_PCM_FMTBIT_U8
2874dfd7954SStas Sergeev #if DMIX_WANTS_S16
2884dfd7954SStas Sergeev 		    | SNDRV_PCM_FMTBIT_S16_LE
2894dfd7954SStas Sergeev #endif
2904dfd7954SStas Sergeev 	    ),
2919ab4d072SStas Sergeev 	.rates = SNDRV_PCM_RATE_KNOT,
2929ab4d072SStas Sergeev 	.rate_min = PCSP_DEFAULT_SRATE,
2939ab4d072SStas Sergeev 	.rate_max = PCSP_DEFAULT_SRATE,
2949ab4d072SStas Sergeev 	.channels_min = 1,
2959ab4d072SStas Sergeev 	.channels_max = 1,
2969ab4d072SStas Sergeev 	.buffer_bytes_max = PCSP_BUFFER_SIZE,
2979ab4d072SStas Sergeev 	.period_bytes_min = 64,
2989ab4d072SStas Sergeev 	.period_bytes_max = PCSP_MAX_PERIOD_SIZE,
2999ab4d072SStas Sergeev 	.periods_min = 2,
3009ab4d072SStas Sergeev 	.periods_max = PCSP_MAX_PERIODS,
3019ab4d072SStas Sergeev 	.fifo_size = 0,
3029ab4d072SStas Sergeev };
3039ab4d072SStas Sergeev 
snd_pcsp_playback_open(struct snd_pcm_substream * substream)3049ab4d072SStas Sergeev static int snd_pcsp_playback_open(struct snd_pcm_substream *substream)
3059ab4d072SStas Sergeev {
3069ab4d072SStas Sergeev 	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
3079ab4d072SStas Sergeev 	struct snd_pcm_runtime *runtime = substream->runtime;
3089ab4d072SStas Sergeev #if PCSP_DEBUG
3099ab4d072SStas Sergeev 	printk(KERN_INFO "PCSP: open called\n");
3109ab4d072SStas Sergeev #endif
3119ab4d072SStas Sergeev 	if (atomic_read(&chip->timer_active)) {
3129ab4d072SStas Sergeev 		printk(KERN_ERR "PCSP: still active!!\n");
3139ab4d072SStas Sergeev 		return -EBUSY;
3149ab4d072SStas Sergeev 	}
3159ab4d072SStas Sergeev 	runtime->hw = snd_pcsp_playback;
3169ab4d072SStas Sergeev 	chip->playback_substream = substream;
3179ab4d072SStas Sergeev 	return 0;
3189ab4d072SStas Sergeev }
3199ab4d072SStas Sergeev 
320642b7589SArvind Yadav static const struct snd_pcm_ops snd_pcsp_playback_ops = {
3219ab4d072SStas Sergeev 	.open = snd_pcsp_playback_open,
3229ab4d072SStas Sergeev 	.close = snd_pcsp_playback_close,
3239ab4d072SStas Sergeev 	.hw_params = snd_pcsp_playback_hw_params,
3249ab4d072SStas Sergeev 	.hw_free = snd_pcsp_playback_hw_free,
3259ab4d072SStas Sergeev 	.prepare = snd_pcsp_playback_prepare,
3269ab4d072SStas Sergeev 	.trigger = snd_pcsp_trigger,
3279ab4d072SStas Sergeev 	.pointer = snd_pcsp_playback_pointer,
3289ab4d072SStas Sergeev };
3299ab4d072SStas Sergeev 
snd_pcsp_new_pcm(struct snd_pcsp * chip)330fbbb01a1SBill Pemberton int snd_pcsp_new_pcm(struct snd_pcsp *chip)
3319ab4d072SStas Sergeev {
3329ab4d072SStas Sergeev 	int err;
3339ab4d072SStas Sergeev 
3349ab4d072SStas Sergeev 	err = snd_pcm_new(chip->card, "pcspeaker", 0, 1, 0, &chip->pcm);
3359ab4d072SStas Sergeev 	if (err < 0)
3369ab4d072SStas Sergeev 		return err;
3379ab4d072SStas Sergeev 
3389ab4d072SStas Sergeev 	snd_pcm_set_ops(chip->pcm, SNDRV_PCM_STREAM_PLAYBACK,
3399ab4d072SStas Sergeev 			&snd_pcsp_playback_ops);
3409ab4d072SStas Sergeev 
3419ab4d072SStas Sergeev 	chip->pcm->private_data = chip;
3429ab4d072SStas Sergeev 	chip->pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
3439ab4d072SStas Sergeev 	strcpy(chip->pcm->name, "pcsp");
3449ab4d072SStas Sergeev 
345d635f09eSTakashi Iwai 	snd_pcm_set_managed_buffer_all(chip->pcm,
3469ab4d072SStas Sergeev 				       SNDRV_DMA_TYPE_CONTINUOUS,
3478fd9da75STakashi Iwai 				       NULL,
3488fd9da75STakashi Iwai 				       PCSP_BUFFER_SIZE,
3499ab4d072SStas Sergeev 				       PCSP_BUFFER_SIZE);
3509ab4d072SStas Sergeev 
3519ab4d072SStas Sergeev 	return 0;
3529ab4d072SStas Sergeev }
353