18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * pcm emulation on emu8000 wavetable 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2002 Takashi Iwai <tiwai@suse.de> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include "emu8000_local.h" 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/sched/signal.h> 118c2ecf20Sopenharmony_ci#include <linux/init.h> 128c2ecf20Sopenharmony_ci#include <linux/slab.h> 138c2ecf20Sopenharmony_ci#include <sound/initval.h> 148c2ecf20Sopenharmony_ci#include <sound/pcm.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci/* 178c2ecf20Sopenharmony_ci * define the following if you want to use this pcm with non-interleaved mode 188c2ecf20Sopenharmony_ci */ 198c2ecf20Sopenharmony_ci/* #define USE_NONINTERLEAVE */ 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci/* NOTE: for using the non-interleaved mode with alsa-lib, you have to set 228c2ecf20Sopenharmony_ci * mmap_emulation flag to 1 in your .asoundrc, such like 238c2ecf20Sopenharmony_ci * 248c2ecf20Sopenharmony_ci * pcm.emu8k { 258c2ecf20Sopenharmony_ci * type plug 268c2ecf20Sopenharmony_ci * slave.pcm { 278c2ecf20Sopenharmony_ci * type hw 288c2ecf20Sopenharmony_ci * card 0 298c2ecf20Sopenharmony_ci * device 1 308c2ecf20Sopenharmony_ci * mmap_emulation 1 318c2ecf20Sopenharmony_ci * } 328c2ecf20Sopenharmony_ci * } 338c2ecf20Sopenharmony_ci * 348c2ecf20Sopenharmony_ci * besides, for the time being, the non-interleaved mode doesn't work well on 358c2ecf20Sopenharmony_ci * alsa-lib... 368c2ecf20Sopenharmony_ci */ 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistruct snd_emu8k_pcm { 408c2ecf20Sopenharmony_ci struct snd_emu8000 *emu; 418c2ecf20Sopenharmony_ci struct snd_pcm_substream *substream; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci unsigned int allocated_bytes; 448c2ecf20Sopenharmony_ci struct snd_util_memblk *block; 458c2ecf20Sopenharmony_ci unsigned int offset; 468c2ecf20Sopenharmony_ci unsigned int buf_size; 478c2ecf20Sopenharmony_ci unsigned int period_size; 488c2ecf20Sopenharmony_ci unsigned int loop_start[2]; 498c2ecf20Sopenharmony_ci unsigned int pitch; 508c2ecf20Sopenharmony_ci int panning[2]; 518c2ecf20Sopenharmony_ci int last_ptr; 528c2ecf20Sopenharmony_ci int period_pos; 538c2ecf20Sopenharmony_ci int voices; 548c2ecf20Sopenharmony_ci unsigned int dram_opened: 1; 558c2ecf20Sopenharmony_ci unsigned int running: 1; 568c2ecf20Sopenharmony_ci unsigned int timer_running: 1; 578c2ecf20Sopenharmony_ci struct timer_list timer; 588c2ecf20Sopenharmony_ci spinlock_t timer_lock; 598c2ecf20Sopenharmony_ci}; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci#define LOOP_BLANK_SIZE 8 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci/* 658c2ecf20Sopenharmony_ci * open up channels for the simultaneous data transfer and playback 668c2ecf20Sopenharmony_ci */ 678c2ecf20Sopenharmony_cistatic int 688c2ecf20Sopenharmony_ciemu8k_open_dram_for_pcm(struct snd_emu8000 *emu, int channels) 698c2ecf20Sopenharmony_ci{ 708c2ecf20Sopenharmony_ci int i; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci /* reserve up to 2 voices for playback */ 738c2ecf20Sopenharmony_ci snd_emux_lock_voice(emu->emu, 0); 748c2ecf20Sopenharmony_ci if (channels > 1) 758c2ecf20Sopenharmony_ci snd_emux_lock_voice(emu->emu, 1); 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci /* reserve 28 voices for loading */ 788c2ecf20Sopenharmony_ci for (i = channels + 1; i < EMU8000_DRAM_VOICES; i++) { 798c2ecf20Sopenharmony_ci unsigned int mode = EMU8000_RAM_WRITE; 808c2ecf20Sopenharmony_ci snd_emux_lock_voice(emu->emu, i); 818c2ecf20Sopenharmony_ci#ifndef USE_NONINTERLEAVE 828c2ecf20Sopenharmony_ci if (channels > 1 && (i & 1) != 0) 838c2ecf20Sopenharmony_ci mode |= EMU8000_RAM_RIGHT; 848c2ecf20Sopenharmony_ci#endif 858c2ecf20Sopenharmony_ci snd_emu8000_dma_chan(emu, i, mode); 868c2ecf20Sopenharmony_ci } 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci /* assign voice 31 and 32 to ROM */ 898c2ecf20Sopenharmony_ci EMU8000_VTFT_WRITE(emu, 30, 0); 908c2ecf20Sopenharmony_ci EMU8000_PSST_WRITE(emu, 30, 0x1d8); 918c2ecf20Sopenharmony_ci EMU8000_CSL_WRITE(emu, 30, 0x1e0); 928c2ecf20Sopenharmony_ci EMU8000_CCCA_WRITE(emu, 30, 0x1d8); 938c2ecf20Sopenharmony_ci EMU8000_VTFT_WRITE(emu, 31, 0); 948c2ecf20Sopenharmony_ci EMU8000_PSST_WRITE(emu, 31, 0x1d8); 958c2ecf20Sopenharmony_ci EMU8000_CSL_WRITE(emu, 31, 0x1e0); 968c2ecf20Sopenharmony_ci EMU8000_CCCA_WRITE(emu, 31, 0x1d8); 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci return 0; 998c2ecf20Sopenharmony_ci} 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci/* 1028c2ecf20Sopenharmony_ci */ 1038c2ecf20Sopenharmony_cistatic void 1048c2ecf20Sopenharmony_cisnd_emu8000_write_wait(struct snd_emu8000 *emu, int can_schedule) 1058c2ecf20Sopenharmony_ci{ 1068c2ecf20Sopenharmony_ci while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) { 1078c2ecf20Sopenharmony_ci if (can_schedule) { 1088c2ecf20Sopenharmony_ci schedule_timeout_interruptible(1); 1098c2ecf20Sopenharmony_ci if (signal_pending(current)) 1108c2ecf20Sopenharmony_ci break; 1118c2ecf20Sopenharmony_ci } 1128c2ecf20Sopenharmony_ci } 1138c2ecf20Sopenharmony_ci} 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci/* 1168c2ecf20Sopenharmony_ci * close all channels 1178c2ecf20Sopenharmony_ci */ 1188c2ecf20Sopenharmony_cistatic void 1198c2ecf20Sopenharmony_ciemu8k_close_dram(struct snd_emu8000 *emu) 1208c2ecf20Sopenharmony_ci{ 1218c2ecf20Sopenharmony_ci int i; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci for (i = 0; i < 2; i++) 1248c2ecf20Sopenharmony_ci snd_emux_unlock_voice(emu->emu, i); 1258c2ecf20Sopenharmony_ci for (; i < EMU8000_DRAM_VOICES; i++) { 1268c2ecf20Sopenharmony_ci snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE); 1278c2ecf20Sopenharmony_ci snd_emux_unlock_voice(emu->emu, i); 1288c2ecf20Sopenharmony_ci } 1298c2ecf20Sopenharmony_ci} 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci/* 1328c2ecf20Sopenharmony_ci * convert Hz to AWE32 rate offset (see emux/soundfont.c) 1338c2ecf20Sopenharmony_ci */ 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci#define OFFSET_SAMPLERATE 1011119 /* base = 44100 */ 1368c2ecf20Sopenharmony_ci#define SAMPLERATE_RATIO 4096 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_cistatic int calc_rate_offset(int hz) 1398c2ecf20Sopenharmony_ci{ 1408c2ecf20Sopenharmony_ci return snd_sf_linear_to_log(hz, OFFSET_SAMPLERATE, SAMPLERATE_RATIO); 1418c2ecf20Sopenharmony_ci} 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci/* 1458c2ecf20Sopenharmony_ci */ 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_cistatic const struct snd_pcm_hardware emu8k_pcm_hw = { 1488c2ecf20Sopenharmony_ci#ifdef USE_NONINTERLEAVE 1498c2ecf20Sopenharmony_ci .info = SNDRV_PCM_INFO_NONINTERLEAVED, 1508c2ecf20Sopenharmony_ci#else 1518c2ecf20Sopenharmony_ci .info = SNDRV_PCM_INFO_INTERLEAVED, 1528c2ecf20Sopenharmony_ci#endif 1538c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S16_LE, 1548c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, 1558c2ecf20Sopenharmony_ci .rate_min = 4000, 1568c2ecf20Sopenharmony_ci .rate_max = 48000, 1578c2ecf20Sopenharmony_ci .channels_min = 1, 1588c2ecf20Sopenharmony_ci .channels_max = 2, 1598c2ecf20Sopenharmony_ci .buffer_bytes_max = (128*1024), 1608c2ecf20Sopenharmony_ci .period_bytes_min = 1024, 1618c2ecf20Sopenharmony_ci .period_bytes_max = (128*1024), 1628c2ecf20Sopenharmony_ci .periods_min = 2, 1638c2ecf20Sopenharmony_ci .periods_max = 1024, 1648c2ecf20Sopenharmony_ci .fifo_size = 0, 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci}; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci/* 1698c2ecf20Sopenharmony_ci * get the current position at the given channel from CCCA register 1708c2ecf20Sopenharmony_ci */ 1718c2ecf20Sopenharmony_cistatic inline int emu8k_get_curpos(struct snd_emu8k_pcm *rec, int ch) 1728c2ecf20Sopenharmony_ci{ 1738c2ecf20Sopenharmony_ci int val = EMU8000_CCCA_READ(rec->emu, ch) & 0xfffffff; 1748c2ecf20Sopenharmony_ci val -= rec->loop_start[ch] - 1; 1758c2ecf20Sopenharmony_ci return val; 1768c2ecf20Sopenharmony_ci} 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci/* 1808c2ecf20Sopenharmony_ci * timer interrupt handler 1818c2ecf20Sopenharmony_ci * check the current position and update the period if necessary. 1828c2ecf20Sopenharmony_ci */ 1838c2ecf20Sopenharmony_cistatic void emu8k_pcm_timer_func(struct timer_list *t) 1848c2ecf20Sopenharmony_ci{ 1858c2ecf20Sopenharmony_ci struct snd_emu8k_pcm *rec = from_timer(rec, t, timer); 1868c2ecf20Sopenharmony_ci int ptr, delta; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci spin_lock(&rec->timer_lock); 1898c2ecf20Sopenharmony_ci /* update the current pointer */ 1908c2ecf20Sopenharmony_ci ptr = emu8k_get_curpos(rec, 0); 1918c2ecf20Sopenharmony_ci if (ptr < rec->last_ptr) 1928c2ecf20Sopenharmony_ci delta = ptr + rec->buf_size - rec->last_ptr; 1938c2ecf20Sopenharmony_ci else 1948c2ecf20Sopenharmony_ci delta = ptr - rec->last_ptr; 1958c2ecf20Sopenharmony_ci rec->period_pos += delta; 1968c2ecf20Sopenharmony_ci rec->last_ptr = ptr; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci /* reprogram timer */ 1998c2ecf20Sopenharmony_ci mod_timer(&rec->timer, jiffies + 1); 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci /* update period */ 2028c2ecf20Sopenharmony_ci if (rec->period_pos >= (int)rec->period_size) { 2038c2ecf20Sopenharmony_ci rec->period_pos %= rec->period_size; 2048c2ecf20Sopenharmony_ci spin_unlock(&rec->timer_lock); 2058c2ecf20Sopenharmony_ci snd_pcm_period_elapsed(rec->substream); 2068c2ecf20Sopenharmony_ci return; 2078c2ecf20Sopenharmony_ci } 2088c2ecf20Sopenharmony_ci spin_unlock(&rec->timer_lock); 2098c2ecf20Sopenharmony_ci} 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci/* 2138c2ecf20Sopenharmony_ci * open pcm 2148c2ecf20Sopenharmony_ci * creating an instance here 2158c2ecf20Sopenharmony_ci */ 2168c2ecf20Sopenharmony_cistatic int emu8k_pcm_open(struct snd_pcm_substream *subs) 2178c2ecf20Sopenharmony_ci{ 2188c2ecf20Sopenharmony_ci struct snd_emu8000 *emu = snd_pcm_substream_chip(subs); 2198c2ecf20Sopenharmony_ci struct snd_emu8k_pcm *rec; 2208c2ecf20Sopenharmony_ci struct snd_pcm_runtime *runtime = subs->runtime; 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci rec = kzalloc(sizeof(*rec), GFP_KERNEL); 2238c2ecf20Sopenharmony_ci if (! rec) 2248c2ecf20Sopenharmony_ci return -ENOMEM; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci rec->emu = emu; 2278c2ecf20Sopenharmony_ci rec->substream = subs; 2288c2ecf20Sopenharmony_ci runtime->private_data = rec; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci spin_lock_init(&rec->timer_lock); 2318c2ecf20Sopenharmony_ci timer_setup(&rec->timer, emu8k_pcm_timer_func, 0); 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci runtime->hw = emu8k_pcm_hw; 2348c2ecf20Sopenharmony_ci runtime->hw.buffer_bytes_max = emu->mem_size - LOOP_BLANK_SIZE * 3; 2358c2ecf20Sopenharmony_ci runtime->hw.period_bytes_max = runtime->hw.buffer_bytes_max / 2; 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci /* use timer to update periods.. (specified in msec) */ 2388c2ecf20Sopenharmony_ci snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 2398c2ecf20Sopenharmony_ci (1000000 + HZ - 1) / HZ, UINT_MAX); 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci return 0; 2428c2ecf20Sopenharmony_ci} 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_cistatic int emu8k_pcm_close(struct snd_pcm_substream *subs) 2458c2ecf20Sopenharmony_ci{ 2468c2ecf20Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 2478c2ecf20Sopenharmony_ci kfree(rec); 2488c2ecf20Sopenharmony_ci subs->runtime->private_data = NULL; 2498c2ecf20Sopenharmony_ci return 0; 2508c2ecf20Sopenharmony_ci} 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci/* 2538c2ecf20Sopenharmony_ci * calculate pitch target 2548c2ecf20Sopenharmony_ci */ 2558c2ecf20Sopenharmony_cistatic int calc_pitch_target(int pitch) 2568c2ecf20Sopenharmony_ci{ 2578c2ecf20Sopenharmony_ci int ptarget = 1 << (pitch >> 12); 2588c2ecf20Sopenharmony_ci if (pitch & 0x800) ptarget += (ptarget * 0x102e) / 0x2710; 2598c2ecf20Sopenharmony_ci if (pitch & 0x400) ptarget += (ptarget * 0x764) / 0x2710; 2608c2ecf20Sopenharmony_ci if (pitch & 0x200) ptarget += (ptarget * 0x389) / 0x2710; 2618c2ecf20Sopenharmony_ci ptarget += (ptarget >> 1); 2628c2ecf20Sopenharmony_ci if (ptarget > 0xffff) ptarget = 0xffff; 2638c2ecf20Sopenharmony_ci return ptarget; 2648c2ecf20Sopenharmony_ci} 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci/* 2678c2ecf20Sopenharmony_ci * set up the voice 2688c2ecf20Sopenharmony_ci */ 2698c2ecf20Sopenharmony_cistatic void setup_voice(struct snd_emu8k_pcm *rec, int ch) 2708c2ecf20Sopenharmony_ci{ 2718c2ecf20Sopenharmony_ci struct snd_emu8000 *hw = rec->emu; 2728c2ecf20Sopenharmony_ci unsigned int temp; 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci /* channel to be silent and idle */ 2758c2ecf20Sopenharmony_ci EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080); 2768c2ecf20Sopenharmony_ci EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF); 2778c2ecf20Sopenharmony_ci EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF); 2788c2ecf20Sopenharmony_ci EMU8000_PTRX_WRITE(hw, ch, 0); 2798c2ecf20Sopenharmony_ci EMU8000_CPF_WRITE(hw, ch, 0); 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci /* pitch offset */ 2828c2ecf20Sopenharmony_ci EMU8000_IP_WRITE(hw, ch, rec->pitch); 2838c2ecf20Sopenharmony_ci /* set envelope parameters */ 2848c2ecf20Sopenharmony_ci EMU8000_ENVVAL_WRITE(hw, ch, 0x8000); 2858c2ecf20Sopenharmony_ci EMU8000_ATKHLD_WRITE(hw, ch, 0x7f7f); 2868c2ecf20Sopenharmony_ci EMU8000_DCYSUS_WRITE(hw, ch, 0x7f7f); 2878c2ecf20Sopenharmony_ci EMU8000_ENVVOL_WRITE(hw, ch, 0x8000); 2888c2ecf20Sopenharmony_ci EMU8000_ATKHLDV_WRITE(hw, ch, 0x7f7f); 2898c2ecf20Sopenharmony_ci /* decay/sustain parameter for volume envelope is used 2908c2ecf20Sopenharmony_ci for triggerg the voice */ 2918c2ecf20Sopenharmony_ci /* modulation envelope heights */ 2928c2ecf20Sopenharmony_ci EMU8000_PEFE_WRITE(hw, ch, 0x0); 2938c2ecf20Sopenharmony_ci /* lfo1/2 delay */ 2948c2ecf20Sopenharmony_ci EMU8000_LFO1VAL_WRITE(hw, ch, 0x8000); 2958c2ecf20Sopenharmony_ci EMU8000_LFO2VAL_WRITE(hw, ch, 0x8000); 2968c2ecf20Sopenharmony_ci /* lfo1 pitch & cutoff shift */ 2978c2ecf20Sopenharmony_ci EMU8000_FMMOD_WRITE(hw, ch, 0); 2988c2ecf20Sopenharmony_ci /* lfo1 volume & freq */ 2998c2ecf20Sopenharmony_ci EMU8000_TREMFRQ_WRITE(hw, ch, 0); 3008c2ecf20Sopenharmony_ci /* lfo2 pitch & freq */ 3018c2ecf20Sopenharmony_ci EMU8000_FM2FRQ2_WRITE(hw, ch, 0); 3028c2ecf20Sopenharmony_ci /* pan & loop start */ 3038c2ecf20Sopenharmony_ci temp = rec->panning[ch]; 3048c2ecf20Sopenharmony_ci temp = (temp <<24) | ((unsigned int)rec->loop_start[ch] - 1); 3058c2ecf20Sopenharmony_ci EMU8000_PSST_WRITE(hw, ch, temp); 3068c2ecf20Sopenharmony_ci /* chorus & loop end (chorus 8bit, MSB) */ 3078c2ecf20Sopenharmony_ci temp = 0; // chorus 3088c2ecf20Sopenharmony_ci temp = (temp << 24) | ((unsigned int)rec->loop_start[ch] + rec->buf_size - 1); 3098c2ecf20Sopenharmony_ci EMU8000_CSL_WRITE(hw, ch, temp); 3108c2ecf20Sopenharmony_ci /* Q & current address (Q 4bit value, MSB) */ 3118c2ecf20Sopenharmony_ci temp = 0; // filterQ 3128c2ecf20Sopenharmony_ci temp = (temp << 28) | ((unsigned int)rec->loop_start[ch] - 1); 3138c2ecf20Sopenharmony_ci EMU8000_CCCA_WRITE(hw, ch, temp); 3148c2ecf20Sopenharmony_ci /* clear unknown registers */ 3158c2ecf20Sopenharmony_ci EMU8000_00A0_WRITE(hw, ch, 0); 3168c2ecf20Sopenharmony_ci EMU8000_0080_WRITE(hw, ch, 0); 3178c2ecf20Sopenharmony_ci} 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci/* 3208c2ecf20Sopenharmony_ci * trigger the voice 3218c2ecf20Sopenharmony_ci */ 3228c2ecf20Sopenharmony_cistatic void start_voice(struct snd_emu8k_pcm *rec, int ch) 3238c2ecf20Sopenharmony_ci{ 3248c2ecf20Sopenharmony_ci unsigned long flags; 3258c2ecf20Sopenharmony_ci struct snd_emu8000 *hw = rec->emu; 3268c2ecf20Sopenharmony_ci unsigned int temp, aux; 3278c2ecf20Sopenharmony_ci int pt = calc_pitch_target(rec->pitch); 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_ci /* cutoff and volume */ 3308c2ecf20Sopenharmony_ci EMU8000_IFATN_WRITE(hw, ch, 0xff00); 3318c2ecf20Sopenharmony_ci EMU8000_VTFT_WRITE(hw, ch, 0xffff); 3328c2ecf20Sopenharmony_ci EMU8000_CVCF_WRITE(hw, ch, 0xffff); 3338c2ecf20Sopenharmony_ci /* trigger envelope */ 3348c2ecf20Sopenharmony_ci EMU8000_DCYSUSV_WRITE(hw, ch, 0x7f7f); 3358c2ecf20Sopenharmony_ci /* set reverb and pitch target */ 3368c2ecf20Sopenharmony_ci temp = 0; // reverb 3378c2ecf20Sopenharmony_ci if (rec->panning[ch] == 0) 3388c2ecf20Sopenharmony_ci aux = 0xff; 3398c2ecf20Sopenharmony_ci else 3408c2ecf20Sopenharmony_ci aux = (-rec->panning[ch]) & 0xff; 3418c2ecf20Sopenharmony_ci temp = (temp << 8) | (pt << 16) | aux; 3428c2ecf20Sopenharmony_ci EMU8000_PTRX_WRITE(hw, ch, temp); 3438c2ecf20Sopenharmony_ci EMU8000_CPF_WRITE(hw, ch, pt << 16); 3448c2ecf20Sopenharmony_ci 3458c2ecf20Sopenharmony_ci /* start timer */ 3468c2ecf20Sopenharmony_ci spin_lock_irqsave(&rec->timer_lock, flags); 3478c2ecf20Sopenharmony_ci if (! rec->timer_running) { 3488c2ecf20Sopenharmony_ci mod_timer(&rec->timer, jiffies + 1); 3498c2ecf20Sopenharmony_ci rec->timer_running = 1; 3508c2ecf20Sopenharmony_ci } 3518c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&rec->timer_lock, flags); 3528c2ecf20Sopenharmony_ci} 3538c2ecf20Sopenharmony_ci 3548c2ecf20Sopenharmony_ci/* 3558c2ecf20Sopenharmony_ci * stop the voice immediately 3568c2ecf20Sopenharmony_ci */ 3578c2ecf20Sopenharmony_cistatic void stop_voice(struct snd_emu8k_pcm *rec, int ch) 3588c2ecf20Sopenharmony_ci{ 3598c2ecf20Sopenharmony_ci unsigned long flags; 3608c2ecf20Sopenharmony_ci struct snd_emu8000 *hw = rec->emu; 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_ci EMU8000_DCYSUSV_WRITE(hw, ch, 0x807F); 3638c2ecf20Sopenharmony_ci 3648c2ecf20Sopenharmony_ci /* stop timer */ 3658c2ecf20Sopenharmony_ci spin_lock_irqsave(&rec->timer_lock, flags); 3668c2ecf20Sopenharmony_ci if (rec->timer_running) { 3678c2ecf20Sopenharmony_ci del_timer(&rec->timer); 3688c2ecf20Sopenharmony_ci rec->timer_running = 0; 3698c2ecf20Sopenharmony_ci } 3708c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&rec->timer_lock, flags); 3718c2ecf20Sopenharmony_ci} 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_cistatic int emu8k_pcm_trigger(struct snd_pcm_substream *subs, int cmd) 3748c2ecf20Sopenharmony_ci{ 3758c2ecf20Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 3768c2ecf20Sopenharmony_ci int ch; 3778c2ecf20Sopenharmony_ci 3788c2ecf20Sopenharmony_ci switch (cmd) { 3798c2ecf20Sopenharmony_ci case SNDRV_PCM_TRIGGER_START: 3808c2ecf20Sopenharmony_ci for (ch = 0; ch < rec->voices; ch++) 3818c2ecf20Sopenharmony_ci start_voice(rec, ch); 3828c2ecf20Sopenharmony_ci rec->running = 1; 3838c2ecf20Sopenharmony_ci break; 3848c2ecf20Sopenharmony_ci case SNDRV_PCM_TRIGGER_STOP: 3858c2ecf20Sopenharmony_ci rec->running = 0; 3868c2ecf20Sopenharmony_ci for (ch = 0; ch < rec->voices; ch++) 3878c2ecf20Sopenharmony_ci stop_voice(rec, ch); 3888c2ecf20Sopenharmony_ci break; 3898c2ecf20Sopenharmony_ci default: 3908c2ecf20Sopenharmony_ci return -EINVAL; 3918c2ecf20Sopenharmony_ci } 3928c2ecf20Sopenharmony_ci return 0; 3938c2ecf20Sopenharmony_ci} 3948c2ecf20Sopenharmony_ci 3958c2ecf20Sopenharmony_ci 3968c2ecf20Sopenharmony_ci/* 3978c2ecf20Sopenharmony_ci * copy / silence ops 3988c2ecf20Sopenharmony_ci */ 3998c2ecf20Sopenharmony_ci 4008c2ecf20Sopenharmony_ci/* 4018c2ecf20Sopenharmony_ci * this macro should be inserted in the copy/silence loops 4028c2ecf20Sopenharmony_ci * to reduce the latency. without this, the system will hang up 4038c2ecf20Sopenharmony_ci * during the whole loop. 4048c2ecf20Sopenharmony_ci */ 4058c2ecf20Sopenharmony_ci#define CHECK_SCHEDULER() \ 4068c2ecf20Sopenharmony_cido { \ 4078c2ecf20Sopenharmony_ci cond_resched();\ 4088c2ecf20Sopenharmony_ci if (signal_pending(current))\ 4098c2ecf20Sopenharmony_ci return -EAGAIN;\ 4108c2ecf20Sopenharmony_ci} while (0) 4118c2ecf20Sopenharmony_ci 4128c2ecf20Sopenharmony_cienum { 4138c2ecf20Sopenharmony_ci COPY_USER, COPY_KERNEL, FILL_SILENCE, 4148c2ecf20Sopenharmony_ci}; 4158c2ecf20Sopenharmony_ci 4168c2ecf20Sopenharmony_ci#define GET_VAL(sval, buf, mode) \ 4178c2ecf20Sopenharmony_ci do { \ 4188c2ecf20Sopenharmony_ci switch (mode) { \ 4198c2ecf20Sopenharmony_ci case FILL_SILENCE: \ 4208c2ecf20Sopenharmony_ci sval = 0; \ 4218c2ecf20Sopenharmony_ci break; \ 4228c2ecf20Sopenharmony_ci case COPY_KERNEL: \ 4238c2ecf20Sopenharmony_ci sval = *buf++; \ 4248c2ecf20Sopenharmony_ci break; \ 4258c2ecf20Sopenharmony_ci default: \ 4268c2ecf20Sopenharmony_ci if (get_user(sval, (unsigned short __user *)buf)) \ 4278c2ecf20Sopenharmony_ci return -EFAULT; \ 4288c2ecf20Sopenharmony_ci buf++; \ 4298c2ecf20Sopenharmony_ci break; \ 4308c2ecf20Sopenharmony_ci } \ 4318c2ecf20Sopenharmony_ci } while (0) 4328c2ecf20Sopenharmony_ci 4338c2ecf20Sopenharmony_ci#ifdef USE_NONINTERLEAVE 4348c2ecf20Sopenharmony_ci 4358c2ecf20Sopenharmony_ci#define LOOP_WRITE(rec, offset, _buf, count, mode) \ 4368c2ecf20Sopenharmony_ci do { \ 4378c2ecf20Sopenharmony_ci struct snd_emu8000 *emu = (rec)->emu; \ 4388c2ecf20Sopenharmony_ci unsigned short *buf = (__force unsigned short *)(_buf); \ 4398c2ecf20Sopenharmony_ci snd_emu8000_write_wait(emu, 1); \ 4408c2ecf20Sopenharmony_ci EMU8000_SMALW_WRITE(emu, offset); \ 4418c2ecf20Sopenharmony_ci while (count > 0) { \ 4428c2ecf20Sopenharmony_ci unsigned short sval; \ 4438c2ecf20Sopenharmony_ci CHECK_SCHEDULER(); \ 4448c2ecf20Sopenharmony_ci GET_VAL(sval, buf, mode); \ 4458c2ecf20Sopenharmony_ci EMU8000_SMLD_WRITE(emu, sval); \ 4468c2ecf20Sopenharmony_ci count--; \ 4478c2ecf20Sopenharmony_ci } \ 4488c2ecf20Sopenharmony_ci } while (0) 4498c2ecf20Sopenharmony_ci 4508c2ecf20Sopenharmony_ci/* copy one channel block */ 4518c2ecf20Sopenharmony_cistatic int emu8k_pcm_copy(struct snd_pcm_substream *subs, 4528c2ecf20Sopenharmony_ci int voice, unsigned long pos, 4538c2ecf20Sopenharmony_ci void __user *src, unsigned long count) 4548c2ecf20Sopenharmony_ci{ 4558c2ecf20Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 4568c2ecf20Sopenharmony_ci 4578c2ecf20Sopenharmony_ci /* convert to word unit */ 4588c2ecf20Sopenharmony_ci pos = (pos << 1) + rec->loop_start[voice]; 4598c2ecf20Sopenharmony_ci count <<= 1; 4608c2ecf20Sopenharmony_ci LOOP_WRITE(rec, pos, src, count, COPY_USER); 4618c2ecf20Sopenharmony_ci return 0; 4628c2ecf20Sopenharmony_ci} 4638c2ecf20Sopenharmony_ci 4648c2ecf20Sopenharmony_cistatic int emu8k_pcm_copy_kernel(struct snd_pcm_substream *subs, 4658c2ecf20Sopenharmony_ci int voice, unsigned long pos, 4668c2ecf20Sopenharmony_ci void *src, unsigned long count) 4678c2ecf20Sopenharmony_ci{ 4688c2ecf20Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 4698c2ecf20Sopenharmony_ci 4708c2ecf20Sopenharmony_ci /* convert to word unit */ 4718c2ecf20Sopenharmony_ci pos = (pos << 1) + rec->loop_start[voice]; 4728c2ecf20Sopenharmony_ci count <<= 1; 4738c2ecf20Sopenharmony_ci LOOP_WRITE(rec, pos, src, count, COPY_KERNEL); 4748c2ecf20Sopenharmony_ci return 0; 4758c2ecf20Sopenharmony_ci} 4768c2ecf20Sopenharmony_ci 4778c2ecf20Sopenharmony_ci/* make a channel block silence */ 4788c2ecf20Sopenharmony_cistatic int emu8k_pcm_silence(struct snd_pcm_substream *subs, 4798c2ecf20Sopenharmony_ci int voice, unsigned long pos, unsigned long count) 4808c2ecf20Sopenharmony_ci{ 4818c2ecf20Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 4828c2ecf20Sopenharmony_ci 4838c2ecf20Sopenharmony_ci /* convert to word unit */ 4848c2ecf20Sopenharmony_ci pos = (pos << 1) + rec->loop_start[voice]; 4858c2ecf20Sopenharmony_ci count <<= 1; 4868c2ecf20Sopenharmony_ci LOOP_WRITE(rec, pos, NULL, count, FILL_SILENCE); 4878c2ecf20Sopenharmony_ci return 0; 4888c2ecf20Sopenharmony_ci} 4898c2ecf20Sopenharmony_ci 4908c2ecf20Sopenharmony_ci#else /* interleave */ 4918c2ecf20Sopenharmony_ci 4928c2ecf20Sopenharmony_ci#define LOOP_WRITE(rec, pos, _buf, count, mode) \ 4938c2ecf20Sopenharmony_ci do { \ 4948c2ecf20Sopenharmony_ci struct snd_emu8000 *emu = rec->emu; \ 4958c2ecf20Sopenharmony_ci unsigned short *buf = (__force unsigned short *)(_buf); \ 4968c2ecf20Sopenharmony_ci snd_emu8000_write_wait(emu, 1); \ 4978c2ecf20Sopenharmony_ci EMU8000_SMALW_WRITE(emu, pos + rec->loop_start[0]); \ 4988c2ecf20Sopenharmony_ci if (rec->voices > 1) \ 4998c2ecf20Sopenharmony_ci EMU8000_SMARW_WRITE(emu, pos + rec->loop_start[1]); \ 5008c2ecf20Sopenharmony_ci while (count > 0) { \ 5018c2ecf20Sopenharmony_ci unsigned short sval; \ 5028c2ecf20Sopenharmony_ci CHECK_SCHEDULER(); \ 5038c2ecf20Sopenharmony_ci GET_VAL(sval, buf, mode); \ 5048c2ecf20Sopenharmony_ci EMU8000_SMLD_WRITE(emu, sval); \ 5058c2ecf20Sopenharmony_ci if (rec->voices > 1) { \ 5068c2ecf20Sopenharmony_ci CHECK_SCHEDULER(); \ 5078c2ecf20Sopenharmony_ci GET_VAL(sval, buf, mode); \ 5088c2ecf20Sopenharmony_ci EMU8000_SMRD_WRITE(emu, sval); \ 5098c2ecf20Sopenharmony_ci } \ 5108c2ecf20Sopenharmony_ci count--; \ 5118c2ecf20Sopenharmony_ci } \ 5128c2ecf20Sopenharmony_ci } while (0) 5138c2ecf20Sopenharmony_ci 5148c2ecf20Sopenharmony_ci 5158c2ecf20Sopenharmony_ci/* 5168c2ecf20Sopenharmony_ci * copy the interleaved data can be done easily by using 5178c2ecf20Sopenharmony_ci * DMA "left" and "right" channels on emu8k engine. 5188c2ecf20Sopenharmony_ci */ 5198c2ecf20Sopenharmony_cistatic int emu8k_pcm_copy(struct snd_pcm_substream *subs, 5208c2ecf20Sopenharmony_ci int voice, unsigned long pos, 5218c2ecf20Sopenharmony_ci void __user *src, unsigned long count) 5228c2ecf20Sopenharmony_ci{ 5238c2ecf20Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 5248c2ecf20Sopenharmony_ci 5258c2ecf20Sopenharmony_ci /* convert to frames */ 5268c2ecf20Sopenharmony_ci pos = bytes_to_frames(subs->runtime, pos); 5278c2ecf20Sopenharmony_ci count = bytes_to_frames(subs->runtime, count); 5288c2ecf20Sopenharmony_ci LOOP_WRITE(rec, pos, src, count, COPY_USER); 5298c2ecf20Sopenharmony_ci return 0; 5308c2ecf20Sopenharmony_ci} 5318c2ecf20Sopenharmony_ci 5328c2ecf20Sopenharmony_cistatic int emu8k_pcm_copy_kernel(struct snd_pcm_substream *subs, 5338c2ecf20Sopenharmony_ci int voice, unsigned long pos, 5348c2ecf20Sopenharmony_ci void *src, unsigned long count) 5358c2ecf20Sopenharmony_ci{ 5368c2ecf20Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 5378c2ecf20Sopenharmony_ci 5388c2ecf20Sopenharmony_ci /* convert to frames */ 5398c2ecf20Sopenharmony_ci pos = bytes_to_frames(subs->runtime, pos); 5408c2ecf20Sopenharmony_ci count = bytes_to_frames(subs->runtime, count); 5418c2ecf20Sopenharmony_ci LOOP_WRITE(rec, pos, src, count, COPY_KERNEL); 5428c2ecf20Sopenharmony_ci return 0; 5438c2ecf20Sopenharmony_ci} 5448c2ecf20Sopenharmony_ci 5458c2ecf20Sopenharmony_cistatic int emu8k_pcm_silence(struct snd_pcm_substream *subs, 5468c2ecf20Sopenharmony_ci int voice, unsigned long pos, unsigned long count) 5478c2ecf20Sopenharmony_ci{ 5488c2ecf20Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 5498c2ecf20Sopenharmony_ci 5508c2ecf20Sopenharmony_ci /* convert to frames */ 5518c2ecf20Sopenharmony_ci pos = bytes_to_frames(subs->runtime, pos); 5528c2ecf20Sopenharmony_ci count = bytes_to_frames(subs->runtime, count); 5538c2ecf20Sopenharmony_ci LOOP_WRITE(rec, pos, NULL, count, FILL_SILENCE); 5548c2ecf20Sopenharmony_ci return 0; 5558c2ecf20Sopenharmony_ci} 5568c2ecf20Sopenharmony_ci#endif 5578c2ecf20Sopenharmony_ci 5588c2ecf20Sopenharmony_ci 5598c2ecf20Sopenharmony_ci/* 5608c2ecf20Sopenharmony_ci * allocate a memory block 5618c2ecf20Sopenharmony_ci */ 5628c2ecf20Sopenharmony_cistatic int emu8k_pcm_hw_params(struct snd_pcm_substream *subs, 5638c2ecf20Sopenharmony_ci struct snd_pcm_hw_params *hw_params) 5648c2ecf20Sopenharmony_ci{ 5658c2ecf20Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 5668c2ecf20Sopenharmony_ci 5678c2ecf20Sopenharmony_ci if (rec->block) { 5688c2ecf20Sopenharmony_ci /* reallocation - release the old block */ 5698c2ecf20Sopenharmony_ci snd_util_mem_free(rec->emu->memhdr, rec->block); 5708c2ecf20Sopenharmony_ci rec->block = NULL; 5718c2ecf20Sopenharmony_ci } 5728c2ecf20Sopenharmony_ci 5738c2ecf20Sopenharmony_ci rec->allocated_bytes = params_buffer_bytes(hw_params) + LOOP_BLANK_SIZE * 4; 5748c2ecf20Sopenharmony_ci rec->block = snd_util_mem_alloc(rec->emu->memhdr, rec->allocated_bytes); 5758c2ecf20Sopenharmony_ci if (! rec->block) 5768c2ecf20Sopenharmony_ci return -ENOMEM; 5778c2ecf20Sopenharmony_ci rec->offset = EMU8000_DRAM_OFFSET + (rec->block->offset >> 1); /* in word */ 5788c2ecf20Sopenharmony_ci /* at least dma_bytes must be set for non-interleaved mode */ 5798c2ecf20Sopenharmony_ci subs->dma_buffer.bytes = params_buffer_bytes(hw_params); 5808c2ecf20Sopenharmony_ci 5818c2ecf20Sopenharmony_ci return 0; 5828c2ecf20Sopenharmony_ci} 5838c2ecf20Sopenharmony_ci 5848c2ecf20Sopenharmony_ci/* 5858c2ecf20Sopenharmony_ci * free the memory block 5868c2ecf20Sopenharmony_ci */ 5878c2ecf20Sopenharmony_cistatic int emu8k_pcm_hw_free(struct snd_pcm_substream *subs) 5888c2ecf20Sopenharmony_ci{ 5898c2ecf20Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 5908c2ecf20Sopenharmony_ci 5918c2ecf20Sopenharmony_ci if (rec->block) { 5928c2ecf20Sopenharmony_ci int ch; 5938c2ecf20Sopenharmony_ci for (ch = 0; ch < rec->voices; ch++) 5948c2ecf20Sopenharmony_ci stop_voice(rec, ch); // to be sure 5958c2ecf20Sopenharmony_ci if (rec->dram_opened) 5968c2ecf20Sopenharmony_ci emu8k_close_dram(rec->emu); 5978c2ecf20Sopenharmony_ci snd_util_mem_free(rec->emu->memhdr, rec->block); 5988c2ecf20Sopenharmony_ci rec->block = NULL; 5998c2ecf20Sopenharmony_ci } 6008c2ecf20Sopenharmony_ci return 0; 6018c2ecf20Sopenharmony_ci} 6028c2ecf20Sopenharmony_ci 6038c2ecf20Sopenharmony_ci/* 6048c2ecf20Sopenharmony_ci */ 6058c2ecf20Sopenharmony_cistatic int emu8k_pcm_prepare(struct snd_pcm_substream *subs) 6068c2ecf20Sopenharmony_ci{ 6078c2ecf20Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 6088c2ecf20Sopenharmony_ci 6098c2ecf20Sopenharmony_ci rec->pitch = 0xe000 + calc_rate_offset(subs->runtime->rate); 6108c2ecf20Sopenharmony_ci rec->last_ptr = 0; 6118c2ecf20Sopenharmony_ci rec->period_pos = 0; 6128c2ecf20Sopenharmony_ci 6138c2ecf20Sopenharmony_ci rec->buf_size = subs->runtime->buffer_size; 6148c2ecf20Sopenharmony_ci rec->period_size = subs->runtime->period_size; 6158c2ecf20Sopenharmony_ci rec->voices = subs->runtime->channels; 6168c2ecf20Sopenharmony_ci rec->loop_start[0] = rec->offset + LOOP_BLANK_SIZE; 6178c2ecf20Sopenharmony_ci if (rec->voices > 1) 6188c2ecf20Sopenharmony_ci rec->loop_start[1] = rec->loop_start[0] + rec->buf_size + LOOP_BLANK_SIZE; 6198c2ecf20Sopenharmony_ci if (rec->voices > 1) { 6208c2ecf20Sopenharmony_ci rec->panning[0] = 0xff; 6218c2ecf20Sopenharmony_ci rec->panning[1] = 0x00; 6228c2ecf20Sopenharmony_ci } else 6238c2ecf20Sopenharmony_ci rec->panning[0] = 0x80; 6248c2ecf20Sopenharmony_ci 6258c2ecf20Sopenharmony_ci if (! rec->dram_opened) { 6268c2ecf20Sopenharmony_ci int err, i, ch; 6278c2ecf20Sopenharmony_ci 6288c2ecf20Sopenharmony_ci snd_emux_terminate_all(rec->emu->emu); 6298c2ecf20Sopenharmony_ci if ((err = emu8k_open_dram_for_pcm(rec->emu, rec->voices)) != 0) 6308c2ecf20Sopenharmony_ci return err; 6318c2ecf20Sopenharmony_ci rec->dram_opened = 1; 6328c2ecf20Sopenharmony_ci 6338c2ecf20Sopenharmony_ci /* clear loop blanks */ 6348c2ecf20Sopenharmony_ci snd_emu8000_write_wait(rec->emu, 0); 6358c2ecf20Sopenharmony_ci EMU8000_SMALW_WRITE(rec->emu, rec->offset); 6368c2ecf20Sopenharmony_ci for (i = 0; i < LOOP_BLANK_SIZE; i++) 6378c2ecf20Sopenharmony_ci EMU8000_SMLD_WRITE(rec->emu, 0); 6388c2ecf20Sopenharmony_ci for (ch = 0; ch < rec->voices; ch++) { 6398c2ecf20Sopenharmony_ci EMU8000_SMALW_WRITE(rec->emu, rec->loop_start[ch] + rec->buf_size); 6408c2ecf20Sopenharmony_ci for (i = 0; i < LOOP_BLANK_SIZE; i++) 6418c2ecf20Sopenharmony_ci EMU8000_SMLD_WRITE(rec->emu, 0); 6428c2ecf20Sopenharmony_ci } 6438c2ecf20Sopenharmony_ci } 6448c2ecf20Sopenharmony_ci 6458c2ecf20Sopenharmony_ci setup_voice(rec, 0); 6468c2ecf20Sopenharmony_ci if (rec->voices > 1) 6478c2ecf20Sopenharmony_ci setup_voice(rec, 1); 6488c2ecf20Sopenharmony_ci return 0; 6498c2ecf20Sopenharmony_ci} 6508c2ecf20Sopenharmony_ci 6518c2ecf20Sopenharmony_cistatic snd_pcm_uframes_t emu8k_pcm_pointer(struct snd_pcm_substream *subs) 6528c2ecf20Sopenharmony_ci{ 6538c2ecf20Sopenharmony_ci struct snd_emu8k_pcm *rec = subs->runtime->private_data; 6548c2ecf20Sopenharmony_ci if (rec->running) 6558c2ecf20Sopenharmony_ci return emu8k_get_curpos(rec, 0); 6568c2ecf20Sopenharmony_ci return 0; 6578c2ecf20Sopenharmony_ci} 6588c2ecf20Sopenharmony_ci 6598c2ecf20Sopenharmony_ci 6608c2ecf20Sopenharmony_cistatic const struct snd_pcm_ops emu8k_pcm_ops = { 6618c2ecf20Sopenharmony_ci .open = emu8k_pcm_open, 6628c2ecf20Sopenharmony_ci .close = emu8k_pcm_close, 6638c2ecf20Sopenharmony_ci .hw_params = emu8k_pcm_hw_params, 6648c2ecf20Sopenharmony_ci .hw_free = emu8k_pcm_hw_free, 6658c2ecf20Sopenharmony_ci .prepare = emu8k_pcm_prepare, 6668c2ecf20Sopenharmony_ci .trigger = emu8k_pcm_trigger, 6678c2ecf20Sopenharmony_ci .pointer = emu8k_pcm_pointer, 6688c2ecf20Sopenharmony_ci .copy_user = emu8k_pcm_copy, 6698c2ecf20Sopenharmony_ci .copy_kernel = emu8k_pcm_copy_kernel, 6708c2ecf20Sopenharmony_ci .fill_silence = emu8k_pcm_silence, 6718c2ecf20Sopenharmony_ci}; 6728c2ecf20Sopenharmony_ci 6738c2ecf20Sopenharmony_ci 6748c2ecf20Sopenharmony_cistatic void snd_emu8000_pcm_free(struct snd_pcm *pcm) 6758c2ecf20Sopenharmony_ci{ 6768c2ecf20Sopenharmony_ci struct snd_emu8000 *emu = pcm->private_data; 6778c2ecf20Sopenharmony_ci emu->pcm = NULL; 6788c2ecf20Sopenharmony_ci} 6798c2ecf20Sopenharmony_ci 6808c2ecf20Sopenharmony_ciint snd_emu8000_pcm_new(struct snd_card *card, struct snd_emu8000 *emu, int index) 6818c2ecf20Sopenharmony_ci{ 6828c2ecf20Sopenharmony_ci struct snd_pcm *pcm; 6838c2ecf20Sopenharmony_ci int err; 6848c2ecf20Sopenharmony_ci 6858c2ecf20Sopenharmony_ci if ((err = snd_pcm_new(card, "Emu8000 PCM", index, 1, 0, &pcm)) < 0) 6868c2ecf20Sopenharmony_ci return err; 6878c2ecf20Sopenharmony_ci pcm->private_data = emu; 6888c2ecf20Sopenharmony_ci pcm->private_free = snd_emu8000_pcm_free; 6898c2ecf20Sopenharmony_ci snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &emu8k_pcm_ops); 6908c2ecf20Sopenharmony_ci emu->pcm = pcm; 6918c2ecf20Sopenharmony_ci 6928c2ecf20Sopenharmony_ci snd_device_register(card, pcm); 6938c2ecf20Sopenharmony_ci 6948c2ecf20Sopenharmony_ci return 0; 6958c2ecf20Sopenharmony_ci} 696