summaryrefslogtreecommitdiffstats
path: root/sound/drivers/pcsp
diff options
context:
space:
mode:
Diffstat (limited to 'sound/drivers/pcsp')
-rw-r--r--sound/drivers/pcsp/Makefile3
-rw-r--r--sound/drivers/pcsp/pcsp.c221
-rw-r--r--sound/drivers/pcsp/pcsp.h83
-rw-r--r--sound/drivers/pcsp/pcsp_input.c103
-rw-r--r--sound/drivers/pcsp/pcsp_input.h14
-rw-r--r--sound/drivers/pcsp/pcsp_lib.c352
-rw-r--r--sound/drivers/pcsp/pcsp_mixer.c164
7 files changed, 940 insertions, 0 deletions
diff --git a/sound/drivers/pcsp/Makefile b/sound/drivers/pcsp/Makefile
new file mode 100644
index 0000000000..77dc0ee1b5
--- /dev/null
+++ b/sound/drivers/pcsp/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+snd-pcsp-objs := pcsp.o pcsp_lib.o pcsp_mixer.o pcsp_input.o
+obj-$(CONFIG_SND_PCSP) += snd-pcsp.o
diff --git a/sound/drivers/pcsp/pcsp.c b/sound/drivers/pcsp/pcsp.c
new file mode 100644
index 0000000000..c7be1c395b
--- /dev/null
+++ b/sound/drivers/pcsp/pcsp.c
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PC-Speaker driver for Linux
+ *
+ * Copyright (C) 1997-2001 David Woodhouse
+ * Copyright (C) 2001-2008 Stas Sergeev
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <linux/input.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/mm.h>
+#include "pcsp_input.h"
+#include "pcsp.h"
+
+MODULE_AUTHOR("Stas Sergeev <stsp@users.sourceforge.net>");
+MODULE_DESCRIPTION("PC-Speaker driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pcspkr");
+
+static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */
+static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */
+static bool enable = SNDRV_DEFAULT_ENABLE1; /* Enable this card */
+static bool nopcm; /* Disable PCM capability of the driver */
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for pcsp soundcard.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for pcsp soundcard.");
+module_param(enable, bool, 0444);
+MODULE_PARM_DESC(enable, "Enable PC-Speaker sound.");
+module_param(nopcm, bool, 0444);
+MODULE_PARM_DESC(nopcm, "Disable PC-Speaker PCM sound. Only beeps remain.");
+
+struct snd_pcsp pcsp_chip;
+
+static int snd_pcsp_create(struct snd_card *card)
+{
+ unsigned int resolution = hrtimer_resolution;
+ int div, min_div, order;
+
+ if (!nopcm) {
+ if (resolution > PCSP_MAX_PERIOD_NS) {
+ printk(KERN_ERR "PCSP: Timer resolution is not sufficient "
+ "(%unS)\n", resolution);
+ printk(KERN_ERR "PCSP: Make sure you have HPET and ACPI "
+ "enabled.\n");
+ printk(KERN_ERR "PCSP: Turned into nopcm mode.\n");
+ nopcm = 1;
+ }
+ }
+
+ if (loops_per_jiffy >= PCSP_MIN_LPJ && resolution <= PCSP_MIN_PERIOD_NS)
+ min_div = MIN_DIV;
+ else
+ min_div = MAX_DIV;
+#if PCSP_DEBUG
+ printk(KERN_DEBUG "PCSP: lpj=%li, min_div=%i, res=%u\n",
+ loops_per_jiffy, min_div, resolution);
+#endif
+
+ div = MAX_DIV / min_div;
+ order = fls(div) - 1;
+
+ pcsp_chip.max_treble = min(order, PCSP_MAX_TREBLE);
+ pcsp_chip.treble = min(pcsp_chip.max_treble, PCSP_DEFAULT_TREBLE);
+ pcsp_chip.playback_ptr = 0;
+ pcsp_chip.period_ptr = 0;
+ atomic_set(&pcsp_chip.timer_active, 0);
+ pcsp_chip.enable = 1;
+ pcsp_chip.pcspkr = 1;
+
+ spin_lock_init(&pcsp_chip.substream_lock);
+
+ pcsp_chip.card = card;
+ pcsp_chip.port = 0x61;
+ pcsp_chip.irq = -1;
+ pcsp_chip.dma = -1;
+ card->private_data = &pcsp_chip;
+
+ return 0;
+}
+
+static void pcsp_stop_beep(struct snd_pcsp *chip);
+
+static void alsa_card_pcsp_free(struct snd_card *card)
+{
+ pcsp_stop_beep(card->private_data);
+}
+
+static int snd_card_pcsp_probe(int devnum, struct device *dev)
+{
+ struct snd_card *card;
+ int err;
+
+ if (devnum != 0)
+ return -EINVAL;
+
+ hrtimer_init(&pcsp_chip.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ pcsp_chip.timer.function = pcsp_do_timer;
+
+ err = snd_devm_card_new(dev, index, id, THIS_MODULE, 0, &card);
+ if (err < 0)
+ return err;
+
+ err = snd_pcsp_create(card);
+ if (err < 0)
+ return err;
+
+ if (!nopcm) {
+ err = snd_pcsp_new_pcm(&pcsp_chip);
+ if (err < 0)
+ return err;
+ }
+ err = snd_pcsp_new_mixer(&pcsp_chip, nopcm);
+ if (err < 0)
+ return err;
+
+ strcpy(card->driver, "PC-Speaker");
+ strcpy(card->shortname, "pcsp");
+ sprintf(card->longname, "Internal PC-Speaker at port 0x%x",
+ pcsp_chip.port);
+
+ err = snd_card_register(card);
+ if (err < 0)
+ return err;
+ card->private_free = alsa_card_pcsp_free;
+
+ return 0;
+}
+
+static int alsa_card_pcsp_init(struct device *dev)
+{
+ int err;
+
+ err = snd_card_pcsp_probe(0, dev);
+ if (err) {
+ printk(KERN_ERR "PC-Speaker initialization failed.\n");
+ return err;
+ }
+
+ /* Well, CONFIG_DEBUG_PAGEALLOC makes the sound horrible. Lets alert */
+ if (debug_pagealloc_enabled()) {
+ printk(KERN_WARNING "PCSP: CONFIG_DEBUG_PAGEALLOC is enabled, "
+ "which may make the sound noisy.\n");
+ }
+
+ return 0;
+}
+
+static int pcsp_probe(struct platform_device *dev)
+{
+ int err;
+
+ err = pcspkr_input_init(&pcsp_chip.input_dev, &dev->dev);
+ if (err < 0)
+ return err;
+
+ err = alsa_card_pcsp_init(&dev->dev);
+ if (err < 0)
+ return err;
+
+ platform_set_drvdata(dev, &pcsp_chip);
+ return 0;
+}
+
+static void pcsp_stop_beep(struct snd_pcsp *chip)
+{
+ pcsp_sync_stop(chip);
+ pcspkr_stop_sound();
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int pcsp_suspend(struct device *dev)
+{
+ struct snd_pcsp *chip = dev_get_drvdata(dev);
+ pcsp_stop_beep(chip);
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(pcsp_pm, pcsp_suspend, NULL);
+#define PCSP_PM_OPS &pcsp_pm
+#else
+#define PCSP_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static void pcsp_shutdown(struct platform_device *dev)
+{
+ struct snd_pcsp *chip = platform_get_drvdata(dev);
+ pcsp_stop_beep(chip);
+}
+
+static struct platform_driver pcsp_platform_driver = {
+ .driver = {
+ .name = "pcspkr",
+ .pm = PCSP_PM_OPS,
+ },
+ .probe = pcsp_probe,
+ .shutdown = pcsp_shutdown,
+};
+
+static int __init pcsp_init(void)
+{
+ if (!enable)
+ return -ENODEV;
+ return platform_driver_register(&pcsp_platform_driver);
+}
+
+static void __exit pcsp_exit(void)
+{
+ platform_driver_unregister(&pcsp_platform_driver);
+}
+
+module_init(pcsp_init);
+module_exit(pcsp_exit);
diff --git a/sound/drivers/pcsp/pcsp.h b/sound/drivers/pcsp/pcsp.h
new file mode 100644
index 0000000000..036ad3c99a
--- /dev/null
+++ b/sound/drivers/pcsp/pcsp.h
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * PC-Speaker driver for Linux
+ *
+ * Copyright (C) 1993-1997 Michael Beck
+ * Copyright (C) 1997-2001 David Woodhouse
+ * Copyright (C) 2001-2008 Stas Sergeev
+ */
+
+#ifndef __PCSP_H__
+#define __PCSP_H__
+
+#include <linux/hrtimer.h>
+#include <linux/i8253.h>
+#include <linux/timex.h>
+
+#define PCSP_SOUND_VERSION 0x400 /* read 4.00 */
+#define PCSP_DEBUG 0
+
+/* default timer freq for PC-Speaker: 18643 Hz */
+#define DIV_18KHZ 64
+#define MAX_DIV DIV_18KHZ
+#define CALC_DIV(d) (MAX_DIV >> (d))
+#define CUR_DIV() CALC_DIV(chip->treble)
+#define PCSP_MAX_TREBLE 1
+
+/* unfortunately, with hrtimers 37KHz does not work very well :( */
+#define PCSP_DEFAULT_TREBLE 0
+#define MIN_DIV (MAX_DIV >> PCSP_MAX_TREBLE)
+
+/* wild guess */
+#define PCSP_MIN_LPJ 1000000
+#define PCSP_DEFAULT_SDIV (DIV_18KHZ >> 1)
+#define PCSP_DEFAULT_SRATE (PIT_TICK_RATE / PCSP_DEFAULT_SDIV)
+#define PCSP_INDEX_INC() (1 << (PCSP_MAX_TREBLE - chip->treble))
+#define PCSP_CALC_RATE(i) (PIT_TICK_RATE / CALC_DIV(i))
+#define PCSP_RATE() PCSP_CALC_RATE(chip->treble)
+#define PCSP_MIN_RATE__1 MAX_DIV/PIT_TICK_RATE
+#define PCSP_MAX_RATE__1 MIN_DIV/PIT_TICK_RATE
+#define PCSP_MAX_PERIOD_NS (1000000000ULL * PCSP_MIN_RATE__1)
+#define PCSP_MIN_PERIOD_NS (1000000000ULL * PCSP_MAX_RATE__1)
+#define PCSP_CALC_NS(div) ({ \
+ u64 __val = 1000000000ULL * (div); \
+ do_div(__val, PIT_TICK_RATE); \
+ __val; \
+})
+#define PCSP_PERIOD_NS() PCSP_CALC_NS(CUR_DIV())
+
+#define PCSP_MAX_PERIOD_SIZE (64*1024)
+#define PCSP_MAX_PERIODS 512
+#define PCSP_BUFFER_SIZE (128*1024)
+
+struct snd_pcsp {
+ struct snd_card *card;
+ struct snd_pcm *pcm;
+ struct input_dev *input_dev;
+ struct hrtimer timer;
+ unsigned short port, irq, dma;
+ spinlock_t substream_lock;
+ struct snd_pcm_substream *playback_substream;
+ unsigned int fmt_size;
+ unsigned int is_signed;
+ size_t playback_ptr;
+ size_t period_ptr;
+ atomic_t timer_active;
+ int thalf;
+ u64 ns_rem;
+ unsigned char val61;
+ int enable;
+ int max_treble;
+ int treble;
+ int pcspkr;
+};
+
+extern struct snd_pcsp pcsp_chip;
+
+extern enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle);
+extern void pcsp_sync_stop(struct snd_pcsp *chip);
+
+extern int snd_pcsp_new_pcm(struct snd_pcsp *chip);
+extern int snd_pcsp_new_mixer(struct snd_pcsp *chip, int nopcm);
+
+#endif
diff --git a/sound/drivers/pcsp/pcsp_input.c b/sound/drivers/pcsp/pcsp_input.c
new file mode 100644
index 0000000000..5a799f7f00
--- /dev/null
+++ b/sound/drivers/pcsp/pcsp_input.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PC Speaker beeper driver for Linux
+ *
+ * Copyright (c) 2002 Vojtech Pavlik
+ * Copyright (c) 1992 Orest Zborowski
+ */
+
+
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/io.h>
+#include "pcsp.h"
+#include "pcsp_input.h"
+
+static void pcspkr_do_sound(unsigned int count)
+{
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&i8253_lock, flags);
+
+ if (count) {
+ /* set command for counter 2, 2 byte write */
+ outb_p(0xB6, 0x43);
+ /* select desired HZ */
+ outb_p(count & 0xff, 0x42);
+ outb((count >> 8) & 0xff, 0x42);
+ /* enable counter 2 */
+ outb_p(inb_p(0x61) | 3, 0x61);
+ } else {
+ /* disable counter 2 */
+ outb(inb_p(0x61) & 0xFC, 0x61);
+ }
+
+ raw_spin_unlock_irqrestore(&i8253_lock, flags);
+}
+
+void pcspkr_stop_sound(void)
+{
+ pcspkr_do_sound(0);
+}
+
+static int pcspkr_input_event(struct input_dev *dev, unsigned int type,
+ unsigned int code, int value)
+{
+ unsigned int count = 0;
+
+ if (atomic_read(&pcsp_chip.timer_active) || !pcsp_chip.pcspkr)
+ return 0;
+
+ switch (type) {
+ case EV_SND:
+ switch (code) {
+ case SND_BELL:
+ if (value)
+ value = 1000;
+ break;
+ case SND_TONE:
+ break;
+ default:
+ return -1;
+ }
+ break;
+
+ default:
+ return -1;
+ }
+
+ if (value > 20 && value < 32767)
+ count = PIT_TICK_RATE / value;
+
+ pcspkr_do_sound(count);
+
+ return 0;
+}
+
+int pcspkr_input_init(struct input_dev **rdev, struct device *dev)
+{
+ int err;
+
+ struct input_dev *input_dev = devm_input_allocate_device(dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ input_dev->name = "PC Speaker";
+ input_dev->phys = "isa0061/input0";
+ input_dev->id.bustype = BUS_ISA;
+ input_dev->id.vendor = 0x001f;
+ input_dev->id.product = 0x0001;
+ input_dev->id.version = 0x0100;
+ input_dev->dev.parent = dev;
+
+ input_dev->evbit[0] = BIT(EV_SND);
+ input_dev->sndbit[0] = BIT(SND_BELL) | BIT(SND_TONE);
+ input_dev->event = pcspkr_input_event;
+
+ err = input_register_device(input_dev);
+ if (err)
+ return err;
+
+ *rdev = input_dev;
+ return 0;
+}
diff --git a/sound/drivers/pcsp/pcsp_input.h b/sound/drivers/pcsp/pcsp_input.h
new file mode 100644
index 0000000000..42bfc9eab6
--- /dev/null
+++ b/sound/drivers/pcsp/pcsp_input.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * PC-Speaker driver for Linux
+ *
+ * Copyright (C) 2001-2008 Stas Sergeev
+ */
+
+#ifndef __PCSP_INPUT_H__
+#define __PCSP_INPUT_H__
+
+int pcspkr_input_init(struct input_dev **rdev, struct device *dev);
+void pcspkr_stop_sound(void);
+
+#endif
diff --git a/sound/drivers/pcsp/pcsp_lib.c b/sound/drivers/pcsp/pcsp_lib.c
new file mode 100644
index 0000000000..773db4bf08
--- /dev/null
+++ b/sound/drivers/pcsp/pcsp_lib.c
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PC-Speaker driver for Linux
+ *
+ * Copyright (C) 1993-1997 Michael Beck
+ * Copyright (C) 1997-2001 David Woodhouse
+ * Copyright (C) 2001-2008 Stas Sergeev
+ */
+
+#include <linux/module.h>
+#include <linux/gfp.h>
+#include <linux/moduleparam.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <sound/pcm.h>
+#include "pcsp.h"
+
+static bool nforce_wa;
+module_param(nforce_wa, bool, 0444);
+MODULE_PARM_DESC(nforce_wa, "Apply NForce chipset workaround "
+ "(expect bad sound)");
+
+#define DMIX_WANTS_S16 1
+
+/*
+ * Call snd_pcm_period_elapsed in a work
+ * This avoids spinlock messes and long-running irq contexts
+ */
+static void pcsp_call_pcm_elapsed(struct work_struct *work)
+{
+ if (atomic_read(&pcsp_chip.timer_active)) {
+ struct snd_pcm_substream *substream;
+ substream = pcsp_chip.playback_substream;
+ if (substream)
+ snd_pcm_period_elapsed(substream);
+ }
+}
+
+static DECLARE_WORK(pcsp_pcm_work, pcsp_call_pcm_elapsed);
+
+/* write the port and returns the next expire time in ns;
+ * called at the trigger-start and in hrtimer callback
+ */
+static u64 pcsp_timer_update(struct snd_pcsp *chip)
+{
+ unsigned char timer_cnt, val;
+ u64 ns;
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ unsigned long flags;
+
+ if (chip->thalf) {
+ outb(chip->val61, 0x61);
+ chip->thalf = 0;
+ return chip->ns_rem;
+ }
+
+ substream = chip->playback_substream;
+ if (!substream)
+ return 0;
+
+ runtime = substream->runtime;
+ /* assume it is mono! */
+ val = runtime->dma_area[chip->playback_ptr + chip->fmt_size - 1];
+ if (chip->is_signed)
+ val ^= 0x80;
+ timer_cnt = val * CUR_DIV() / 256;
+
+ if (timer_cnt && chip->enable) {
+ raw_spin_lock_irqsave(&i8253_lock, flags);
+ if (!nforce_wa) {
+ outb_p(chip->val61, 0x61);
+ outb_p(timer_cnt, 0x42);
+ outb(chip->val61 ^ 1, 0x61);
+ } else {
+ outb(chip->val61 ^ 2, 0x61);
+ chip->thalf = 1;
+ }
+ raw_spin_unlock_irqrestore(&i8253_lock, flags);
+ }
+
+ chip->ns_rem = PCSP_PERIOD_NS();
+ ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem);
+ chip->ns_rem -= ns;
+ return ns;
+}
+
+static void pcsp_pointer_update(struct snd_pcsp *chip)
+{
+ struct snd_pcm_substream *substream;
+ size_t period_bytes, buffer_bytes;
+ int periods_elapsed;
+ unsigned long flags;
+
+ /* update the playback position */
+ substream = chip->playback_substream;
+ if (!substream)
+ return;
+
+ period_bytes = snd_pcm_lib_period_bytes(substream);
+ buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
+
+ spin_lock_irqsave(&chip->substream_lock, flags);
+ chip->playback_ptr += PCSP_INDEX_INC() * chip->fmt_size;
+ periods_elapsed = chip->playback_ptr - chip->period_ptr;
+ if (periods_elapsed < 0) {
+#if PCSP_DEBUG
+ printk(KERN_INFO "PCSP: buffer_bytes mod period_bytes != 0 ? "
+ "(%zi %zi %zi)\n",
+ chip->playback_ptr, period_bytes, buffer_bytes);
+#endif
+ periods_elapsed += buffer_bytes;
+ }
+ periods_elapsed /= period_bytes;
+ /* wrap the pointer _before_ calling snd_pcm_period_elapsed(),
+ * or ALSA will BUG on us. */
+ chip->playback_ptr %= buffer_bytes;
+
+ if (periods_elapsed) {
+ chip->period_ptr += periods_elapsed * period_bytes;
+ chip->period_ptr %= buffer_bytes;
+ queue_work(system_highpri_wq, &pcsp_pcm_work);
+ }
+ spin_unlock_irqrestore(&chip->substream_lock, flags);
+}
+
+enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle)
+{
+ struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer);
+ int pointer_update;
+ u64 ns;
+
+ if (!atomic_read(&chip->timer_active) || !chip->playback_substream)
+ return HRTIMER_NORESTART;
+
+ pointer_update = !chip->thalf;
+ ns = pcsp_timer_update(chip);
+ if (!ns) {
+ printk(KERN_WARNING "PCSP: unexpected stop\n");
+ return HRTIMER_NORESTART;
+ }
+
+ if (pointer_update)
+ pcsp_pointer_update(chip);
+
+ hrtimer_forward_now(handle, ns_to_ktime(ns));
+
+ return HRTIMER_RESTART;
+}
+
+static int pcsp_start_playing(struct snd_pcsp *chip)
+{
+#if PCSP_DEBUG
+ printk(KERN_INFO "PCSP: start_playing called\n");
+#endif
+ if (atomic_read(&chip->timer_active)) {
+ printk(KERN_ERR "PCSP: Timer already active\n");
+ return -EIO;
+ }
+
+ raw_spin_lock(&i8253_lock);
+ chip->val61 = inb(0x61) | 0x03;
+ outb_p(0x92, 0x43); /* binary, mode 1, LSB only, ch 2 */
+ raw_spin_unlock(&i8253_lock);
+ atomic_set(&chip->timer_active, 1);
+ chip->thalf = 0;
+
+ hrtimer_start(&pcsp_chip.timer, 0, HRTIMER_MODE_REL);
+ return 0;
+}
+
+static void pcsp_stop_playing(struct snd_pcsp *chip)
+{
+#if PCSP_DEBUG
+ printk(KERN_INFO "PCSP: stop_playing called\n");
+#endif
+ if (!atomic_read(&chip->timer_active))
+ return;
+
+ atomic_set(&chip->timer_active, 0);
+ raw_spin_lock(&i8253_lock);
+ /* restore the timer */
+ outb_p(0xb6, 0x43); /* binary, mode 3, LSB/MSB, ch 2 */
+ outb(chip->val61 & 0xFC, 0x61);
+ raw_spin_unlock(&i8253_lock);
+}
+
+/*
+ * Force to stop and sync the stream
+ */
+void pcsp_sync_stop(struct snd_pcsp *chip)
+{
+ local_irq_disable();
+ pcsp_stop_playing(chip);
+ local_irq_enable();
+ hrtimer_cancel(&chip->timer);
+ cancel_work_sync(&pcsp_pcm_work);
+}
+
+static int snd_pcsp_playback_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
+#if PCSP_DEBUG
+ printk(KERN_INFO "PCSP: close called\n");
+#endif
+ pcsp_sync_stop(chip);
+ chip->playback_substream = NULL;
+ return 0;
+}
+
+static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
+ pcsp_sync_stop(chip);
+ return 0;
+}
+
+static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
+#if PCSP_DEBUG
+ printk(KERN_INFO "PCSP: hw_free called\n");
+#endif
+ pcsp_sync_stop(chip);
+ return 0;
+}
+
+static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
+ pcsp_sync_stop(chip);
+ chip->playback_ptr = 0;
+ chip->period_ptr = 0;
+ chip->fmt_size =
+ snd_pcm_format_physical_width(substream->runtime->format) >> 3;
+ chip->is_signed = snd_pcm_format_signed(substream->runtime->format);
+#if PCSP_DEBUG
+ printk(KERN_INFO "PCSP: prepare called, "
+ "size=%zi psize=%zi f=%zi f1=%i fsize=%i\n",
+ snd_pcm_lib_buffer_bytes(substream),
+ snd_pcm_lib_period_bytes(substream),
+ snd_pcm_lib_buffer_bytes(substream) /
+ snd_pcm_lib_period_bytes(substream),
+ substream->runtime->periods,
+ chip->fmt_size);
+#endif
+ return 0;
+}
+
+static int snd_pcsp_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
+#if PCSP_DEBUG
+ printk(KERN_INFO "PCSP: trigger called\n");
+#endif
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ return pcsp_start_playing(chip);
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ pcsp_stop_playing(chip);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static snd_pcm_uframes_t snd_pcsp_playback_pointer(struct snd_pcm_substream
+ *substream)
+{
+ struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
+ unsigned int pos;
+ spin_lock(&chip->substream_lock);
+ pos = chip->playback_ptr;
+ spin_unlock(&chip->substream_lock);
+ return bytes_to_frames(substream->runtime, pos);
+}
+
+static const struct snd_pcm_hardware snd_pcsp_playback = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_HALF_DUPLEX |
+ SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = (SNDRV_PCM_FMTBIT_U8
+#if DMIX_WANTS_S16
+ | SNDRV_PCM_FMTBIT_S16_LE
+#endif
+ ),
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .rate_min = PCSP_DEFAULT_SRATE,
+ .rate_max = PCSP_DEFAULT_SRATE,
+ .channels_min = 1,
+ .channels_max = 1,
+ .buffer_bytes_max = PCSP_BUFFER_SIZE,
+ .period_bytes_min = 64,
+ .period_bytes_max = PCSP_MAX_PERIOD_SIZE,
+ .periods_min = 2,
+ .periods_max = PCSP_MAX_PERIODS,
+ .fifo_size = 0,
+};
+
+static int snd_pcsp_playback_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+#if PCSP_DEBUG
+ printk(KERN_INFO "PCSP: open called\n");
+#endif
+ if (atomic_read(&chip->timer_active)) {
+ printk(KERN_ERR "PCSP: still active!!\n");
+ return -EBUSY;
+ }
+ runtime->hw = snd_pcsp_playback;
+ chip->playback_substream = substream;
+ return 0;
+}
+
+static const struct snd_pcm_ops snd_pcsp_playback_ops = {
+ .open = snd_pcsp_playback_open,
+ .close = snd_pcsp_playback_close,
+ .hw_params = snd_pcsp_playback_hw_params,
+ .hw_free = snd_pcsp_playback_hw_free,
+ .prepare = snd_pcsp_playback_prepare,
+ .trigger = snd_pcsp_trigger,
+ .pointer = snd_pcsp_playback_pointer,
+};
+
+int snd_pcsp_new_pcm(struct snd_pcsp *chip)
+{
+ int err;
+
+ err = snd_pcm_new(chip->card, "pcspeaker", 0, 1, 0, &chip->pcm);
+ if (err < 0)
+ return err;
+
+ snd_pcm_set_ops(chip->pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_pcsp_playback_ops);
+
+ chip->pcm->private_data = chip;
+ chip->pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+ strcpy(chip->pcm->name, "pcsp");
+
+ snd_pcm_set_managed_buffer_all(chip->pcm,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ NULL,
+ PCSP_BUFFER_SIZE,
+ PCSP_BUFFER_SIZE);
+
+ return 0;
+}
diff --git a/sound/drivers/pcsp/pcsp_mixer.c b/sound/drivers/pcsp/pcsp_mixer.c
new file mode 100644
index 0000000000..da33e5b620
--- /dev/null
+++ b/sound/drivers/pcsp/pcsp_mixer.c
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PC-Speaker driver for Linux
+ *
+ * Mixer implementation.
+ * Copyright (C) 2001-2008 Stas Sergeev
+ */
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include "pcsp.h"
+
+
+static int pcsp_enable_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int pcsp_enable_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.integer.value[0] = chip->enable;
+ return 0;
+}
+
+static int pcsp_enable_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+ int enab = ucontrol->value.integer.value[0];
+ if (enab != chip->enable) {
+ chip->enable = enab;
+ changed = 1;
+ }
+ return changed;
+}
+
+static int pcsp_treble_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = chip->max_treble + 1;
+ if (uinfo->value.enumerated.item > chip->max_treble)
+ uinfo->value.enumerated.item = chip->max_treble;
+ sprintf(uinfo->value.enumerated.name, "%lu",
+ (unsigned long)PCSP_CALC_RATE(uinfo->value.enumerated.item));
+ return 0;
+}
+
+static int pcsp_treble_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.enumerated.item[0] = chip->treble;
+ return 0;
+}
+
+static int pcsp_treble_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+ int treble = ucontrol->value.enumerated.item[0];
+ if (treble != chip->treble) {
+ chip->treble = treble;
+#if PCSP_DEBUG
+ printk(KERN_INFO "PCSP: rate set to %li\n", PCSP_RATE());
+#endif
+ changed = 1;
+ }
+ return changed;
+}
+
+static int pcsp_pcspkr_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int pcsp_pcspkr_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.integer.value[0] = chip->pcspkr;
+ return 0;
+}
+
+static int pcsp_pcspkr_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+ int spkr = ucontrol->value.integer.value[0];
+ if (spkr != chip->pcspkr) {
+ chip->pcspkr = spkr;
+ changed = 1;
+ }
+ return changed;
+}
+
+#define PCSP_MIXER_CONTROL(ctl_type, ctl_name) \
+{ \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = ctl_name, \
+ .info = pcsp_##ctl_type##_info, \
+ .get = pcsp_##ctl_type##_get, \
+ .put = pcsp_##ctl_type##_put, \
+}
+
+static const struct snd_kcontrol_new snd_pcsp_controls_pcm[] = {
+ PCSP_MIXER_CONTROL(enable, "Master Playback Switch"),
+ PCSP_MIXER_CONTROL(treble, "BaseFRQ Playback Volume"),
+};
+
+static const struct snd_kcontrol_new snd_pcsp_controls_spkr[] = {
+ PCSP_MIXER_CONTROL(pcspkr, "Beep Playback Switch"),
+};
+
+static int snd_pcsp_ctls_add(struct snd_pcsp *chip,
+ const struct snd_kcontrol_new *ctls, int num)
+{
+ int i, err;
+ struct snd_card *card = chip->card;
+ for (i = 0; i < num; i++) {
+ err = snd_ctl_add(card, snd_ctl_new1(ctls + i, chip));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+int snd_pcsp_new_mixer(struct snd_pcsp *chip, int nopcm)
+{
+ int err;
+ struct snd_card *card = chip->card;
+
+ if (!nopcm) {
+ err = snd_pcsp_ctls_add(chip, snd_pcsp_controls_pcm,
+ ARRAY_SIZE(snd_pcsp_controls_pcm));
+ if (err < 0)
+ return err;
+ }
+ err = snd_pcsp_ctls_add(chip, snd_pcsp_controls_spkr,
+ ARRAY_SIZE(snd_pcsp_controls_spkr));
+ if (err < 0)
+ return err;
+
+ strcpy(card->mixername, "PC-Speaker");
+
+ return 0;
+}