19ab4d072SStas Sergeev /* 29ab4d072SStas Sergeev * PC-Speaker driver for Linux 39ab4d072SStas Sergeev * 49ab4d072SStas Sergeev * Copyright (C) 1997-2001 David Woodhouse 59ab4d072SStas Sergeev * Copyright (C) 2001-2008 Stas Sergeev 69ab4d072SStas Sergeev */ 79ab4d072SStas Sergeev 89ab4d072SStas Sergeev #include <linux/init.h> 99ab4d072SStas Sergeev #include <linux/moduleparam.h> 109ab4d072SStas Sergeev #include <linux/platform_device.h> 119ab4d072SStas Sergeev #include <sound/core.h> 129ab4d072SStas Sergeev #include <sound/initval.h> 139ab4d072SStas Sergeev #include <sound/pcm.h> 149ab4d072SStas Sergeev #include <linux/input.h> 159ab4d072SStas Sergeev #include <asm/bitops.h> 169ab4d072SStas Sergeev #include "pcsp_input.h" 179ab4d072SStas Sergeev #include "pcsp.h" 189ab4d072SStas Sergeev 199ab4d072SStas Sergeev MODULE_AUTHOR("Stas Sergeev <stsp@users.sourceforge.net>"); 209ab4d072SStas Sergeev MODULE_DESCRIPTION("PC-Speaker driver"); 219ab4d072SStas Sergeev MODULE_LICENSE("GPL"); 229ab4d072SStas Sergeev MODULE_SUPPORTED_DEVICE("{{PC-Speaker, pcsp}}"); 239ab4d072SStas Sergeev MODULE_ALIAS("platform:pcspkr"); 249ab4d072SStas Sergeev 259ab4d072SStas Sergeev static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ 269ab4d072SStas Sergeev static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ 279ab4d072SStas Sergeev static int enable = SNDRV_DEFAULT_ENABLE1; /* Enable this card */ 289ab4d072SStas Sergeev 299ab4d072SStas Sergeev module_param(index, int, 0444); 309ab4d072SStas Sergeev MODULE_PARM_DESC(index, "Index value for pcsp soundcard."); 319ab4d072SStas Sergeev module_param(id, charp, 0444); 329ab4d072SStas Sergeev MODULE_PARM_DESC(id, "ID string for pcsp soundcard."); 339ab4d072SStas Sergeev module_param(enable, bool, 0444); 3452337310SStas Sergeev MODULE_PARM_DESC(enable, "Enable PC-Speaker sound."); 359ab4d072SStas Sergeev 369ab4d072SStas Sergeev struct snd_pcsp pcsp_chip; 379ab4d072SStas Sergeev 389ab4d072SStas Sergeev static int __devinit snd_pcsp_create(struct snd_card *card) 399ab4d072SStas Sergeev { 409ab4d072SStas Sergeev static struct snd_device_ops ops = { }; 419ab4d072SStas Sergeev struct timespec tp; 429ab4d072SStas Sergeev int err; 439ab4d072SStas Sergeev int div, min_div, order; 449ab4d072SStas Sergeev 459ab4d072SStas Sergeev hrtimer_get_res(CLOCK_MONOTONIC, &tp); 469ab4d072SStas Sergeev if (tp.tv_sec || tp.tv_nsec > PCSP_MAX_PERIOD_NS) { 479ab4d072SStas Sergeev printk(KERN_ERR "PCSP: Timer resolution is not sufficient " 489ab4d072SStas Sergeev "(%linS)\n", tp.tv_nsec); 499ab4d072SStas Sergeev printk(KERN_ERR "PCSP: Make sure you have HPET and ACPI " 509ab4d072SStas Sergeev "enabled.\n"); 519ab4d072SStas Sergeev return -EIO; 529ab4d072SStas Sergeev } 539ab4d072SStas Sergeev 549ab4d072SStas Sergeev if (loops_per_jiffy >= PCSP_MIN_LPJ && tp.tv_nsec <= PCSP_MIN_PERIOD_NS) 559ab4d072SStas Sergeev min_div = MIN_DIV; 569ab4d072SStas Sergeev else 579ab4d072SStas Sergeev min_div = MAX_DIV; 589ab4d072SStas Sergeev #if PCSP_DEBUG 599ab4d072SStas Sergeev printk("PCSP: lpj=%li, min_div=%i, res=%li\n", 609ab4d072SStas Sergeev loops_per_jiffy, min_div, tp.tv_nsec); 619ab4d072SStas Sergeev #endif 629ab4d072SStas Sergeev 639ab4d072SStas Sergeev div = MAX_DIV / min_div; 649ab4d072SStas Sergeev order = fls(div) - 1; 659ab4d072SStas Sergeev 669ab4d072SStas Sergeev pcsp_chip.max_treble = min(order, PCSP_MAX_TREBLE); 679ab4d072SStas Sergeev pcsp_chip.treble = min(pcsp_chip.max_treble, PCSP_DEFAULT_TREBLE); 689ab4d072SStas Sergeev pcsp_chip.playback_ptr = 0; 699ab4d072SStas Sergeev pcsp_chip.period_ptr = 0; 709ab4d072SStas Sergeev atomic_set(&pcsp_chip.timer_active, 0); 719ab4d072SStas Sergeev pcsp_chip.enable = 1; 729ab4d072SStas Sergeev pcsp_chip.pcspkr = 1; 739ab4d072SStas Sergeev 749ab4d072SStas Sergeev spin_lock_init(&pcsp_chip.substream_lock); 759ab4d072SStas Sergeev 769ab4d072SStas Sergeev pcsp_chip.card = card; 779ab4d072SStas Sergeev pcsp_chip.port = 0x61; 789ab4d072SStas Sergeev pcsp_chip.irq = -1; 799ab4d072SStas Sergeev pcsp_chip.dma = -1; 809ab4d072SStas Sergeev 819ab4d072SStas Sergeev /* Register device */ 829ab4d072SStas Sergeev err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, &pcsp_chip, &ops); 839ab4d072SStas Sergeev if (err < 0) 849ab4d072SStas Sergeev return err; 859ab4d072SStas Sergeev 869ab4d072SStas Sergeev return 0; 879ab4d072SStas Sergeev } 889ab4d072SStas Sergeev 899ab4d072SStas Sergeev static int __devinit snd_card_pcsp_probe(int devnum, struct device *dev) 909ab4d072SStas Sergeev { 919ab4d072SStas Sergeev struct snd_card *card; 929ab4d072SStas Sergeev int err; 939ab4d072SStas Sergeev 949ab4d072SStas Sergeev if (devnum != 0) 959ab4d072SStas Sergeev return -EINVAL; 969ab4d072SStas Sergeev 979ab4d072SStas Sergeev hrtimer_init(&pcsp_chip.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); 989ab4d072SStas Sergeev pcsp_chip.timer.cb_mode = HRTIMER_CB_IRQSAFE; 999ab4d072SStas Sergeev pcsp_chip.timer.function = pcsp_do_timer; 1009ab4d072SStas Sergeev 1019ab4d072SStas Sergeev card = snd_card_new(index, id, THIS_MODULE, 0); 1029ab4d072SStas Sergeev if (!card) 1039ab4d072SStas Sergeev return -ENOMEM; 1049ab4d072SStas Sergeev 1059ab4d072SStas Sergeev err = snd_pcsp_create(card); 1069ab4d072SStas Sergeev if (err < 0) { 1079ab4d072SStas Sergeev snd_card_free(card); 1089ab4d072SStas Sergeev return err; 1099ab4d072SStas Sergeev } 1109ab4d072SStas Sergeev err = snd_pcsp_new_pcm(&pcsp_chip); 1119ab4d072SStas Sergeev if (err < 0) { 1129ab4d072SStas Sergeev snd_card_free(card); 1139ab4d072SStas Sergeev return err; 1149ab4d072SStas Sergeev } 1159ab4d072SStas Sergeev err = snd_pcsp_new_mixer(&pcsp_chip); 1169ab4d072SStas Sergeev if (err < 0) { 1179ab4d072SStas Sergeev snd_card_free(card); 1189ab4d072SStas Sergeev return err; 1199ab4d072SStas Sergeev } 1209ab4d072SStas Sergeev 1219ab4d072SStas Sergeev snd_card_set_dev(pcsp_chip.card, dev); 1229ab4d072SStas Sergeev 1239ab4d072SStas Sergeev strcpy(card->driver, "PC-Speaker"); 1249ab4d072SStas Sergeev strcpy(card->shortname, "pcsp"); 1259ab4d072SStas Sergeev sprintf(card->longname, "Internal PC-Speaker at port 0x%x", 1269ab4d072SStas Sergeev pcsp_chip.port); 1279ab4d072SStas Sergeev 1289ab4d072SStas Sergeev err = snd_card_register(card); 1299ab4d072SStas Sergeev if (err < 0) { 1309ab4d072SStas Sergeev snd_card_free(card); 1319ab4d072SStas Sergeev return err; 1329ab4d072SStas Sergeev } 1339ab4d072SStas Sergeev 1349ab4d072SStas Sergeev return 0; 1359ab4d072SStas Sergeev } 1369ab4d072SStas Sergeev 1379ab4d072SStas Sergeev static int __devinit alsa_card_pcsp_init(struct device *dev) 1389ab4d072SStas Sergeev { 13952337310SStas Sergeev int err; 14052337310SStas Sergeev 14152337310SStas Sergeev err = snd_card_pcsp_probe(0, dev); 14252337310SStas Sergeev if (err) { 14352337310SStas Sergeev printk(KERN_ERR "PC-Speaker initialization failed.\n"); 14452337310SStas Sergeev return err; 14552337310SStas Sergeev } 1469ab4d072SStas Sergeev 1479ab4d072SStas Sergeev #ifdef CONFIG_DEBUG_PAGEALLOC 1489ab4d072SStas Sergeev /* Well, CONFIG_DEBUG_PAGEALLOC makes the sound horrible. Lets alert */ 1499ab4d072SStas Sergeev printk(KERN_WARNING 1509ab4d072SStas Sergeev "PCSP: Warning, CONFIG_DEBUG_PAGEALLOC is enabled!\n" 1519ab4d072SStas Sergeev "You have to disable it if you want to use the PC-Speaker " 1529ab4d072SStas Sergeev "driver.\n" 1539ab4d072SStas Sergeev "Unless it is disabled, enjoy the horrible, distorted " 1549ab4d072SStas Sergeev "and crackling noise.\n"); 1559ab4d072SStas Sergeev #endif 1569ab4d072SStas Sergeev 1579ab4d072SStas Sergeev return 0; 1589ab4d072SStas Sergeev } 1599ab4d072SStas Sergeev 1609ab4d072SStas Sergeev static void __devexit alsa_card_pcsp_exit(struct snd_pcsp *chip) 1619ab4d072SStas Sergeev { 1629ab4d072SStas Sergeev snd_card_free(chip->card); 1639ab4d072SStas Sergeev } 1649ab4d072SStas Sergeev 1659ab4d072SStas Sergeev static int __devinit pcsp_probe(struct platform_device *dev) 1669ab4d072SStas Sergeev { 1679ab4d072SStas Sergeev int err; 16852337310SStas Sergeev 1699ab4d072SStas Sergeev err = pcspkr_input_init(&pcsp_chip.input_dev, &dev->dev); 1709ab4d072SStas Sergeev if (err < 0) 1719ab4d072SStas Sergeev return err; 1729ab4d072SStas Sergeev 1739ab4d072SStas Sergeev err = alsa_card_pcsp_init(&dev->dev); 1749ab4d072SStas Sergeev if (err < 0) { 1759ab4d072SStas Sergeev pcspkr_input_remove(pcsp_chip.input_dev); 1769ab4d072SStas Sergeev return err; 1779ab4d072SStas Sergeev } 1789ab4d072SStas Sergeev 1799ab4d072SStas Sergeev platform_set_drvdata(dev, &pcsp_chip); 1809ab4d072SStas Sergeev return 0; 1819ab4d072SStas Sergeev } 1829ab4d072SStas Sergeev 1839ab4d072SStas Sergeev static int __devexit pcsp_remove(struct platform_device *dev) 1849ab4d072SStas Sergeev { 1859ab4d072SStas Sergeev struct snd_pcsp *chip = platform_get_drvdata(dev); 1869ab4d072SStas Sergeev alsa_card_pcsp_exit(chip); 1879ab4d072SStas Sergeev pcspkr_input_remove(chip->input_dev); 1889ab4d072SStas Sergeev platform_set_drvdata(dev, NULL); 1899ab4d072SStas Sergeev return 0; 1909ab4d072SStas Sergeev } 1919ab4d072SStas Sergeev 1929ab4d072SStas Sergeev static void pcsp_stop_beep(struct snd_pcsp *chip) 1939ab4d072SStas Sergeev { 194*1bc1f305SStas Sergeev spin_lock_irq(&chip->substream_lock); 1959ab4d072SStas Sergeev if (!chip->playback_substream) 1969ab4d072SStas Sergeev pcspkr_stop_sound(); 197*1bc1f305SStas Sergeev spin_unlock_irq(&chip->substream_lock); 1989ab4d072SStas Sergeev } 1999ab4d072SStas Sergeev 2009ab4d072SStas Sergeev static int pcsp_suspend(struct platform_device *dev, pm_message_t state) 2019ab4d072SStas Sergeev { 2029ab4d072SStas Sergeev struct snd_pcsp *chip = platform_get_drvdata(dev); 2039ab4d072SStas Sergeev pcsp_stop_beep(chip); 2049ab4d072SStas Sergeev snd_pcm_suspend_all(chip->pcm); 2059ab4d072SStas Sergeev return 0; 2069ab4d072SStas Sergeev } 2079ab4d072SStas Sergeev 2089ab4d072SStas Sergeev static void pcsp_shutdown(struct platform_device *dev) 2099ab4d072SStas Sergeev { 2109ab4d072SStas Sergeev struct snd_pcsp *chip = platform_get_drvdata(dev); 2119ab4d072SStas Sergeev pcsp_stop_beep(chip); 2129ab4d072SStas Sergeev } 2139ab4d072SStas Sergeev 2149ab4d072SStas Sergeev static struct platform_driver pcsp_platform_driver = { 2159ab4d072SStas Sergeev .driver = { 2169ab4d072SStas Sergeev .name = "pcspkr", 2179ab4d072SStas Sergeev .owner = THIS_MODULE, 2189ab4d072SStas Sergeev }, 2199ab4d072SStas Sergeev .probe = pcsp_probe, 2209ab4d072SStas Sergeev .remove = __devexit_p(pcsp_remove), 2219ab4d072SStas Sergeev .suspend = pcsp_suspend, 2229ab4d072SStas Sergeev .shutdown = pcsp_shutdown, 2239ab4d072SStas Sergeev }; 2249ab4d072SStas Sergeev 2259ab4d072SStas Sergeev static int __init pcsp_init(void) 2269ab4d072SStas Sergeev { 22752337310SStas Sergeev if (!enable) 22852337310SStas Sergeev return -ENODEV; 2299ab4d072SStas Sergeev return platform_driver_register(&pcsp_platform_driver); 2309ab4d072SStas Sergeev } 2319ab4d072SStas Sergeev 2329ab4d072SStas Sergeev static void __exit pcsp_exit(void) 2339ab4d072SStas Sergeev { 2349ab4d072SStas Sergeev platform_driver_unregister(&pcsp_platform_driver); 2359ab4d072SStas Sergeev } 2369ab4d072SStas Sergeev 2379ab4d072SStas Sergeev module_init(pcsp_init); 2389ab4d072SStas Sergeev module_exit(pcsp_exit); 239