// SPDX-License-Identifier: GPL-2.0-or-later /* * Patch transfer callback for Emu10k1 * * Copyright (C) 2000 Takashi iwai */ /* * All the code for loading in a patch. There is very little that is * chip specific here. Just the actual writing to the board. */ #include "emu10k1_synth_local.h" /* */ #define BLANK_LOOP_START 4 #define BLANK_LOOP_END 8 #define BLANK_LOOP_SIZE 12 #define BLANK_HEAD_SIZE 3 /* * allocate a sample block and copy data from userspace */ int snd_emu10k1_sample_new(struct snd_emux *rec, struct snd_sf_sample *sp, struct snd_util_memhdr *hdr, const void __user *data, long count) { u8 fill; u32 xor; int shift; int offset; int truesize, size, blocksize; int loop_start, loop_end, loop_size, data_end, unroll; struct snd_emu10k1 *emu; emu = rec->hw; if (snd_BUG_ON(!sp || !hdr)) return -EINVAL; if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP | SNDRV_SFNT_SAMPLE_REVERSE_LOOP)) { /* should instead return -ENOTSUPP; but compatibility */ printk(KERN_WARNING "Emu10k1 wavetable patch %d with unsupported loop feature\n", sp->v.sample); } if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS) { shift = 0; fill = 0x80; xor = (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_UNSIGNED) ? 0 : 0x80808080; } else { shift = 1; fill = 0; xor = (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_UNSIGNED) ? 0x80008000 : 0; } /* compute true data size to be loaded */ truesize = sp->v.size + BLANK_HEAD_SIZE; if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK) { truesize += BLANK_LOOP_SIZE; /* if no blank loop is attached in the sample, add it */ if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_SINGLESHOT) { sp->v.loopstart = sp->v.end + BLANK_LOOP_START; sp->v.loopend = sp->v.end + BLANK_LOOP_END; } } loop_start = sp->v.loopstart; loop_end = sp->v.loopend; loop_size = loop_end - loop_start; if (!loop_size) return -EINVAL; data_end = sp->v.end; /* recalculate offset */ sp->v.start += BLANK_HEAD_SIZE; sp->v.end += BLANK_HEAD_SIZE; sp->v.loopstart += BLANK_HEAD_SIZE; sp->v.loopend += BLANK_HEAD_SIZE; // Automatic pre-filling of the cache does not work in the presence // of loops (*), and we don't want to fill it manually, as that is // fiddly and slow. So we unroll the loop until the loop end is // beyond the cache size. // (*) Strictly speaking, a single iteration is supported (that's // how it works when the playback engine runs), but handling this // special case is not worth it. unroll = 0; while (sp->v.loopend < 64) { truesize += loop_size; sp->v.loopstart += loop_size; sp->v.loopend += loop_size; sp->v.end += loop_size; unroll++; } /* try to allocate a memory block */ blocksize = truesize << shift; sp->block = snd_emu10k1_synth_alloc(emu, blocksize); if (sp->block == NULL) { dev_dbg(emu->card->dev, "synth malloc failed (size=%d)\n", blocksize); /* not ENOMEM (for compatibility with OSS) */ return -ENOSPC; } /* set the total size */ sp->v.truesize = blocksize; /* write blank samples at head */ offset = 0; size = BLANK_HEAD_SIZE << shift; snd_emu10k1_synth_memset(emu, sp->block, offset, size, fill); offset += size; /* copy provided samples */ if (unroll && loop_end <= data_end) { size = loop_end << shift; if (snd_emu10k1_synth_copy_from_user(emu, sp->block, offset, data, size, xor)) goto faulty; offset += size; data += loop_start << shift; while (--unroll > 0) { size = loop_size << shift; if (snd_emu10k1_synth_copy_from_user(emu, sp->block, offset, data, size, xor)) goto faulty; offset += size; } size = (data_end - loop_start) << shift; } else { size = data_end << shift; } if (snd_emu10k1_synth_copy_from_user(emu, sp->block, offset, data, size, xor)) goto faulty; offset += size; /* clear rest of samples (if any) */ if (offset < blocksize) snd_emu10k1_synth_memset(emu, sp->block, offset, blocksize - offset, fill); return 0; faulty: snd_emu10k1_synth_free(emu, sp->block); sp->block = NULL; return -EFAULT; } /* * free a sample block */ int snd_emu10k1_sample_free(struct snd_emux *rec, struct snd_sf_sample *sp, struct snd_util_memhdr *hdr) { struct snd_emu10k1 *emu; emu = rec->hw; if (snd_BUG_ON(!sp || !hdr)) return -EINVAL; if (sp->block) { snd_emu10k1_synth_free(emu, sp->block); sp->block = NULL; } return 0; }