162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * pcm emulation on emu8000 wavetable 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2002 Takashi Iwai <tiwai@suse.de> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include "emu8000_local.h" 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/sched/signal.h> 1162306a36Sopenharmony_ci#include <linux/init.h> 1262306a36Sopenharmony_ci#include <linux/slab.h> 1362306a36Sopenharmony_ci#include <sound/initval.h> 1462306a36Sopenharmony_ci#include <sound/pcm.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci/* 1762306a36Sopenharmony_ci * define the following if you want to use this pcm with non-interleaved mode 1862306a36Sopenharmony_ci */ 1962306a36Sopenharmony_ci/* #define USE_NONINTERLEAVE */ 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci/* NOTE: for using the non-interleaved mode with alsa-lib, you have to set 2262306a36Sopenharmony_ci * mmap_emulation flag to 1 in your .asoundrc, such like 2362306a36Sopenharmony_ci * 2462306a36Sopenharmony_ci * pcm.emu8k { 2562306a36Sopenharmony_ci * type plug 2662306a36Sopenharmony_ci * slave.pcm { 2762306a36Sopenharmony_ci * type hw 2862306a36Sopenharmony_ci * card 0 2962306a36Sopenharmony_ci * device 1 3062306a36Sopenharmony_ci * mmap_emulation 1 3162306a36Sopenharmony_ci * } 3262306a36Sopenharmony_ci * } 3362306a36Sopenharmony_ci * 3462306a36Sopenharmony_ci * besides, for the time being, the non-interleaved mode doesn't work well on 3562306a36Sopenharmony_ci * alsa-lib... 3662306a36Sopenharmony_ci */ 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistruct snd_emu8k_pcm { 4062306a36Sopenharmony_ci struct snd_emu8000 *emu; 4162306a36Sopenharmony_ci struct snd_pcm_substream *substream; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci unsigned int allocated_bytes; 4462306a36Sopenharmony_ci struct snd_util_memblk *block; 4562306a36Sopenharmony_ci unsigned int offset; 4662306a36Sopenharmony_ci unsigned int buf_size; 4762306a36Sopenharmony_ci unsigned int period_size; 4862306a36Sopenharmony_ci unsigned int loop_start[2]; 4962306a36Sopenharmony_ci unsigned int pitch; 5062306a36Sopenharmony_ci int panning[2]; 5162306a36Sopenharmony_ci int last_ptr; 5262306a36Sopenharmony_ci int period_pos; 5362306a36Sopenharmony_ci int voices; 5462306a36Sopenharmony_ci unsigned int dram_opened: 1; 5562306a36Sopenharmony_ci unsigned int running: 1; 5662306a36Sopenharmony_ci unsigned int timer_running: 1; 5762306a36Sopenharmony_ci struct timer_list timer; 5862306a36Sopenharmony_ci spinlock_t timer_lock; 5962306a36Sopenharmony_ci}; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci#define LOOP_BLANK_SIZE 8 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci/* 6562306a36Sopenharmony_ci * open up channels for the simultaneous data transfer and playback 6662306a36Sopenharmony_ci */ 6762306a36Sopenharmony_cistatic int 6862306a36Sopenharmony_ciemu8k_open_dram_for_pcm(struct snd_emu8000 *emu, int channels) 6962306a36Sopenharmony_ci{ 7062306a36Sopenharmony_ci int i; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci /* reserve up to 2 voices for playback */ 7362306a36Sopenharmony_ci snd_emux_lock_voice(emu->emu, 0); 7462306a36Sopenharmony_ci if (channels > 1) 7562306a36Sopenharmony_ci snd_emux_lock_voice(emu->emu, 1); 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci /* reserve 28 voices for loading */ 7862306a36Sopenharmony_ci for (i = channels + 1; i < EMU8000_DRAM_VOICES; i++) { 7962306a36Sopenharmony_ci unsigned int mode = EMU8000_RAM_WRITE; 8062306a36Sopenharmony_ci snd_emux_lock_voice(emu->emu, i); 8162306a36Sopenharmony_ci#ifndef USE_NONINTERLEAVE 8262306a36Sopenharmony_ci if (channels > 1 && (i & 1) != 0) 8362306a36Sopenharmony_ci mode |= EMU8000_RAM_RIGHT; 8462306a36Sopenharmony_ci#endif 8562306a36Sopenharmony_ci snd_emu8000_dma_chan(emu, i, mode); 8662306a36Sopenharmony_ci } 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci /* assign voice 31 and 32 to ROM */ 8962306a36Sopenharmony_ci EMU8000_VTFT_WRITE(emu, 30, 0); 9062306a36Sopenharmony_ci EMU8000_PSST_WRITE(emu, 30, 0x1d8); 9162306a36Sopenharmony_ci EMU8000_CSL_WRITE(emu, 30, 0x1e0); 9262306a36Sopenharmony_ci EMU8000_CCCA_WRITE(emu, 30, 0x1d8); 9362306a36Sopenharmony_ci EMU8000_VTFT_WRITE(emu, 31, 0); 9462306a36Sopenharmony_ci EMU8000_PSST_WRITE(emu, 31, 0x1d8); 9562306a36Sopenharmony_ci EMU8000_CSL_WRITE(emu, 31, 0x1e0); 9662306a36Sopenharmony_ci EMU8000_CCCA_WRITE(emu, 31, 0x1d8); 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci return 0; 9962306a36Sopenharmony_ci} 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci/* 10262306a36Sopenharmony_ci */ 10362306a36Sopenharmony_cistatic void 10462306a36Sopenharmony_cisnd_emu8000_write_wait(struct snd_emu8000 *emu, int can_schedule) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) { 10762306a36Sopenharmony_ci if (can_schedule) { 10862306a36Sopenharmony_ci schedule_timeout_interruptible(1); 10962306a36Sopenharmony_ci if (signal_pending(current)) 11062306a36Sopenharmony_ci break; 11162306a36Sopenharmony_ci } 11262306a36Sopenharmony_ci } 11362306a36Sopenharmony_ci} 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci/* 11662306a36Sopenharmony_ci * close all channels 11762306a36Sopenharmony_ci */ 11862306a36Sopenharmony_cistatic void 11962306a36Sopenharmony_ciemu8k_close_dram(struct snd_emu8000 *emu) 12062306a36Sopenharmony_ci{ 12162306a36Sopenharmony_ci int i; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci for (i = 0; i < 2; i++) 12462306a36Sopenharmony_ci snd_emux_unlock_voice(emu->emu, i); 12562306a36Sopenharmony_ci for (; i < EMU8000_DRAM_VOICES; i++) { 12662306a36Sopenharmony_ci snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE); 12762306a36Sopenharmony_ci snd_emux_unlock_voice(emu->emu, i); 12862306a36Sopenharmony_ci } 12962306a36Sopenharmony_ci} 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci/* 13262306a36Sopenharmony_ci * convert Hz to AWE32 rate offset (see emux/soundfont.c) 13362306a36Sopenharmony_ci */ 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci#define OFFSET_SAMPLERATE 1011119 /* base = 44100 */ 13662306a36Sopenharmony_ci#define SAMPLERATE_RATIO 4096 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_cistatic int calc_rate_offset(int hz) 13962306a36Sopenharmony_ci{ 14062306a36Sopenharmony_ci return snd_sf_linear_to_log(hz, OFFSET_SAMPLERATE, SAMPLERATE_RATIO); 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci/* 14562306a36Sopenharmony_ci */ 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_cistatic const struct snd_pcm_hardware emu8k_pcm_hw = { 14862306a36Sopenharmony_ci#ifdef USE_NONINTERLEAVE 14962306a36Sopenharmony_ci .info = SNDRV_PCM_INFO_NONINTERLEAVED, 15062306a36Sopenharmony_ci#else 15162306a36Sopenharmony_ci .info = SNDRV_PCM_INFO_INTERLEAVED, 15262306a36Sopenharmony_ci#endif 15362306a36Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S16_LE, 15462306a36Sopenharmony_ci .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, 15562306a36Sopenharmony_ci .rate_min = 4000, 15662306a36Sopenharmony_ci .rate_max = 48000, 15762306a36Sopenharmony_ci .channels_min = 1, 15862306a36Sopenharmony_ci .channels_max = 2, 15962306a36Sopenharmony_ci .buffer_bytes_max = (128*1024), 16062306a36Sopenharmony_ci .period_bytes_min = 1024, 16162306a36Sopenharmony_ci .period_bytes_max = (128*1024), 16262306a36Sopenharmony_ci .periods_min = 2, 16362306a36Sopenharmony_ci .periods_max = 1024, 16462306a36Sopenharmony_ci .fifo_size = 0, 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci}; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci/* 16962306a36Sopenharmony_ci * get the current position at the given channel from CCCA register 17062306a36Sopenharmony_ci */ 17162306a36Sopenharmony_cistatic inline int emu8k_get_curpos(struct snd_emu8k_pcm *rec, int ch) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci int val = EMU8000_CCCA_READ(rec->emu, ch) & 0xfffffff; 17462306a36Sopenharmony_ci val -= rec->loop_start[ch] - 1; 17562306a36Sopenharmony_ci return val; 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci/* 18062306a36Sopenharmony_ci * timer interrupt handler 18162306a36Sopenharmony_ci * check the current position and update the period if necessary. 18262306a36Sopenharmony_ci */ 18362306a36Sopenharmony_cistatic void emu8k_pcm_timer_func(struct timer_list *t) 18462306a36Sopenharmony_ci{ 18562306a36Sopenharmony_ci struct snd_emu8k_pcm *rec = from_timer(rec, t, timer); 18662306a36Sopenharmony_ci int ptr, delta; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci spin_lock(&rec->timer_lock); 18962306a36Sopenharmony_ci /* update the current pointer */ 19062306a36Sopenharmony_ci ptr = emu8k_get_curpos(rec, 0); 19162306a36Sopenharmony_ci if (ptr < rec->last_ptr) 19262306a36Sopenharmony_ci delta = ptr + rec->buf_size - rec->last_ptr; 19362306a36Sopenharmony_ci else 19462306a36Sopenharmony_ci delta = ptr - rec->last_ptr; 19562306a36Sopenharmony_ci rec->period_pos += delta; 19662306a36Sopenharmony_ci rec->last_ptr = ptr; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci /* reprogram timer */ 19962306a36Sopenharmony_ci mod_timer(&rec->timer, jiffies + 1); 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci /* update period */ 20262306a36Sopenharmony_ci if (rec->period_pos >= (int)rec->period_size) { 20362306a36Sopenharmony_ci rec->period_pos %= rec->period_size; 20462306a36Sopenharmony_ci spin_unlock(&rec->timer_lock); 20562306a36Sopenharmony_ci snd_pcm_period_elapsed(rec->substream); 20662306a36Sopenharmony_ci return; 20762306a36Sopenharmony_ci } 20862306a36Sopenharmony_ci spin_unlock(&rec->timer_lock); 20962306a36Sopenharmony_ci} 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci/* 21362306a36Sopenharmony_ci * open pcm 21462306a36Sopenharmony_ci * creating an instance here 21562306a36Sopenharmony_ci */ 21662306a36Sopenharmony_cistatic int emu8k_pcm_open(struct snd_pcm_substream *subs) 21762306a36Sopenharmony_ci{ 21862306a36Sopenharmony_ci struct snd_emu8000 *emu = snd_pcm_substream_chip(subs); 21962306a36Sopenharmony_ci struct snd_emu8k_pcm *rec; 22062306a36Sopenharmony_ci struct snd_pcm_runtime *runtime = subs->runtime; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci rec = kzalloc(sizeof(*rec), GFP_KERNEL); 22362306a36Sopenharmony_ci if (! rec) 22462306a36Sopenharmony_ci return -ENOMEM; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci rec->emu = emu; 22762306a36Sopenharmony_ci rec->substream = subs; 22862306a36Sopenharmony_ci runtime->private_data = rec; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci spin_lock_init(&rec->timer_lock); 23162306a36Sopenharmony_ci timer_setup(&rec->timer, emu8k_pcm_timer_func, 0); 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci runtime->hw = emu8k_pcm_hw; 23462306a36Sopenharmony_ci runtime->hw.buffer_bytes_max = emu->mem_size - LOOP_BLANK_SIZE * 3; 23562306a36Sopenharmony_ci runtime->hw.period_bytes_max = runtime->hw.buffer_bytes_max / 2; 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci /* use timer to update periods.. (specified in msec) */ 23862306a36Sopenharmony_ci snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 23962306a36Sopenharmony_ci DIV_ROUND_UP(1000000, HZ), UINT_MAX); 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci return 0; 24262306a36Sopenharmony_ci} 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_cistatic int emu8k_pcm_close(struct snd_pcm_substream *subs) 24562306a36Sopenharmony_ci{ 24662306a36Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 24762306a36Sopenharmony_ci kfree(rec); 24862306a36Sopenharmony_ci subs->runtime->private_data = NULL; 24962306a36Sopenharmony_ci return 0; 25062306a36Sopenharmony_ci} 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci/* 25362306a36Sopenharmony_ci * calculate pitch target 25462306a36Sopenharmony_ci */ 25562306a36Sopenharmony_cistatic int calc_pitch_target(int pitch) 25662306a36Sopenharmony_ci{ 25762306a36Sopenharmony_ci int ptarget = 1 << (pitch >> 12); 25862306a36Sopenharmony_ci if (pitch & 0x800) ptarget += (ptarget * 0x102e) / 0x2710; 25962306a36Sopenharmony_ci if (pitch & 0x400) ptarget += (ptarget * 0x764) / 0x2710; 26062306a36Sopenharmony_ci if (pitch & 0x200) ptarget += (ptarget * 0x389) / 0x2710; 26162306a36Sopenharmony_ci ptarget += (ptarget >> 1); 26262306a36Sopenharmony_ci if (ptarget > 0xffff) ptarget = 0xffff; 26362306a36Sopenharmony_ci return ptarget; 26462306a36Sopenharmony_ci} 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci/* 26762306a36Sopenharmony_ci * set up the voice 26862306a36Sopenharmony_ci */ 26962306a36Sopenharmony_cistatic void setup_voice(struct snd_emu8k_pcm *rec, int ch) 27062306a36Sopenharmony_ci{ 27162306a36Sopenharmony_ci struct snd_emu8000 *hw = rec->emu; 27262306a36Sopenharmony_ci unsigned int temp; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci /* channel to be silent and idle */ 27562306a36Sopenharmony_ci EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080); 27662306a36Sopenharmony_ci EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF); 27762306a36Sopenharmony_ci EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF); 27862306a36Sopenharmony_ci EMU8000_PTRX_WRITE(hw, ch, 0); 27962306a36Sopenharmony_ci EMU8000_CPF_WRITE(hw, ch, 0); 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci /* pitch offset */ 28262306a36Sopenharmony_ci EMU8000_IP_WRITE(hw, ch, rec->pitch); 28362306a36Sopenharmony_ci /* set envelope parameters */ 28462306a36Sopenharmony_ci EMU8000_ENVVAL_WRITE(hw, ch, 0x8000); 28562306a36Sopenharmony_ci EMU8000_ATKHLD_WRITE(hw, ch, 0x7f7f); 28662306a36Sopenharmony_ci EMU8000_DCYSUS_WRITE(hw, ch, 0x7f7f); 28762306a36Sopenharmony_ci EMU8000_ENVVOL_WRITE(hw, ch, 0x8000); 28862306a36Sopenharmony_ci EMU8000_ATKHLDV_WRITE(hw, ch, 0x7f7f); 28962306a36Sopenharmony_ci /* decay/sustain parameter for volume envelope is used 29062306a36Sopenharmony_ci for triggerg the voice */ 29162306a36Sopenharmony_ci /* modulation envelope heights */ 29262306a36Sopenharmony_ci EMU8000_PEFE_WRITE(hw, ch, 0x0); 29362306a36Sopenharmony_ci /* lfo1/2 delay */ 29462306a36Sopenharmony_ci EMU8000_LFO1VAL_WRITE(hw, ch, 0x8000); 29562306a36Sopenharmony_ci EMU8000_LFO2VAL_WRITE(hw, ch, 0x8000); 29662306a36Sopenharmony_ci /* lfo1 pitch & cutoff shift */ 29762306a36Sopenharmony_ci EMU8000_FMMOD_WRITE(hw, ch, 0); 29862306a36Sopenharmony_ci /* lfo1 volume & freq */ 29962306a36Sopenharmony_ci EMU8000_TREMFRQ_WRITE(hw, ch, 0); 30062306a36Sopenharmony_ci /* lfo2 pitch & freq */ 30162306a36Sopenharmony_ci EMU8000_FM2FRQ2_WRITE(hw, ch, 0); 30262306a36Sopenharmony_ci /* pan & loop start */ 30362306a36Sopenharmony_ci temp = rec->panning[ch]; 30462306a36Sopenharmony_ci temp = (temp <<24) | ((unsigned int)rec->loop_start[ch] - 1); 30562306a36Sopenharmony_ci EMU8000_PSST_WRITE(hw, ch, temp); 30662306a36Sopenharmony_ci /* chorus & loop end (chorus 8bit, MSB) */ 30762306a36Sopenharmony_ci temp = 0; // chorus 30862306a36Sopenharmony_ci temp = (temp << 24) | ((unsigned int)rec->loop_start[ch] + rec->buf_size - 1); 30962306a36Sopenharmony_ci EMU8000_CSL_WRITE(hw, ch, temp); 31062306a36Sopenharmony_ci /* Q & current address (Q 4bit value, MSB) */ 31162306a36Sopenharmony_ci temp = 0; // filterQ 31262306a36Sopenharmony_ci temp = (temp << 28) | ((unsigned int)rec->loop_start[ch] - 1); 31362306a36Sopenharmony_ci EMU8000_CCCA_WRITE(hw, ch, temp); 31462306a36Sopenharmony_ci /* clear unknown registers */ 31562306a36Sopenharmony_ci EMU8000_00A0_WRITE(hw, ch, 0); 31662306a36Sopenharmony_ci EMU8000_0080_WRITE(hw, ch, 0); 31762306a36Sopenharmony_ci} 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci/* 32062306a36Sopenharmony_ci * trigger the voice 32162306a36Sopenharmony_ci */ 32262306a36Sopenharmony_cistatic void start_voice(struct snd_emu8k_pcm *rec, int ch) 32362306a36Sopenharmony_ci{ 32462306a36Sopenharmony_ci unsigned long flags; 32562306a36Sopenharmony_ci struct snd_emu8000 *hw = rec->emu; 32662306a36Sopenharmony_ci unsigned int temp, aux; 32762306a36Sopenharmony_ci int pt = calc_pitch_target(rec->pitch); 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci /* cutoff and volume */ 33062306a36Sopenharmony_ci EMU8000_IFATN_WRITE(hw, ch, 0xff00); 33162306a36Sopenharmony_ci EMU8000_VTFT_WRITE(hw, ch, 0xffff); 33262306a36Sopenharmony_ci EMU8000_CVCF_WRITE(hw, ch, 0xffff); 33362306a36Sopenharmony_ci /* trigger envelope */ 33462306a36Sopenharmony_ci EMU8000_DCYSUSV_WRITE(hw, ch, 0x7f7f); 33562306a36Sopenharmony_ci /* set reverb and pitch target */ 33662306a36Sopenharmony_ci temp = 0; // reverb 33762306a36Sopenharmony_ci if (rec->panning[ch] == 0) 33862306a36Sopenharmony_ci aux = 0xff; 33962306a36Sopenharmony_ci else 34062306a36Sopenharmony_ci aux = (-rec->panning[ch]) & 0xff; 34162306a36Sopenharmony_ci temp = (temp << 8) | (pt << 16) | aux; 34262306a36Sopenharmony_ci EMU8000_PTRX_WRITE(hw, ch, temp); 34362306a36Sopenharmony_ci EMU8000_CPF_WRITE(hw, ch, pt << 16); 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci /* start timer */ 34662306a36Sopenharmony_ci spin_lock_irqsave(&rec->timer_lock, flags); 34762306a36Sopenharmony_ci if (! rec->timer_running) { 34862306a36Sopenharmony_ci mod_timer(&rec->timer, jiffies + 1); 34962306a36Sopenharmony_ci rec->timer_running = 1; 35062306a36Sopenharmony_ci } 35162306a36Sopenharmony_ci spin_unlock_irqrestore(&rec->timer_lock, flags); 35262306a36Sopenharmony_ci} 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci/* 35562306a36Sopenharmony_ci * stop the voice immediately 35662306a36Sopenharmony_ci */ 35762306a36Sopenharmony_cistatic void stop_voice(struct snd_emu8k_pcm *rec, int ch) 35862306a36Sopenharmony_ci{ 35962306a36Sopenharmony_ci unsigned long flags; 36062306a36Sopenharmony_ci struct snd_emu8000 *hw = rec->emu; 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci EMU8000_DCYSUSV_WRITE(hw, ch, 0x807F); 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci /* stop timer */ 36562306a36Sopenharmony_ci spin_lock_irqsave(&rec->timer_lock, flags); 36662306a36Sopenharmony_ci if (rec->timer_running) { 36762306a36Sopenharmony_ci del_timer(&rec->timer); 36862306a36Sopenharmony_ci rec->timer_running = 0; 36962306a36Sopenharmony_ci } 37062306a36Sopenharmony_ci spin_unlock_irqrestore(&rec->timer_lock, flags); 37162306a36Sopenharmony_ci} 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_cistatic int emu8k_pcm_trigger(struct snd_pcm_substream *subs, int cmd) 37462306a36Sopenharmony_ci{ 37562306a36Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 37662306a36Sopenharmony_ci int ch; 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci switch (cmd) { 37962306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_START: 38062306a36Sopenharmony_ci for (ch = 0; ch < rec->voices; ch++) 38162306a36Sopenharmony_ci start_voice(rec, ch); 38262306a36Sopenharmony_ci rec->running = 1; 38362306a36Sopenharmony_ci break; 38462306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_STOP: 38562306a36Sopenharmony_ci rec->running = 0; 38662306a36Sopenharmony_ci for (ch = 0; ch < rec->voices; ch++) 38762306a36Sopenharmony_ci stop_voice(rec, ch); 38862306a36Sopenharmony_ci break; 38962306a36Sopenharmony_ci default: 39062306a36Sopenharmony_ci return -EINVAL; 39162306a36Sopenharmony_ci } 39262306a36Sopenharmony_ci return 0; 39362306a36Sopenharmony_ci} 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci/* 39762306a36Sopenharmony_ci * copy / silence ops 39862306a36Sopenharmony_ci */ 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci/* 40162306a36Sopenharmony_ci * this macro should be inserted in the copy/silence loops 40262306a36Sopenharmony_ci * to reduce the latency. without this, the system will hang up 40362306a36Sopenharmony_ci * during the whole loop. 40462306a36Sopenharmony_ci */ 40562306a36Sopenharmony_ci#define CHECK_SCHEDULER() \ 40662306a36Sopenharmony_cido { \ 40762306a36Sopenharmony_ci cond_resched();\ 40862306a36Sopenharmony_ci if (signal_pending(current))\ 40962306a36Sopenharmony_ci return -EAGAIN;\ 41062306a36Sopenharmony_ci} while (0) 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci#define GET_VAL(sval, iter) \ 41362306a36Sopenharmony_ci do { \ 41462306a36Sopenharmony_ci if (!iter) \ 41562306a36Sopenharmony_ci sval = 0; \ 41662306a36Sopenharmony_ci else if (copy_from_iter(&sval, 2, iter) != 2) \ 41762306a36Sopenharmony_ci return -EFAULT; \ 41862306a36Sopenharmony_ci } while (0) 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_ci#ifdef USE_NONINTERLEAVE 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci#define LOOP_WRITE(rec, offset, iter, count) \ 42362306a36Sopenharmony_ci do { \ 42462306a36Sopenharmony_ci struct snd_emu8000 *emu = (rec)->emu; \ 42562306a36Sopenharmony_ci snd_emu8000_write_wait(emu, 1); \ 42662306a36Sopenharmony_ci EMU8000_SMALW_WRITE(emu, offset); \ 42762306a36Sopenharmony_ci while (count > 0) { \ 42862306a36Sopenharmony_ci unsigned short sval; \ 42962306a36Sopenharmony_ci CHECK_SCHEDULER(); \ 43062306a36Sopenharmony_ci GET_VAL(sval, iter); \ 43162306a36Sopenharmony_ci EMU8000_SMLD_WRITE(emu, sval); \ 43262306a36Sopenharmony_ci count--; \ 43362306a36Sopenharmony_ci } \ 43462306a36Sopenharmony_ci } while (0) 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci/* copy one channel block */ 43762306a36Sopenharmony_cistatic int emu8k_pcm_copy(struct snd_pcm_substream *subs, 43862306a36Sopenharmony_ci int voice, unsigned long pos, 43962306a36Sopenharmony_ci struct iov_iter *src, unsigned long count) 44062306a36Sopenharmony_ci{ 44162306a36Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci /* convert to word unit */ 44462306a36Sopenharmony_ci pos = (pos << 1) + rec->loop_start[voice]; 44562306a36Sopenharmony_ci count <<= 1; 44662306a36Sopenharmony_ci LOOP_WRITE(rec, pos, src, count); 44762306a36Sopenharmony_ci return 0; 44862306a36Sopenharmony_ci} 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci/* make a channel block silence */ 45162306a36Sopenharmony_cistatic int emu8k_pcm_silence(struct snd_pcm_substream *subs, 45262306a36Sopenharmony_ci int voice, unsigned long pos, unsigned long count) 45362306a36Sopenharmony_ci{ 45462306a36Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci /* convert to word unit */ 45762306a36Sopenharmony_ci pos = (pos << 1) + rec->loop_start[voice]; 45862306a36Sopenharmony_ci count <<= 1; 45962306a36Sopenharmony_ci LOOP_WRITE(rec, pos, NULL, count); 46062306a36Sopenharmony_ci return 0; 46162306a36Sopenharmony_ci} 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_ci#else /* interleave */ 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci#define LOOP_WRITE(rec, pos, iter, count) \ 46662306a36Sopenharmony_ci do { \ 46762306a36Sopenharmony_ci struct snd_emu8000 *emu = rec->emu; \ 46862306a36Sopenharmony_ci snd_emu8000_write_wait(emu, 1); \ 46962306a36Sopenharmony_ci EMU8000_SMALW_WRITE(emu, pos + rec->loop_start[0]); \ 47062306a36Sopenharmony_ci if (rec->voices > 1) \ 47162306a36Sopenharmony_ci EMU8000_SMARW_WRITE(emu, pos + rec->loop_start[1]); \ 47262306a36Sopenharmony_ci while (count > 0) { \ 47362306a36Sopenharmony_ci unsigned short sval; \ 47462306a36Sopenharmony_ci CHECK_SCHEDULER(); \ 47562306a36Sopenharmony_ci GET_VAL(sval, iter); \ 47662306a36Sopenharmony_ci EMU8000_SMLD_WRITE(emu, sval); \ 47762306a36Sopenharmony_ci if (rec->voices > 1) { \ 47862306a36Sopenharmony_ci CHECK_SCHEDULER(); \ 47962306a36Sopenharmony_ci GET_VAL(sval, iter); \ 48062306a36Sopenharmony_ci EMU8000_SMRD_WRITE(emu, sval); \ 48162306a36Sopenharmony_ci } \ 48262306a36Sopenharmony_ci count--; \ 48362306a36Sopenharmony_ci } \ 48462306a36Sopenharmony_ci } while (0) 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ci/* 48862306a36Sopenharmony_ci * copy the interleaved data can be done easily by using 48962306a36Sopenharmony_ci * DMA "left" and "right" channels on emu8k engine. 49062306a36Sopenharmony_ci */ 49162306a36Sopenharmony_cistatic int emu8k_pcm_copy(struct snd_pcm_substream *subs, 49262306a36Sopenharmony_ci int voice, unsigned long pos, 49362306a36Sopenharmony_ci struct iov_iter *src, unsigned long count) 49462306a36Sopenharmony_ci{ 49562306a36Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_ci /* convert to frames */ 49862306a36Sopenharmony_ci pos = bytes_to_frames(subs->runtime, pos); 49962306a36Sopenharmony_ci count = bytes_to_frames(subs->runtime, count); 50062306a36Sopenharmony_ci LOOP_WRITE(rec, pos, src, count); 50162306a36Sopenharmony_ci return 0; 50262306a36Sopenharmony_ci} 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_cistatic int emu8k_pcm_silence(struct snd_pcm_substream *subs, 50562306a36Sopenharmony_ci int voice, unsigned long pos, unsigned long count) 50662306a36Sopenharmony_ci{ 50762306a36Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci /* convert to frames */ 51062306a36Sopenharmony_ci pos = bytes_to_frames(subs->runtime, pos); 51162306a36Sopenharmony_ci count = bytes_to_frames(subs->runtime, count); 51262306a36Sopenharmony_ci LOOP_WRITE(rec, pos, NULL, count); 51362306a36Sopenharmony_ci return 0; 51462306a36Sopenharmony_ci} 51562306a36Sopenharmony_ci#endif 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci 51862306a36Sopenharmony_ci/* 51962306a36Sopenharmony_ci * allocate a memory block 52062306a36Sopenharmony_ci */ 52162306a36Sopenharmony_cistatic int emu8k_pcm_hw_params(struct snd_pcm_substream *subs, 52262306a36Sopenharmony_ci struct snd_pcm_hw_params *hw_params) 52362306a36Sopenharmony_ci{ 52462306a36Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_ci if (rec->block) { 52762306a36Sopenharmony_ci /* reallocation - release the old block */ 52862306a36Sopenharmony_ci snd_util_mem_free(rec->emu->memhdr, rec->block); 52962306a36Sopenharmony_ci rec->block = NULL; 53062306a36Sopenharmony_ci } 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_ci rec->allocated_bytes = params_buffer_bytes(hw_params) + LOOP_BLANK_SIZE * 4; 53362306a36Sopenharmony_ci rec->block = snd_util_mem_alloc(rec->emu->memhdr, rec->allocated_bytes); 53462306a36Sopenharmony_ci if (! rec->block) 53562306a36Sopenharmony_ci return -ENOMEM; 53662306a36Sopenharmony_ci rec->offset = EMU8000_DRAM_OFFSET + (rec->block->offset >> 1); /* in word */ 53762306a36Sopenharmony_ci /* at least dma_bytes must be set for non-interleaved mode */ 53862306a36Sopenharmony_ci subs->dma_buffer.bytes = params_buffer_bytes(hw_params); 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ci return 0; 54162306a36Sopenharmony_ci} 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci/* 54462306a36Sopenharmony_ci * free the memory block 54562306a36Sopenharmony_ci */ 54662306a36Sopenharmony_cistatic int emu8k_pcm_hw_free(struct snd_pcm_substream *subs) 54762306a36Sopenharmony_ci{ 54862306a36Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci if (rec->block) { 55162306a36Sopenharmony_ci int ch; 55262306a36Sopenharmony_ci for (ch = 0; ch < rec->voices; ch++) 55362306a36Sopenharmony_ci stop_voice(rec, ch); // to be sure 55462306a36Sopenharmony_ci if (rec->dram_opened) 55562306a36Sopenharmony_ci emu8k_close_dram(rec->emu); 55662306a36Sopenharmony_ci snd_util_mem_free(rec->emu->memhdr, rec->block); 55762306a36Sopenharmony_ci rec->block = NULL; 55862306a36Sopenharmony_ci } 55962306a36Sopenharmony_ci return 0; 56062306a36Sopenharmony_ci} 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci/* 56362306a36Sopenharmony_ci */ 56462306a36Sopenharmony_cistatic int emu8k_pcm_prepare(struct snd_pcm_substream *subs) 56562306a36Sopenharmony_ci{ 56662306a36Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci rec->pitch = 0xe000 + calc_rate_offset(subs->runtime->rate); 56962306a36Sopenharmony_ci rec->last_ptr = 0; 57062306a36Sopenharmony_ci rec->period_pos = 0; 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci rec->buf_size = subs->runtime->buffer_size; 57362306a36Sopenharmony_ci rec->period_size = subs->runtime->period_size; 57462306a36Sopenharmony_ci rec->voices = subs->runtime->channels; 57562306a36Sopenharmony_ci rec->loop_start[0] = rec->offset + LOOP_BLANK_SIZE; 57662306a36Sopenharmony_ci if (rec->voices > 1) 57762306a36Sopenharmony_ci rec->loop_start[1] = rec->loop_start[0] + rec->buf_size + LOOP_BLANK_SIZE; 57862306a36Sopenharmony_ci if (rec->voices > 1) { 57962306a36Sopenharmony_ci rec->panning[0] = 0xff; 58062306a36Sopenharmony_ci rec->panning[1] = 0x00; 58162306a36Sopenharmony_ci } else 58262306a36Sopenharmony_ci rec->panning[0] = 0x80; 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_ci if (! rec->dram_opened) { 58562306a36Sopenharmony_ci int err, i, ch; 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci snd_emux_terminate_all(rec->emu->emu); 58862306a36Sopenharmony_ci err = emu8k_open_dram_for_pcm(rec->emu, rec->voices); 58962306a36Sopenharmony_ci if (err) 59062306a36Sopenharmony_ci return err; 59162306a36Sopenharmony_ci rec->dram_opened = 1; 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci /* clear loop blanks */ 59462306a36Sopenharmony_ci snd_emu8000_write_wait(rec->emu, 0); 59562306a36Sopenharmony_ci EMU8000_SMALW_WRITE(rec->emu, rec->offset); 59662306a36Sopenharmony_ci for (i = 0; i < LOOP_BLANK_SIZE; i++) 59762306a36Sopenharmony_ci EMU8000_SMLD_WRITE(rec->emu, 0); 59862306a36Sopenharmony_ci for (ch = 0; ch < rec->voices; ch++) { 59962306a36Sopenharmony_ci EMU8000_SMALW_WRITE(rec->emu, rec->loop_start[ch] + rec->buf_size); 60062306a36Sopenharmony_ci for (i = 0; i < LOOP_BLANK_SIZE; i++) 60162306a36Sopenharmony_ci EMU8000_SMLD_WRITE(rec->emu, 0); 60262306a36Sopenharmony_ci } 60362306a36Sopenharmony_ci } 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci setup_voice(rec, 0); 60662306a36Sopenharmony_ci if (rec->voices > 1) 60762306a36Sopenharmony_ci setup_voice(rec, 1); 60862306a36Sopenharmony_ci return 0; 60962306a36Sopenharmony_ci} 61062306a36Sopenharmony_ci 61162306a36Sopenharmony_cistatic snd_pcm_uframes_t emu8k_pcm_pointer(struct snd_pcm_substream *subs) 61262306a36Sopenharmony_ci{ 61362306a36Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 61462306a36Sopenharmony_ci if (rec->running) 61562306a36Sopenharmony_ci return emu8k_get_curpos(rec, 0); 61662306a36Sopenharmony_ci return 0; 61762306a36Sopenharmony_ci} 61862306a36Sopenharmony_ci 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_cistatic const struct snd_pcm_ops emu8k_pcm_ops = { 62162306a36Sopenharmony_ci .open = emu8k_pcm_open, 62262306a36Sopenharmony_ci .close = emu8k_pcm_close, 62362306a36Sopenharmony_ci .hw_params = emu8k_pcm_hw_params, 62462306a36Sopenharmony_ci .hw_free = emu8k_pcm_hw_free, 62562306a36Sopenharmony_ci .prepare = emu8k_pcm_prepare, 62662306a36Sopenharmony_ci .trigger = emu8k_pcm_trigger, 62762306a36Sopenharmony_ci .pointer = emu8k_pcm_pointer, 62862306a36Sopenharmony_ci .copy = emu8k_pcm_copy, 62962306a36Sopenharmony_ci .fill_silence = emu8k_pcm_silence, 63062306a36Sopenharmony_ci}; 63162306a36Sopenharmony_ci 63262306a36Sopenharmony_ci 63362306a36Sopenharmony_cistatic void snd_emu8000_pcm_free(struct snd_pcm *pcm) 63462306a36Sopenharmony_ci{ 63562306a36Sopenharmony_ci struct snd_emu8000 *emu = pcm->private_data; 63662306a36Sopenharmony_ci emu->pcm = NULL; 63762306a36Sopenharmony_ci} 63862306a36Sopenharmony_ci 63962306a36Sopenharmony_ciint snd_emu8000_pcm_new(struct snd_card *card, struct snd_emu8000 *emu, int index) 64062306a36Sopenharmony_ci{ 64162306a36Sopenharmony_ci struct snd_pcm *pcm; 64262306a36Sopenharmony_ci int err; 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_ci err = snd_pcm_new(card, "Emu8000 PCM", index, 1, 0, &pcm); 64562306a36Sopenharmony_ci if (err < 0) 64662306a36Sopenharmony_ci return err; 64762306a36Sopenharmony_ci pcm->private_data = emu; 64862306a36Sopenharmony_ci pcm->private_free = snd_emu8000_pcm_free; 64962306a36Sopenharmony_ci snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &emu8k_pcm_ops); 65062306a36Sopenharmony_ci emu->pcm = pcm; 65162306a36Sopenharmony_ci 65262306a36Sopenharmony_ci snd_device_register(card, pcm); 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci return 0; 65562306a36Sopenharmony_ci} 656