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_OLD(pcsp_pcm_tasklet, pcsp_call_pcm_elapsed);
  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        pcsp_sync_stop(chip);
 218        return 0;
 219}
 220
 221static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream)
 222{
 223        struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
 224#if PCSP_DEBUG
 225        printk(KERN_INFO "PCSP: hw_free called\n");
 226#endif
 227        pcsp_sync_stop(chip);
 228        return 0;
 229}
 230
 231static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream)
 232{
 233        struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
 234        pcsp_sync_stop(chip);
 235        chip->playback_ptr = 0;
 236        chip->period_ptr = 0;
 237        chip->fmt_size =
 238                snd_pcm_format_physical_width(substream->runtime->format) >> 3;
 239        chip->is_signed = snd_pcm_format_signed(substream->runtime->format);
 240#if PCSP_DEBUG
 241        printk(KERN_INFO "PCSP: prepare called, "
 242                        "size=%zi psize=%zi f=%zi f1=%i fsize=%i\n",
 243                        snd_pcm_lib_buffer_bytes(substream),
 244                        snd_pcm_lib_period_bytes(substream),
 245                        snd_pcm_lib_buffer_bytes(substream) /
 246                        snd_pcm_lib_period_bytes(substream),
 247                        substream->runtime->periods,
 248                        chip->fmt_size);
 249#endif
 250        return 0;
 251}
 252
 253static int snd_pcsp_trigger(struct snd_pcm_substream *substream, int cmd)
 254{
 255        struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
 256#if PCSP_DEBUG
 257        printk(KERN_INFO "PCSP: trigger called\n");
 258#endif
 259        switch (cmd) {
 260        case SNDRV_PCM_TRIGGER_START:
 261        case SNDRV_PCM_TRIGGER_RESUME:
 262                return pcsp_start_playing(chip);
 263        case SNDRV_PCM_TRIGGER_STOP:
 264        case SNDRV_PCM_TRIGGER_SUSPEND:
 265                pcsp_stop_playing(chip);
 266                break;
 267        default:
 268                return -EINVAL;
 269        }
 270        return 0;
 271}
 272
 273static snd_pcm_uframes_t snd_pcsp_playback_pointer(struct snd_pcm_substream
 274                                                   *substream)
 275{
 276        struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
 277        unsigned int pos;
 278        spin_lock(&chip->substream_lock);
 279        pos = chip->playback_ptr;
 280        spin_unlock(&chip->substream_lock);
 281        return bytes_to_frames(substream->runtime, pos);
 282}
 283
 284static const struct snd_pcm_hardware snd_pcsp_playback = {
 285        .info = (SNDRV_PCM_INFO_INTERLEAVED |
 286                 SNDRV_PCM_INFO_HALF_DUPLEX |
 287                 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
 288        .formats = (SNDRV_PCM_FMTBIT_U8
 289#if DMIX_WANTS_S16
 290                    | SNDRV_PCM_FMTBIT_S16_LE
 291#endif
 292            ),
 293        .rates = SNDRV_PCM_RATE_KNOT,
 294        .rate_min = PCSP_DEFAULT_SRATE,
 295        .rate_max = PCSP_DEFAULT_SRATE,
 296        .channels_min = 1,
 297        .channels_max = 1,
 298        .buffer_bytes_max = PCSP_BUFFER_SIZE,
 299        .period_bytes_min = 64,
 300        .period_bytes_max = PCSP_MAX_PERIOD_SIZE,
 301        .periods_min = 2,
 302        .periods_max = PCSP_MAX_PERIODS,
 303        .fifo_size = 0,
 304};
 305
 306static int snd_pcsp_playback_open(struct snd_pcm_substream *substream)
 307{
 308        struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
 309        struct snd_pcm_runtime *runtime = substream->runtime;
 310#if PCSP_DEBUG
 311        printk(KERN_INFO "PCSP: open called\n");
 312#endif
 313        if (atomic_read(&chip->timer_active)) {
 314                printk(KERN_ERR "PCSP: still active!!\n");
 315                return -EBUSY;
 316        }
 317        runtime->hw = snd_pcsp_playback;
 318        chip->playback_substream = substream;
 319        return 0;
 320}
 321
 322static const struct snd_pcm_ops snd_pcsp_playback_ops = {
 323        .open = snd_pcsp_playback_open,
 324        .close = snd_pcsp_playback_close,
 325        .hw_params = snd_pcsp_playback_hw_params,
 326        .hw_free = snd_pcsp_playback_hw_free,
 327        .prepare = snd_pcsp_playback_prepare,
 328        .trigger = snd_pcsp_trigger,
 329        .pointer = snd_pcsp_playback_pointer,
 330};
 331
 332int snd_pcsp_new_pcm(struct snd_pcsp *chip)
 333{
 334        int err;
 335
 336        err = snd_pcm_new(chip->card, "pcspeaker", 0, 1, 0, &chip->pcm);
 337        if (err < 0)
 338                return err;
 339
 340        snd_pcm_set_ops(chip->pcm, SNDRV_PCM_STREAM_PLAYBACK,
 341                        &snd_pcsp_playback_ops);
 342
 343        chip->pcm->private_data = chip;
 344        chip->pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
 345        strcpy(chip->pcm->name, "pcsp");
 346
 347        snd_pcm_set_managed_buffer_all(chip->pcm,
 348                                       SNDRV_DMA_TYPE_CONTINUOUS,
 349                                       NULL,
 350                                       PCSP_BUFFER_SIZE,
 351                                       PCSP_BUFFER_SIZE);
 352
 353        return 0;
 354}
 355