xref: /openbmc/linux/sound/drivers/pcsp/pcsp.c (revision 9ab4d072ad67793d70b8707e14fb9261749c4e07)
1*9ab4d072SStas Sergeev /*
2*9ab4d072SStas Sergeev  * PC-Speaker driver for Linux
3*9ab4d072SStas Sergeev  *
4*9ab4d072SStas Sergeev  * Copyright (C) 1997-2001  David Woodhouse
5*9ab4d072SStas Sergeev  * Copyright (C) 2001-2008  Stas Sergeev
6*9ab4d072SStas Sergeev  */
7*9ab4d072SStas Sergeev 
8*9ab4d072SStas Sergeev #include <linux/init.h>
9*9ab4d072SStas Sergeev #include <linux/moduleparam.h>
10*9ab4d072SStas Sergeev #include <linux/platform_device.h>
11*9ab4d072SStas Sergeev #include <sound/core.h>
12*9ab4d072SStas Sergeev #include <sound/initval.h>
13*9ab4d072SStas Sergeev #include <sound/pcm.h>
14*9ab4d072SStas Sergeev 
15*9ab4d072SStas Sergeev #include <linux/input.h>
16*9ab4d072SStas Sergeev #include <linux/delay.h>
17*9ab4d072SStas Sergeev #include <asm/bitops.h>
18*9ab4d072SStas Sergeev #include "pcsp_input.h"
19*9ab4d072SStas Sergeev #include "pcsp.h"
20*9ab4d072SStas Sergeev 
21*9ab4d072SStas Sergeev MODULE_AUTHOR("Stas Sergeev <stsp@users.sourceforge.net>");
22*9ab4d072SStas Sergeev MODULE_DESCRIPTION("PC-Speaker driver");
23*9ab4d072SStas Sergeev MODULE_LICENSE("GPL");
24*9ab4d072SStas Sergeev MODULE_SUPPORTED_DEVICE("{{PC-Speaker, pcsp}}");
25*9ab4d072SStas Sergeev MODULE_ALIAS("platform:pcspkr");
26*9ab4d072SStas Sergeev 
27*9ab4d072SStas Sergeev static int index = SNDRV_DEFAULT_IDX1;	/* Index 0-MAX */
28*9ab4d072SStas Sergeev static char *id = SNDRV_DEFAULT_STR1;	/* ID for this card */
29*9ab4d072SStas Sergeev static int enable = SNDRV_DEFAULT_ENABLE1;	/* Enable this card */
30*9ab4d072SStas Sergeev 
31*9ab4d072SStas Sergeev module_param(index, int, 0444);
32*9ab4d072SStas Sergeev MODULE_PARM_DESC(index, "Index value for pcsp soundcard.");
33*9ab4d072SStas Sergeev module_param(id, charp, 0444);
34*9ab4d072SStas Sergeev MODULE_PARM_DESC(id, "ID string for pcsp soundcard.");
35*9ab4d072SStas Sergeev module_param(enable, bool, 0444);
36*9ab4d072SStas Sergeev MODULE_PARM_DESC(enable, "dummy");
37*9ab4d072SStas Sergeev 
38*9ab4d072SStas Sergeev struct snd_pcsp pcsp_chip;
39*9ab4d072SStas Sergeev 
40*9ab4d072SStas Sergeev static int __devinit snd_pcsp_create(struct snd_card *card)
41*9ab4d072SStas Sergeev {
42*9ab4d072SStas Sergeev 	static struct snd_device_ops ops = { };
43*9ab4d072SStas Sergeev 	struct timespec tp;
44*9ab4d072SStas Sergeev 	int err;
45*9ab4d072SStas Sergeev 	int div, min_div, order;
46*9ab4d072SStas Sergeev 
47*9ab4d072SStas Sergeev 	hrtimer_get_res(CLOCK_MONOTONIC, &tp);
48*9ab4d072SStas Sergeev 	if (tp.tv_sec || tp.tv_nsec > PCSP_MAX_PERIOD_NS) {
49*9ab4d072SStas Sergeev 		printk(KERN_ERR "PCSP: Timer resolution is not sufficient "
50*9ab4d072SStas Sergeev 		       "(%linS)\n", tp.tv_nsec);
51*9ab4d072SStas Sergeev 		printk(KERN_ERR "PCSP: Make sure you have HPET and ACPI "
52*9ab4d072SStas Sergeev 		       "enabled.\n");
53*9ab4d072SStas Sergeev 		return -EIO;
54*9ab4d072SStas Sergeev 	}
55*9ab4d072SStas Sergeev 
56*9ab4d072SStas Sergeev 	if (loops_per_jiffy >= PCSP_MIN_LPJ && tp.tv_nsec <= PCSP_MIN_PERIOD_NS)
57*9ab4d072SStas Sergeev 		min_div = MIN_DIV;
58*9ab4d072SStas Sergeev 	else
59*9ab4d072SStas Sergeev 		min_div = MAX_DIV;
60*9ab4d072SStas Sergeev #if PCSP_DEBUG
61*9ab4d072SStas Sergeev 	printk("PCSP: lpj=%li, min_div=%i, res=%li\n",
62*9ab4d072SStas Sergeev 	       loops_per_jiffy, min_div, tp.tv_nsec);
63*9ab4d072SStas Sergeev #endif
64*9ab4d072SStas Sergeev 
65*9ab4d072SStas Sergeev 	div = MAX_DIV / min_div;
66*9ab4d072SStas Sergeev 	order = fls(div) - 1;
67*9ab4d072SStas Sergeev 
68*9ab4d072SStas Sergeev 	pcsp_chip.max_treble = min(order, PCSP_MAX_TREBLE);
69*9ab4d072SStas Sergeev 	pcsp_chip.treble = min(pcsp_chip.max_treble, PCSP_DEFAULT_TREBLE);
70*9ab4d072SStas Sergeev 	pcsp_chip.playback_ptr = 0;
71*9ab4d072SStas Sergeev 	pcsp_chip.period_ptr = 0;
72*9ab4d072SStas Sergeev 	atomic_set(&pcsp_chip.timer_active, 0);
73*9ab4d072SStas Sergeev 	pcsp_chip.enable = 1;
74*9ab4d072SStas Sergeev 	pcsp_chip.pcspkr = 1;
75*9ab4d072SStas Sergeev 
76*9ab4d072SStas Sergeev 	spin_lock_init(&pcsp_chip.substream_lock);
77*9ab4d072SStas Sergeev 
78*9ab4d072SStas Sergeev 	pcsp_chip.card = card;
79*9ab4d072SStas Sergeev 	pcsp_chip.port = 0x61;
80*9ab4d072SStas Sergeev 	pcsp_chip.irq = -1;
81*9ab4d072SStas Sergeev 	pcsp_chip.dma = -1;
82*9ab4d072SStas Sergeev 
83*9ab4d072SStas Sergeev 	/* Register device */
84*9ab4d072SStas Sergeev 	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, &pcsp_chip, &ops);
85*9ab4d072SStas Sergeev 	if (err < 0)
86*9ab4d072SStas Sergeev 		return err;
87*9ab4d072SStas Sergeev 
88*9ab4d072SStas Sergeev 	return 0;
89*9ab4d072SStas Sergeev }
90*9ab4d072SStas Sergeev 
91*9ab4d072SStas Sergeev static int __devinit snd_card_pcsp_probe(int devnum, struct device *dev)
92*9ab4d072SStas Sergeev {
93*9ab4d072SStas Sergeev 	struct snd_card *card;
94*9ab4d072SStas Sergeev 	int err;
95*9ab4d072SStas Sergeev 
96*9ab4d072SStas Sergeev 	if (devnum != 0)
97*9ab4d072SStas Sergeev 		return -EINVAL;
98*9ab4d072SStas Sergeev 
99*9ab4d072SStas Sergeev 	hrtimer_init(&pcsp_chip.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
100*9ab4d072SStas Sergeev 	pcsp_chip.timer.cb_mode = HRTIMER_CB_IRQSAFE;
101*9ab4d072SStas Sergeev 	pcsp_chip.timer.function = pcsp_do_timer;
102*9ab4d072SStas Sergeev 
103*9ab4d072SStas Sergeev 	card = snd_card_new(index, id, THIS_MODULE, 0);
104*9ab4d072SStas Sergeev 	if (!card)
105*9ab4d072SStas Sergeev 		return -ENOMEM;
106*9ab4d072SStas Sergeev 
107*9ab4d072SStas Sergeev 	err = snd_pcsp_create(card);
108*9ab4d072SStas Sergeev 	if (err < 0) {
109*9ab4d072SStas Sergeev 		snd_card_free(card);
110*9ab4d072SStas Sergeev 		return err;
111*9ab4d072SStas Sergeev 	}
112*9ab4d072SStas Sergeev 	err = snd_pcsp_new_pcm(&pcsp_chip);
113*9ab4d072SStas Sergeev 	if (err < 0) {
114*9ab4d072SStas Sergeev 		snd_card_free(card);
115*9ab4d072SStas Sergeev 		return err;
116*9ab4d072SStas Sergeev 	}
117*9ab4d072SStas Sergeev 	err = snd_pcsp_new_mixer(&pcsp_chip);
118*9ab4d072SStas Sergeev 	if (err < 0) {
119*9ab4d072SStas Sergeev 		snd_card_free(card);
120*9ab4d072SStas Sergeev 		return err;
121*9ab4d072SStas Sergeev 	}
122*9ab4d072SStas Sergeev 
123*9ab4d072SStas Sergeev 	snd_card_set_dev(pcsp_chip.card, dev);
124*9ab4d072SStas Sergeev 
125*9ab4d072SStas Sergeev 	strcpy(card->driver, "PC-Speaker");
126*9ab4d072SStas Sergeev 	strcpy(card->shortname, "pcsp");
127*9ab4d072SStas Sergeev 	sprintf(card->longname, "Internal PC-Speaker at port 0x%x",
128*9ab4d072SStas Sergeev 		pcsp_chip.port);
129*9ab4d072SStas Sergeev 
130*9ab4d072SStas Sergeev 	err = snd_card_register(card);
131*9ab4d072SStas Sergeev 	if (err < 0) {
132*9ab4d072SStas Sergeev 		snd_card_free(card);
133*9ab4d072SStas Sergeev 		return err;
134*9ab4d072SStas Sergeev 	}
135*9ab4d072SStas Sergeev 
136*9ab4d072SStas Sergeev 	return 0;
137*9ab4d072SStas Sergeev }
138*9ab4d072SStas Sergeev 
139*9ab4d072SStas Sergeev static int __devinit alsa_card_pcsp_init(struct device *dev)
140*9ab4d072SStas Sergeev {
141*9ab4d072SStas Sergeev 	int devnum = 0, cards = 0;
142*9ab4d072SStas Sergeev 
143*9ab4d072SStas Sergeev #ifdef CONFIG_DEBUG_PAGEALLOC
144*9ab4d072SStas Sergeev 	/* Well, CONFIG_DEBUG_PAGEALLOC makes the sound horrible. Lets alert */
145*9ab4d072SStas Sergeev 	printk(KERN_WARNING
146*9ab4d072SStas Sergeev 	       "PCSP: Warning, CONFIG_DEBUG_PAGEALLOC is enabled!\n"
147*9ab4d072SStas Sergeev 	       "You have to disable it if you want to use the PC-Speaker "
148*9ab4d072SStas Sergeev 	       "driver.\n"
149*9ab4d072SStas Sergeev 	       "Unless it is disabled, enjoy the horrible, distorted "
150*9ab4d072SStas Sergeev 	       "and crackling noise.\n");
151*9ab4d072SStas Sergeev #endif
152*9ab4d072SStas Sergeev 
153*9ab4d072SStas Sergeev 	if (enable) {
154*9ab4d072SStas Sergeev 		if (snd_card_pcsp_probe(devnum, dev) >= 0)
155*9ab4d072SStas Sergeev 			cards++;
156*9ab4d072SStas Sergeev 		if (!cards) {
157*9ab4d072SStas Sergeev 			printk(KERN_ERR "PC-Speaker initialization failed.\n");
158*9ab4d072SStas Sergeev 			return -ENODEV;
159*9ab4d072SStas Sergeev 		}
160*9ab4d072SStas Sergeev 	}
161*9ab4d072SStas Sergeev 
162*9ab4d072SStas Sergeev 	return 0;
163*9ab4d072SStas Sergeev }
164*9ab4d072SStas Sergeev 
165*9ab4d072SStas Sergeev static void __devexit alsa_card_pcsp_exit(struct snd_pcsp *chip)
166*9ab4d072SStas Sergeev {
167*9ab4d072SStas Sergeev 	snd_card_free(chip->card);
168*9ab4d072SStas Sergeev }
169*9ab4d072SStas Sergeev 
170*9ab4d072SStas Sergeev static int __devinit pcsp_probe(struct platform_device *dev)
171*9ab4d072SStas Sergeev {
172*9ab4d072SStas Sergeev 	int err;
173*9ab4d072SStas Sergeev 	err = pcspkr_input_init(&pcsp_chip.input_dev, &dev->dev);
174*9ab4d072SStas Sergeev 	if (err < 0)
175*9ab4d072SStas Sergeev 		return err;
176*9ab4d072SStas Sergeev 
177*9ab4d072SStas Sergeev 	err = alsa_card_pcsp_init(&dev->dev);
178*9ab4d072SStas Sergeev 	if (err < 0) {
179*9ab4d072SStas Sergeev 		pcspkr_input_remove(pcsp_chip.input_dev);
180*9ab4d072SStas Sergeev 		return err;
181*9ab4d072SStas Sergeev 	}
182*9ab4d072SStas Sergeev 
183*9ab4d072SStas Sergeev 	platform_set_drvdata(dev, &pcsp_chip);
184*9ab4d072SStas Sergeev 	return 0;
185*9ab4d072SStas Sergeev }
186*9ab4d072SStas Sergeev 
187*9ab4d072SStas Sergeev static int __devexit pcsp_remove(struct platform_device *dev)
188*9ab4d072SStas Sergeev {
189*9ab4d072SStas Sergeev 	struct snd_pcsp *chip = platform_get_drvdata(dev);
190*9ab4d072SStas Sergeev 	alsa_card_pcsp_exit(chip);
191*9ab4d072SStas Sergeev 	pcspkr_input_remove(chip->input_dev);
192*9ab4d072SStas Sergeev 	platform_set_drvdata(dev, NULL);
193*9ab4d072SStas Sergeev 	return 0;
194*9ab4d072SStas Sergeev }
195*9ab4d072SStas Sergeev 
196*9ab4d072SStas Sergeev static void pcsp_stop_beep(struct snd_pcsp *chip)
197*9ab4d072SStas Sergeev {
198*9ab4d072SStas Sergeev 	unsigned long flags;
199*9ab4d072SStas Sergeev 	spin_lock_irqsave(&chip->substream_lock, flags);
200*9ab4d072SStas Sergeev 	if (!chip->playback_substream)
201*9ab4d072SStas Sergeev 		pcspkr_stop_sound();
202*9ab4d072SStas Sergeev 	spin_unlock_irqrestore(&chip->substream_lock, flags);
203*9ab4d072SStas Sergeev }
204*9ab4d072SStas Sergeev 
205*9ab4d072SStas Sergeev static int pcsp_suspend(struct platform_device *dev, pm_message_t state)
206*9ab4d072SStas Sergeev {
207*9ab4d072SStas Sergeev 	struct snd_pcsp *chip = platform_get_drvdata(dev);
208*9ab4d072SStas Sergeev 	pcsp_stop_beep(chip);
209*9ab4d072SStas Sergeev 	snd_pcm_suspend_all(chip->pcm);
210*9ab4d072SStas Sergeev 	return 0;
211*9ab4d072SStas Sergeev }
212*9ab4d072SStas Sergeev 
213*9ab4d072SStas Sergeev static void pcsp_shutdown(struct platform_device *dev)
214*9ab4d072SStas Sergeev {
215*9ab4d072SStas Sergeev 	struct snd_pcsp *chip = platform_get_drvdata(dev);
216*9ab4d072SStas Sergeev 	pcsp_stop_beep(chip);
217*9ab4d072SStas Sergeev }
218*9ab4d072SStas Sergeev 
219*9ab4d072SStas Sergeev static struct platform_driver pcsp_platform_driver = {
220*9ab4d072SStas Sergeev 	.driver		= {
221*9ab4d072SStas Sergeev 		.name	= "pcspkr",
222*9ab4d072SStas Sergeev 		.owner	= THIS_MODULE,
223*9ab4d072SStas Sergeev 	},
224*9ab4d072SStas Sergeev 	.probe		= pcsp_probe,
225*9ab4d072SStas Sergeev 	.remove		= __devexit_p(pcsp_remove),
226*9ab4d072SStas Sergeev 	.suspend	= pcsp_suspend,
227*9ab4d072SStas Sergeev 	.shutdown	= pcsp_shutdown,
228*9ab4d072SStas Sergeev };
229*9ab4d072SStas Sergeev 
230*9ab4d072SStas Sergeev static int __init pcsp_init(void)
231*9ab4d072SStas Sergeev {
232*9ab4d072SStas Sergeev 	return platform_driver_register(&pcsp_platform_driver);
233*9ab4d072SStas Sergeev }
234*9ab4d072SStas Sergeev 
235*9ab4d072SStas Sergeev static void __exit pcsp_exit(void)
236*9ab4d072SStas Sergeev {
237*9ab4d072SStas Sergeev 	platform_driver_unregister(&pcsp_platform_driver);
238*9ab4d072SStas Sergeev }
239*9ab4d072SStas Sergeev 
240*9ab4d072SStas Sergeev module_init(pcsp_init);
241*9ab4d072SStas Sergeev module_exit(pcsp_exit);
242