Logo Search packages:      
Sourcecode: alsa-driver version File versions  Download package

pcsp.c

/*
 * PC-Speaker driver for Linux
 *
 * Copyright (C) 1997-2001  David Woodhouse
 * Copyright (C) 2001-2004  Stas Sergeev
 */

#include <linux/config.h>
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>

#include <linux/interrupt.h>
#include <linux/timex.h>
#include <linux/kd.h>
#include <linux/ioport.h>
#include <asm/io.h>
#include <asm/bitops.h>
#ifdef CONFIG_APM_CPU_IDLE
#include <linux/pm.h>
#endif
#include "pcsp_defs.h"

MODULE_AUTHOR("Stas Sergeev <stsp@users.sourceforge.net>");
MODULE_DESCRIPTION("PC-Speaker driver");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{PC-Speaker, pcsp}}");

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 */
static int no_test_speed = 0, no_beeps = 0;

module_param(id, charp, 0444);
MODULE_PARM_DESC(id, "ID string for pcsp soundcard.");
module_param(enable, bool, 0444);
MODULE_PARM_DESC(enable, "dummy");
module_param(no_test_speed, int, 0444);
MODULE_PARM_DESC(no_test_speed, "dont test the CPU speed on startup");
module_param(no_beeps, int, 0444);
MODULE_PARM_DESC(no_beeps, "disable pc-speaker beeps");

snd_card_t *snd_pcsp_card = SNDRV_DEFAULT_PTR1;

#ifdef CONFIG_APM_CPU_IDLE
static void (*saved_pm_idle)(void);
#endif

static int (*pcsp_IRQ)(chip_t *chip) = NULL;

/*
 * this is the PCSP IRQ handler
 */
static irqreturn_t pcsp_run_IRQ(int irq, void *dev_id, struct pt_regs *regs)
{
      pcsp_t *chip = dev_id;
      int ret = IRQ_NONE;
      if (pcsp_IRQ) {
            ret = IRQ_HANDLED;
            if (!pcsp_IRQ(chip))
                  ret |= IRQ_DONE;
      }
      return ret;
}

/*
 * Set the function func to be executed as the timer int.
 * if func returns a 0, the old IRQ0-handler(s) is called
 */
void pcsp_set_irq(chip_t *chip, int (*func)(chip_t *chip))
{
      unsigned long flags;
      if (!func)
            return;
      spin_lock_irqsave(&chip->lock, flags);
      pcsp_IRQ = func;
#if defined(CONFIG_INPUT_PCSPKR) || defined(CONFIG_INPUT_PCSPKR_MODULE)
      use_speaker_beep = 0;
#endif
#ifdef CONFIG_APM_CPU_IDLE
      saved_pm_idle = pm_idle;
      pm_idle = NULL;
#endif
      spin_unlock_irqrestore(&chip->lock, flags);
}

/*
 * reset the IRQ0 to the old handling
 */
void pcsp_release_irq(chip_t *chip)
{
      unsigned long flags;
      spin_lock_irqsave(&chip->lock, flags);
      pcsp_IRQ = NULL;
#if defined(CONFIG_INPUT_PCSPKR) || defined(CONFIG_INPUT_PCSPKR_MODULE)
      use_speaker_beep = !no_beeps;
#endif
#ifdef CONFIG_APM_CPU_IDLE
      pm_idle = saved_pm_idle;
#endif
      spin_unlock_irqrestore(&chip->lock, flags);
}

static int tst_len[2];
volatile static int pcsp_test_running;
/*
   this is a stupid beep which occurs if PCSP is disabled;
   it's not needed because we have the message, but who reads it...
   and this is the PC-Speaker driver :-)
*/
void __init pcsp_beep(int count, int cycles)
{
      unsigned long flags;
      spin_lock_irqsave(&i8253_lock, flags);
      /* 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);

      while (cycles--);
             
      /* disable counter 2 */
      outb(inb_p(0x61)&0xFC, 0x61);
      spin_unlock_irqrestore(&i8253_lock, flags);
}

/*
   the timer-int for testing cpu-speed, mostly the same as
   for PC-Speaker
 */
static int __init pcsp_test_intspeed(chip_t *chip)
{
      unsigned char test_buffer[256];
      if (chip->index < tst_len[chip->cur_buf]) {
            spin_lock(&i8253_lock);
            outb(chip->e,     0x61);
            outb(test_buffer[chip->index], 0x42);
            outb(chip->e ^ 1, 0x61);
            spin_unlock(&i8253_lock);

            chip->index += 2;
      }
      if (chip->index >= tst_len[chip->cur_buf]) {
            chip->index = 0;
            tst_len[chip->cur_buf] = 0;
            chip->cur_buf ^= 1;
            if (tst_len[chip->cur_buf] == 0xFFFF)
                  chip->cur_buf ^= 1;
      }

      ++pcsp_test_running;
      return 1;
}

/*
   this routine measures the time needed for one timer-int if
   we play thru PC-Speaker. This is kind of ugly but does the
   trick.
 */
static int __init pcsp_measurement(chip_t *chip, int addon)
{
      int count;
      unsigned long flags;

      chip->index   = 0;
      tst_len[0]        = 5 + addon;
      tst_len[1]    = 0;
      chip->cur_buf     = 0;
      chip->e       = inb(0x61) & 0xFC;

      pcsp_test_running = 0;

      pcsp_set_irq(chip, pcsp_test_intspeed);
      /*
        Perhaps we need some sort of timeout here, but if IRQ0
        isn't working the system hangs later ...
      */
      while (pcsp_test_running < 5);
      pcsp_release_irq(chip);

      spin_lock_irqsave(&i8253_lock, flags);
      outb_p(0x00, 0x43);           /* latch the count ASAP */
      count = inb_p(0x40);          /* read the latched count */
      count |= inb(0x40) << 8;
      spin_unlock_irqrestore(&i8253_lock, flags);

      return (LATCH - count);
}

static int __init pcsp_test_speed(chip_t *chip, int *rmin_div)
{
      int worst, worst1, best, best1, min_div;

      worst  = pcsp_measurement(chip, 0);
      worst1 = pcsp_measurement(chip, 0);
      best   = pcsp_measurement(chip, 5);
      best1  = pcsp_measurement(chip, 5);

      worst = max(worst, worst1);
      best  = min(best, best1);

#if PCSP_DEBUG
      printk(KERN_INFO "PCSP-Timerint needs %d Ticks in worst case\n", worst);
      printk(KERN_INFO "PCSP-Timerint needs %d Ticks in best case\n", best);
#endif
      /* We allow a CPU-usage of 80 % for the best-case ! */
      *rmin_div = min_div = best * 10 / 8;
      printk(KERN_INFO "PCSP: Measurement: maximal modulation freq %d Hz.\n",
            CLOCK_TICK_RATE / min_div);

      if (min_div > MAX_DIV) {
            printk(KERN_WARNING "This is too SLOW! PCSP-driver DISABLED\n");
            /* very ugly beep, but you hopefully never hear it */
            pcsp_beep(12000,800000);
            pcsp_beep(10000,800000);
            return 0;
      }
      return 1;
}

static int snd_pcsp_free(pcsp_t *chip)
{
      free_irq(chip->irq, chip);
      kfree(chip);
#if defined(CONFIG_INPUT_PCSPKR) || defined(CONFIG_INPUT_PCSPKR_MODULE)
      use_speaker_beep = 1;
#endif
      return 0;
}

static int snd_pcsp_dev_free(snd_device_t *device)
{
      pcsp_t *chip = device->device_data;
      return snd_pcsp_free(chip);
}

static int __init snd_pcsp_create(snd_card_t *card, pcsp_t **rchip)
{
      static snd_device_ops_t ops = {
            .dev_free = snd_pcsp_dev_free,
      };
      pcsp_t *chip;
      int err;
      int div, min_div, order;
      int pcsp_enabled = 1;

      chip = kzalloc(sizeof(pcsp_t), GFP_KERNEL);
      if (chip == NULL)
            return -ENOMEM;
      spin_lock_init(&chip->lock);

      chip->card = card;
      chip->port = 0x61;
      chip->irq = TIMER_IRQ;
      chip->dma = -1;

      if (request_irq(chip->irq, pcsp_run_IRQ,
                  SA_INTERRUPT | SA_SHIRQ | SA_FIRST,
                  "pcsp", chip)) {
            printk(KERN_WARNING "PCSP could not modify timer IRQ!");
            snd_pcsp_free(chip);
            return -EBUSY;
      }

      if (!no_test_speed) {
            pcsp_enabled = pcsp_test_speed(chip, &min_div);
      } else {
            min_div = MAX_DIV;
      }
      if (!pcsp_enabled) {
            snd_pcsp_free(chip);
            return -EIO;
      }

      div = MAX_DIV / min_div;
      order = fls(div) - 1;

      chip->max_treble   = min(order, PCSP_MAX_POSS_TREBLE);
      chip->treble       = min(chip->max_treble, 1);
      chip->gain         = PCSP_DEFAULT_GAIN;
      chip->index    = 0;
      chip->bass         = 0;
      chip->cur_buf        = 0;
      chip->timer_active = 0;
      chip->last_clocks  =
      chip->timer_latch  =
      chip->clockticks   = LATCH;
      chip->volume       = PCSP_MAX_VOLUME;

      /* Register device */
      if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
            snd_pcsp_free(chip);
            return err;
      }

      *rchip = chip;
      return 0;
}

static int __init snd_card_pcsp_probe(int dev)
{
      snd_card_t *card;
      pcsp_t *chip;
      int err;

      if (dev != 0)
            return -EINVAL;

      card = snd_card_new(index, id, THIS_MODULE, 0);
      if (card == NULL)
            return -ENOMEM;

      if ((err = snd_pcsp_create(card, &chip)) < 0) {
            snd_card_free(card);
            return err;
      }
      if ((err = snd_pcsp_new_pcm(chip)) < 0) {
            snd_card_free(card);
            return err;
      }
      if ((err = snd_pcsp_new_mixer(chip)) < 0) {
            snd_card_free(card);
            return err;
      }

      strcpy(card->driver, "PC-Speaker");
      strcpy(card->shortname, chip->pcm->name);
      sprintf(card->longname, "Internal PC-Speaker at port 0x%x, irq %d",
          chip->port, chip->irq);

      if ((err = snd_card_register(card)) < 0) {
            snd_card_free(card);
            return err;
      }
      snd_pcsp_card = card;

#if defined(CONFIG_INPUT_PCSPKR) || defined(CONFIG_INPUT_PCSPKR_MODULE)
      use_speaker_beep = !no_beeps;
#endif
      return 0;
}

static int __init alsa_card_pcsp_init(void)
{
      int dev = 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(dev) >= 0)
                  cards++;
      }
      if (!cards) {
#ifdef MODULE
            printk(KERN_ERR "PC-Speaker soundcard not found or device busy\n");
#endif
            return -ENODEV;
      }
      return 0;
}

static void __exit alsa_card_pcsp_exit(void)
{

      snd_card_free(snd_pcsp_card);
}

module_init(alsa_card_pcsp_init);
module_exit(alsa_card_pcsp_exit);

Generated by  Doxygen 1.6.0   Back to index