18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  PC Speaker beeper driver for Linux
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *  Copyright (c) 2002 Vojtech Pavlik
68c2ecf20Sopenharmony_ci *  Copyright (c) 1992 Orest Zborowski
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/kernel.h>
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <linux/i8253.h>
138c2ecf20Sopenharmony_ci#include <linux/input.h>
148c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
158c2ecf20Sopenharmony_ci#include <linux/timex.h>
168c2ecf20Sopenharmony_ci#include <linux/io.h>
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ciMODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
198c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("PC Speaker beeper driver");
208c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
218c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:pcspkr");
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_cistatic int pcspkr_event(struct input_dev *dev, unsigned int type,
248c2ecf20Sopenharmony_ci			unsigned int code, int value)
258c2ecf20Sopenharmony_ci{
268c2ecf20Sopenharmony_ci	unsigned int count = 0;
278c2ecf20Sopenharmony_ci	unsigned long flags;
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci	if (type != EV_SND)
308c2ecf20Sopenharmony_ci		return -EINVAL;
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci	switch (code) {
338c2ecf20Sopenharmony_ci	case SND_BELL:
348c2ecf20Sopenharmony_ci		if (value)
358c2ecf20Sopenharmony_ci			value = 1000;
368c2ecf20Sopenharmony_ci	case SND_TONE:
378c2ecf20Sopenharmony_ci		break;
388c2ecf20Sopenharmony_ci	default:
398c2ecf20Sopenharmony_ci		return -EINVAL;
408c2ecf20Sopenharmony_ci	}
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	if (value > 20 && value < 32767)
438c2ecf20Sopenharmony_ci		count = PIT_TICK_RATE / value;
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci	raw_spin_lock_irqsave(&i8253_lock, flags);
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	if (count) {
488c2ecf20Sopenharmony_ci		/* set command for counter 2, 2 byte write */
498c2ecf20Sopenharmony_ci		outb_p(0xB6, 0x43);
508c2ecf20Sopenharmony_ci		/* select desired HZ */
518c2ecf20Sopenharmony_ci		outb_p(count & 0xff, 0x42);
528c2ecf20Sopenharmony_ci		outb((count >> 8) & 0xff, 0x42);
538c2ecf20Sopenharmony_ci		/* enable counter 2 */
548c2ecf20Sopenharmony_ci		outb_p(inb_p(0x61) | 3, 0x61);
558c2ecf20Sopenharmony_ci	} else {
568c2ecf20Sopenharmony_ci		/* disable counter 2 */
578c2ecf20Sopenharmony_ci		outb(inb_p(0x61) & 0xFC, 0x61);
588c2ecf20Sopenharmony_ci	}
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	raw_spin_unlock_irqrestore(&i8253_lock, flags);
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	return 0;
638c2ecf20Sopenharmony_ci}
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_cistatic int pcspkr_probe(struct platform_device *dev)
668c2ecf20Sopenharmony_ci{
678c2ecf20Sopenharmony_ci	struct input_dev *pcspkr_dev;
688c2ecf20Sopenharmony_ci	int err;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	pcspkr_dev = input_allocate_device();
718c2ecf20Sopenharmony_ci	if (!pcspkr_dev)
728c2ecf20Sopenharmony_ci		return -ENOMEM;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	pcspkr_dev->name = "PC Speaker";
758c2ecf20Sopenharmony_ci	pcspkr_dev->phys = "isa0061/input0";
768c2ecf20Sopenharmony_ci	pcspkr_dev->id.bustype = BUS_ISA;
778c2ecf20Sopenharmony_ci	pcspkr_dev->id.vendor = 0x001f;
788c2ecf20Sopenharmony_ci	pcspkr_dev->id.product = 0x0001;
798c2ecf20Sopenharmony_ci	pcspkr_dev->id.version = 0x0100;
808c2ecf20Sopenharmony_ci	pcspkr_dev->dev.parent = &dev->dev;
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	pcspkr_dev->evbit[0] = BIT_MASK(EV_SND);
838c2ecf20Sopenharmony_ci	pcspkr_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE);
848c2ecf20Sopenharmony_ci	pcspkr_dev->event = pcspkr_event;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	err = input_register_device(pcspkr_dev);
878c2ecf20Sopenharmony_ci	if (err) {
888c2ecf20Sopenharmony_ci		input_free_device(pcspkr_dev);
898c2ecf20Sopenharmony_ci		return err;
908c2ecf20Sopenharmony_ci	}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	platform_set_drvdata(dev, pcspkr_dev);
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	return 0;
958c2ecf20Sopenharmony_ci}
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_cistatic int pcspkr_remove(struct platform_device *dev)
988c2ecf20Sopenharmony_ci{
998c2ecf20Sopenharmony_ci	struct input_dev *pcspkr_dev = platform_get_drvdata(dev);
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	input_unregister_device(pcspkr_dev);
1028c2ecf20Sopenharmony_ci	/* turn off the speaker */
1038c2ecf20Sopenharmony_ci	pcspkr_event(NULL, EV_SND, SND_BELL, 0);
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	return 0;
1068c2ecf20Sopenharmony_ci}
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_cistatic int pcspkr_suspend(struct device *dev)
1098c2ecf20Sopenharmony_ci{
1108c2ecf20Sopenharmony_ci	pcspkr_event(NULL, EV_SND, SND_BELL, 0);
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	return 0;
1138c2ecf20Sopenharmony_ci}
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_cistatic void pcspkr_shutdown(struct platform_device *dev)
1168c2ecf20Sopenharmony_ci{
1178c2ecf20Sopenharmony_ci	/* turn off the speaker */
1188c2ecf20Sopenharmony_ci	pcspkr_event(NULL, EV_SND, SND_BELL, 0);
1198c2ecf20Sopenharmony_ci}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_cistatic const struct dev_pm_ops pcspkr_pm_ops = {
1228c2ecf20Sopenharmony_ci	.suspend = pcspkr_suspend,
1238c2ecf20Sopenharmony_ci};
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_cistatic struct platform_driver pcspkr_platform_driver = {
1268c2ecf20Sopenharmony_ci	.driver		= {
1278c2ecf20Sopenharmony_ci		.name	= "pcspkr",
1288c2ecf20Sopenharmony_ci		.pm	= &pcspkr_pm_ops,
1298c2ecf20Sopenharmony_ci	},
1308c2ecf20Sopenharmony_ci	.probe		= pcspkr_probe,
1318c2ecf20Sopenharmony_ci	.remove		= pcspkr_remove,
1328c2ecf20Sopenharmony_ci	.shutdown	= pcspkr_shutdown,
1338c2ecf20Sopenharmony_ci};
1348c2ecf20Sopenharmony_cimodule_platform_driver(pcspkr_platform_driver);
1358c2ecf20Sopenharmony_ci
136