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 work
  27 * This avoids spinlock messes and long-running irq contexts
  28 */
  29static void pcsp_call_pcm_elapsed(struct work_struct *work)
  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_WORK(pcsp_pcm_work, 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                queue_work(system_highpri_wq, &pcsp_pcm_work);
 123        }
 124        spin_unlock_irqrestore(&chip->substream_lock, flags);
 125}
 126
 127enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle)
 128{
 129        struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer);
 130        int pointer_update;
 131        u64 ns;
 132
 133        if (!atomic_read(&chip->timer_active) || !chip->playback_substream)
 134                return HRTIMER_NORESTART;
 135
 136        pointer_update = !chip->thalf;
 137        ns = pcsp_timer_update(chip);
 138        if (!ns) {
 139                printk(KERN_WARNING "PCSP: unexpected stop\n");
 140                return HRTIMER_NORESTART;
 141        }
 142
 143        if (pointer_update)
 144                pcsp_pointer_update(chip);
 145
 146        hrtimer_forward(handle, hrtimer_get_expires(handle), ns_to_ktime(ns));
 147
 148        return HRTIMER_RESTART;
 149}
 150
 151static int pcsp_start_playing(struct snd_pcsp *chip)
 152{
 153#if PCSP_DEBUG
 154        printk(KERN_INFO "PCSP: start_playing called\n");
 155#endif
 156        if (atomic_read(&chip->timer_active)) {
 157                printk(KERN_ERR "PCSP: Timer already active\n");
 158                return -EIO;
 159        }
 160
 161        raw_spin_lock(&i8253_lock);
 162        chip->val61 = inb(0x61) | 0x03;
 163        outb_p(0x92, 0x43);     /* binary, mode 1, LSB only, ch 2 */
 164        raw_spin_unlock(&i8253_lock);
 165        atomic_set(&chip->timer_active, 1);
 166        chip->thalf = 0;
 167
 168        hrtimer_start(&pcsp_chip.timer, 0, HRTIMER_MODE_REL);
 169        return 0;
 170}
 171
 172static void pcsp_stop_playing(struct snd_pcsp *chip)
 173{
 174#if PCSP_DEBUG
 175        printk(KERN_INFO "PCSP: stop_playing called\n");
 176#endif
 177        if (!atomic_read(&chip->timer_active))
 178                return;
 179
 180        atomic_set(&chip->timer_active, 0);
 181        raw_spin_lock(&i8253_lock);
 182        /* restore the timer */
 183        outb_p(0xb6, 0x43);     /* binary, mode 3, LSB/MSB, ch 2 */
 184        outb(chip->val61 & 0xFC, 0x61);
 185        raw_spin_unlock(&i8253_lock);
 186}
 187
 188/*
 189 * Force to stop and sync the stream
 190 */
 191void pcsp_sync_stop(struct snd_pcsp *chip)
 192{
 193        local_irq_disable();
 194        pcsp_stop_playing(chip);
 195        local_irq_enable();
 196        hrtimer_cancel(&chip->timer);
 197        cancel_work_sync(&pcsp_pcm_work);
 198}
 199
 200static int snd_pcsp_playback_close(struct snd_pcm_substream *substream)
 201{
 202        struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
 203#if PCSP_DEBUG
 204        printk(KERN_INFO "PCSP: close called\n");
 205#endif
 206        pcsp_sync_stop(chip);
 207        chip->playback_substream = NULL;
 208        return 0;
 209}
 210
 211static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream,
 212                                       struct snd_pcm_hw_params *hw_params)
 213{
 214        struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
 215        pcsp_sync_stop(chip);
 216        return 0;
 217}
 218
 219static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream)
 220{
 221        struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
 222#if PCSP_DEBUG
 223        printk(KERN_INFO "PCSP: hw_free called\n");
 224#endif
 225        pcsp_sync_stop(chip);
 226        return 0;
 227}
 228
 229static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream)
 230{
 231        struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
 232        pcsp_sync_stop(chip);
 233        chip->playback_ptr = 0;
 234        chip->period_ptr = 0;
 235        chip->fmt_size =
 236                snd_pcm_format_physical_width(substream->runtime->format) >> 3;
 237        chip->is_signed = snd_pcm_format_signed(substream->runtime->format);
 238#if PCSP_DEBUG
 239        printk(KERN_INFO "PCSP: prepare called, "
 240                        "size=%zi psize=%zi f=%zi f1=%i fsize=%i\n",
 241                        snd_pcm_lib_buffer_bytes(substream),
 242                        snd_pcm_lib_period_bytes(substream),
 243                        snd_pcm_lib_buffer_bytes(substream) /
 244                        snd_pcm_lib_period_bytes(substream),
 245                        substream->runtime->periods,
 246                        chip->fmt_size);
 247#endif
 248        return 0;
 249}
 250
 251static int snd_pcsp_trigger(struct snd_pcm_substream *substream, int cmd)
 252{
 253        struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
 254#if PCSP_DEBUG
 255        printk(KERN_INFO "PCSP: trigger called\n");
 256#endif
 257        switch (cmd) {
 258        case SNDRV_PCM_TRIGGER_START:
 259        case SNDRV_PCM_TRIGGER_RESUME:
 260                return pcsp_start_playing(chip);
 261        case SNDRV_PCM_TRIGGER_STOP:
 262        case SNDRV_PCM_TRIGGER_SUSPEND:
 263                pcsp_stop_playing(chip);
 264                break;
 265        default:
 266                return -EINVAL;
 267        }
 268        return 0;
 269}
 270
 271static snd_pcm_uframes_t snd_pcsp_playback_pointer(struct snd_pcm_substream
 272                                                   *substream)
 273{
 274        struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
 275        unsigned int pos;
 276        spin_lock(&chip->substream_lock);
 277        pos = chip->playback_ptr;
 278        spin_unlock(&chip->substream_lock);
 279        return bytes_to_frames(substream->runtime, pos);
 280}
 281
 282static const struct snd_pcm_hardware snd_pcsp_playback = {
 283        .info = (SNDRV_PCM_INFO_INTERLEAVED |
 284                 SNDRV_PCM_INFO_HALF_DUPLEX |
 285                 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
 286        .formats = (SNDRV_PCM_FMTBIT_U8
 287#if DMIX_WANTS_S16
 288                    | SNDRV_PCM_FMTBIT_S16_LE
 289#endif
 290            ),
 291        .rates = SNDRV_PCM_RATE_KNOT,
 292        .rate_min = PCSP_DEFAULT_SRATE,
 293        .rate_max = PCSP_DEFAULT_SRATE,
 294        .channels_min = 1,
 295        .channels_max = 1,
 296        .buffer_bytes_max = PCSP_BUFFER_SIZE,
 297        .period_bytes_min = 64,
 298        .period_bytes_max = PCSP_MAX_PERIOD_SIZE,
 299        .periods_min = 2,
 300        .periods_max = PCSP_MAX_PERIODS,
 301        .fifo_size = 0,
 302};
 303
 304static int snd_pcsp_playback_open(struct snd_pcm_substream *substream)
 305{
 306        struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
 307        struct snd_pcm_runtime *runtime = substream->runtime;
 308#if PCSP_DEBUG
 309        printk(KERN_INFO "PCSP: open called\n");
 310#endif
 311        if (atomic_read(&chip->timer_active)) {
 312                printk(KERN_ERR "PCSP: still active!!\n");
 313                return -EBUSY;
 314        }
 315        runtime->hw = snd_pcsp_playback;
 316        chip->playback_substream = substream;
 317        return 0;
 318}
 319
 320static const struct snd_pcm_ops snd_pcsp_playback_ops = {
 321        .open = snd_pcsp_playback_open,
 322        .close = snd_pcsp_playback_close,
 323        .hw_params = snd_pcsp_playback_hw_params,
 324        .hw_free = snd_pcsp_playback_hw_free,
 325        .prepare = snd_pcsp_playback_prepare,
 326        .trigger = snd_pcsp_trigger,
 327        .pointer = snd_pcsp_playback_pointer,
 328};
 329
 330int snd_pcsp_new_pcm(struct snd_pcsp *chip)
 331{
 332        int err;
 333
 334        err = snd_pcm_new(chip->card, "pcspeaker", 0, 1, 0, &chip->pcm);
 335        if (err < 0)
 336                return err;
 337
 338        snd_pcm_set_ops(chip->pcm, SNDRV_PCM_STREAM_PLAYBACK,
 339                        &snd_pcsp_playback_ops);
 340
 341        chip->pcm->private_data = chip;
 342        chip->pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
 343        strcpy(chip->pcm->name, "pcsp");
 344
 345        snd_pcm_set_managed_buffer_all(chip->pcm,
 346                                       SNDRV_DMA_TYPE_CONTINUOUS,
 347                                       NULL,
 348                                       PCSP_BUFFER_SIZE,
 349                                       PCSP_BUFFER_SIZE);
 350
 351        return 0;
 352}
 353