diff options
author | Stas Sergeev <stsp@aknet.ru> | 2008-03-03 04:53:54 -0500 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2008-04-24 06:00:20 -0400 |
commit | 9ab4d072ad67793d70b8707e14fb9261749c4e07 (patch) | |
tree | c6ae32f1d3d3b273fae30b0fa59b8c79bd5e8044 /sound/drivers/pcsp/pcsp.c | |
parent | 40ac8c4f208111cdc1542ccc9feb21b98a6b0219 (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/pcsp.c')
-rw-r--r-- | sound/drivers/pcsp/pcsp.c | 241 |
1 files changed, 241 insertions, 0 deletions
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 | |||
21 | MODULE_AUTHOR("Stas Sergeev <stsp@users.sourceforge.net>"); | ||
22 | MODULE_DESCRIPTION("PC-Speaker driver"); | ||
23 | MODULE_LICENSE("GPL"); | ||
24 | MODULE_SUPPORTED_DEVICE("{{PC-Speaker, pcsp}}"); | ||
25 | MODULE_ALIAS("platform:pcspkr"); | ||
26 | |||
27 | static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ | ||
28 | static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ | ||
29 | static int enable = SNDRV_DEFAULT_ENABLE1; /* Enable this card */ | ||
30 | |||
31 | module_param(index, int, 0444); | ||
32 | MODULE_PARM_DESC(index, "Index value for pcsp soundcard."); | ||
33 | module_param(id, charp, 0444); | ||
34 | MODULE_PARM_DESC(id, "ID string for pcsp soundcard."); | ||
35 | module_param(enable, bool, 0444); | ||
36 | MODULE_PARM_DESC(enable, "dummy"); | ||
37 | |||
38 | struct snd_pcsp pcsp_chip; | ||
39 | |||
40 | static 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 | |||
91 | static 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 | |||
139 | static 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 | |||
165 | static void __devexit alsa_card_pcsp_exit(struct snd_pcsp *chip) | ||
166 | { | ||
167 | snd_card_free(chip->card); | ||
168 | } | ||
169 | |||
170 | static 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 | |||
187 | static 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 | |||
196 | static 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 | |||
205 | static 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 | |||
213 | static void pcsp_shutdown(struct platform_device *dev) | ||
214 | { | ||
215 | struct snd_pcsp *chip = platform_get_drvdata(dev); | ||
216 | pcsp_stop_beep(chip); | ||
217 | } | ||
218 | |||
219 | static 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 | |||
230 | static int __init pcsp_init(void) | ||
231 | { | ||
232 | return platform_driver_register(&pcsp_platform_driver); | ||
233 | } | ||
234 | |||
235 | static void __exit pcsp_exit(void) | ||
236 | { | ||
237 | platform_driver_unregister(&pcsp_platform_driver); | ||
238 | } | ||
239 | |||
240 | module_init(pcsp_init); | ||
241 | module_exit(pcsp_exit); | ||