[ALSA] Add PC-speaker sound driver
Stas Sergeev [Mon, 3 Mar 2008 09:53:54 +0000 (10:53 +0100)]
Added PC-speaker sound driver (snd-pcsp).

Signed-off-by: Stas Sergeev <stsp@aknet.ru>
Signed-off-by: Takashi Iwai <tiwai@suse.de>

sound/drivers/Kconfig
sound/drivers/Makefile
sound/drivers/pcsp/Makefile [new file with mode: 0644]
sound/drivers/pcsp/pcsp.c [new file with mode: 0644]
sound/drivers/pcsp/pcsp.h [new file with mode: 0644]
sound/drivers/pcsp/pcsp_input.c [new file with mode: 0644]
sound/drivers/pcsp/pcsp_input.h [new file with mode: 0644]
sound/drivers/pcsp/pcsp_lib.c [new file with mode: 0644]
sound/drivers/pcsp/pcsp_mixer.c [new file with mode: 0644]

index 75d4fe0..78648c4 100644 (file)
@@ -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
index 8e55300..d4a07f9 100644 (file)
@@ -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 (file)
index 0000000..b19555b
--- /dev/null
@@ -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 (file)
index 0000000..3447728
--- /dev/null
@@ -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 (file)
index 0000000..f07cc1e
--- /dev/null
@@ -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 (file)
index 0000000..cd9b83e
--- /dev/null
@@ -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 (file)
index 0000000..e66738c
--- /dev/null
@@ -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 (file)
index 0000000..6bdcb89
--- /dev/null
@@ -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 (file)
index 0000000..64a695f
--- /dev/null
@@ -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;
+}