root/sound/drivers/pcsp/pcsp_lib.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. pcsp_call_pcm_elapsed
  2. pcsp_timer_update
  3. pcsp_pointer_update
  4. pcsp_do_timer
  5. pcsp_start_playing
  6. pcsp_stop_playing
  7. pcsp_sync_stop
  8. snd_pcsp_playback_close
  9. snd_pcsp_playback_hw_params
  10. snd_pcsp_playback_hw_free
  11. snd_pcsp_playback_prepare
  12. snd_pcsp_trigger
  13. snd_pcsp_playback_pointer
  14. snd_pcsp_playback_open
  15. snd_pcsp_new_pcm

   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 
  18 static bool nforce_wa;
  19 module_param(nforce_wa, bool, 0444);
  20 MODULE_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  */
  29 static 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 
  39 static 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  */
  44 static 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 
  88 static 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 
 129 enum 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 
 153 static 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 
 174 static 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  */
 193 void 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 
 202 static 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 
 213 static 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 
 226 static 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 
 236 static 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 
 258 static 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 
 278 static 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 
 289 static 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 
 311 static 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 
 327 static 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 
 338 int 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 }

/* [<][>][^][v][top][bottom][index][help] */