162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * PC-Speaker driver for Linux 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 1993-1997 Michael Beck 662306a36Sopenharmony_ci * Copyright (C) 1997-2001 David Woodhouse 762306a36Sopenharmony_ci * Copyright (C) 2001-2008 Stas Sergeev 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci#include <linux/gfp.h> 1262306a36Sopenharmony_ci#include <linux/moduleparam.h> 1362306a36Sopenharmony_ci#include <linux/interrupt.h> 1462306a36Sopenharmony_ci#include <linux/io.h> 1562306a36Sopenharmony_ci#include <sound/pcm.h> 1662306a36Sopenharmony_ci#include "pcsp.h" 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_cistatic bool nforce_wa; 1962306a36Sopenharmony_cimodule_param(nforce_wa, bool, 0444); 2062306a36Sopenharmony_ciMODULE_PARM_DESC(nforce_wa, "Apply NForce chipset workaround " 2162306a36Sopenharmony_ci "(expect bad sound)"); 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#define DMIX_WANTS_S16 1 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci/* 2662306a36Sopenharmony_ci * Call snd_pcm_period_elapsed in a work 2762306a36Sopenharmony_ci * This avoids spinlock messes and long-running irq contexts 2862306a36Sopenharmony_ci */ 2962306a36Sopenharmony_cistatic void pcsp_call_pcm_elapsed(struct work_struct *work) 3062306a36Sopenharmony_ci{ 3162306a36Sopenharmony_ci if (atomic_read(&pcsp_chip.timer_active)) { 3262306a36Sopenharmony_ci struct snd_pcm_substream *substream; 3362306a36Sopenharmony_ci substream = pcsp_chip.playback_substream; 3462306a36Sopenharmony_ci if (substream) 3562306a36Sopenharmony_ci snd_pcm_period_elapsed(substream); 3662306a36Sopenharmony_ci } 3762306a36Sopenharmony_ci} 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistatic DECLARE_WORK(pcsp_pcm_work, pcsp_call_pcm_elapsed); 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci/* write the port and returns the next expire time in ns; 4262306a36Sopenharmony_ci * called at the trigger-start and in hrtimer callback 4362306a36Sopenharmony_ci */ 4462306a36Sopenharmony_cistatic u64 pcsp_timer_update(struct snd_pcsp *chip) 4562306a36Sopenharmony_ci{ 4662306a36Sopenharmony_ci unsigned char timer_cnt, val; 4762306a36Sopenharmony_ci u64 ns; 4862306a36Sopenharmony_ci struct snd_pcm_substream *substream; 4962306a36Sopenharmony_ci struct snd_pcm_runtime *runtime; 5062306a36Sopenharmony_ci unsigned long flags; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci if (chip->thalf) { 5362306a36Sopenharmony_ci outb(chip->val61, 0x61); 5462306a36Sopenharmony_ci chip->thalf = 0; 5562306a36Sopenharmony_ci return chip->ns_rem; 5662306a36Sopenharmony_ci } 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci substream = chip->playback_substream; 5962306a36Sopenharmony_ci if (!substream) 6062306a36Sopenharmony_ci return 0; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci runtime = substream->runtime; 6362306a36Sopenharmony_ci /* assume it is mono! */ 6462306a36Sopenharmony_ci val = runtime->dma_area[chip->playback_ptr + chip->fmt_size - 1]; 6562306a36Sopenharmony_ci if (chip->is_signed) 6662306a36Sopenharmony_ci val ^= 0x80; 6762306a36Sopenharmony_ci timer_cnt = val * CUR_DIV() / 256; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci if (timer_cnt && chip->enable) { 7062306a36Sopenharmony_ci raw_spin_lock_irqsave(&i8253_lock, flags); 7162306a36Sopenharmony_ci if (!nforce_wa) { 7262306a36Sopenharmony_ci outb_p(chip->val61, 0x61); 7362306a36Sopenharmony_ci outb_p(timer_cnt, 0x42); 7462306a36Sopenharmony_ci outb(chip->val61 ^ 1, 0x61); 7562306a36Sopenharmony_ci } else { 7662306a36Sopenharmony_ci outb(chip->val61 ^ 2, 0x61); 7762306a36Sopenharmony_ci chip->thalf = 1; 7862306a36Sopenharmony_ci } 7962306a36Sopenharmony_ci raw_spin_unlock_irqrestore(&i8253_lock, flags); 8062306a36Sopenharmony_ci } 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci chip->ns_rem = PCSP_PERIOD_NS(); 8362306a36Sopenharmony_ci ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem); 8462306a36Sopenharmony_ci chip->ns_rem -= ns; 8562306a36Sopenharmony_ci return ns; 8662306a36Sopenharmony_ci} 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistatic void pcsp_pointer_update(struct snd_pcsp *chip) 8962306a36Sopenharmony_ci{ 9062306a36Sopenharmony_ci struct snd_pcm_substream *substream; 9162306a36Sopenharmony_ci size_t period_bytes, buffer_bytes; 9262306a36Sopenharmony_ci int periods_elapsed; 9362306a36Sopenharmony_ci unsigned long flags; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci /* update the playback position */ 9662306a36Sopenharmony_ci substream = chip->playback_substream; 9762306a36Sopenharmony_ci if (!substream) 9862306a36Sopenharmony_ci return; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci period_bytes = snd_pcm_lib_period_bytes(substream); 10162306a36Sopenharmony_ci buffer_bytes = snd_pcm_lib_buffer_bytes(substream); 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci spin_lock_irqsave(&chip->substream_lock, flags); 10462306a36Sopenharmony_ci chip->playback_ptr += PCSP_INDEX_INC() * chip->fmt_size; 10562306a36Sopenharmony_ci periods_elapsed = chip->playback_ptr - chip->period_ptr; 10662306a36Sopenharmony_ci if (periods_elapsed < 0) { 10762306a36Sopenharmony_ci#if PCSP_DEBUG 10862306a36Sopenharmony_ci printk(KERN_INFO "PCSP: buffer_bytes mod period_bytes != 0 ? " 10962306a36Sopenharmony_ci "(%zi %zi %zi)\n", 11062306a36Sopenharmony_ci chip->playback_ptr, period_bytes, buffer_bytes); 11162306a36Sopenharmony_ci#endif 11262306a36Sopenharmony_ci periods_elapsed += buffer_bytes; 11362306a36Sopenharmony_ci } 11462306a36Sopenharmony_ci periods_elapsed /= period_bytes; 11562306a36Sopenharmony_ci /* wrap the pointer _before_ calling snd_pcm_period_elapsed(), 11662306a36Sopenharmony_ci * or ALSA will BUG on us. */ 11762306a36Sopenharmony_ci chip->playback_ptr %= buffer_bytes; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci if (periods_elapsed) { 12062306a36Sopenharmony_ci chip->period_ptr += periods_elapsed * period_bytes; 12162306a36Sopenharmony_ci chip->period_ptr %= buffer_bytes; 12262306a36Sopenharmony_ci queue_work(system_highpri_wq, &pcsp_pcm_work); 12362306a36Sopenharmony_ci } 12462306a36Sopenharmony_ci spin_unlock_irqrestore(&chip->substream_lock, flags); 12562306a36Sopenharmony_ci} 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_cienum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer); 13062306a36Sopenharmony_ci int pointer_update; 13162306a36Sopenharmony_ci u64 ns; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci if (!atomic_read(&chip->timer_active) || !chip->playback_substream) 13462306a36Sopenharmony_ci return HRTIMER_NORESTART; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci pointer_update = !chip->thalf; 13762306a36Sopenharmony_ci ns = pcsp_timer_update(chip); 13862306a36Sopenharmony_ci if (!ns) { 13962306a36Sopenharmony_ci printk(KERN_WARNING "PCSP: unexpected stop\n"); 14062306a36Sopenharmony_ci return HRTIMER_NORESTART; 14162306a36Sopenharmony_ci } 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci if (pointer_update) 14462306a36Sopenharmony_ci pcsp_pointer_update(chip); 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci hrtimer_forward_now(handle, ns_to_ktime(ns)); 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci return HRTIMER_RESTART; 14962306a36Sopenharmony_ci} 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_cistatic int pcsp_start_playing(struct snd_pcsp *chip) 15262306a36Sopenharmony_ci{ 15362306a36Sopenharmony_ci#if PCSP_DEBUG 15462306a36Sopenharmony_ci printk(KERN_INFO "PCSP: start_playing called\n"); 15562306a36Sopenharmony_ci#endif 15662306a36Sopenharmony_ci if (atomic_read(&chip->timer_active)) { 15762306a36Sopenharmony_ci printk(KERN_ERR "PCSP: Timer already active\n"); 15862306a36Sopenharmony_ci return -EIO; 15962306a36Sopenharmony_ci } 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci raw_spin_lock(&i8253_lock); 16262306a36Sopenharmony_ci chip->val61 = inb(0x61) | 0x03; 16362306a36Sopenharmony_ci outb_p(0x92, 0x43); /* binary, mode 1, LSB only, ch 2 */ 16462306a36Sopenharmony_ci raw_spin_unlock(&i8253_lock); 16562306a36Sopenharmony_ci atomic_set(&chip->timer_active, 1); 16662306a36Sopenharmony_ci chip->thalf = 0; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci hrtimer_start(&pcsp_chip.timer, 0, HRTIMER_MODE_REL); 16962306a36Sopenharmony_ci return 0; 17062306a36Sopenharmony_ci} 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_cistatic void pcsp_stop_playing(struct snd_pcsp *chip) 17362306a36Sopenharmony_ci{ 17462306a36Sopenharmony_ci#if PCSP_DEBUG 17562306a36Sopenharmony_ci printk(KERN_INFO "PCSP: stop_playing called\n"); 17662306a36Sopenharmony_ci#endif 17762306a36Sopenharmony_ci if (!atomic_read(&chip->timer_active)) 17862306a36Sopenharmony_ci return; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci atomic_set(&chip->timer_active, 0); 18162306a36Sopenharmony_ci raw_spin_lock(&i8253_lock); 18262306a36Sopenharmony_ci /* restore the timer */ 18362306a36Sopenharmony_ci outb_p(0xb6, 0x43); /* binary, mode 3, LSB/MSB, ch 2 */ 18462306a36Sopenharmony_ci outb(chip->val61 & 0xFC, 0x61); 18562306a36Sopenharmony_ci raw_spin_unlock(&i8253_lock); 18662306a36Sopenharmony_ci} 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci/* 18962306a36Sopenharmony_ci * Force to stop and sync the stream 19062306a36Sopenharmony_ci */ 19162306a36Sopenharmony_civoid pcsp_sync_stop(struct snd_pcsp *chip) 19262306a36Sopenharmony_ci{ 19362306a36Sopenharmony_ci local_irq_disable(); 19462306a36Sopenharmony_ci pcsp_stop_playing(chip); 19562306a36Sopenharmony_ci local_irq_enable(); 19662306a36Sopenharmony_ci hrtimer_cancel(&chip->timer); 19762306a36Sopenharmony_ci cancel_work_sync(&pcsp_pcm_work); 19862306a36Sopenharmony_ci} 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_cistatic int snd_pcsp_playback_close(struct snd_pcm_substream *substream) 20162306a36Sopenharmony_ci{ 20262306a36Sopenharmony_ci struct snd_pcsp *chip = snd_pcm_substream_chip(substream); 20362306a36Sopenharmony_ci#if PCSP_DEBUG 20462306a36Sopenharmony_ci printk(KERN_INFO "PCSP: close called\n"); 20562306a36Sopenharmony_ci#endif 20662306a36Sopenharmony_ci pcsp_sync_stop(chip); 20762306a36Sopenharmony_ci chip->playback_substream = NULL; 20862306a36Sopenharmony_ci return 0; 20962306a36Sopenharmony_ci} 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_cistatic int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream, 21262306a36Sopenharmony_ci struct snd_pcm_hw_params *hw_params) 21362306a36Sopenharmony_ci{ 21462306a36Sopenharmony_ci struct snd_pcsp *chip = snd_pcm_substream_chip(substream); 21562306a36Sopenharmony_ci pcsp_sync_stop(chip); 21662306a36Sopenharmony_ci return 0; 21762306a36Sopenharmony_ci} 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_cistatic int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream) 22062306a36Sopenharmony_ci{ 22162306a36Sopenharmony_ci struct snd_pcsp *chip = snd_pcm_substream_chip(substream); 22262306a36Sopenharmony_ci#if PCSP_DEBUG 22362306a36Sopenharmony_ci printk(KERN_INFO "PCSP: hw_free called\n"); 22462306a36Sopenharmony_ci#endif 22562306a36Sopenharmony_ci pcsp_sync_stop(chip); 22662306a36Sopenharmony_ci return 0; 22762306a36Sopenharmony_ci} 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_cistatic int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream) 23062306a36Sopenharmony_ci{ 23162306a36Sopenharmony_ci struct snd_pcsp *chip = snd_pcm_substream_chip(substream); 23262306a36Sopenharmony_ci pcsp_sync_stop(chip); 23362306a36Sopenharmony_ci chip->playback_ptr = 0; 23462306a36Sopenharmony_ci chip->period_ptr = 0; 23562306a36Sopenharmony_ci chip->fmt_size = 23662306a36Sopenharmony_ci snd_pcm_format_physical_width(substream->runtime->format) >> 3; 23762306a36Sopenharmony_ci chip->is_signed = snd_pcm_format_signed(substream->runtime->format); 23862306a36Sopenharmony_ci#if PCSP_DEBUG 23962306a36Sopenharmony_ci printk(KERN_INFO "PCSP: prepare called, " 24062306a36Sopenharmony_ci "size=%zi psize=%zi f=%zi f1=%i fsize=%i\n", 24162306a36Sopenharmony_ci snd_pcm_lib_buffer_bytes(substream), 24262306a36Sopenharmony_ci snd_pcm_lib_period_bytes(substream), 24362306a36Sopenharmony_ci snd_pcm_lib_buffer_bytes(substream) / 24462306a36Sopenharmony_ci snd_pcm_lib_period_bytes(substream), 24562306a36Sopenharmony_ci substream->runtime->periods, 24662306a36Sopenharmony_ci chip->fmt_size); 24762306a36Sopenharmony_ci#endif 24862306a36Sopenharmony_ci return 0; 24962306a36Sopenharmony_ci} 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_cistatic int snd_pcsp_trigger(struct snd_pcm_substream *substream, int cmd) 25262306a36Sopenharmony_ci{ 25362306a36Sopenharmony_ci struct snd_pcsp *chip = snd_pcm_substream_chip(substream); 25462306a36Sopenharmony_ci#if PCSP_DEBUG 25562306a36Sopenharmony_ci printk(KERN_INFO "PCSP: trigger called\n"); 25662306a36Sopenharmony_ci#endif 25762306a36Sopenharmony_ci switch (cmd) { 25862306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_START: 25962306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_RESUME: 26062306a36Sopenharmony_ci return pcsp_start_playing(chip); 26162306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_STOP: 26262306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_SUSPEND: 26362306a36Sopenharmony_ci pcsp_stop_playing(chip); 26462306a36Sopenharmony_ci break; 26562306a36Sopenharmony_ci default: 26662306a36Sopenharmony_ci return -EINVAL; 26762306a36Sopenharmony_ci } 26862306a36Sopenharmony_ci return 0; 26962306a36Sopenharmony_ci} 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_cistatic snd_pcm_uframes_t snd_pcsp_playback_pointer(struct snd_pcm_substream 27262306a36Sopenharmony_ci *substream) 27362306a36Sopenharmony_ci{ 27462306a36Sopenharmony_ci struct snd_pcsp *chip = snd_pcm_substream_chip(substream); 27562306a36Sopenharmony_ci unsigned int pos; 27662306a36Sopenharmony_ci spin_lock(&chip->substream_lock); 27762306a36Sopenharmony_ci pos = chip->playback_ptr; 27862306a36Sopenharmony_ci spin_unlock(&chip->substream_lock); 27962306a36Sopenharmony_ci return bytes_to_frames(substream->runtime, pos); 28062306a36Sopenharmony_ci} 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_cistatic const struct snd_pcm_hardware snd_pcsp_playback = { 28362306a36Sopenharmony_ci .info = (SNDRV_PCM_INFO_INTERLEAVED | 28462306a36Sopenharmony_ci SNDRV_PCM_INFO_HALF_DUPLEX | 28562306a36Sopenharmony_ci SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID), 28662306a36Sopenharmony_ci .formats = (SNDRV_PCM_FMTBIT_U8 28762306a36Sopenharmony_ci#if DMIX_WANTS_S16 28862306a36Sopenharmony_ci | SNDRV_PCM_FMTBIT_S16_LE 28962306a36Sopenharmony_ci#endif 29062306a36Sopenharmony_ci ), 29162306a36Sopenharmony_ci .rates = SNDRV_PCM_RATE_KNOT, 29262306a36Sopenharmony_ci .rate_min = PCSP_DEFAULT_SRATE, 29362306a36Sopenharmony_ci .rate_max = PCSP_DEFAULT_SRATE, 29462306a36Sopenharmony_ci .channels_min = 1, 29562306a36Sopenharmony_ci .channels_max = 1, 29662306a36Sopenharmony_ci .buffer_bytes_max = PCSP_BUFFER_SIZE, 29762306a36Sopenharmony_ci .period_bytes_min = 64, 29862306a36Sopenharmony_ci .period_bytes_max = PCSP_MAX_PERIOD_SIZE, 29962306a36Sopenharmony_ci .periods_min = 2, 30062306a36Sopenharmony_ci .periods_max = PCSP_MAX_PERIODS, 30162306a36Sopenharmony_ci .fifo_size = 0, 30262306a36Sopenharmony_ci}; 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_cistatic int snd_pcsp_playback_open(struct snd_pcm_substream *substream) 30562306a36Sopenharmony_ci{ 30662306a36Sopenharmony_ci struct snd_pcsp *chip = snd_pcm_substream_chip(substream); 30762306a36Sopenharmony_ci struct snd_pcm_runtime *runtime = substream->runtime; 30862306a36Sopenharmony_ci#if PCSP_DEBUG 30962306a36Sopenharmony_ci printk(KERN_INFO "PCSP: open called\n"); 31062306a36Sopenharmony_ci#endif 31162306a36Sopenharmony_ci if (atomic_read(&chip->timer_active)) { 31262306a36Sopenharmony_ci printk(KERN_ERR "PCSP: still active!!\n"); 31362306a36Sopenharmony_ci return -EBUSY; 31462306a36Sopenharmony_ci } 31562306a36Sopenharmony_ci runtime->hw = snd_pcsp_playback; 31662306a36Sopenharmony_ci chip->playback_substream = substream; 31762306a36Sopenharmony_ci return 0; 31862306a36Sopenharmony_ci} 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_cistatic const struct snd_pcm_ops snd_pcsp_playback_ops = { 32162306a36Sopenharmony_ci .open = snd_pcsp_playback_open, 32262306a36Sopenharmony_ci .close = snd_pcsp_playback_close, 32362306a36Sopenharmony_ci .hw_params = snd_pcsp_playback_hw_params, 32462306a36Sopenharmony_ci .hw_free = snd_pcsp_playback_hw_free, 32562306a36Sopenharmony_ci .prepare = snd_pcsp_playback_prepare, 32662306a36Sopenharmony_ci .trigger = snd_pcsp_trigger, 32762306a36Sopenharmony_ci .pointer = snd_pcsp_playback_pointer, 32862306a36Sopenharmony_ci}; 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ciint snd_pcsp_new_pcm(struct snd_pcsp *chip) 33162306a36Sopenharmony_ci{ 33262306a36Sopenharmony_ci int err; 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci err = snd_pcm_new(chip->card, "pcspeaker", 0, 1, 0, &chip->pcm); 33562306a36Sopenharmony_ci if (err < 0) 33662306a36Sopenharmony_ci return err; 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci snd_pcm_set_ops(chip->pcm, SNDRV_PCM_STREAM_PLAYBACK, 33962306a36Sopenharmony_ci &snd_pcsp_playback_ops); 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci chip->pcm->private_data = chip; 34262306a36Sopenharmony_ci chip->pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX; 34362306a36Sopenharmony_ci strcpy(chip->pcm->name, "pcsp"); 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci snd_pcm_set_managed_buffer_all(chip->pcm, 34662306a36Sopenharmony_ci SNDRV_DMA_TYPE_CONTINUOUS, 34762306a36Sopenharmony_ci NULL, 34862306a36Sopenharmony_ci PCSP_BUFFER_SIZE, 34962306a36Sopenharmony_ci PCSP_BUFFER_SIZE); 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci return 0; 35262306a36Sopenharmony_ci} 353