109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 29ab4d072SStas Sergeev /* 39ab4d072SStas Sergeev * PC-Speaker driver for Linux 49ab4d072SStas Sergeev * 59ab4d072SStas Sergeev * Copyright (C) 1997-2001 David Woodhouse 69ab4d072SStas Sergeev * Copyright (C) 2001-2008 Stas Sergeev 79ab4d072SStas Sergeev */ 89ab4d072SStas Sergeev 99ab4d072SStas Sergeev #include <linux/init.h> 1065a77217SPaul Gortmaker #include <linux/module.h> 119ab4d072SStas Sergeev #include <linux/platform_device.h> 129ab4d072SStas Sergeev #include <sound/core.h> 139ab4d072SStas Sergeev #include <sound/initval.h> 149ab4d072SStas Sergeev #include <sound/pcm.h> 159ab4d072SStas Sergeev #include <linux/input.h> 169ecaedaeSMariusz Kozlowski #include <linux/delay.h> 17976412fbSTakashi Iwai #include <linux/bitops.h> 18505f6d22SJoonsoo Kim #include <linux/mm.h> 199ab4d072SStas Sergeev #include "pcsp_input.h" 209ab4d072SStas Sergeev #include "pcsp.h" 219ab4d072SStas Sergeev 229ab4d072SStas Sergeev MODULE_AUTHOR("Stas Sergeev <stsp@users.sourceforge.net>"); 239ab4d072SStas Sergeev MODULE_DESCRIPTION("PC-Speaker driver"); 249ab4d072SStas Sergeev MODULE_LICENSE("GPL"); 259ab4d072SStas Sergeev MODULE_ALIAS("platform:pcspkr"); 269ab4d072SStas Sergeev 279ab4d072SStas Sergeev static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ 289ab4d072SStas Sergeev static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ 29a67ff6a5SRusty Russell static bool enable = SNDRV_DEFAULT_ENABLE1; /* Enable this card */ 30a67ff6a5SRusty Russell static bool nopcm; /* Disable PCM capability of the driver */ 319ab4d072SStas Sergeev 329ab4d072SStas Sergeev module_param(index, int, 0444); 339ab4d072SStas Sergeev MODULE_PARM_DESC(index, "Index value for pcsp soundcard."); 349ab4d072SStas Sergeev module_param(id, charp, 0444); 359ab4d072SStas Sergeev MODULE_PARM_DESC(id, "ID string for pcsp soundcard."); 369ab4d072SStas Sergeev module_param(enable, bool, 0444); 3752337310SStas Sergeev MODULE_PARM_DESC(enable, "Enable PC-Speaker sound."); 38bcc2c6b7SStas Sergeev module_param(nopcm, bool, 0444); 39bcc2c6b7SStas Sergeev MODULE_PARM_DESC(nopcm, "Disable PC-Speaker PCM sound. Only beeps remain."); 409ab4d072SStas Sergeev 419ab4d072SStas Sergeev struct snd_pcsp pcsp_chip; 429ab4d072SStas Sergeev 43fbbb01a1SBill Pemberton static int snd_pcsp_create(struct snd_card *card) 449ab4d072SStas Sergeev { 45447fbbdcSThomas Gleixner unsigned int resolution = hrtimer_resolution; 463a1e341cSTakashi Iwai int div, min_div, order; 4775415df8STakashi Iwai 4875415df8STakashi Iwai if (!nopcm) { 49447fbbdcSThomas Gleixner if (resolution > PCSP_MAX_PERIOD_NS) { 50*9cbe416bSTakashi Iwai dev_err(card->dev, 51*9cbe416bSTakashi Iwai "PCSP: Timer resolution is not sufficient (%unS)\n", 52*9cbe416bSTakashi Iwai resolution); 53*9cbe416bSTakashi Iwai dev_err(card->dev, 54*9cbe416bSTakashi Iwai "PCSP: Make sure you have HPET and ACPI enabled.\n"); 55*9cbe416bSTakashi Iwai dev_err(card->dev, "PCSP: Turned into nopcm mode.\n"); 56bcc2c6b7SStas Sergeev nopcm = 1; 57bcc2c6b7SStas Sergeev } 589ab4d072SStas Sergeev } 599ab4d072SStas Sergeev 60447fbbdcSThomas Gleixner if (loops_per_jiffy >= PCSP_MIN_LPJ && resolution <= PCSP_MIN_PERIOD_NS) 619ab4d072SStas Sergeev min_div = MIN_DIV; 629ab4d072SStas Sergeev else 639ab4d072SStas Sergeev min_div = MAX_DIV; 649ab4d072SStas Sergeev #if PCSP_DEBUG 65*9cbe416bSTakashi Iwai dev_dbg(card->dev, "PCSP: lpj=%li, min_div=%i, res=%u\n", 66447fbbdcSThomas Gleixner loops_per_jiffy, min_div, resolution); 679ab4d072SStas Sergeev #endif 689ab4d072SStas Sergeev 699ab4d072SStas Sergeev div = MAX_DIV / min_div; 709ab4d072SStas Sergeev order = fls(div) - 1; 719ab4d072SStas Sergeev 729ab4d072SStas Sergeev pcsp_chip.max_treble = min(order, PCSP_MAX_TREBLE); 739ab4d072SStas Sergeev pcsp_chip.treble = min(pcsp_chip.max_treble, PCSP_DEFAULT_TREBLE); 749ab4d072SStas Sergeev pcsp_chip.playback_ptr = 0; 759ab4d072SStas Sergeev pcsp_chip.period_ptr = 0; 769ab4d072SStas Sergeev atomic_set(&pcsp_chip.timer_active, 0); 779ab4d072SStas Sergeev pcsp_chip.enable = 1; 789ab4d072SStas Sergeev pcsp_chip.pcspkr = 1; 799ab4d072SStas Sergeev 809ab4d072SStas Sergeev spin_lock_init(&pcsp_chip.substream_lock); 819ab4d072SStas Sergeev 829ab4d072SStas Sergeev pcsp_chip.card = card; 839ab4d072SStas Sergeev pcsp_chip.port = 0x61; 849ab4d072SStas Sergeev pcsp_chip.irq = -1; 859ab4d072SStas Sergeev pcsp_chip.dma = -1; 863a1e341cSTakashi Iwai card->private_data = &pcsp_chip; 879ab4d072SStas Sergeev 889ab4d072SStas Sergeev return 0; 899ab4d072SStas Sergeev } 909ab4d072SStas Sergeev 913a1e341cSTakashi Iwai static void pcsp_stop_beep(struct snd_pcsp *chip); 923a1e341cSTakashi Iwai 933a1e341cSTakashi Iwai static void alsa_card_pcsp_free(struct snd_card *card) 943a1e341cSTakashi Iwai { 953a1e341cSTakashi Iwai pcsp_stop_beep(card->private_data); 963a1e341cSTakashi Iwai } 973a1e341cSTakashi Iwai 98fbbb01a1SBill Pemberton static int snd_card_pcsp_probe(int devnum, struct device *dev) 999ab4d072SStas Sergeev { 1009ab4d072SStas Sergeev struct snd_card *card; 1019ab4d072SStas Sergeev int err; 1029ab4d072SStas Sergeev 1039ab4d072SStas Sergeev if (devnum != 0) 1049ab4d072SStas Sergeev return -EINVAL; 1059ab4d072SStas Sergeev 1069ab4d072SStas Sergeev hrtimer_init(&pcsp_chip.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); 1079ab4d072SStas Sergeev pcsp_chip.timer.function = pcsp_do_timer; 1089ab4d072SStas Sergeev 1093a1e341cSTakashi Iwai err = snd_devm_card_new(dev, index, id, THIS_MODULE, 0, &card); 110bd7dd77cSTakashi Iwai if (err < 0) 111bd7dd77cSTakashi Iwai return err; 1129ab4d072SStas Sergeev 1139ab4d072SStas Sergeev err = snd_pcsp_create(card); 1142cded8c8SMarkus Elfring if (err < 0) 1153a1e341cSTakashi Iwai return err; 1162cded8c8SMarkus Elfring 117bcc2c6b7SStas Sergeev if (!nopcm) { 1189ab4d072SStas Sergeev err = snd_pcsp_new_pcm(&pcsp_chip); 1192cded8c8SMarkus Elfring if (err < 0) 1203a1e341cSTakashi Iwai return err; 121bcc2c6b7SStas Sergeev } 122bcc2c6b7SStas Sergeev err = snd_pcsp_new_mixer(&pcsp_chip, nopcm); 1232cded8c8SMarkus Elfring if (err < 0) 1243a1e341cSTakashi Iwai return err; 1259ab4d072SStas Sergeev 1269ab4d072SStas Sergeev strcpy(card->driver, "PC-Speaker"); 1279ab4d072SStas Sergeev strcpy(card->shortname, "pcsp"); 1289ab4d072SStas Sergeev sprintf(card->longname, "Internal PC-Speaker at port 0x%x", 1299ab4d072SStas Sergeev pcsp_chip.port); 1309ab4d072SStas Sergeev 1319ab4d072SStas Sergeev err = snd_card_register(card); 1322cded8c8SMarkus Elfring if (err < 0) 1333a1e341cSTakashi Iwai return err; 1343a1e341cSTakashi Iwai card->private_free = alsa_card_pcsp_free; 1359ab4d072SStas Sergeev 1369ab4d072SStas Sergeev return 0; 1379ab4d072SStas Sergeev } 1389ab4d072SStas Sergeev 139fbbb01a1SBill Pemberton static int alsa_card_pcsp_init(struct device *dev) 1409ab4d072SStas Sergeev { 14152337310SStas Sergeev int err; 14252337310SStas Sergeev 14352337310SStas Sergeev err = snd_card_pcsp_probe(0, dev); 14452337310SStas Sergeev if (err) { 145*9cbe416bSTakashi Iwai dev_err(dev, "PC-Speaker initialization failed.\n"); 14652337310SStas Sergeev return err; 14752337310SStas Sergeev } 1489ab4d072SStas Sergeev 1499ab4d072SStas Sergeev /* Well, CONFIG_DEBUG_PAGEALLOC makes the sound horrible. Lets alert */ 150505f6d22SJoonsoo Kim if (debug_pagealloc_enabled()) { 151*9cbe416bSTakashi Iwai dev_warn(dev, 152*9cbe416bSTakashi Iwai "PCSP: CONFIG_DEBUG_PAGEALLOC is enabled, which may make the sound noisy.\n"); 153505f6d22SJoonsoo Kim } 1549ab4d072SStas Sergeev 1559ab4d072SStas Sergeev return 0; 1569ab4d072SStas Sergeev } 1579ab4d072SStas Sergeev 158fbbb01a1SBill Pemberton static int pcsp_probe(struct platform_device *dev) 1599ab4d072SStas Sergeev { 1609ab4d072SStas Sergeev int err; 16152337310SStas Sergeev 1629ab4d072SStas Sergeev err = pcspkr_input_init(&pcsp_chip.input_dev, &dev->dev); 1639ab4d072SStas Sergeev if (err < 0) 1649ab4d072SStas Sergeev return err; 1659ab4d072SStas Sergeev 1669ab4d072SStas Sergeev err = alsa_card_pcsp_init(&dev->dev); 1673a1e341cSTakashi Iwai if (err < 0) 1689ab4d072SStas Sergeev return err; 1699ab4d072SStas Sergeev 1709ab4d072SStas Sergeev platform_set_drvdata(dev, &pcsp_chip); 1719ab4d072SStas Sergeev return 0; 1729ab4d072SStas Sergeev } 1739ab4d072SStas Sergeev 1749ab4d072SStas Sergeev static void pcsp_stop_beep(struct snd_pcsp *chip) 1759ab4d072SStas Sergeev { 17696c7d478STakashi Iwai pcsp_sync_stop(chip); 1779ab4d072SStas Sergeev pcspkr_stop_sound(); 1789ab4d072SStas Sergeev } 1799ab4d072SStas Sergeev 180284e7ca7STakashi Iwai static int pcsp_suspend(struct device *dev) 1819ab4d072SStas Sergeev { 182284e7ca7STakashi Iwai struct snd_pcsp *chip = dev_get_drvdata(dev); 1839ab4d072SStas Sergeev pcsp_stop_beep(chip); 1849ab4d072SStas Sergeev return 0; 1859ab4d072SStas Sergeev } 186284e7ca7STakashi Iwai 18719e332e5STakashi Iwai static DEFINE_SIMPLE_DEV_PM_OPS(pcsp_pm, pcsp_suspend, NULL); 1889ab4d072SStas Sergeev 1899ab4d072SStas Sergeev static void pcsp_shutdown(struct platform_device *dev) 1909ab4d072SStas Sergeev { 1919ab4d072SStas Sergeev struct snd_pcsp *chip = platform_get_drvdata(dev); 1929ab4d072SStas Sergeev pcsp_stop_beep(chip); 1939ab4d072SStas Sergeev } 1949ab4d072SStas Sergeev 1959ab4d072SStas Sergeev static struct platform_driver pcsp_platform_driver = { 1969ab4d072SStas Sergeev .driver = { 1979ab4d072SStas Sergeev .name = "pcspkr", 19819e332e5STakashi Iwai .pm = &pcsp_pm, 1999ab4d072SStas Sergeev }, 2009ab4d072SStas Sergeev .probe = pcsp_probe, 2019ab4d072SStas Sergeev .shutdown = pcsp_shutdown, 2029ab4d072SStas Sergeev }; 2039ab4d072SStas Sergeev 2049ab4d072SStas Sergeev static int __init pcsp_init(void) 2059ab4d072SStas Sergeev { 20652337310SStas Sergeev if (!enable) 20752337310SStas Sergeev return -ENODEV; 2089ab4d072SStas Sergeev return platform_driver_register(&pcsp_platform_driver); 2099ab4d072SStas Sergeev } 2109ab4d072SStas Sergeev 2119ab4d072SStas Sergeev static void __exit pcsp_exit(void) 2129ab4d072SStas Sergeev { 2139ab4d072SStas Sergeev platform_driver_unregister(&pcsp_platform_driver); 2149ab4d072SStas Sergeev } 2159ab4d072SStas Sergeev 2169ab4d072SStas Sergeev module_init(pcsp_init); 2179ab4d072SStas Sergeev module_exit(pcsp_exit); 218