aboutsummaryrefslogtreecommitdiffstats
path: root/sound/drivers/pcsp
diff options
context:
space:
mode:
authorStas Sergeev <stsp@aknet.ru>2008-03-03 04:53:54 -0500
committerTakashi Iwai <tiwai@suse.de>2008-04-24 06:00:20 -0400
commit9ab4d072ad67793d70b8707e14fb9261749c4e07 (patch)
treec6ae32f1d3d3b273fae30b0fa59b8c79bd5e8044 /sound/drivers/pcsp
parent40ac8c4f208111cdc1542ccc9feb21b98a6b0219 (diff)
[ALSA] Add PC-speaker sound driver
Added PC-speaker sound driver (snd-pcsp). Signed-off-by: Stas Sergeev <stsp@aknet.ru> Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/drivers/pcsp')
-rw-r--r--sound/drivers/pcsp/Makefile2
-rw-r--r--sound/drivers/pcsp/pcsp.c241
-rw-r--r--sound/drivers/pcsp/pcsp.h82
-rw-r--r--sound/drivers/pcsp/pcsp_input.c116
-rw-r--r--sound/drivers/pcsp/pcsp_input.h14
-rw-r--r--sound/drivers/pcsp/pcsp_lib.c347
-rw-r--r--sound/drivers/pcsp/pcsp_mixer.c143
7 files changed, 945 insertions, 0 deletions
diff --git a/sound/drivers/pcsp/Makefile b/sound/drivers/pcsp/Makefile
new file mode 100644
index 000000000000..b19555b440da
--- /dev/null
+++ b/sound/drivers/pcsp/Makefile
@@ -0,0 +1,2 @@
1snd-pcsp-objs := pcsp.o pcsp_lib.o pcsp_mixer.o pcsp_input.o
2obj-$(CONFIG_SND_PCSP) += snd-pcsp.o
diff --git a/sound/drivers/pcsp/pcsp.c b/sound/drivers/pcsp/pcsp.c
new file mode 100644
index 000000000000..34477286b394
--- /dev/null
+++ b/sound/drivers/pcsp/pcsp.c
@@ -0,0 +1,241 @@
1/*
2 * PC-Speaker driver for Linux
3 *
4 * Copyright (C) 1997-2001 David Woodhouse
5 * Copyright (C) 2001-2008 Stas Sergeev
6 */
7
8#include <linux/init.h>
9#include <linux/moduleparam.h>
10#include <linux/platform_device.h>
11#include <sound/core.h>
12#include <sound/initval.h>
13#include <sound/pcm.h>
14
15#include <linux/input.h>
16#include <linux/delay.h>
17#include <asm/bitops.h>
18#include "pcsp_input.h"
19#include "pcsp.h"
20
21MODULE_AUTHOR("Stas Sergeev <stsp@users.sourceforge.net>");
22MODULE_DESCRIPTION("PC-Speaker driver");
23MODULE_LICENSE("GPL");
24MODULE_SUPPORTED_DEVICE("{{PC-Speaker, pcsp}}");
25MODULE_ALIAS("platform:pcspkr");
26
27static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */
28static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */
29static int enable = SNDRV_DEFAULT_ENABLE1; /* Enable this card */
30
31module_param(index, int, 0444);
32MODULE_PARM_DESC(index, "Index value for pcsp soundcard.");
33module_param(id, charp, 0444);
34MODULE_PARM_DESC(id, "ID string for pcsp soundcard.");
35module_param(enable, bool, 0444);
36MODULE_PARM_DESC(enable, "dummy");
37
38struct snd_pcsp pcsp_chip;
39
40static int __devinit snd_pcsp_create(struct snd_card *card)
41{
42 static struct snd_device_ops ops = { };
43 struct timespec tp;
44 int err;
45 int div, min_div, order;
46
47 hrtimer_get_res(CLOCK_MONOTONIC, &tp);
48 if (tp.tv_sec || tp.tv_nsec > PCSP_MAX_PERIOD_NS) {
49 printk(KERN_ERR "PCSP: Timer resolution is not sufficient "
50 "(%linS)\n", tp.tv_nsec);
51 printk(KERN_ERR "PCSP: Make sure you have HPET and ACPI "
52 "enabled.\n");
53 return -EIO;
54 }
55
56 if (loops_per_jiffy >= PCSP_MIN_LPJ && tp.tv_nsec <= PCSP_MIN_PERIOD_NS)
57 min_div = MIN_DIV;
58 else
59 min_div = MAX_DIV;
60#if PCSP_DEBUG
61 printk("PCSP: lpj=%li, min_div=%i, res=%li\n",
62 loops_per_jiffy, min_div, tp.tv_nsec);
63#endif
64
65 div = MAX_DIV / min_div;
66 order = fls(div) - 1;
67
68 pcsp_chip.max_treble = min(order, PCSP_MAX_TREBLE);
69 pcsp_chip.treble = min(pcsp_chip.max_treble, PCSP_DEFAULT_TREBLE);
70 pcsp_chip.playback_ptr = 0;
71 pcsp_chip.period_ptr = 0;
72 atomic_set(&pcsp_chip.timer_active, 0);
73 pcsp_chip.enable = 1;
74 pcsp_chip.pcspkr = 1;
75
76 spin_lock_init(&pcsp_chip.substream_lock);
77
78 pcsp_chip.card = card;
79 pcsp_chip.port = 0x61;
80 pcsp_chip.irq = -1;
81 pcsp_chip.dma = -1;
82
83 /* Register device */
84 err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, &pcsp_chip, &ops);
85 if (err < 0)
86 return err;
87
88 return 0;
89}
90
91static int __devinit snd_card_pcsp_probe(int devnum, struct device *dev)
92{
93 struct snd_card *card;
94 int err;
95
96 if (devnum != 0)
97 return -EINVAL;
98
99 hrtimer_init(&pcsp_chip.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
100 pcsp_chip.timer.cb_mode = HRTIMER_CB_IRQSAFE;
101 pcsp_chip.timer.function = pcsp_do_timer;
102
103 card = snd_card_new(index, id, THIS_MODULE, 0);
104 if (!card)
105 return -ENOMEM;
106
107 err = snd_pcsp_create(card);
108 if (err < 0) {
109 snd_card_free(card);
110 return err;
111 }
112 err = snd_pcsp_new_pcm(&pcsp_chip);
113 if (err < 0) {
114 snd_card_free(card);
115 return err;
116 }
117 err = snd_pcsp_new_mixer(&pcsp_chip);
118 if (err < 0) {
119 snd_card_free(card);
120 return err;
121 }
122
123 snd_card_set_dev(pcsp_chip.card, dev);
124
125 strcpy(card->driver, "PC-Speaker");
126 strcpy(card->shortname, "pcsp");
127 sprintf(card->longname, "Internal PC-Speaker at port 0x%x",
128 pcsp_chip.port);
129
130 err = snd_card_register(card);
131 if (err < 0) {
132 snd_card_free(card);
133 return err;
134 }
135
136 return 0;
137}
138
139static int __devinit alsa_card_pcsp_init(struct device *dev)
140{
141 int devnum = 0, cards = 0;
142
143#ifdef CONFIG_DEBUG_PAGEALLOC
144 /* Well, CONFIG_DEBUG_PAGEALLOC makes the sound horrible. Lets alert */
145 printk(KERN_WARNING
146 "PCSP: Warning, CONFIG_DEBUG_PAGEALLOC is enabled!\n"
147 "You have to disable it if you want to use the PC-Speaker "
148 "driver.\n"
149 "Unless it is disabled, enjoy the horrible, distorted "
150 "and crackling noise.\n");
151#endif
152
153 if (enable) {
154 if (snd_card_pcsp_probe(devnum, dev) >= 0)
155 cards++;
156 if (!cards) {
157 printk(KERN_ERR "PC-Speaker initialization failed.\n");
158 return -ENODEV;
159 }
160 }
161
162 return 0;
163}
164
165static void __devexit alsa_card_pcsp_exit(struct snd_pcsp *chip)
166{
167 snd_card_free(chip->card);
168}
169
170static int __devinit pcsp_probe(struct platform_device *dev)
171{
172 int err;
173 err = pcspkr_input_init(&pcsp_chip.input_dev, &dev->dev);
174 if (err < 0)
175 return err;
176
177 err = alsa_card_pcsp_init(&dev->dev);
178 if (err < 0) {
179 pcspkr_input_remove(pcsp_chip.input_dev);
180 return err;
181 }
182
183 platform_set_drvdata(dev, &pcsp_chip);
184 return 0;
185}
186
187static int __devexit pcsp_remove(struct platform_device *dev)
188{
189 struct snd_pcsp *chip = platform_get_drvdata(dev);
190 alsa_card_pcsp_exit(chip);
191 pcspkr_input_remove(chip->input_dev);
192 platform_set_drvdata(dev, NULL);
193 return 0;
194}
195
196static void pcsp_stop_beep(struct snd_pcsp *chip)
197{
198 unsigned long flags;
199 spin_lock_irqsave(&chip->substream_lock, flags);
200 if (!chip->playback_substream)
201 pcspkr_stop_sound();
202 spin_unlock_irqrestore(&chip->substream_lock, flags);
203}
204
205static int pcsp_suspend(struct platform_device *dev, pm_message_t state)
206{
207 struct snd_pcsp *chip = platform_get_drvdata(dev);
208 pcsp_stop_beep(chip);
209 snd_pcm_suspend_all(chip->pcm);
210 return 0;
211}
212
213static void pcsp_shutdown(struct platform_device *dev)
214{
215 struct snd_pcsp *chip = platform_get_drvdata(dev);
216 pcsp_stop_beep(chip);
217}
218
219static struct platform_driver pcsp_platform_driver = {
220 .driver = {
221 .name = "pcspkr",
222 .owner = THIS_MODULE,
223 },
224 .probe = pcsp_probe,
225 .remove = __devexit_p(pcsp_remove),
226 .suspend = pcsp_suspend,
227 .shutdown = pcsp_shutdown,
228};
229
230static int __init pcsp_init(void)
231{
232 return platform_driver_register(&pcsp_platform_driver);
233}
234
235static void __exit pcsp_exit(void)
236{
237 platform_driver_unregister(&pcsp_platform_driver);
238}
239
240module_init(pcsp_init);
241module_exit(pcsp_exit);
diff --git a/sound/drivers/pcsp/pcsp.h b/sound/drivers/pcsp/pcsp.h
new file mode 100644
index 000000000000..f07cc1ee1fe7
--- /dev/null
+++ b/sound/drivers/pcsp/pcsp.h
@@ -0,0 +1,82 @@
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#ifndef __PCSP_H__
10#define __PCSP_H__
11
12#include <linux/hrtimer.h>
13#if defined(CONFIG_MIPS) || defined(CONFIG_X86)
14/* Use the global PIT lock ! */
15#include <asm/i8253.h>
16#else
17#include <asm/8253pit.h>
18static DEFINE_SPINLOCK(i8253_lock);
19#endif
20
21#define PCSP_SOUND_VERSION 0x400 /* read 4.00 */
22#define PCSP_DEBUG 0
23
24/* default timer freq for PC-Speaker: 18643 Hz */
25#define DIV_18KHZ 64
26#define MAX_DIV DIV_18KHZ
27#define CUR_DIV() (MAX_DIV >> chip->treble)
28#define PCSP_MAX_TREBLE 1
29
30/* unfortunately, with hrtimers 37KHz does not work very well :( */
31#define PCSP_DEFAULT_TREBLE 0
32#define MIN_DIV (MAX_DIV >> PCSP_MAX_TREBLE)
33
34/* wild guess */
35#define PCSP_MIN_LPJ 1000000
36#define PCSP_DEFAULT_SDIV (DIV_18KHZ >> 1)
37#define PCSP_DEFAULT_SRATE (PIT_TICK_RATE / PCSP_DEFAULT_SDIV)
38#define PCSP_INDEX_INC() (1 << (PCSP_MAX_TREBLE - chip->treble))
39#define PCSP_RATE() (PIT_TICK_RATE / CUR_DIV())
40#define PCSP_MIN_RATE__1 MAX_DIV/PIT_TICK_RATE
41#define PCSP_MAX_RATE__1 MIN_DIV/PIT_TICK_RATE
42#define PCSP_MAX_PERIOD_NS (1000000000ULL * PCSP_MIN_RATE__1)
43#define PCSP_MIN_PERIOD_NS (1000000000ULL * PCSP_MAX_RATE__1)
44#define PCSP_CALC_NS(div) ({ \
45 u64 __val = 1000000000ULL * (div); \
46 do_div(__val, PIT_TICK_RATE); \
47 __val; \
48})
49#define PCSP_PERIOD_NS() PCSP_CALC_NS(CUR_DIV())
50
51#define PCSP_MAX_PERIOD_SIZE (64*1024)
52#define PCSP_MAX_PERIODS 512
53#define PCSP_BUFFER_SIZE (128*1024)
54
55struct snd_pcsp {
56 struct snd_card *card;
57 struct snd_pcm *pcm;
58 struct input_dev *input_dev;
59 struct hrtimer timer;
60 unsigned short port, irq, dma;
61 spinlock_t substream_lock;
62 struct snd_pcm_substream *playback_substream;
63 size_t playback_ptr;
64 size_t period_ptr;
65 atomic_t timer_active;
66 int thalf;
67 u64 ns_rem;
68 unsigned char val61;
69 int enable;
70 int max_treble;
71 int treble;
72 int pcspkr;
73};
74
75extern struct snd_pcsp pcsp_chip;
76
77extern enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle);
78
79extern int snd_pcsp_new_pcm(struct snd_pcsp *chip);
80extern int snd_pcsp_new_mixer(struct snd_pcsp *chip);
81
82#endif
diff --git a/sound/drivers/pcsp/pcsp_input.c b/sound/drivers/pcsp/pcsp_input.c
new file mode 100644
index 000000000000..cd9b83e7f7d1
--- /dev/null
+++ b/sound/drivers/pcsp/pcsp_input.c
@@ -0,0 +1,116 @@
1/*
2 * PC Speaker beeper driver for Linux
3 *
4 * Copyright (c) 2002 Vojtech Pavlik
5 * Copyright (c) 1992 Orest Zborowski
6 *
7 */
8
9/*
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU General Public License version 2 as published by
12 * the Free Software Foundation
13 */
14
15#include <linux/init.h>
16#include <linux/input.h>
17#include <asm/io.h>
18#include "pcsp.h"
19
20static void pcspkr_do_sound(unsigned int count)
21{
22 unsigned long flags;
23
24 spin_lock_irqsave(&i8253_lock, flags);
25
26 if (count) {
27 /* enable counter 2 */
28 outb_p(inb_p(0x61) | 3, 0x61);
29 /* set command for counter 2, 2 byte write */
30 outb_p(0xB6, 0x43);
31 /* select desired HZ */
32 outb_p(count & 0xff, 0x42);
33 outb((count >> 8) & 0xff, 0x42);
34 } else {
35 /* disable counter 2 */
36 outb(inb_p(0x61) & 0xFC, 0x61);
37 }
38
39 spin_unlock_irqrestore(&i8253_lock, flags);
40}
41
42void pcspkr_stop_sound(void)
43{
44 pcspkr_do_sound(0);
45}
46
47static int pcspkr_input_event(struct input_dev *dev, unsigned int type,
48 unsigned int code, int value)
49{
50 unsigned int count = 0;
51
52 if (atomic_read(&pcsp_chip.timer_active) || !pcsp_chip.pcspkr)
53 return 0;
54
55 switch (type) {
56 case EV_SND:
57 switch (code) {
58 case SND_BELL:
59 if (value)
60 value = 1000;
61 case SND_TONE:
62 break;
63 default:
64 return -1;
65 }
66 break;
67
68 default:
69 return -1;
70 }
71
72 if (value > 20 && value < 32767)
73 count = PIT_TICK_RATE / value;
74
75 pcspkr_do_sound(count);
76
77 return 0;
78}
79
80int __devinit pcspkr_input_init(struct input_dev **rdev, struct device *dev)
81{
82 int err;
83
84 struct input_dev *input_dev = input_allocate_device();
85 if (!input_dev)
86 return -ENOMEM;
87
88 input_dev->name = "PC Speaker";
89 input_dev->phys = "isa0061/input0";
90 input_dev->id.bustype = BUS_ISA;
91 input_dev->id.vendor = 0x001f;
92 input_dev->id.product = 0x0001;
93 input_dev->id.version = 0x0100;
94 input_dev->dev.parent = dev;
95
96 input_dev->evbit[0] = BIT(EV_SND);
97 input_dev->sndbit[0] = BIT(SND_BELL) | BIT(SND_TONE);
98 input_dev->event = pcspkr_input_event;
99
100 err = input_register_device(input_dev);
101 if (err) {
102 input_free_device(input_dev);
103 return err;
104 }
105
106 *rdev = input_dev;
107 return 0;
108}
109
110int pcspkr_input_remove(struct input_dev *dev)
111{
112 pcspkr_stop_sound();
113 input_unregister_device(dev); /* this also does kfree() */
114
115 return 0;
116}
diff --git a/sound/drivers/pcsp/pcsp_input.h b/sound/drivers/pcsp/pcsp_input.h
new file mode 100644
index 000000000000..e66738c78333
--- /dev/null
+++ b/sound/drivers/pcsp/pcsp_input.h
@@ -0,0 +1,14 @@
1/*
2 * PC-Speaker driver for Linux
3 *
4 * Copyright (C) 2001-2008 Stas Sergeev
5 */
6
7#ifndef __PCSP_INPUT_H__
8#define __PCSP_INPUT_H__
9
10int __devinit pcspkr_input_init(struct input_dev **rdev, struct device *dev);
11int pcspkr_input_remove(struct input_dev *dev);
12void pcspkr_stop_sound(void);
13
14#endif
diff --git a/sound/drivers/pcsp/pcsp_lib.c b/sound/drivers/pcsp/pcsp_lib.c
new file mode 100644
index 000000000000..6bdcb89129d8
--- /dev/null
+++ b/sound/drivers/pcsp/pcsp_lib.c
@@ -0,0 +1,347 @@
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 <sound/pcm.h>
12#include <sound/pcm_params.h>
13#include <linux/interrupt.h>
14#include <asm/io.h>
15#include <asm/i8253.h>
16#include "pcsp.h"
17
18static int nforce_wa;
19module_param(nforce_wa, bool, 0444);
20MODULE_PARM_DESC(nforce_wa, "Apply NForce chipset workaround "
21 "(expect bad sound)");
22
23#define DMIX_WANTS_S16 1
24
25static void pcsp_start_timer(unsigned long dummy)
26{
27 hrtimer_start(&pcsp_chip.timer, ktime_set(0, 0), HRTIMER_MODE_REL);
28}
29
30/*
31 * We need the hrtimer_start as a tasklet to avoid
32 * the nasty locking problem. :(
33 * The problem:
34 * - The timer handler is called with the cpu_base->lock
35 * already held by hrtimer code.
36 * - snd_pcm_period_elapsed() takes the
37 * substream->self_group.lock.
38 * So far so good.
39 * But the snd_pcsp_trigger() is called with the
40 * substream->self_group.lock held, and it calls
41 * hrtimer_start(), which takes the cpu_base->lock.
42 * You see the problem. We have the code pathes
43 * which take two locks in a reverse order. This
44 * can deadlock and the lock validator complains.
45 * The only solution I could find was to move the
46 * hrtimer_start() into a tasklet. -stsp
47 */
48DECLARE_TASKLET(pcsp_start_timer_tasklet, pcsp_start_timer, 0);
49
50enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle)
51{
52 unsigned long flags;
53 unsigned char timer_cnt, val;
54 int fmt_size, periods_elapsed;
55 u64 ns;
56 size_t period_bytes, buffer_bytes;
57 struct snd_pcm_substream *substream;
58 struct snd_pcm_runtime *runtime;
59 struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer);
60
61 if (chip->thalf) {
62 outb(chip->val61, 0x61);
63 chip->thalf = 0;
64 if (!atomic_read(&chip->timer_active))
65 return HRTIMER_NORESTART;
66 hrtimer_forward(&chip->timer, chip->timer.expires,
67 ktime_set(0, chip->ns_rem));
68 return HRTIMER_RESTART;
69 }
70
71 /* hrtimer calls us from both hardirq and softirq contexts,
72 * so irqsave :( */
73 spin_lock_irqsave(&chip->substream_lock, flags);
74 /* Takashi Iwai says regarding this extra lock:
75
76 If the irq handler handles some data on the DMA buffer, it should
77 do snd_pcm_stream_lock().
78 That protects basically against all races among PCM callbacks, yes.
79 However, there are two remaining issues:
80 1. The substream pointer you try to lock isn't protected _before_
81 this lock yet.
82 2. snd_pcm_period_elapsed() itself acquires the lock.
83 The requirement of another lock is because of 1. When you get
84 chip->playback_substream, it's not protected.
85 Keeping this lock while snd_pcm_period_elapsed() assures the substream
86 is still protected (at least, not released). And the other status is
87 handled properly inside snd_pcm_stream_lock() in
88 snd_pcm_period_elapsed().
89
90 */
91 if (!chip->playback_substream)
92 goto exit_nr_unlock1;
93 substream = chip->playback_substream;
94 snd_pcm_stream_lock(substream);
95 if (!atomic_read(&chip->timer_active))
96 goto exit_nr_unlock2;
97
98 runtime = substream->runtime;
99 fmt_size = snd_pcm_format_physical_width(runtime->format) >> 3;
100 /* assume it is mono! */
101 val = runtime->dma_area[chip->playback_ptr + fmt_size - 1];
102 if (snd_pcm_format_signed(runtime->format))
103 val ^= 0x80;
104 timer_cnt = val * CUR_DIV() / 256;
105
106 if (timer_cnt && chip->enable) {
107 spin_lock(&i8253_lock);
108 if (!nforce_wa) {
109 outb_p(chip->val61, 0x61);
110 outb_p(timer_cnt, 0x42);
111 outb(chip->val61 ^ 1, 0x61);
112 } else {
113 outb(chip->val61 ^ 2, 0x61);
114 chip->thalf = 1;
115 }
116 spin_unlock(&i8253_lock);
117 }
118
119 period_bytes = snd_pcm_lib_period_bytes(substream);
120 buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
121 chip->playback_ptr += PCSP_INDEX_INC() * fmt_size;
122 periods_elapsed = chip->playback_ptr - chip->period_ptr;
123 if (periods_elapsed < 0) {
124 printk(KERN_WARNING "PCSP: playback_ptr inconsistent "
125 "(%zi %zi %zi)\n",
126 chip->playback_ptr, period_bytes, buffer_bytes);
127 periods_elapsed += buffer_bytes;
128 }
129 periods_elapsed /= period_bytes;
130 /* wrap the pointer _before_ calling snd_pcm_period_elapsed(),
131 * or ALSA will BUG on us. */
132 chip->playback_ptr %= buffer_bytes;
133
134 snd_pcm_stream_unlock(substream);
135
136 if (periods_elapsed) {
137 snd_pcm_period_elapsed(substream);
138 chip->period_ptr += periods_elapsed * period_bytes;
139 chip->period_ptr %= buffer_bytes;
140 }
141
142 spin_unlock_irqrestore(&chip->substream_lock, flags);
143
144 if (!atomic_read(&chip->timer_active))
145 return HRTIMER_NORESTART;
146
147 chip->ns_rem = PCSP_PERIOD_NS();
148 ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem);
149 chip->ns_rem -= ns;
150 hrtimer_forward(&chip->timer, chip->timer.expires, ktime_set(0, ns));
151 return HRTIMER_RESTART;
152
153exit_nr_unlock2:
154 snd_pcm_stream_unlock(substream);
155exit_nr_unlock1:
156 spin_unlock_irqrestore(&chip->substream_lock, flags);
157 return HRTIMER_NORESTART;
158}
159
160static void pcsp_start_playing(struct snd_pcsp *chip)
161{
162#if PCSP_DEBUG
163 printk(KERN_INFO "PCSP: start_playing called\n");
164#endif
165 if (atomic_read(&chip->timer_active)) {
166 printk(KERN_ERR "PCSP: Timer already active\n");
167 return;
168 }
169
170 spin_lock(&i8253_lock);
171 chip->val61 = inb(0x61) | 0x03;
172 outb_p(0x92, 0x43); /* binary, mode 1, LSB only, ch 2 */
173 spin_unlock(&i8253_lock);
174 atomic_set(&chip->timer_active, 1);
175 chip->thalf = 0;
176
177 tasklet_schedule(&pcsp_start_timer_tasklet);
178}
179
180static void pcsp_stop_playing(struct snd_pcsp *chip)
181{
182#if PCSP_DEBUG
183 printk(KERN_INFO "PCSP: stop_playing called\n");
184#endif
185 if (!atomic_read(&chip->timer_active))
186 return;
187
188 atomic_set(&chip->timer_active, 0);
189 spin_lock(&i8253_lock);
190 /* restore the timer */
191 outb_p(0xb6, 0x43); /* binary, mode 3, LSB/MSB, ch 2 */
192 outb(chip->val61 & 0xFC, 0x61);
193 spin_unlock(&i8253_lock);
194}
195
196static int snd_pcsp_playback_close(struct snd_pcm_substream *substream)
197{
198 struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
199#if PCSP_DEBUG
200 printk(KERN_INFO "PCSP: close called\n");
201#endif
202 if (atomic_read(&chip->timer_active)) {
203 printk(KERN_ERR "PCSP: timer still active\n");
204 pcsp_stop_playing(chip);
205 }
206 spin_lock_irq(&chip->substream_lock);
207 chip->playback_substream = NULL;
208 spin_unlock_irq(&chip->substream_lock);
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 int err;
216 err = snd_pcm_lib_malloc_pages(substream,
217 params_buffer_bytes(hw_params));
218 if (err < 0)
219 return err;
220 return 0;
221}
222
223static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream)
224{
225#if PCSP_DEBUG
226 printk(KERN_INFO "PCSP: hw_free called\n");
227#endif
228 return snd_pcm_lib_free_pages(substream);
229}
230
231static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream)
232{
233 struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
234#if PCSP_DEBUG
235 printk(KERN_INFO "PCSP: prepare called, "
236 "size=%zi psize=%zi f=%zi f1=%i\n",
237 snd_pcm_lib_buffer_bytes(substream),
238 snd_pcm_lib_period_bytes(substream),
239 snd_pcm_lib_buffer_bytes(substream) /
240 snd_pcm_lib_period_bytes(substream),
241 substream->runtime->periods);
242#endif
243 chip->playback_ptr = 0;
244 chip->period_ptr = 0;
245 return 0;
246}
247
248static int snd_pcsp_trigger(struct snd_pcm_substream *substream, int cmd)
249{
250 struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
251#if PCSP_DEBUG
252 printk(KERN_INFO "PCSP: trigger called\n");
253#endif
254 switch (cmd) {
255 case SNDRV_PCM_TRIGGER_START:
256 case SNDRV_PCM_TRIGGER_RESUME:
257 pcsp_start_playing(chip);
258 break;
259 case SNDRV_PCM_TRIGGER_STOP:
260 case SNDRV_PCM_TRIGGER_SUSPEND:
261 pcsp_stop_playing(chip);
262 break;
263 default:
264 return -EINVAL;
265 }
266 return 0;
267}
268
269static snd_pcm_uframes_t snd_pcsp_playback_pointer(struct snd_pcm_substream
270 *substream)
271{
272 struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
273 return bytes_to_frames(substream->runtime, chip->playback_ptr);
274}
275
276static struct snd_pcm_hardware snd_pcsp_playback = {
277 .info = (SNDRV_PCM_INFO_INTERLEAVED |
278 SNDRV_PCM_INFO_HALF_DUPLEX |
279 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
280 .formats = (SNDRV_PCM_FMTBIT_U8
281#if DMIX_WANTS_S16
282 | SNDRV_PCM_FMTBIT_S16_LE
283#endif
284 ),
285 .rates = SNDRV_PCM_RATE_KNOT,
286 .rate_min = PCSP_DEFAULT_SRATE,
287 .rate_max = PCSP_DEFAULT_SRATE,
288 .channels_min = 1,
289 .channels_max = 1,
290 .buffer_bytes_max = PCSP_BUFFER_SIZE,
291 .period_bytes_min = 64,
292 .period_bytes_max = PCSP_MAX_PERIOD_SIZE,
293 .periods_min = 2,
294 .periods_max = PCSP_MAX_PERIODS,
295 .fifo_size = 0,
296};
297
298static int snd_pcsp_playback_open(struct snd_pcm_substream *substream)
299{
300 struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
301 struct snd_pcm_runtime *runtime = substream->runtime;
302#if PCSP_DEBUG
303 printk(KERN_INFO "PCSP: open called\n");
304#endif
305 if (atomic_read(&chip->timer_active)) {
306 printk(KERN_ERR "PCSP: still active!!\n");
307 return -EBUSY;
308 }
309 runtime->hw = snd_pcsp_playback;
310 chip->playback_substream = substream;
311 return 0;
312}
313
314static struct snd_pcm_ops snd_pcsp_playback_ops = {
315 .open = snd_pcsp_playback_open,
316 .close = snd_pcsp_playback_close,
317 .ioctl = snd_pcm_lib_ioctl,
318 .hw_params = snd_pcsp_playback_hw_params,
319 .hw_free = snd_pcsp_playback_hw_free,
320 .prepare = snd_pcsp_playback_prepare,
321 .trigger = snd_pcsp_trigger,
322 .pointer = snd_pcsp_playback_pointer,
323};
324
325int __devinit snd_pcsp_new_pcm(struct snd_pcsp *chip)
326{
327 int err;
328
329 err = snd_pcm_new(chip->card, "pcspeaker", 0, 1, 0, &chip->pcm);
330 if (err < 0)
331 return err;
332
333 snd_pcm_set_ops(chip->pcm, SNDRV_PCM_STREAM_PLAYBACK,
334 &snd_pcsp_playback_ops);
335
336 chip->pcm->private_data = chip;
337 chip->pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
338 strcpy(chip->pcm->name, "pcsp");
339
340 snd_pcm_lib_preallocate_pages_for_all(chip->pcm,
341 SNDRV_DMA_TYPE_CONTINUOUS,
342 snd_dma_continuous_data
343 (GFP_KERNEL), PCSP_BUFFER_SIZE,
344 PCSP_BUFFER_SIZE);
345
346 return 0;
347}
diff --git a/sound/drivers/pcsp/pcsp_mixer.c b/sound/drivers/pcsp/pcsp_mixer.c
new file mode 100644
index 000000000000..64a695fef74e
--- /dev/null
+++ b/sound/drivers/pcsp/pcsp_mixer.c
@@ -0,0 +1,143 @@
1/*
2 * PC-Speaker driver for Linux
3 *
4 * Mixer implementation.
5 * Copyright (C) 2001-2008 Stas Sergeev
6 */
7
8#include <sound/core.h>
9#include <sound/control.h>
10#include "pcsp.h"
11
12
13static int pcsp_enable_info(struct snd_kcontrol *kcontrol,
14 struct snd_ctl_elem_info *uinfo)
15{
16 uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
17 uinfo->count = 1;
18 uinfo->value.integer.min = 0;
19 uinfo->value.integer.max = 1;
20 return 0;
21}
22
23static int pcsp_enable_get(struct snd_kcontrol *kcontrol,
24 struct snd_ctl_elem_value *ucontrol)
25{
26 struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
27 ucontrol->value.integer.value[0] = chip->enable;
28 return 0;
29}
30
31static int pcsp_enable_put(struct snd_kcontrol *kcontrol,
32 struct snd_ctl_elem_value *ucontrol)
33{
34 struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
35 int changed = 0;
36 int enab = ucontrol->value.integer.value[0];
37 if (enab != chip->enable) {
38 chip->enable = enab;
39 changed = 1;
40 }
41 return changed;
42}
43
44static int pcsp_treble_info(struct snd_kcontrol *kcontrol,
45 struct snd_ctl_elem_info *uinfo)
46{
47 struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
48 uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
49 uinfo->count = 1;
50 uinfo->value.enumerated.items = chip->max_treble + 1;
51 if (uinfo->value.enumerated.item > chip->max_treble)
52 uinfo->value.enumerated.item = chip->max_treble;
53 sprintf(uinfo->value.enumerated.name, "%d", PCSP_RATE());
54 return 0;
55}
56
57static int pcsp_treble_get(struct snd_kcontrol *kcontrol,
58 struct snd_ctl_elem_value *ucontrol)
59{
60 struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
61 ucontrol->value.enumerated.item[0] = chip->treble;
62 return 0;
63}
64
65static int pcsp_treble_put(struct snd_kcontrol *kcontrol,
66 struct snd_ctl_elem_value *ucontrol)
67{
68 struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
69 int changed = 0;
70 int treble = ucontrol->value.enumerated.item[0];
71 if (treble != chip->treble) {
72 chip->treble = treble;
73#if PCSP_DEBUG
74 printk(KERN_INFO "PCSP: rate set to %i\n", PCSP_RATE());
75#endif
76 changed = 1;
77 }
78 return changed;
79}
80
81static int pcsp_pcspkr_info(struct snd_kcontrol *kcontrol,
82 struct snd_ctl_elem_info *uinfo)
83{
84 uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
85 uinfo->count = 1;
86 uinfo->value.integer.min = 0;
87 uinfo->value.integer.max = 1;
88 return 0;
89}
90
91static int pcsp_pcspkr_get(struct snd_kcontrol *kcontrol,
92 struct snd_ctl_elem_value *ucontrol)
93{
94 struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
95 ucontrol->value.integer.value[0] = chip->pcspkr;
96 return 0;
97}
98
99static int pcsp_pcspkr_put(struct snd_kcontrol *kcontrol,
100 struct snd_ctl_elem_value *ucontrol)
101{
102 struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
103 int changed = 0;
104 int spkr = ucontrol->value.integer.value[0];
105 if (spkr != chip->pcspkr) {
106 chip->pcspkr = spkr;
107 changed = 1;
108 }
109 return changed;
110}
111
112#define PCSP_MIXER_CONTROL(ctl_type, ctl_name) \
113{ \
114 .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
115 .name = ctl_name, \
116 .info = pcsp_##ctl_type##_info, \
117 .get = pcsp_##ctl_type##_get, \
118 .put = pcsp_##ctl_type##_put, \
119}
120
121static struct snd_kcontrol_new __devinitdata snd_pcsp_controls[] = {
122 PCSP_MIXER_CONTROL(enable, "Master Playback Switch"),
123 PCSP_MIXER_CONTROL(treble, "BaseFRQ Playback Volume"),
124 PCSP_MIXER_CONTROL(pcspkr, "PC Speaker Playback Switch"),
125};
126
127int __devinit snd_pcsp_new_mixer(struct snd_pcsp *chip)
128{
129 struct snd_card *card = chip->card;
130 int i, err;
131
132 for (i = 0; i < ARRAY_SIZE(snd_pcsp_controls); i++) {
133 err = snd_ctl_add(card,
134 snd_ctl_new1(snd_pcsp_controls + i,
135 chip));
136 if (err < 0)
137 return err;
138 }
139
140 strcpy(card->mixername, "PC-Speaker");
141
142 return 0;
143}