| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * PC-Speaker driver for Linux | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright (C) 1997-2001  David Woodhouse | 
					
						
							|  |  |  |  * Copyright (C) 2001-2008  Stas Sergeev | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/init.h>
 | 
					
						
							| 
									
										
										
										
											2011-07-15 13:13:37 -04:00
										 |  |  | #include <linux/module.h>
 | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | #include <linux/platform_device.h>
 | 
					
						
							|  |  |  | #include <sound/core.h>
 | 
					
						
							|  |  |  | #include <sound/initval.h>
 | 
					
						
							|  |  |  | #include <sound/pcm.h>
 | 
					
						
							|  |  |  | #include <linux/input.h>
 | 
					
						
							| 
									
										
										
										
											2008-03-18 09:03:03 +01:00
										 |  |  | #include <linux/delay.h>
 | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | #include <asm/bitops.h>
 | 
					
						
							|  |  |  | #include "pcsp_input.h"
 | 
					
						
							|  |  |  | #include "pcsp.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | MODULE_AUTHOR("Stas Sergeev <stsp@users.sourceforge.net>"); | 
					
						
							|  |  |  | MODULE_DESCRIPTION("PC-Speaker driver"); | 
					
						
							|  |  |  | MODULE_LICENSE("GPL"); | 
					
						
							|  |  |  | MODULE_SUPPORTED_DEVICE("{{PC-Speaker, pcsp}}"); | 
					
						
							|  |  |  | MODULE_ALIAS("platform:pcspkr"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int index = SNDRV_DEFAULT_IDX1;	/* Index 0-MAX */ | 
					
						
							|  |  |  | static char *id = SNDRV_DEFAULT_STR1;	/* ID for this card */ | 
					
						
							| 
									
										
										
										
											2011-12-15 13:49:36 +10:30
										 |  |  | static bool enable = SNDRV_DEFAULT_ENABLE1;	/* Enable this card */ | 
					
						
							|  |  |  | static bool nopcm;	/* Disable PCM capability of the driver */ | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | module_param(index, int, 0444); | 
					
						
							|  |  |  | MODULE_PARM_DESC(index, "Index value for pcsp soundcard."); | 
					
						
							|  |  |  | module_param(id, charp, 0444); | 
					
						
							|  |  |  | MODULE_PARM_DESC(id, "ID string for pcsp soundcard."); | 
					
						
							|  |  |  | module_param(enable, bool, 0444); | 
					
						
							| 
									
										
										
										
											2008-03-06 11:01:16 +01:00
										 |  |  | MODULE_PARM_DESC(enable, "Enable PC-Speaker sound."); | 
					
						
							| 
									
										
										
										
											2009-11-01 11:13:19 +01:00
										 |  |  | module_param(nopcm, bool, 0444); | 
					
						
							|  |  |  | MODULE_PARM_DESC(nopcm, "Disable PC-Speaker PCM sound. Only beeps remain."); | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | struct snd_pcsp pcsp_chip; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-12-06 12:35:27 -05:00
										 |  |  | static int snd_pcsp_create(struct snd_card *card) | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | { | 
					
						
							|  |  |  | 	static struct snd_device_ops ops = { }; | 
					
						
							|  |  |  | 	struct timespec tp; | 
					
						
							|  |  |  | 	int err; | 
					
						
							|  |  |  | 	int div, min_div, order; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-10-29 15:15:20 +01:00
										 |  |  | 	hrtimer_get_res(CLOCK_MONOTONIC, &tp); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-01 11:13:19 +01:00
										 |  |  | 	if (!nopcm) { | 
					
						
							|  |  |  | 		if (tp.tv_sec || tp.tv_nsec > PCSP_MAX_PERIOD_NS) { | 
					
						
							|  |  |  | 			printk(KERN_ERR "PCSP: Timer resolution is not sufficient " | 
					
						
							|  |  |  | 				"(%linS)\n", tp.tv_nsec); | 
					
						
							|  |  |  | 			printk(KERN_ERR "PCSP: Make sure you have HPET and ACPI " | 
					
						
							|  |  |  | 				"enabled.\n"); | 
					
						
							|  |  |  | 			printk(KERN_ERR "PCSP: Turned into nopcm mode.\n"); | 
					
						
							|  |  |  | 			nopcm = 1; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (loops_per_jiffy >= PCSP_MIN_LPJ && tp.tv_nsec <= PCSP_MIN_PERIOD_NS) | 
					
						
							|  |  |  | 		min_div = MIN_DIV; | 
					
						
							|  |  |  | 	else | 
					
						
							|  |  |  | 		min_div = MAX_DIV; | 
					
						
							|  |  |  | #if PCSP_DEBUG
 | 
					
						
							| 
									
										
										
										
											2009-02-05 15:51:50 +01:00
										 |  |  | 	printk(KERN_DEBUG "PCSP: lpj=%li, min_div=%i, res=%li\n", | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | 	       loops_per_jiffy, min_div, tp.tv_nsec); | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	div = MAX_DIV / min_div; | 
					
						
							|  |  |  | 	order = fls(div) - 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pcsp_chip.max_treble = min(order, PCSP_MAX_TREBLE); | 
					
						
							|  |  |  | 	pcsp_chip.treble = min(pcsp_chip.max_treble, PCSP_DEFAULT_TREBLE); | 
					
						
							|  |  |  | 	pcsp_chip.playback_ptr = 0; | 
					
						
							|  |  |  | 	pcsp_chip.period_ptr = 0; | 
					
						
							|  |  |  | 	atomic_set(&pcsp_chip.timer_active, 0); | 
					
						
							|  |  |  | 	pcsp_chip.enable = 1; | 
					
						
							|  |  |  | 	pcsp_chip.pcspkr = 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	spin_lock_init(&pcsp_chip.substream_lock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pcsp_chip.card = card; | 
					
						
							|  |  |  | 	pcsp_chip.port = 0x61; | 
					
						
							|  |  |  | 	pcsp_chip.irq = -1; | 
					
						
							|  |  |  | 	pcsp_chip.dma = -1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Register device */ | 
					
						
							|  |  |  | 	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, &pcsp_chip, &ops); | 
					
						
							|  |  |  | 	if (err < 0) | 
					
						
							|  |  |  | 		return err; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-12-06 12:35:27 -05:00
										 |  |  | static int snd_card_pcsp_probe(int devnum, struct device *dev) | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct snd_card *card; | 
					
						
							|  |  |  | 	int err; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (devnum != 0) | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	hrtimer_init(&pcsp_chip.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); | 
					
						
							|  |  |  | 	pcsp_chip.timer.function = pcsp_do_timer; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-01-29 12:59:08 +01:00
										 |  |  | 	err = snd_card_new(dev, index, id, THIS_MODULE, 0, &card); | 
					
						
							| 
									
										
										
										
											2008-12-28 16:45:02 +01:00
										 |  |  | 	if (err < 0) | 
					
						
							|  |  |  | 		return err; | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	err = snd_pcsp_create(card); | 
					
						
							|  |  |  | 	if (err < 0) { | 
					
						
							|  |  |  | 		snd_card_free(card); | 
					
						
							|  |  |  | 		return err; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2009-11-01 11:13:19 +01:00
										 |  |  | 	if (!nopcm) { | 
					
						
							|  |  |  | 		err = snd_pcsp_new_pcm(&pcsp_chip); | 
					
						
							|  |  |  | 		if (err < 0) { | 
					
						
							|  |  |  | 			snd_card_free(card); | 
					
						
							|  |  |  | 			return err; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2009-11-01 11:13:19 +01:00
										 |  |  | 	err = snd_pcsp_new_mixer(&pcsp_chip, nopcm); | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | 	if (err < 0) { | 
					
						
							|  |  |  | 		snd_card_free(card); | 
					
						
							|  |  |  | 		return err; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	strcpy(card->driver, "PC-Speaker"); | 
					
						
							|  |  |  | 	strcpy(card->shortname, "pcsp"); | 
					
						
							|  |  |  | 	sprintf(card->longname, "Internal PC-Speaker at port 0x%x", | 
					
						
							|  |  |  | 		pcsp_chip.port); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err = snd_card_register(card); | 
					
						
							|  |  |  | 	if (err < 0) { | 
					
						
							|  |  |  | 		snd_card_free(card); | 
					
						
							|  |  |  | 		return err; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-12-06 12:35:27 -05:00
										 |  |  | static int alsa_card_pcsp_init(struct device *dev) | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2008-03-06 11:01:16 +01:00
										 |  |  | 	int err; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err = snd_card_pcsp_probe(0, dev); | 
					
						
							|  |  |  | 	if (err) { | 
					
						
							|  |  |  | 		printk(KERN_ERR "PC-Speaker initialization failed.\n"); | 
					
						
							|  |  |  | 		return err; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | #ifdef CONFIG_DEBUG_PAGEALLOC
 | 
					
						
							|  |  |  | 	/* Well, CONFIG_DEBUG_PAGEALLOC makes the sound horrible. Lets alert */ | 
					
						
							| 
									
										
										
										
											2008-04-23 17:16:38 +02:00
										 |  |  | 	printk(KERN_WARNING "PCSP: CONFIG_DEBUG_PAGEALLOC is enabled, " | 
					
						
							|  |  |  | 	       "which may make the sound noisy.\n"); | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-12-06 12:35:27 -05:00
										 |  |  | static void alsa_card_pcsp_exit(struct snd_pcsp *chip) | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | { | 
					
						
							|  |  |  | 	snd_card_free(chip->card); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-12-06 12:35:27 -05:00
										 |  |  | static int pcsp_probe(struct platform_device *dev) | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | { | 
					
						
							|  |  |  | 	int err; | 
					
						
							| 
									
										
										
										
											2008-03-06 11:01:16 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | 	err = pcspkr_input_init(&pcsp_chip.input_dev, &dev->dev); | 
					
						
							|  |  |  | 	if (err < 0) | 
					
						
							|  |  |  | 		return err; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err = alsa_card_pcsp_init(&dev->dev); | 
					
						
							|  |  |  | 	if (err < 0) { | 
					
						
							|  |  |  | 		pcspkr_input_remove(pcsp_chip.input_dev); | 
					
						
							|  |  |  | 		return err; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	platform_set_drvdata(dev, &pcsp_chip); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-12-06 12:35:27 -05:00
										 |  |  | static int pcsp_remove(struct platform_device *dev) | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct snd_pcsp *chip = platform_get_drvdata(dev); | 
					
						
							|  |  |  | 	pcspkr_input_remove(chip->input_dev); | 
					
						
							| 
									
										
										
										
											2013-11-14 15:45:12 +01:00
										 |  |  | 	alsa_card_pcsp_exit(chip); | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void pcsp_stop_beep(struct snd_pcsp *chip) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2008-08-11 10:18:39 +02:00
										 |  |  | 	pcsp_sync_stop(chip); | 
					
						
							|  |  |  | 	pcspkr_stop_sound(); | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-08-09 15:47:15 +02:00
										 |  |  | #ifdef CONFIG_PM_SLEEP
 | 
					
						
							| 
									
										
										
										
											2012-07-02 11:22:40 +02:00
										 |  |  | static int pcsp_suspend(struct device *dev) | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2012-07-02 11:22:40 +02:00
										 |  |  | 	struct snd_pcsp *chip = dev_get_drvdata(dev); | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | 	pcsp_stop_beep(chip); | 
					
						
							|  |  |  | 	snd_pcm_suspend_all(chip->pcm); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2012-07-02 11:22:40 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | static SIMPLE_DEV_PM_OPS(pcsp_pm, pcsp_suspend, NULL); | 
					
						
							|  |  |  | #define PCSP_PM_OPS	&pcsp_pm
 | 
					
						
							| 
									
										
										
										
											2008-05-02 09:54:31 +02:00
										 |  |  | #else
 | 
					
						
							| 
									
										
										
										
											2012-07-02 11:22:40 +02:00
										 |  |  | #define PCSP_PM_OPS	NULL
 | 
					
						
							| 
									
										
										
										
											2012-08-09 15:47:15 +02:00
										 |  |  | #endif	/* CONFIG_PM_SLEEP */
 | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | static void pcsp_shutdown(struct platform_device *dev) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct snd_pcsp *chip = platform_get_drvdata(dev); | 
					
						
							|  |  |  | 	pcsp_stop_beep(chip); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct platform_driver pcsp_platform_driver = { | 
					
						
							|  |  |  | 	.driver		= { | 
					
						
							|  |  |  | 		.name	= "pcspkr", | 
					
						
							|  |  |  | 		.owner	= THIS_MODULE, | 
					
						
							| 
									
										
										
										
											2012-07-02 11:22:40 +02:00
										 |  |  | 		.pm	= PCSP_PM_OPS, | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 	.probe		= pcsp_probe, | 
					
						
							| 
									
										
										
										
											2012-12-06 12:35:27 -05:00
										 |  |  | 	.remove		= pcsp_remove, | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | 	.shutdown	= pcsp_shutdown, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int __init pcsp_init(void) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2008-03-06 11:01:16 +01:00
										 |  |  | 	if (!enable) | 
					
						
							|  |  |  | 		return -ENODEV; | 
					
						
							| 
									
										
										
										
											2008-03-03 10:53:54 +01:00
										 |  |  | 	return platform_driver_register(&pcsp_platform_driver); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void __exit pcsp_exit(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	platform_driver_unregister(&pcsp_platform_driver); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module_init(pcsp_init); | 
					
						
							|  |  |  | module_exit(pcsp_exit); |