353 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * PC-Speaker driver for Linux4 *5 * Copyright (C) 1993-1997 Michael Beck6 * Copyright (C) 1997-2001 David Woodhouse7 * Copyright (C) 2001-2008 Stas Sergeev8 */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/core.h>16#include <sound/pcm.h>17#include "pcsp.h"18 19static bool nforce_wa;20module_param(nforce_wa, bool, 0444);21MODULE_PARM_DESC(nforce_wa, "Apply NForce chipset workaround "22 "(expect bad sound)");23 24#define DMIX_WANTS_S16 125 26/*27 * Call snd_pcm_period_elapsed in a work28 * This avoids spinlock messes and long-running irq contexts29 */30static void pcsp_call_pcm_elapsed(struct work_struct *work)31{32 if (atomic_read(&pcsp_chip.timer_active)) {33 struct snd_pcm_substream *substream;34 substream = pcsp_chip.playback_substream;35 if (substream)36 snd_pcm_period_elapsed(substream);37 }38}39 40static DECLARE_WORK(pcsp_pcm_work, pcsp_call_pcm_elapsed);41 42/* write the port and returns the next expire time in ns;43 * called at the trigger-start and in hrtimer callback44 */45static u64 pcsp_timer_update(struct snd_pcsp *chip)46{47 unsigned char timer_cnt, val;48 u64 ns;49 struct snd_pcm_substream *substream;50 struct snd_pcm_runtime *runtime;51 unsigned long flags;52 53 if (chip->thalf) {54 outb(chip->val61, 0x61);55 chip->thalf = 0;56 return chip->ns_rem;57 }58 59 substream = chip->playback_substream;60 if (!substream)61 return 0;62 63 runtime = substream->runtime;64 /* assume it is mono! */65 val = runtime->dma_area[chip->playback_ptr + chip->fmt_size - 1];66 if (chip->is_signed)67 val ^= 0x80;68 timer_cnt = val * CUR_DIV() / 256;69 70 if (timer_cnt && chip->enable) {71 raw_spin_lock_irqsave(&i8253_lock, flags);72 if (!nforce_wa) {73 outb_p(chip->val61, 0x61);74 outb_p(timer_cnt, 0x42);75 outb(chip->val61 ^ 1, 0x61);76 } else {77 outb(chip->val61 ^ 2, 0x61);78 chip->thalf = 1;79 }80 raw_spin_unlock_irqrestore(&i8253_lock, flags);81 }82 83 chip->ns_rem = PCSP_PERIOD_NS();84 ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem);85 chip->ns_rem -= ns;86 return ns;87}88 89static void pcsp_pointer_update(struct snd_pcsp *chip)90{91 struct snd_pcm_substream *substream;92 size_t period_bytes, buffer_bytes;93 int periods_elapsed;94 unsigned long flags;95 96 /* update the playback position */97 substream = chip->playback_substream;98 if (!substream)99 return;100 101 period_bytes = snd_pcm_lib_period_bytes(substream);102 buffer_bytes = snd_pcm_lib_buffer_bytes(substream);103 104 spin_lock_irqsave(&chip->substream_lock, flags);105 chip->playback_ptr += PCSP_INDEX_INC() * chip->fmt_size;106 periods_elapsed = chip->playback_ptr - chip->period_ptr;107 if (periods_elapsed < 0) {108#if PCSP_DEBUG109 dev_dbg(chip->card->dev,110 "PCSP: buffer_bytes mod period_bytes != 0 ? (%zi %zi %zi)\n",111 chip->playback_ptr, period_bytes, buffer_bytes);112#endif113 periods_elapsed += buffer_bytes;114 }115 periods_elapsed /= period_bytes;116 /* wrap the pointer _before_ calling snd_pcm_period_elapsed(),117 * or ALSA will BUG on us. */118 chip->playback_ptr %= buffer_bytes;119 120 if (periods_elapsed) {121 chip->period_ptr += periods_elapsed * period_bytes;122 chip->period_ptr %= buffer_bytes;123 queue_work(system_highpri_wq, &pcsp_pcm_work);124 }125 spin_unlock_irqrestore(&chip->substream_lock, flags);126}127 128enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle)129{130 struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer);131 int pointer_update;132 u64 ns;133 134 if (!atomic_read(&chip->timer_active) || !chip->playback_substream)135 return HRTIMER_NORESTART;136 137 pointer_update = !chip->thalf;138 ns = pcsp_timer_update(chip);139 if (!ns) {140 dev_warn(chip->card->dev, "PCSP: unexpected stop\n");141 return HRTIMER_NORESTART;142 }143 144 if (pointer_update)145 pcsp_pointer_update(chip);146 147 hrtimer_forward_now(handle, ns_to_ktime(ns));148 149 return HRTIMER_RESTART;150}151 152static int pcsp_start_playing(struct snd_pcsp *chip)153{154#if PCSP_DEBUG155 dev_dbg(chip->card->dev, "PCSP: start_playing called\n");156#endif157 if (atomic_read(&chip->timer_active)) {158 dev_err(chip->card->dev, "PCSP: Timer already active\n");159 return -EIO;160 }161 162 raw_spin_lock(&i8253_lock);163 chip->val61 = inb(0x61) | 0x03;164 outb_p(0x92, 0x43); /* binary, mode 1, LSB only, ch 2 */165 raw_spin_unlock(&i8253_lock);166 atomic_set(&chip->timer_active, 1);167 chip->thalf = 0;168 169 hrtimer_start(&pcsp_chip.timer, 0, HRTIMER_MODE_REL);170 return 0;171}172 173static void pcsp_stop_playing(struct snd_pcsp *chip)174{175#if PCSP_DEBUG176 dev_dbg(chip->card->dev, "PCSP: stop_playing called\n");177#endif178 if (!atomic_read(&chip->timer_active))179 return;180 181 atomic_set(&chip->timer_active, 0);182 raw_spin_lock(&i8253_lock);183 /* restore the timer */184 outb_p(0xb6, 0x43); /* binary, mode 3, LSB/MSB, ch 2 */185 outb(chip->val61 & 0xFC, 0x61);186 raw_spin_unlock(&i8253_lock);187}188 189/*190 * Force to stop and sync the stream191 */192void pcsp_sync_stop(struct snd_pcsp *chip)193{194 local_irq_disable();195 pcsp_stop_playing(chip);196 local_irq_enable();197 hrtimer_cancel(&chip->timer);198 cancel_work_sync(&pcsp_pcm_work);199}200 201static int snd_pcsp_playback_close(struct snd_pcm_substream *substream)202{203 struct snd_pcsp *chip = snd_pcm_substream_chip(substream);204#if PCSP_DEBUG205 dev_dbg(chip->card->dev, "PCSP: close called\n");206#endif207 pcsp_sync_stop(chip);208 chip->playback_substream = NULL;209 return 0;210}211 212static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream,213 struct snd_pcm_hw_params *hw_params)214{215 struct snd_pcsp *chip = snd_pcm_substream_chip(substream);216 pcsp_sync_stop(chip);217 return 0;218}219 220static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream)221{222 struct snd_pcsp *chip = snd_pcm_substream_chip(substream);223#if PCSP_DEBUG224 dev_dbg(chip->card->dev, "PCSP: hw_free called\n");225#endif226 pcsp_sync_stop(chip);227 return 0;228}229 230static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream)231{232 struct snd_pcsp *chip = snd_pcm_substream_chip(substream);233 pcsp_sync_stop(chip);234 chip->playback_ptr = 0;235 chip->period_ptr = 0;236 chip->fmt_size =237 snd_pcm_format_physical_width(substream->runtime->format) >> 3;238 chip->is_signed = snd_pcm_format_signed(substream->runtime->format);239#if PCSP_DEBUG240 dev_dbg(chip->card->dev, "PCSP: prepare called, 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#endif248 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_DEBUG255 dev_dbg(chip->card->dev, "PCSP: trigger called\n");256#endif257 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_substream272 *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_U8287#if DMIX_WANTS_S16288 | SNDRV_PCM_FMTBIT_S16_LE289#endif290 ),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_DEBUG309 dev_dbg(chip->card->dev, "PCSP: open called\n");310#endif311 if (atomic_read(&chip->timer_active)) {312 dev_err(chip->card->dev, "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