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 <linux/io.h>
14#include <sound/pcm.h>
15#include "pcsp.h"
16
17static bool nforce_wa;
18module_param(nforce_wa, bool, 0444);
19MODULE_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 */
28static 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
38static 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 */
43static 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
87static 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
128enum 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
152static 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
173static 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 */
192void 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
201static 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
212static 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
225static 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
235static 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
257static 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
277static 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
288static 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
310static 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
326static 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
337int 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