linux/sound/drivers/pcsp/pcsp_lib.c
<<
>>
Prefs
   1/*
   2 * PC-Speaker driver for Linux
   3 *
   4 * Copyright (C) 1993-1997  Michael Beck
   5 * Copyright (C) 1997-2001  David Woodhouse
   6 * Copyright (C) 2001-2008  Stas Sergeev
   7 */
   8
   9#include <linux/module.h>
  10#include <linux/gfp.h>
  11#include <linux/moduleparam.h>
  12#include <linux/interrupt.h>
  13#include <linux/io.h>
  14#include <sound/pcm.h>
  15#include "pcsp.h"
  16
  17static bool nforce_wa;
  18module_param(nforce_wa, bool, 0444);
  19MODULE_PARM_DESC(nforce_wa, "Apply NForce chipset workaround "
  20                "(expect bad sound)");
  21
  22#define DMIX_WANTS_S16  1
  23
  24/*
  25 * Call snd_pcm_period_elapsed in a tasklet
  26 * This avoids spinlock messes and long-running irq contexts
  27 */
  28static void pcsp_call_pcm_elapsed(unsigned long priv)
  29{
  30        if (atomic_read(&pcsp_chip.timer_active)) {
  31                struct snd_pcm_substream *substream;
  32                substream = pcsp_chip.playback_substream;
  33                if (substream)
  34                        snd_pcm_period_elapsed(substream);
  35        }
  36}
  37
  38static DECLARE_TASKLET(pcsp_pcm_tasklet, pcsp_call_pcm_elapsed, 0);
  39
  40/* write the port and returns the next expire time in ns;
  41 * called at the trigger-start and in hrtimer callback
  42 */
  43static u64 pcsp_timer_update(struct snd_pcsp *chip)
  44{
  45        unsigned char timer_cnt, val;
  46        u64 ns;
  47        struct snd_pcm_substream *substream;
  48        struct snd_pcm_runtime *runtime;
  49        unsigned long flags;
  50
  51        if (chip->thalf) {
  52                outb(chip->val61, 0x61);
  53                chip->thalf = 0;
  54                return chip->ns_rem;
  55        }
  56
  57        substream = chip->playback_substream;
  58        if (!substream)
  59                return 0;
  60
  61        runtime = substream->runtime;
  62        /* assume it is mono! */
  63        val = runtime->dma_area[chip->playback_ptr + chip->fmt_size - 1];
  64        if (chip->is_signed)
  65                val ^= 0x80;
  66        timer_cnt = val * CUR_DIV() / 256;
  67
  68        if (timer_cnt && chip->enable) {
  69                raw_spin_lock_irqsave(&i8253_lock, flags);
  70                if (!nforce_wa) {
  71                        outb_p(chip->val61, 0x61);
  72                        outb_p(timer_cnt, 0x42);
  73                        outb(chip->val61 ^ 1, 0x61);
  74                } else {
  75                        outb(chip->val61 ^ 2, 0x61);
  76                        chip->thalf = 1;
  77                }
  78                raw_spin_unlock_irqrestore(&i8253_lock, flags);
  79        }
  80
  81        chip->ns_rem = PCSP_PERIOD_NS();
  82        ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem);
  83        chip->ns_rem -= ns;
  84        return ns;
  85}
  86
  87static void pcsp_pointer_update(struct snd_pcsp *chip)
  88{
  89        struct snd_pcm_substream *substream;
  90        size_t period_bytes, buffer_bytes;
  91        int periods_elapsed;
  92        unsigned long flags;
  93
  94        /* update the playback position */
  95        substream = chip->playback_substream;
  96        if (!substream)
  97                return;
  98
  99        period_bytes = snd_pcm_lib_period_bytes(substream);
 100        buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
 101
 102        spin_lock_irqsave(&chip->substream_lock, flags);
 103        chip->playback_ptr += PCSP_INDEX_INC() * chip->fmt_size;
 104        periods_elapsed = chip->playback_ptr - chip->period_ptr;
 105        if (periods_elapsed < 0) {
 106#if PCSP_DEBUG
 107                printk(KERN_INFO "PCSP: buffer_bytes mod period_bytes != 0 ? "
 108                        "(%zi %zi %zi)\n",
 109                        chip->playback_ptr, period_bytes, buffer_bytes);
 110#endif
 111                periods_elapsed += buffer_bytes;
 112        }
 113        periods_elapsed /= period_bytes;
 114        /* wrap the pointer _before_ calling snd_pcm_period_elapsed(),
 115         * or ALSA will BUG on us. */
 116        chip->playback_ptr %= buffer_bytes;
 117
 118        if (periods_elapsed) {
 119                chip->period_ptr += periods_elapsed * period_bytes;
 120                chip->period_ptr %= buffer_bytes;
 121        }
 122        spin_unlock_irqrestore(&chip->substream_lock, flags);
 123
 124        if (periods_elapsed)
 125                tasklet_schedule(&pcsp_pcm_tasklet);
 126}
 127
 128enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle)
 129{
 130        struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer);
 131        int pointer_update;
 132        u64 ns;
 133
 134        if (!atomic_read(&chip->timer_active) || !chip->playback_substream)
 135                return HRTIMER_NORESTART;
 136
 137        pointer_update = !chip->thalf;
 138        ns = pcsp_timer_update(chip);
 139        if (!ns) {
 140                printk(KERN_WARNING "PCSP: unexpected stop\n");
 141                return HRTIMER_NORESTART;
 142        }
 143
 144        if (pointer_update)
 145                pcsp_pointer_update(chip);
 146
 147        hrtimer_forward(handle, hrtimer_get_expires(handle), ns_to_ktime(ns));
 148
 149        return HRTIMER_RESTART;
 150}
 151
 152static int pcsp_start_playing(struct snd_pcsp *chip)
 153{
 154#if PCSP_DEBUG
 155        printk(KERN_INFO "PCSP: start_playing called\n");
 156#endif
 157        if (atomic_read(&chip->timer_active)) {
 158                printk(KERN_ERR "PCSP: Timer already active\n");
 159                return -EIO;
 160        }
 161
 162        raw_spin_lock(&i8253_lock);
 163        chip->val61 = inb(0x61) | 0x03;
 164        outb_p(0x92, 0x43);     /* binary, mode 1, LSB only, ch 2 */
 165        raw_spin_unlock(&i8253_lock);
 166        atomic_set(&chip->timer_active, 1);
 167        chip->thalf = 0;
 168
 169        hrtimer_start(&pcsp_chip.timer, ktime_set(0, 0), HRTIMER_MODE_REL);
 170        return 0;
 171}
 172
 173static void pcsp_stop_playing(struct snd_pcsp *chip)
 174{
 175#if PCSP_DEBUG
 176        printk(KERN_INFO "PCSP: stop_playing called\n");
 177#endif
 178        if (!atomic_read(&chip->timer_active))
 179                return;
 180
 181        atomic_set(&chip->timer_active, 0);
 182        raw_spin_lock(&i8253_lock);
 183        /* restore the timer */
 184        outb_p(0xb6, 0x43);     /* binary, mode 3, LSB/MSB, ch 2 */
 185        outb(chip->val61 & 0xFC, 0x61);
 186        raw_spin_unlock(&i8253_lock);
 187}
 188
 189/*
 190 * Force to stop and sync the stream
 191 */
 192void pcsp_sync_stop(struct snd_pcsp *chip)
 193{
 194        local_irq_disable();
 195        pcsp_stop_playing(chip);
 196        local_irq_enable();
 197        hrtimer_cancel(&chip->timer);
 198        tasklet_kill(&pcsp_pcm_tasklet);
 199}
 200
 201static int snd_pcsp_playback_close(struct snd_pcm_substream *substream)
 202{
 203        struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
 204#if PCSP_DEBUG
 205        printk(KERN_INFO "PCSP: close called\n");
 206#endif
 207        pcsp_sync_stop(chip);
 208        chip->playback_substream = NULL;
 209        return 0;
 210}
 211
 212static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream,
 213                                       struct snd_pcm_hw_params *hw_params)
 214{
 215        struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
 216        int err;
 217        pcsp_sync_stop(chip);
 218        err = snd_pcm_lib_malloc_pages(substream,
 219                                      params_buffer_bytes(hw_params));
 220        if (err < 0)
 221                return err;
 222        return 0;
 223}
 224
 225static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream)
 226{
 227        struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
 228#if PCSP_DEBUG
 229        printk(KERN_INFO "PCSP: hw_free called\n");
 230#endif
 231        pcsp_sync_stop(chip);
 232        return snd_pcm_lib_free_pages(substream);
 233}
 234
 235static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream)
 236{
 237        struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
 238        pcsp_sync_stop(chip);
 239        chip->playback_ptr = 0;
 240        chip->period_ptr = 0;
 241        chip->fmt_size =
 242                snd_pcm_format_physical_width(substream->runtime->format) >> 3;
 243        chip->is_signed = snd_pcm_format_signed(substream->runtime->format);
 244#if PCSP_DEBUG
 245        printk(KERN_INFO "PCSP: prepare called, "
 246                        "size=%zi psize=%zi f=%zi f1=%i fsize=%i\n",
 247                        snd_pcm_lib_buffer_bytes(substream),
 248                        snd_pcm_lib_period_bytes(substream),
 249                        snd_pcm_lib_buffer_bytes(substream) /
 250                        snd_pcm_lib_period_bytes(substream),
 251                        substream->runtime->periods,
 252                        chip->fmt_size);
 253#endif
 254        return 0;
 255}
 256
 257static int snd_pcsp_trigger(struct snd_pcm_substream *substream, int cmd)
 258{
 259        struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
 260#if PCSP_DEBUG
 261        printk(KERN_INFO "PCSP: trigger called\n");
 262#endif
 263        switch (cmd) {
 264        case SNDRV_PCM_TRIGGER_START:
 265        case SNDRV_PCM_TRIGGER_RESUME:
 266                return pcsp_start_playing(chip);
 267        case SNDRV_PCM_TRIGGER_STOP:
 268        case SNDRV_PCM_TRIGGER_SUSPEND:
 269                pcsp_stop_playing(chip);
 270                break;
 271        default:
 272                return -EINVAL;
 273        }
 274        return 0;
 275}
 276
 277static snd_pcm_uframes_t snd_pcsp_playback_pointer(struct snd_pcm_substream
 278                                                   *substream)
 279{
 280        struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
 281        unsigned int pos;
 282        spin_lock(&chip->substream_lock);
 283        pos = chip->playback_ptr;
 284        spin_unlock(&chip->substream_lock);
 285        return bytes_to_frames(substream->runtime, pos);
 286}
 287
 288static struct snd_pcm_hardware snd_pcsp_playback = {
 289        .info = (SNDRV_PCM_INFO_INTERLEAVED |
 290                 SNDRV_PCM_INFO_HALF_DUPLEX |
 291                 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
 292        .formats = (SNDRV_PCM_FMTBIT_U8
 293#if DMIX_WANTS_S16
 294                    | SNDRV_PCM_FMTBIT_S16_LE
 295#endif
 296            ),
 297        .rates = SNDRV_PCM_RATE_KNOT,
 298        .rate_min = PCSP_DEFAULT_SRATE,
 299        .rate_max = PCSP_DEFAULT_SRATE,
 300        .channels_min = 1,
 301        .channels_max = 1,
 302        .buffer_bytes_max = PCSP_BUFFER_SIZE,
 303        .period_bytes_min = 64,
 304        .period_bytes_max = PCSP_MAX_PERIOD_SIZE,
 305        .periods_min = 2,
 306        .periods_max = PCSP_MAX_PERIODS,
 307        .fifo_size = 0,
 308};
 309
 310static int snd_pcsp_playback_open(struct snd_pcm_substream *substream)
 311{
 312        struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
 313        struct snd_pcm_runtime *runtime = substream->runtime;
 314#if PCSP_DEBUG
 315        printk(KERN_INFO "PCSP: open called\n");
 316#endif
 317        if (atomic_read(&chip->timer_active)) {
 318                printk(KERN_ERR "PCSP: still active!!\n");
 319                return -EBUSY;
 320        }
 321        runtime->hw = snd_pcsp_playback;
 322        chip->playback_substream = substream;
 323        return 0;
 324}
 325
 326static struct snd_pcm_ops snd_pcsp_playback_ops = {
 327        .open = snd_pcsp_playback_open,
 328        .close = snd_pcsp_playback_close,
 329        .ioctl = snd_pcm_lib_ioctl,
 330        .hw_params = snd_pcsp_playback_hw_params,
 331        .hw_free = snd_pcsp_playback_hw_free,
 332        .prepare = snd_pcsp_playback_prepare,
 333        .trigger = snd_pcsp_trigger,
 334        .pointer = snd_pcsp_playback_pointer,
 335};
 336
 337int snd_pcsp_new_pcm(struct snd_pcsp *chip)
 338{
 339        int err;
 340
 341        err = snd_pcm_new(chip->card, "pcspeaker", 0, 1, 0, &chip->pcm);
 342        if (err < 0)
 343                return err;
 344
 345        snd_pcm_set_ops(chip->pcm, SNDRV_PCM_STREAM_PLAYBACK,
 346                        &snd_pcsp_playback_ops);
 347
 348        chip->pcm->private_data = chip;
 349        chip->pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
 350        strcpy(chip->pcm->name, "pcsp");
 351
 352        snd_pcm_lib_preallocate_pages_for_all(chip->pcm,
 353                                              SNDRV_DMA_TYPE_CONTINUOUS,
 354                                              snd_dma_continuous_data
 355                                              (GFP_KERNEL), PCSP_BUFFER_SIZE,
 356                                              PCSP_BUFFER_SIZE);
 357
 358        return 0;
 359}
 360