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