18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * PC-Speaker driver for Linux
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 1997-2001  David Woodhouse
68c2ecf20Sopenharmony_ci * Copyright (C) 2001-2008  Stas Sergeev
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/init.h>
108c2ecf20Sopenharmony_ci#include <linux/module.h>
118c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
128c2ecf20Sopenharmony_ci#include <sound/core.h>
138c2ecf20Sopenharmony_ci#include <sound/initval.h>
148c2ecf20Sopenharmony_ci#include <sound/pcm.h>
158c2ecf20Sopenharmony_ci#include <linux/input.h>
168c2ecf20Sopenharmony_ci#include <linux/delay.h>
178c2ecf20Sopenharmony_ci#include <linux/bitops.h>
188c2ecf20Sopenharmony_ci#include <linux/mm.h>
198c2ecf20Sopenharmony_ci#include "pcsp_input.h"
208c2ecf20Sopenharmony_ci#include "pcsp.h"
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ciMODULE_AUTHOR("Stas Sergeev <stsp@users.sourceforge.net>");
238c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("PC-Speaker driver");
248c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
258c2ecf20Sopenharmony_ciMODULE_SUPPORTED_DEVICE("{{PC-Speaker, pcsp}}");
268c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:pcspkr");
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cistatic int index = SNDRV_DEFAULT_IDX1;	/* Index 0-MAX */
298c2ecf20Sopenharmony_cistatic char *id = SNDRV_DEFAULT_STR1;	/* ID for this card */
308c2ecf20Sopenharmony_cistatic bool enable = SNDRV_DEFAULT_ENABLE1;	/* Enable this card */
318c2ecf20Sopenharmony_cistatic bool nopcm;	/* Disable PCM capability of the driver */
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_cimodule_param(index, int, 0444);
348c2ecf20Sopenharmony_ciMODULE_PARM_DESC(index, "Index value for pcsp soundcard.");
358c2ecf20Sopenharmony_cimodule_param(id, charp, 0444);
368c2ecf20Sopenharmony_ciMODULE_PARM_DESC(id, "ID string for pcsp soundcard.");
378c2ecf20Sopenharmony_cimodule_param(enable, bool, 0444);
388c2ecf20Sopenharmony_ciMODULE_PARM_DESC(enable, "Enable PC-Speaker sound.");
398c2ecf20Sopenharmony_cimodule_param(nopcm, bool, 0444);
408c2ecf20Sopenharmony_ciMODULE_PARM_DESC(nopcm, "Disable PC-Speaker PCM sound. Only beeps remain.");
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_cistruct snd_pcsp pcsp_chip;
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_cistatic int snd_pcsp_create(struct snd_card *card)
458c2ecf20Sopenharmony_ci{
468c2ecf20Sopenharmony_ci	static const struct snd_device_ops ops = { };
478c2ecf20Sopenharmony_ci	unsigned int resolution = hrtimer_resolution;
488c2ecf20Sopenharmony_ci	int err, div, min_div, order;
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	if (!nopcm) {
518c2ecf20Sopenharmony_ci		if (resolution > PCSP_MAX_PERIOD_NS) {
528c2ecf20Sopenharmony_ci			printk(KERN_ERR "PCSP: Timer resolution is not sufficient "
538c2ecf20Sopenharmony_ci				"(%unS)\n", resolution);
548c2ecf20Sopenharmony_ci			printk(KERN_ERR "PCSP: Make sure you have HPET and ACPI "
558c2ecf20Sopenharmony_ci				"enabled.\n");
568c2ecf20Sopenharmony_ci			printk(KERN_ERR "PCSP: Turned into nopcm mode.\n");
578c2ecf20Sopenharmony_ci			nopcm = 1;
588c2ecf20Sopenharmony_ci		}
598c2ecf20Sopenharmony_ci	}
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	if (loops_per_jiffy >= PCSP_MIN_LPJ && resolution <= PCSP_MIN_PERIOD_NS)
628c2ecf20Sopenharmony_ci		min_div = MIN_DIV;
638c2ecf20Sopenharmony_ci	else
648c2ecf20Sopenharmony_ci		min_div = MAX_DIV;
658c2ecf20Sopenharmony_ci#if PCSP_DEBUG
668c2ecf20Sopenharmony_ci	printk(KERN_DEBUG "PCSP: lpj=%li, min_div=%i, res=%u\n",
678c2ecf20Sopenharmony_ci	       loops_per_jiffy, min_div, resolution);
688c2ecf20Sopenharmony_ci#endif
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	div = MAX_DIV / min_div;
718c2ecf20Sopenharmony_ci	order = fls(div) - 1;
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	pcsp_chip.max_treble = min(order, PCSP_MAX_TREBLE);
748c2ecf20Sopenharmony_ci	pcsp_chip.treble = min(pcsp_chip.max_treble, PCSP_DEFAULT_TREBLE);
758c2ecf20Sopenharmony_ci	pcsp_chip.playback_ptr = 0;
768c2ecf20Sopenharmony_ci	pcsp_chip.period_ptr = 0;
778c2ecf20Sopenharmony_ci	atomic_set(&pcsp_chip.timer_active, 0);
788c2ecf20Sopenharmony_ci	pcsp_chip.enable = 1;
798c2ecf20Sopenharmony_ci	pcsp_chip.pcspkr = 1;
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	spin_lock_init(&pcsp_chip.substream_lock);
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	pcsp_chip.card = card;
848c2ecf20Sopenharmony_ci	pcsp_chip.port = 0x61;
858c2ecf20Sopenharmony_ci	pcsp_chip.irq = -1;
868c2ecf20Sopenharmony_ci	pcsp_chip.dma = -1;
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	/* Register device */
898c2ecf20Sopenharmony_ci	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, &pcsp_chip, &ops);
908c2ecf20Sopenharmony_ci	if (err < 0)
918c2ecf20Sopenharmony_ci		return err;
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	return 0;
948c2ecf20Sopenharmony_ci}
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_cistatic int snd_card_pcsp_probe(int devnum, struct device *dev)
978c2ecf20Sopenharmony_ci{
988c2ecf20Sopenharmony_ci	struct snd_card *card;
998c2ecf20Sopenharmony_ci	int err;
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	if (devnum != 0)
1028c2ecf20Sopenharmony_ci		return -EINVAL;
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	hrtimer_init(&pcsp_chip.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
1058c2ecf20Sopenharmony_ci	pcsp_chip.timer.function = pcsp_do_timer;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	err = snd_card_new(dev, index, id, THIS_MODULE, 0, &card);
1088c2ecf20Sopenharmony_ci	if (err < 0)
1098c2ecf20Sopenharmony_ci		return err;
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	err = snd_pcsp_create(card);
1128c2ecf20Sopenharmony_ci	if (err < 0)
1138c2ecf20Sopenharmony_ci		goto free_card;
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	if (!nopcm) {
1168c2ecf20Sopenharmony_ci		err = snd_pcsp_new_pcm(&pcsp_chip);
1178c2ecf20Sopenharmony_ci		if (err < 0)
1188c2ecf20Sopenharmony_ci			goto free_card;
1198c2ecf20Sopenharmony_ci	}
1208c2ecf20Sopenharmony_ci	err = snd_pcsp_new_mixer(&pcsp_chip, nopcm);
1218c2ecf20Sopenharmony_ci	if (err < 0)
1228c2ecf20Sopenharmony_ci		goto free_card;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	strcpy(card->driver, "PC-Speaker");
1258c2ecf20Sopenharmony_ci	strcpy(card->shortname, "pcsp");
1268c2ecf20Sopenharmony_ci	sprintf(card->longname, "Internal PC-Speaker at port 0x%x",
1278c2ecf20Sopenharmony_ci		pcsp_chip.port);
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	err = snd_card_register(card);
1308c2ecf20Sopenharmony_ci	if (err < 0)
1318c2ecf20Sopenharmony_ci		goto free_card;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	return 0;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_cifree_card:
1368c2ecf20Sopenharmony_ci	snd_card_free(card);
1378c2ecf20Sopenharmony_ci	return err;
1388c2ecf20Sopenharmony_ci}
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_cistatic int alsa_card_pcsp_init(struct device *dev)
1418c2ecf20Sopenharmony_ci{
1428c2ecf20Sopenharmony_ci	int err;
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	err = snd_card_pcsp_probe(0, dev);
1458c2ecf20Sopenharmony_ci	if (err) {
1468c2ecf20Sopenharmony_ci		printk(KERN_ERR "PC-Speaker initialization failed.\n");
1478c2ecf20Sopenharmony_ci		return err;
1488c2ecf20Sopenharmony_ci	}
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	/* Well, CONFIG_DEBUG_PAGEALLOC makes the sound horrible. Lets alert */
1518c2ecf20Sopenharmony_ci	if (debug_pagealloc_enabled()) {
1528c2ecf20Sopenharmony_ci		printk(KERN_WARNING "PCSP: CONFIG_DEBUG_PAGEALLOC is enabled, "
1538c2ecf20Sopenharmony_ci		       "which may make the sound noisy.\n");
1548c2ecf20Sopenharmony_ci	}
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	return 0;
1578c2ecf20Sopenharmony_ci}
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_cistatic void alsa_card_pcsp_exit(struct snd_pcsp *chip)
1608c2ecf20Sopenharmony_ci{
1618c2ecf20Sopenharmony_ci	snd_card_free(chip->card);
1628c2ecf20Sopenharmony_ci}
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_cistatic int pcsp_probe(struct platform_device *dev)
1658c2ecf20Sopenharmony_ci{
1668c2ecf20Sopenharmony_ci	int err;
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	err = pcspkr_input_init(&pcsp_chip.input_dev, &dev->dev);
1698c2ecf20Sopenharmony_ci	if (err < 0)
1708c2ecf20Sopenharmony_ci		return err;
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	err = alsa_card_pcsp_init(&dev->dev);
1738c2ecf20Sopenharmony_ci	if (err < 0) {
1748c2ecf20Sopenharmony_ci		pcspkr_input_remove(pcsp_chip.input_dev);
1758c2ecf20Sopenharmony_ci		return err;
1768c2ecf20Sopenharmony_ci	}
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	platform_set_drvdata(dev, &pcsp_chip);
1798c2ecf20Sopenharmony_ci	return 0;
1808c2ecf20Sopenharmony_ci}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_cistatic int pcsp_remove(struct platform_device *dev)
1838c2ecf20Sopenharmony_ci{
1848c2ecf20Sopenharmony_ci	struct snd_pcsp *chip = platform_get_drvdata(dev);
1858c2ecf20Sopenharmony_ci	pcspkr_input_remove(chip->input_dev);
1868c2ecf20Sopenharmony_ci	alsa_card_pcsp_exit(chip);
1878c2ecf20Sopenharmony_ci	return 0;
1888c2ecf20Sopenharmony_ci}
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_cistatic void pcsp_stop_beep(struct snd_pcsp *chip)
1918c2ecf20Sopenharmony_ci{
1928c2ecf20Sopenharmony_ci	pcsp_sync_stop(chip);
1938c2ecf20Sopenharmony_ci	pcspkr_stop_sound();
1948c2ecf20Sopenharmony_ci}
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP
1978c2ecf20Sopenharmony_cistatic int pcsp_suspend(struct device *dev)
1988c2ecf20Sopenharmony_ci{
1998c2ecf20Sopenharmony_ci	struct snd_pcsp *chip = dev_get_drvdata(dev);
2008c2ecf20Sopenharmony_ci	pcsp_stop_beep(chip);
2018c2ecf20Sopenharmony_ci	return 0;
2028c2ecf20Sopenharmony_ci}
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(pcsp_pm, pcsp_suspend, NULL);
2058c2ecf20Sopenharmony_ci#define PCSP_PM_OPS	&pcsp_pm
2068c2ecf20Sopenharmony_ci#else
2078c2ecf20Sopenharmony_ci#define PCSP_PM_OPS	NULL
2088c2ecf20Sopenharmony_ci#endif	/* CONFIG_PM_SLEEP */
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_cistatic void pcsp_shutdown(struct platform_device *dev)
2118c2ecf20Sopenharmony_ci{
2128c2ecf20Sopenharmony_ci	struct snd_pcsp *chip = platform_get_drvdata(dev);
2138c2ecf20Sopenharmony_ci	pcsp_stop_beep(chip);
2148c2ecf20Sopenharmony_ci}
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_cistatic struct platform_driver pcsp_platform_driver = {
2178c2ecf20Sopenharmony_ci	.driver		= {
2188c2ecf20Sopenharmony_ci		.name	= "pcspkr",
2198c2ecf20Sopenharmony_ci		.pm	= PCSP_PM_OPS,
2208c2ecf20Sopenharmony_ci	},
2218c2ecf20Sopenharmony_ci	.probe		= pcsp_probe,
2228c2ecf20Sopenharmony_ci	.remove		= pcsp_remove,
2238c2ecf20Sopenharmony_ci	.shutdown	= pcsp_shutdown,
2248c2ecf20Sopenharmony_ci};
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_cistatic int __init pcsp_init(void)
2278c2ecf20Sopenharmony_ci{
2288c2ecf20Sopenharmony_ci	if (!enable)
2298c2ecf20Sopenharmony_ci		return -ENODEV;
2308c2ecf20Sopenharmony_ci	return platform_driver_register(&pcsp_platform_driver);
2318c2ecf20Sopenharmony_ci}
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_cistatic void __exit pcsp_exit(void)
2348c2ecf20Sopenharmony_ci{
2358c2ecf20Sopenharmony_ci	platform_driver_unregister(&pcsp_platform_driver);
2368c2ecf20Sopenharmony_ci}
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_cimodule_init(pcsp_init);
2398c2ecf20Sopenharmony_cimodule_exit(pcsp_exit);
240