aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStas Sergeev <stsp@aknet.ru>2008-03-03 10:53:54 +0100
committerTakashi Iwai <tiwai@suse.de>2008-04-24 12:00:20 +0200
commit9ab4d072ad67793d70b8707e14fb9261749c4e07 (patch)
treec6ae32f1d3d3b273fae30b0fa59b8c79bd5e8044
parent40ac8c4f208111cdc1542ccc9feb21b98a6b0219 (diff)
downloadlinux-2.6.34-ux500-9ab4d072ad67793d70b8707e14fb9261749c4e07.tar.gz
[ALSA] Add PC-speaker sound driver
Added PC-speaker sound driver (snd-pcsp). Signed-off-by: Stas Sergeev <stsp@aknet.ru> Signed-off-by: Takashi Iwai <tiwai@suse.de>
-rw-r--r--sound/drivers/Kconfig17
-rw-r--r--sound/drivers/Makefile2
-rw-r--r--sound/drivers/pcsp/Makefile2
-rw-r--r--sound/drivers/pcsp/pcsp.c241
-rw-r--r--sound/drivers/pcsp/pcsp.h82
-rw-r--r--sound/drivers/pcsp/pcsp_input.c116
-rw-r--r--sound/drivers/pcsp/pcsp_input.h14
-rw-r--r--sound/drivers/pcsp/pcsp_lib.c347
-rw-r--r--sound/drivers/pcsp/pcsp_mixer.c143
9 files changed, 963 insertions, 1 deletions
diff --git a/sound/drivers/Kconfig b/sound/drivers/Kconfig
index 75d4fe09fdf..78648c4e9e7 100644
--- a/sound/drivers/Kconfig
+++ b/sound/drivers/Kconfig
@@ -4,6 +4,23 @@ menu "Generic devices"
depends on SND!=n
+config SND_PCSP
+ tristate "Internal PC speaker support"
+ depends on X86_PC && HIGH_RES_TIMERS
+ help
+ If you don't have a sound card in your computer, you can include a
+ driver for the PC speaker which allows it to act like a primitive
+ sound card.
+ This driver also replaces the pcspkr driver for beeps.
+
+ You can compile this as a module which will be called snd-pcsp.
+
+ You don't need this driver if you only want your pc-speaker to beep.
+ You don't need this driver if you have a tablet piezo beeper
+ in your PC instead of the real speaker.
+
+ It should not hurt to say Y or M here in all other cases.
+
config SND_MPU401_UART
tristate
select SND_RAWMIDI
diff --git a/sound/drivers/Makefile b/sound/drivers/Makefile
index 8e5530006e1..d4a07f9ff2c 100644
--- a/sound/drivers/Makefile
+++ b/sound/drivers/Makefile
@@ -20,4 +20,4 @@ obj-$(CONFIG_SND_MTS64) += snd-mts64.o
obj-$(CONFIG_SND_PORTMAN2X4) += snd-portman2x4.o
obj-$(CONFIG_SND_ML403_AC97CR) += snd-ml403-ac97cr.o
-obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/
+obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ pcsp/
diff --git a/sound/drivers/pcsp/Makefile b/sound/drivers/pcsp/Makefile
new file mode 100644
index 00000000000..b19555b440d
--- /dev/null
+++ b/sound/drivers/pcsp/Makefile
@@ -0,0 +1,2 @@
+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 00000000000..34477286b39
--- /dev/null
+++ b/sound/drivers/pcsp/pcsp.c
@@ -0,0 +1,241 @@
+/*
+ * PC-Speaker driver for Linux
+ *
+ * Copyright (C) 1997-2001 David Woodhouse
+ * Copyright (C) 2001-2008 Stas Sergeev
+ */
+
+#include <linux/init.h>
+#include <linux/moduleparam.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 <asm/bitops.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_SUPPORTED_DEVICE("{{PC-Speaker, pcsp}}");
+MODULE_ALIAS("platform:pcspkr");
+
+static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */
+static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */
+static int enable = SNDRV_DEFAULT_ENABLE1; /* Enable this card */
+
+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, "dummy");
+
+struct snd_pcsp pcsp_chip;
+
+static int __devinit snd_pcsp_create(struct snd_card *card)
+{
+ static struct snd_device_ops ops = { };
+ struct timespec tp;
+ int err;
+ int div, min_div, order;
+
+ hrtimer_get_res(CLOCK_MONOTONIC, &tp);
+ if (tp.tv_sec || tp.tv_nsec > PCSP_MAX_PERIOD_NS) {
+ printk(KERN_ERR "PCSP: Timer resolution is not sufficient "
+ "(%linS)\n", tp.tv_nsec);
+ printk(KERN_ERR "PCSP: Make sure you have HPET and ACPI "
+ "enabled.\n");
+ return -EIO;
+ }
+
+ if (loops_per_jiffy >= PCSP_MIN_LPJ && tp.tv_nsec <= PCSP_MIN_PERIOD_NS)
+ min_div = MIN_DIV;
+ else
+ min_div = MAX_DIV;
+#if PCSP_DEBUG
+ printk("PCSP: lpj=%li, min_div=%i, res=%li\n",
+ loops_per_jiffy, min_div, tp.tv_nsec);
+#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;
+
+ /* Register device */
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, &pcsp_chip, &ops);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int __devinit 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.cb_mode = HRTIMER_CB_IRQSAFE;
+ pcsp_chip.timer.function = pcsp_do_timer;
+
+ card = snd_card_new(index, id, THIS_MODULE, 0);
+ if (!card)
+ return -ENOMEM;
+
+ err = snd_pcsp_create(card);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ err = snd_pcsp_new_pcm(&pcsp_chip);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ err = snd_pcsp_new_mixer(&pcsp_chip);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ snd_card_set_dev(pcsp_chip.card, dev);
+
+ 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) {
+ snd_card_free(card);
+ return err;
+ }
+
+ return 0;
+}
+
+static int __devinit alsa_card_pcsp_init(struct device *dev)
+{
+ int devnum = 0, cards = 0;
+
+#ifdef CONFIG_DEBUG_PAGEALLOC
+ /* Well, CONFIG_DEBUG_PAGEALLOC makes the sound horrible. Lets alert */
+ printk(KERN_WARNING
+ "PCSP: Warning, CONFIG_DEBUG_PAGEALLOC is enabled!\n"
+ "You have to disable it if you want to use the PC-Speaker "
+ "driver.\n"
+ "Unless it is disabled, enjoy the horrible, distorted "
+ "and crackling noise.\n");
+#endif
+
+ if (enable) {
+ if (snd_card_pcsp_probe(devnum, dev) >= 0)
+ cards++;
+ if (!cards) {
+ printk(KERN_ERR "PC-Speaker initialization failed.\n");
+ return -ENODEV;
+ }
+ }
+
+ return 0;
+}
+
+static void __devexit alsa_card_pcsp_exit(struct snd_pcsp *chip)
+{
+ snd_card_free(chip->card);
+}
+
+static int __devinit 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) {
+ pcspkr_input_remove(pcsp_chip.input_dev);
+ return err;
+ }
+
+ platform_set_drvdata(dev, &pcsp_chip);
+ return 0;
+}
+
+static int __devexit pcsp_remove(struct platform_device *dev)
+{
+ struct snd_pcsp *chip = platform_get_drvdata(dev);
+ alsa_card_pcsp_exit(chip);
+ pcspkr_input_remove(chip->input_dev);
+ platform_set_drvdata(dev, NULL);
+ return 0;
+}
+
+static void pcsp_stop_beep(struct snd_pcsp *chip)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&chip->substream_lock, flags);
+ if (!chip->playback_substream)
+ pcspkr_stop_sound();
+ spin_unlock_irqrestore(&chip->substream_lock, flags);
+}
+
+static int pcsp_suspend(struct platform_device *dev, pm_message_t state)
+{
+ struct snd_pcsp *chip = platform_get_drvdata(dev);
+ pcsp_stop_beep(chip);
+ snd_pcm_suspend_all(chip->pcm);
+ return 0;
+}
+
+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",
+ .owner = THIS_MODULE,
+ },
+ .probe = pcsp_probe,
+ .remove = __devexit_p(pcsp_remove),
+ .suspend = pcsp_suspend,
+ .shutdown = pcsp_shutdown,
+};
+
+static int __init pcsp_init(void)
+{
+ 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 00000000000..f07cc1ee1fe
--- /dev/null
+++ b/sound/drivers/pcsp/pcsp.h
@@ -0,0 +1,82 @@
+/*
+ * 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>
+#if defined(CONFIG_MIPS) || defined(CONFIG_X86)
+/* Use the global PIT lock ! */
+#include <asm/i8253.h>
+#else
+#include <asm/8253pit.h>
+static DEFINE_SPINLOCK(i8253_lock);
+#endif
+
+#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 CUR_DIV() (MAX_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_RATE() (PIT_TICK_RATE / CUR_DIV())
+#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;
+ 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 int snd_pcsp_new_pcm(struct snd_pcsp *chip);
+extern int snd_pcsp_new_mixer(struct snd_pcsp *chip);
+
+#endif
diff --git a/sound/drivers/pcsp/pcsp_input.c b/sound/drivers/pcsp/pcsp_input.c
new file mode 100644
index 00000000000..cd9b83e7f7d
--- /dev/null
+++ b/sound/drivers/pcsp/pcsp_input.c
@@ -0,0 +1,116 @@
+/*
+ * PC Speaker beeper driver for Linux
+ *
+ * Copyright (c) 2002 Vojtech Pavlik
+ * Copyright (c) 1992 Orest Zborowski
+ *
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation
+ */
+
+#include <linux/init.h>
+#include <linux/input.h>
+#include <asm/io.h>
+#include "pcsp.h"
+
+static void pcspkr_do_sound(unsigned int count)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&i8253_lock, flags);
+
+ if (count) {
+ /* enable counter 2 */
+ outb_p(inb_p(0x61) | 3, 0x61);
+ /* 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);
+ } else {
+ /* disable counter 2 */
+ outb(inb_p(0x61) & 0xFC, 0x61);
+ }
+
+ 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;
+ 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 __devinit pcspkr_input_init(struct input_dev **rdev, struct device *dev)
+{
+ int err;
+
+ struct input_dev *input_dev = input_allocate_device();
+ 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) {
+ input_free_device(input_dev);
+ return err;
+ }
+
+ *rdev = input_dev;
+ return 0;
+}
+
+int pcspkr_input_remove(struct input_dev *dev)
+{
+ pcspkr_stop_sound();
+ input_unregister_device(dev); /* this also does kfree() */
+
+ return 0;
+}
diff --git a/sound/drivers/pcsp/pcsp_input.h b/sound/drivers/pcsp/pcsp_input.h
new file mode 100644
index 00000000000..e66738c7833
--- /dev/null
+++ b/sound/drivers/pcsp/pcsp_input.h
@@ -0,0 +1,14 @@
+/*
+ * PC-Speaker driver for Linux
+ *
+ * Copyright (C) 2001-2008 Stas Sergeev
+ */
+
+#ifndef __PCSP_INPUT_H__
+#define __PCSP_INPUT_H__
+
+int __devinit pcspkr_input_init(struct input_dev **rdev, struct device *dev);
+int pcspkr_input_remove(struct input_dev *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 00000000000..6bdcb89129d
--- /dev/null
+++ b/sound/drivers/pcsp/pcsp_lib.c
@@ -0,0 +1,347 @@
+/*
+ * 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/moduleparam.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <linux/interrupt.h>
+#include <asm/io.h>
+#include <asm/i8253.h>
+#include "pcsp.h"
+
+static int 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
+
+static void pcsp_start_timer(unsigned long dummy)
+{
+ hrtimer_start(&pcsp_chip.timer, ktime_set(0, 0), HRTIMER_MODE_REL);
+}
+
+/*
+ * We need the hrtimer_start as a tasklet to avoid
+ * the nasty locking problem. :(
+ * The problem:
+ * - The timer handler is called with the cpu_base->lock
+ * already held by hrtimer code.
+ * - snd_pcm_period_elapsed() takes the
+ * substream->self_group.lock.
+ * So far so good.
+ * But the snd_pcsp_trigger() is called with the
+ * substream->self_group.lock held, and it calls
+ * hrtimer_start(), which takes the cpu_base->lock.
+ * You see the problem. We have the code pathes
+ * which take two locks in a reverse order. This
+ * can deadlock and the lock validator complains.
+ * The only solution I could find was to move the
+ * hrtimer_start() into a tasklet. -stsp
+ */
+DECLARE_TASKLET(pcsp_start_timer_tasklet, pcsp_start_timer, 0);
+
+enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle)
+{
+ unsigned long flags;
+ unsigned char timer_cnt, val;
+ int fmt_size, periods_elapsed;
+ u64 ns;
+ size_t period_bytes, buffer_bytes;
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer);
+
+ if (chip->thalf) {
+ outb(chip->val61, 0x61);
+ chip->thalf = 0;
+ if (!atomic_read(&chip->timer_active))
+ return HRTIMER_NORESTART;
+ hrtimer_forward(&chip->timer, chip->timer.expires,
+ ktime_set(0, chip->ns_rem));
+ return HRTIMER_RESTART;
+ }
+
+ /* hrtimer calls us from both hardirq and softirq contexts,
+ * so irqsave :( */
+ spin_lock_irqsave(&chip->substream_lock, flags);
+ /* Takashi Iwai says regarding this extra lock:
+
+ If the irq handler handles some data on the DMA buffer, it should
+ do snd_pcm_stream_lock().
+ That protects basically against all races among PCM callbacks, yes.
+ However, there are two remaining issues:
+ 1. The substream pointer you try to lock isn't protected _before_
+ this lock yet.
+ 2. snd_pcm_period_elapsed() itself acquires the lock.
+ The requirement of another lock is because of 1. When you get
+ chip->playback_substream, it's not protected.
+ Keeping this lock while snd_pcm_period_elapsed() assures the substream
+ is still protected (at least, not released). And the other status is
+ handled properly inside snd_pcm_stream_lock() in
+ snd_pcm_period_elapsed().
+
+ */
+ if (!chip->playback_substream)
+ goto exit_nr_unlock1;
+ substream = chip->playback_substream;
+ snd_pcm_stream_lock(substream);
+ if (!atomic_read(&chip->timer_active))
+ goto exit_nr_unlock2;
+
+ runtime = substream->runtime;
+ fmt_size = snd_pcm_format_physical_width(runtime->format) >> 3;
+ /* assume it is mono! */
+ val = runtime->dma_area[chip->playback_ptr + fmt_size - 1];
+ if (snd_pcm_format_signed(runtime->format))
+ val ^= 0x80;
+ timer_cnt = val * CUR_DIV() / 256;
+
+ if (timer_cnt && chip->enable) {
+ spin_lock(&i8253_lock);
+ 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;
+ }
+ spin_unlock(&i8253_lock);
+ }
+
+ period_bytes = snd_pcm_lib_period_bytes(substream);
+ buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
+ chip->playback_ptr += PCSP_INDEX_INC() * fmt_size;
+ periods_elapsed = chip->playback_ptr - chip->period_ptr;
+ if (periods_elapsed < 0) {
+ printk(KERN_WARNING "PCSP: playback_ptr inconsistent "
+ "(%zi %zi %zi)\n",
+ chip->playback_ptr, period_bytes, buffer_bytes);
+ 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;
+
+ snd_pcm_stream_unlock(substream);
+
+ if (periods_elapsed) {
+ snd_pcm_period_elapsed(substream);
+ chip->period_ptr += periods_elapsed * period_bytes;
+ chip->period_ptr %= buffer_bytes;
+ }
+
+ spin_unlock_irqrestore(&chip->substream_lock, flags);
+
+ if (!atomic_read(&chip->timer_active))
+ return HRTIMER_NORESTART;
+
+ chip->ns_rem = PCSP_PERIOD_NS();
+ ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem);
+ chip->ns_rem -= ns;
+ hrtimer_forward(&chip->timer, chip->timer.expires, ktime_set(0, ns));
+ return HRTIMER_RESTART;
+
+exit_nr_unlock2:
+ snd_pcm_stream_unlock(substream);
+exit_nr_unlock1:
+ spin_unlock_irqrestore(&chip->substream_lock, flags);
+ return HRTIMER_NORESTART;
+}
+
+static void 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;
+ }
+
+ spin_lock(&i8253_lock);
+ chip->val61 = inb(0x61) | 0x03;
+ outb_p(0x92, 0x43); /* binary, mode 1, LSB only, ch 2 */
+ spin_unlock(&i8253_lock);
+ atomic_set(&chip->timer_active, 1);
+ chip->thalf = 0;
+
+ tasklet_schedule(&pcsp_start_timer_tasklet);
+}
+
+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);
+ spin_lock(&i8253_lock);
+ /* restore the timer */
+ outb_p(0xb6, 0x43); /* binary, mode 3, LSB/MSB, ch 2 */
+ outb(chip->val61 & 0xFC, 0x61);
+ spin_unlock(&i8253_lock);
+}
+
+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
+ if (atomic_read(&chip->timer_active)) {
+ printk(KERN_ERR "PCSP: timer still active\n");
+ pcsp_stop_playing(chip);
+ }
+ spin_lock_irq(&chip->substream_lock);
+ chip->playback_substream = NULL;
+ spin_unlock_irq(&chip->substream_lock);
+ return 0;
+}
+
+static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ int err;
+ err = snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream)
+{
+#if PCSP_DEBUG
+ printk(KERN_INFO "PCSP: hw_free called\n");
+#endif
+ return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
+#if PCSP_DEBUG
+ printk(KERN_INFO "PCSP: prepare called, "
+ "size=%zi psize=%zi f=%zi f1=%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);
+#endif
+ chip->playback_ptr = 0;
+ chip->period_ptr = 0;
+ 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:
+ pcsp_start_playing(chip);
+ break;
+ 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);
+ return bytes_to_frames(substream->runtime, chip->playback_ptr);
+}
+
+static 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 struct snd_pcm_ops snd_pcsp_playback_ops = {
+ .open = snd_pcsp_playback_open,
+ .close = snd_pcsp_playback_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .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 __devinit 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_lib_preallocate_pages_for_all(chip->pcm,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data
+ (GFP_KERNEL), 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 00000000000..64a695fef74
--- /dev/null
+++ b/sound/drivers/pcsp/pcsp_mixer.c
@@ -0,0 +1,143 @@
+/*
+ * 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, "%d", PCSP_RATE());
+ 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 %i\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 struct snd_kcontrol_new __devinitdata snd_pcsp_controls[] = {
+ PCSP_MIXER_CONTROL(enable, "Master Playback Switch"),
+ PCSP_MIXER_CONTROL(treble, "BaseFRQ Playback Volume"),
+ PCSP_MIXER_CONTROL(pcspkr, "PC Speaker Playback Switch"),
+};
+
+int __devinit snd_pcsp_new_mixer(struct snd_pcsp *chip)
+{
+ struct snd_card *card = chip->card;
+ int i, err;
+
+ for (i = 0; i < ARRAY_SIZE(snd_pcsp_controls); i++) {
+ err = snd_ctl_add(card,
+ snd_ctl_new1(snd_pcsp_controls + i,
+ chip));
+ if (err < 0)
+ return err;
+ }
+
+ strcpy(card->mixername, "PC-Speaker");
+
+ return 0;
+}