Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * PC-Speaker driver for Linux
0004  *
0005  * Copyright (C) 1997-2001  David Woodhouse
0006  * Copyright (C) 2001-2008  Stas Sergeev
0007  */
0008 
0009 #include <linux/init.h>
0010 #include <linux/module.h>
0011 #include <linux/platform_device.h>
0012 #include <sound/core.h>
0013 #include <sound/initval.h>
0014 #include <sound/pcm.h>
0015 #include <linux/input.h>
0016 #include <linux/delay.h>
0017 #include <linux/bitops.h>
0018 #include <linux/mm.h>
0019 #include "pcsp_input.h"
0020 #include "pcsp.h"
0021 
0022 MODULE_AUTHOR("Stas Sergeev <stsp@users.sourceforge.net>");
0023 MODULE_DESCRIPTION("PC-Speaker driver");
0024 MODULE_LICENSE("GPL");
0025 MODULE_ALIAS("platform:pcspkr");
0026 
0027 static int index = SNDRV_DEFAULT_IDX1;  /* Index 0-MAX */
0028 static char *id = SNDRV_DEFAULT_STR1;   /* ID for this card */
0029 static bool enable = SNDRV_DEFAULT_ENABLE1; /* Enable this card */
0030 static bool nopcm;  /* Disable PCM capability of the driver */
0031 
0032 module_param(index, int, 0444);
0033 MODULE_PARM_DESC(index, "Index value for pcsp soundcard.");
0034 module_param(id, charp, 0444);
0035 MODULE_PARM_DESC(id, "ID string for pcsp soundcard.");
0036 module_param(enable, bool, 0444);
0037 MODULE_PARM_DESC(enable, "Enable PC-Speaker sound.");
0038 module_param(nopcm, bool, 0444);
0039 MODULE_PARM_DESC(nopcm, "Disable PC-Speaker PCM sound. Only beeps remain.");
0040 
0041 struct snd_pcsp pcsp_chip;
0042 
0043 static int snd_pcsp_create(struct snd_card *card)
0044 {
0045     unsigned int resolution = hrtimer_resolution;
0046     int div, min_div, order;
0047 
0048     if (!nopcm) {
0049         if (resolution > PCSP_MAX_PERIOD_NS) {
0050             printk(KERN_ERR "PCSP: Timer resolution is not sufficient "
0051                 "(%unS)\n", resolution);
0052             printk(KERN_ERR "PCSP: Make sure you have HPET and ACPI "
0053                 "enabled.\n");
0054             printk(KERN_ERR "PCSP: Turned into nopcm mode.\n");
0055             nopcm = 1;
0056         }
0057     }
0058 
0059     if (loops_per_jiffy >= PCSP_MIN_LPJ && resolution <= PCSP_MIN_PERIOD_NS)
0060         min_div = MIN_DIV;
0061     else
0062         min_div = MAX_DIV;
0063 #if PCSP_DEBUG
0064     printk(KERN_DEBUG "PCSP: lpj=%li, min_div=%i, res=%u\n",
0065            loops_per_jiffy, min_div, resolution);
0066 #endif
0067 
0068     div = MAX_DIV / min_div;
0069     order = fls(div) - 1;
0070 
0071     pcsp_chip.max_treble = min(order, PCSP_MAX_TREBLE);
0072     pcsp_chip.treble = min(pcsp_chip.max_treble, PCSP_DEFAULT_TREBLE);
0073     pcsp_chip.playback_ptr = 0;
0074     pcsp_chip.period_ptr = 0;
0075     atomic_set(&pcsp_chip.timer_active, 0);
0076     pcsp_chip.enable = 1;
0077     pcsp_chip.pcspkr = 1;
0078 
0079     spin_lock_init(&pcsp_chip.substream_lock);
0080 
0081     pcsp_chip.card = card;
0082     pcsp_chip.port = 0x61;
0083     pcsp_chip.irq = -1;
0084     pcsp_chip.dma = -1;
0085     card->private_data = &pcsp_chip;
0086 
0087     return 0;
0088 }
0089 
0090 static void pcsp_stop_beep(struct snd_pcsp *chip);
0091 
0092 static void alsa_card_pcsp_free(struct snd_card *card)
0093 {
0094     pcsp_stop_beep(card->private_data);
0095 }
0096 
0097 static int snd_card_pcsp_probe(int devnum, struct device *dev)
0098 {
0099     struct snd_card *card;
0100     int err;
0101 
0102     if (devnum != 0)
0103         return -EINVAL;
0104 
0105     hrtimer_init(&pcsp_chip.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
0106     pcsp_chip.timer.function = pcsp_do_timer;
0107 
0108     err = snd_devm_card_new(dev, index, id, THIS_MODULE, 0, &card);
0109     if (err < 0)
0110         return err;
0111 
0112     err = snd_pcsp_create(card);
0113     if (err < 0)
0114         return err;
0115 
0116     if (!nopcm) {
0117         err = snd_pcsp_new_pcm(&pcsp_chip);
0118         if (err < 0)
0119             return err;
0120     }
0121     err = snd_pcsp_new_mixer(&pcsp_chip, nopcm);
0122     if (err < 0)
0123         return err;
0124 
0125     strcpy(card->driver, "PC-Speaker");
0126     strcpy(card->shortname, "pcsp");
0127     sprintf(card->longname, "Internal PC-Speaker at port 0x%x",
0128         pcsp_chip.port);
0129 
0130     err = snd_card_register(card);
0131     if (err < 0)
0132         return err;
0133     card->private_free = alsa_card_pcsp_free;
0134 
0135     return 0;
0136 }
0137 
0138 static int alsa_card_pcsp_init(struct device *dev)
0139 {
0140     int err;
0141 
0142     err = snd_card_pcsp_probe(0, dev);
0143     if (err) {
0144         printk(KERN_ERR "PC-Speaker initialization failed.\n");
0145         return err;
0146     }
0147 
0148     /* Well, CONFIG_DEBUG_PAGEALLOC makes the sound horrible. Lets alert */
0149     if (debug_pagealloc_enabled()) {
0150         printk(KERN_WARNING "PCSP: CONFIG_DEBUG_PAGEALLOC is enabled, "
0151                "which may make the sound noisy.\n");
0152     }
0153 
0154     return 0;
0155 }
0156 
0157 static int pcsp_probe(struct platform_device *dev)
0158 {
0159     int err;
0160 
0161     err = pcspkr_input_init(&pcsp_chip.input_dev, &dev->dev);
0162     if (err < 0)
0163         return err;
0164 
0165     err = alsa_card_pcsp_init(&dev->dev);
0166     if (err < 0)
0167         return err;
0168 
0169     platform_set_drvdata(dev, &pcsp_chip);
0170     return 0;
0171 }
0172 
0173 static void pcsp_stop_beep(struct snd_pcsp *chip)
0174 {
0175     pcsp_sync_stop(chip);
0176     pcspkr_stop_sound();
0177 }
0178 
0179 #ifdef CONFIG_PM_SLEEP
0180 static int pcsp_suspend(struct device *dev)
0181 {
0182     struct snd_pcsp *chip = dev_get_drvdata(dev);
0183     pcsp_stop_beep(chip);
0184     return 0;
0185 }
0186 
0187 static SIMPLE_DEV_PM_OPS(pcsp_pm, pcsp_suspend, NULL);
0188 #define PCSP_PM_OPS &pcsp_pm
0189 #else
0190 #define PCSP_PM_OPS NULL
0191 #endif  /* CONFIG_PM_SLEEP */
0192 
0193 static void pcsp_shutdown(struct platform_device *dev)
0194 {
0195     struct snd_pcsp *chip = platform_get_drvdata(dev);
0196     pcsp_stop_beep(chip);
0197 }
0198 
0199 static struct platform_driver pcsp_platform_driver = {
0200     .driver     = {
0201         .name   = "pcspkr",
0202         .pm = PCSP_PM_OPS,
0203     },
0204     .probe      = pcsp_probe,
0205     .shutdown   = pcsp_shutdown,
0206 };
0207 
0208 static int __init pcsp_init(void)
0209 {
0210     if (!enable)
0211         return -ENODEV;
0212     return platform_driver_register(&pcsp_platform_driver);
0213 }
0214 
0215 static void __exit pcsp_exit(void)
0216 {
0217     platform_driver_unregister(&pcsp_platform_driver);
0218 }
0219 
0220 module_init(pcsp_init);
0221 module_exit(pcsp_exit);