18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 1999 by Uros Bizjak <uros@kss-loka.si> 48c2ecf20Sopenharmony_ci * Takashi Iwai <tiwai@suse.de> 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * SB16ASP/AWE32 CSP control 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * CSP microcode loader: 98c2ecf20Sopenharmony_ci * alsa-tools/sb16_csp/ 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/delay.h> 138c2ecf20Sopenharmony_ci#include <linux/init.h> 148c2ecf20Sopenharmony_ci#include <linux/slab.h> 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci#include <sound/core.h> 178c2ecf20Sopenharmony_ci#include <sound/control.h> 188c2ecf20Sopenharmony_ci#include <sound/info.h> 198c2ecf20Sopenharmony_ci#include <sound/sb16_csp.h> 208c2ecf20Sopenharmony_ci#include <sound/initval.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ciMODULE_AUTHOR("Uros Bizjak <uros@kss-loka.si>"); 238c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ALSA driver for SB16 Creative Signal Processor"); 248c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 258c2ecf20Sopenharmony_ciMODULE_FIRMWARE("sb16/mulaw_main.csp"); 268c2ecf20Sopenharmony_ciMODULE_FIRMWARE("sb16/alaw_main.csp"); 278c2ecf20Sopenharmony_ciMODULE_FIRMWARE("sb16/ima_adpcm_init.csp"); 288c2ecf20Sopenharmony_ciMODULE_FIRMWARE("sb16/ima_adpcm_playback.csp"); 298c2ecf20Sopenharmony_ciMODULE_FIRMWARE("sb16/ima_adpcm_capture.csp"); 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci#ifdef SNDRV_LITTLE_ENDIAN 328c2ecf20Sopenharmony_ci#define CSP_HDR_VALUE(a,b,c,d) ((a) | ((b)<<8) | ((c)<<16) | ((d)<<24)) 338c2ecf20Sopenharmony_ci#else 348c2ecf20Sopenharmony_ci#define CSP_HDR_VALUE(a,b,c,d) ((d) | ((c)<<8) | ((b)<<16) | ((a)<<24)) 358c2ecf20Sopenharmony_ci#endif 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci#define RIFF_HEADER CSP_HDR_VALUE('R', 'I', 'F', 'F') 388c2ecf20Sopenharmony_ci#define CSP__HEADER CSP_HDR_VALUE('C', 'S', 'P', ' ') 398c2ecf20Sopenharmony_ci#define LIST_HEADER CSP_HDR_VALUE('L', 'I', 'S', 'T') 408c2ecf20Sopenharmony_ci#define FUNC_HEADER CSP_HDR_VALUE('f', 'u', 'n', 'c') 418c2ecf20Sopenharmony_ci#define CODE_HEADER CSP_HDR_VALUE('c', 'o', 'd', 'e') 428c2ecf20Sopenharmony_ci#define INIT_HEADER CSP_HDR_VALUE('i', 'n', 'i', 't') 438c2ecf20Sopenharmony_ci#define MAIN_HEADER CSP_HDR_VALUE('m', 'a', 'i', 'n') 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci/* 468c2ecf20Sopenharmony_ci * RIFF data format 478c2ecf20Sopenharmony_ci */ 488c2ecf20Sopenharmony_cistruct riff_header { 498c2ecf20Sopenharmony_ci __le32 name; 508c2ecf20Sopenharmony_ci __le32 len; 518c2ecf20Sopenharmony_ci}; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistruct desc_header { 548c2ecf20Sopenharmony_ci struct riff_header info; 558c2ecf20Sopenharmony_ci __le16 func_nr; 568c2ecf20Sopenharmony_ci __le16 VOC_type; 578c2ecf20Sopenharmony_ci __le16 flags_play_rec; 588c2ecf20Sopenharmony_ci __le16 flags_16bit_8bit; 598c2ecf20Sopenharmony_ci __le16 flags_stereo_mono; 608c2ecf20Sopenharmony_ci __le16 flags_rates; 618c2ecf20Sopenharmony_ci}; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci/* 648c2ecf20Sopenharmony_ci * prototypes 658c2ecf20Sopenharmony_ci */ 668c2ecf20Sopenharmony_cistatic void snd_sb_csp_free(struct snd_hwdep *hw); 678c2ecf20Sopenharmony_cistatic int snd_sb_csp_open(struct snd_hwdep * hw, struct file *file); 688c2ecf20Sopenharmony_cistatic int snd_sb_csp_ioctl(struct snd_hwdep * hw, struct file *file, unsigned int cmd, unsigned long arg); 698c2ecf20Sopenharmony_cistatic int snd_sb_csp_release(struct snd_hwdep * hw, struct file *file); 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_cistatic int csp_detect(struct snd_sb *chip, int *version); 728c2ecf20Sopenharmony_cistatic int set_codec_parameter(struct snd_sb *chip, unsigned char par, unsigned char val); 738c2ecf20Sopenharmony_cistatic int set_register(struct snd_sb *chip, unsigned char reg, unsigned char val); 748c2ecf20Sopenharmony_cistatic int read_register(struct snd_sb *chip, unsigned char reg); 758c2ecf20Sopenharmony_cistatic int set_mode_register(struct snd_sb *chip, unsigned char mode); 768c2ecf20Sopenharmony_cistatic int get_version(struct snd_sb *chip); 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_cistatic int snd_sb_csp_riff_load(struct snd_sb_csp * p, 798c2ecf20Sopenharmony_ci struct snd_sb_csp_microcode __user * code); 808c2ecf20Sopenharmony_cistatic int snd_sb_csp_unload(struct snd_sb_csp * p); 818c2ecf20Sopenharmony_cistatic int snd_sb_csp_load_user(struct snd_sb_csp * p, const unsigned char __user *buf, int size, int load_flags); 828c2ecf20Sopenharmony_cistatic int snd_sb_csp_autoload(struct snd_sb_csp * p, snd_pcm_format_t pcm_sfmt, int play_rec_mode); 838c2ecf20Sopenharmony_cistatic int snd_sb_csp_check_version(struct snd_sb_csp * p); 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistatic int snd_sb_csp_use(struct snd_sb_csp * p); 868c2ecf20Sopenharmony_cistatic int snd_sb_csp_unuse(struct snd_sb_csp * p); 878c2ecf20Sopenharmony_cistatic int snd_sb_csp_start(struct snd_sb_csp * p, int sample_width, int channels); 888c2ecf20Sopenharmony_cistatic int snd_sb_csp_stop(struct snd_sb_csp * p); 898c2ecf20Sopenharmony_cistatic int snd_sb_csp_pause(struct snd_sb_csp * p); 908c2ecf20Sopenharmony_cistatic int snd_sb_csp_restart(struct snd_sb_csp * p); 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_cistatic int snd_sb_qsound_build(struct snd_sb_csp * p); 938c2ecf20Sopenharmony_cistatic void snd_sb_qsound_destroy(struct snd_sb_csp * p); 948c2ecf20Sopenharmony_cistatic int snd_sb_csp_qsound_transfer(struct snd_sb_csp * p); 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_cistatic int init_proc_entry(struct snd_sb_csp * p, int device); 978c2ecf20Sopenharmony_cistatic void info_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci/* 1008c2ecf20Sopenharmony_ci * Detect CSP chip and create a new instance 1018c2ecf20Sopenharmony_ci */ 1028c2ecf20Sopenharmony_ciint snd_sb_csp_new(struct snd_sb *chip, int device, struct snd_hwdep ** rhwdep) 1038c2ecf20Sopenharmony_ci{ 1048c2ecf20Sopenharmony_ci struct snd_sb_csp *p; 1058c2ecf20Sopenharmony_ci int version; 1068c2ecf20Sopenharmony_ci int err; 1078c2ecf20Sopenharmony_ci struct snd_hwdep *hw; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci if (rhwdep) 1108c2ecf20Sopenharmony_ci *rhwdep = NULL; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci if (csp_detect(chip, &version)) 1138c2ecf20Sopenharmony_ci return -ENODEV; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci if ((err = snd_hwdep_new(chip->card, "SB16-CSP", device, &hw)) < 0) 1168c2ecf20Sopenharmony_ci return err; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci if ((p = kzalloc(sizeof(*p), GFP_KERNEL)) == NULL) { 1198c2ecf20Sopenharmony_ci snd_device_free(chip->card, hw); 1208c2ecf20Sopenharmony_ci return -ENOMEM; 1218c2ecf20Sopenharmony_ci } 1228c2ecf20Sopenharmony_ci p->chip = chip; 1238c2ecf20Sopenharmony_ci p->version = version; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci /* CSP operators */ 1268c2ecf20Sopenharmony_ci p->ops.csp_use = snd_sb_csp_use; 1278c2ecf20Sopenharmony_ci p->ops.csp_unuse = snd_sb_csp_unuse; 1288c2ecf20Sopenharmony_ci p->ops.csp_autoload = snd_sb_csp_autoload; 1298c2ecf20Sopenharmony_ci p->ops.csp_start = snd_sb_csp_start; 1308c2ecf20Sopenharmony_ci p->ops.csp_stop = snd_sb_csp_stop; 1318c2ecf20Sopenharmony_ci p->ops.csp_qsound_transfer = snd_sb_csp_qsound_transfer; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci mutex_init(&p->access_mutex); 1348c2ecf20Sopenharmony_ci sprintf(hw->name, "CSP v%d.%d", (version >> 4), (version & 0x0f)); 1358c2ecf20Sopenharmony_ci hw->iface = SNDRV_HWDEP_IFACE_SB16CSP; 1368c2ecf20Sopenharmony_ci hw->private_data = p; 1378c2ecf20Sopenharmony_ci hw->private_free = snd_sb_csp_free; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci /* operators - only write/ioctl */ 1408c2ecf20Sopenharmony_ci hw->ops.open = snd_sb_csp_open; 1418c2ecf20Sopenharmony_ci hw->ops.ioctl = snd_sb_csp_ioctl; 1428c2ecf20Sopenharmony_ci hw->ops.release = snd_sb_csp_release; 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci /* create a proc entry */ 1458c2ecf20Sopenharmony_ci init_proc_entry(p, device); 1468c2ecf20Sopenharmony_ci if (rhwdep) 1478c2ecf20Sopenharmony_ci *rhwdep = hw; 1488c2ecf20Sopenharmony_ci return 0; 1498c2ecf20Sopenharmony_ci} 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci/* 1528c2ecf20Sopenharmony_ci * free_private for hwdep instance 1538c2ecf20Sopenharmony_ci */ 1548c2ecf20Sopenharmony_cistatic void snd_sb_csp_free(struct snd_hwdep *hwdep) 1558c2ecf20Sopenharmony_ci{ 1568c2ecf20Sopenharmony_ci int i; 1578c2ecf20Sopenharmony_ci struct snd_sb_csp *p = hwdep->private_data; 1588c2ecf20Sopenharmony_ci if (p) { 1598c2ecf20Sopenharmony_ci if (p->running & SNDRV_SB_CSP_ST_RUNNING) 1608c2ecf20Sopenharmony_ci snd_sb_csp_stop(p); 1618c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(p->csp_programs); ++i) 1628c2ecf20Sopenharmony_ci release_firmware(p->csp_programs[i]); 1638c2ecf20Sopenharmony_ci kfree(p); 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci} 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci/* ------------------------------ */ 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci/* 1708c2ecf20Sopenharmony_ci * open the device exclusively 1718c2ecf20Sopenharmony_ci */ 1728c2ecf20Sopenharmony_cistatic int snd_sb_csp_open(struct snd_hwdep * hw, struct file *file) 1738c2ecf20Sopenharmony_ci{ 1748c2ecf20Sopenharmony_ci struct snd_sb_csp *p = hw->private_data; 1758c2ecf20Sopenharmony_ci return (snd_sb_csp_use(p)); 1768c2ecf20Sopenharmony_ci} 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci/* 1798c2ecf20Sopenharmony_ci * ioctl for hwdep device: 1808c2ecf20Sopenharmony_ci */ 1818c2ecf20Sopenharmony_cistatic int snd_sb_csp_ioctl(struct snd_hwdep * hw, struct file *file, unsigned int cmd, unsigned long arg) 1828c2ecf20Sopenharmony_ci{ 1838c2ecf20Sopenharmony_ci struct snd_sb_csp *p = hw->private_data; 1848c2ecf20Sopenharmony_ci struct snd_sb_csp_info info; 1858c2ecf20Sopenharmony_ci struct snd_sb_csp_start start_info; 1868c2ecf20Sopenharmony_ci int err; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci if (snd_BUG_ON(!p)) 1898c2ecf20Sopenharmony_ci return -EINVAL; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci if (snd_sb_csp_check_version(p)) 1928c2ecf20Sopenharmony_ci return -ENODEV; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci switch (cmd) { 1958c2ecf20Sopenharmony_ci /* get information */ 1968c2ecf20Sopenharmony_ci case SNDRV_SB_CSP_IOCTL_INFO: 1978c2ecf20Sopenharmony_ci memset(&info, 0, sizeof(info)); 1988c2ecf20Sopenharmony_ci *info.codec_name = *p->codec_name; 1998c2ecf20Sopenharmony_ci info.func_nr = p->func_nr; 2008c2ecf20Sopenharmony_ci info.acc_format = p->acc_format; 2018c2ecf20Sopenharmony_ci info.acc_channels = p->acc_channels; 2028c2ecf20Sopenharmony_ci info.acc_width = p->acc_width; 2038c2ecf20Sopenharmony_ci info.acc_rates = p->acc_rates; 2048c2ecf20Sopenharmony_ci info.csp_mode = p->mode; 2058c2ecf20Sopenharmony_ci info.run_channels = p->run_channels; 2068c2ecf20Sopenharmony_ci info.run_width = p->run_width; 2078c2ecf20Sopenharmony_ci info.version = p->version; 2088c2ecf20Sopenharmony_ci info.state = p->running; 2098c2ecf20Sopenharmony_ci if (copy_to_user((void __user *)arg, &info, sizeof(info))) 2108c2ecf20Sopenharmony_ci err = -EFAULT; 2118c2ecf20Sopenharmony_ci else 2128c2ecf20Sopenharmony_ci err = 0; 2138c2ecf20Sopenharmony_ci break; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci /* load CSP microcode */ 2168c2ecf20Sopenharmony_ci case SNDRV_SB_CSP_IOCTL_LOAD_CODE: 2178c2ecf20Sopenharmony_ci err = (p->running & SNDRV_SB_CSP_ST_RUNNING ? 2188c2ecf20Sopenharmony_ci -EBUSY : snd_sb_csp_riff_load(p, (struct snd_sb_csp_microcode __user *) arg)); 2198c2ecf20Sopenharmony_ci break; 2208c2ecf20Sopenharmony_ci case SNDRV_SB_CSP_IOCTL_UNLOAD_CODE: 2218c2ecf20Sopenharmony_ci err = (p->running & SNDRV_SB_CSP_ST_RUNNING ? 2228c2ecf20Sopenharmony_ci -EBUSY : snd_sb_csp_unload(p)); 2238c2ecf20Sopenharmony_ci break; 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci /* change CSP running state */ 2268c2ecf20Sopenharmony_ci case SNDRV_SB_CSP_IOCTL_START: 2278c2ecf20Sopenharmony_ci if (copy_from_user(&start_info, (void __user *) arg, sizeof(start_info))) 2288c2ecf20Sopenharmony_ci err = -EFAULT; 2298c2ecf20Sopenharmony_ci else 2308c2ecf20Sopenharmony_ci err = snd_sb_csp_start(p, start_info.sample_width, start_info.channels); 2318c2ecf20Sopenharmony_ci break; 2328c2ecf20Sopenharmony_ci case SNDRV_SB_CSP_IOCTL_STOP: 2338c2ecf20Sopenharmony_ci err = snd_sb_csp_stop(p); 2348c2ecf20Sopenharmony_ci break; 2358c2ecf20Sopenharmony_ci case SNDRV_SB_CSP_IOCTL_PAUSE: 2368c2ecf20Sopenharmony_ci err = snd_sb_csp_pause(p); 2378c2ecf20Sopenharmony_ci break; 2388c2ecf20Sopenharmony_ci case SNDRV_SB_CSP_IOCTL_RESTART: 2398c2ecf20Sopenharmony_ci err = snd_sb_csp_restart(p); 2408c2ecf20Sopenharmony_ci break; 2418c2ecf20Sopenharmony_ci default: 2428c2ecf20Sopenharmony_ci err = -ENOTTY; 2438c2ecf20Sopenharmony_ci break; 2448c2ecf20Sopenharmony_ci } 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci return err; 2478c2ecf20Sopenharmony_ci} 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci/* 2508c2ecf20Sopenharmony_ci * close the device 2518c2ecf20Sopenharmony_ci */ 2528c2ecf20Sopenharmony_cistatic int snd_sb_csp_release(struct snd_hwdep * hw, struct file *file) 2538c2ecf20Sopenharmony_ci{ 2548c2ecf20Sopenharmony_ci struct snd_sb_csp *p = hw->private_data; 2558c2ecf20Sopenharmony_ci return (snd_sb_csp_unuse(p)); 2568c2ecf20Sopenharmony_ci} 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci/* ------------------------------ */ 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci/* 2618c2ecf20Sopenharmony_ci * acquire device 2628c2ecf20Sopenharmony_ci */ 2638c2ecf20Sopenharmony_cistatic int snd_sb_csp_use(struct snd_sb_csp * p) 2648c2ecf20Sopenharmony_ci{ 2658c2ecf20Sopenharmony_ci mutex_lock(&p->access_mutex); 2668c2ecf20Sopenharmony_ci if (p->used) { 2678c2ecf20Sopenharmony_ci mutex_unlock(&p->access_mutex); 2688c2ecf20Sopenharmony_ci return -EAGAIN; 2698c2ecf20Sopenharmony_ci } 2708c2ecf20Sopenharmony_ci p->used++; 2718c2ecf20Sopenharmony_ci mutex_unlock(&p->access_mutex); 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci return 0; 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci} 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci/* 2788c2ecf20Sopenharmony_ci * release device 2798c2ecf20Sopenharmony_ci */ 2808c2ecf20Sopenharmony_cistatic int snd_sb_csp_unuse(struct snd_sb_csp * p) 2818c2ecf20Sopenharmony_ci{ 2828c2ecf20Sopenharmony_ci mutex_lock(&p->access_mutex); 2838c2ecf20Sopenharmony_ci p->used--; 2848c2ecf20Sopenharmony_ci mutex_unlock(&p->access_mutex); 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci return 0; 2878c2ecf20Sopenharmony_ci} 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci/* 2908c2ecf20Sopenharmony_ci * load microcode via ioctl: 2918c2ecf20Sopenharmony_ci * code is user-space pointer 2928c2ecf20Sopenharmony_ci */ 2938c2ecf20Sopenharmony_cistatic int snd_sb_csp_riff_load(struct snd_sb_csp * p, 2948c2ecf20Sopenharmony_ci struct snd_sb_csp_microcode __user * mcode) 2958c2ecf20Sopenharmony_ci{ 2968c2ecf20Sopenharmony_ci struct snd_sb_csp_mc_header info; 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ci unsigned char __user *data_ptr; 2998c2ecf20Sopenharmony_ci unsigned char __user *data_end; 3008c2ecf20Sopenharmony_ci unsigned short func_nr = 0; 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ci struct riff_header file_h, item_h, code_h; 3038c2ecf20Sopenharmony_ci __le32 item_type; 3048c2ecf20Sopenharmony_ci struct desc_header funcdesc_h; 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_ci unsigned long flags; 3078c2ecf20Sopenharmony_ci int err; 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci if (copy_from_user(&info, mcode, sizeof(info))) 3108c2ecf20Sopenharmony_ci return -EFAULT; 3118c2ecf20Sopenharmony_ci data_ptr = mcode->data; 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_ci if (copy_from_user(&file_h, data_ptr, sizeof(file_h))) 3148c2ecf20Sopenharmony_ci return -EFAULT; 3158c2ecf20Sopenharmony_ci if ((le32_to_cpu(file_h.name) != RIFF_HEADER) || 3168c2ecf20Sopenharmony_ci (le32_to_cpu(file_h.len) >= SNDRV_SB_CSP_MAX_MICROCODE_FILE_SIZE - sizeof(file_h))) { 3178c2ecf20Sopenharmony_ci snd_printd("%s: Invalid RIFF header\n", __func__); 3188c2ecf20Sopenharmony_ci return -EINVAL; 3198c2ecf20Sopenharmony_ci } 3208c2ecf20Sopenharmony_ci data_ptr += sizeof(file_h); 3218c2ecf20Sopenharmony_ci data_end = data_ptr + le32_to_cpu(file_h.len); 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_ci if (copy_from_user(&item_type, data_ptr, sizeof(item_type))) 3248c2ecf20Sopenharmony_ci return -EFAULT; 3258c2ecf20Sopenharmony_ci if (le32_to_cpu(item_type) != CSP__HEADER) { 3268c2ecf20Sopenharmony_ci snd_printd("%s: Invalid RIFF file type\n", __func__); 3278c2ecf20Sopenharmony_ci return -EINVAL; 3288c2ecf20Sopenharmony_ci } 3298c2ecf20Sopenharmony_ci data_ptr += sizeof (item_type); 3308c2ecf20Sopenharmony_ci 3318c2ecf20Sopenharmony_ci for (; data_ptr < data_end; data_ptr += le32_to_cpu(item_h.len)) { 3328c2ecf20Sopenharmony_ci if (copy_from_user(&item_h, data_ptr, sizeof(item_h))) 3338c2ecf20Sopenharmony_ci return -EFAULT; 3348c2ecf20Sopenharmony_ci data_ptr += sizeof(item_h); 3358c2ecf20Sopenharmony_ci if (le32_to_cpu(item_h.name) != LIST_HEADER) 3368c2ecf20Sopenharmony_ci continue; 3378c2ecf20Sopenharmony_ci 3388c2ecf20Sopenharmony_ci if (copy_from_user(&item_type, data_ptr, sizeof(item_type))) 3398c2ecf20Sopenharmony_ci return -EFAULT; 3408c2ecf20Sopenharmony_ci switch (le32_to_cpu(item_type)) { 3418c2ecf20Sopenharmony_ci case FUNC_HEADER: 3428c2ecf20Sopenharmony_ci if (copy_from_user(&funcdesc_h, data_ptr + sizeof(item_type), sizeof(funcdesc_h))) 3438c2ecf20Sopenharmony_ci return -EFAULT; 3448c2ecf20Sopenharmony_ci func_nr = le16_to_cpu(funcdesc_h.func_nr); 3458c2ecf20Sopenharmony_ci break; 3468c2ecf20Sopenharmony_ci case CODE_HEADER: 3478c2ecf20Sopenharmony_ci if (func_nr != info.func_req) 3488c2ecf20Sopenharmony_ci break; /* not required function, try next */ 3498c2ecf20Sopenharmony_ci data_ptr += sizeof(item_type); 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_ci /* destroy QSound mixer element */ 3528c2ecf20Sopenharmony_ci if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) { 3538c2ecf20Sopenharmony_ci snd_sb_qsound_destroy(p); 3548c2ecf20Sopenharmony_ci } 3558c2ecf20Sopenharmony_ci /* Clear all flags */ 3568c2ecf20Sopenharmony_ci p->running = 0; 3578c2ecf20Sopenharmony_ci p->mode = 0; 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci /* load microcode blocks */ 3608c2ecf20Sopenharmony_ci for (;;) { 3618c2ecf20Sopenharmony_ci if (data_ptr >= data_end) 3628c2ecf20Sopenharmony_ci return -EINVAL; 3638c2ecf20Sopenharmony_ci if (copy_from_user(&code_h, data_ptr, sizeof(code_h))) 3648c2ecf20Sopenharmony_ci return -EFAULT; 3658c2ecf20Sopenharmony_ci 3668c2ecf20Sopenharmony_ci /* init microcode blocks */ 3678c2ecf20Sopenharmony_ci if (le32_to_cpu(code_h.name) != INIT_HEADER) 3688c2ecf20Sopenharmony_ci break; 3698c2ecf20Sopenharmony_ci data_ptr += sizeof(code_h); 3708c2ecf20Sopenharmony_ci err = snd_sb_csp_load_user(p, data_ptr, le32_to_cpu(code_h.len), 3718c2ecf20Sopenharmony_ci SNDRV_SB_CSP_LOAD_INITBLOCK); 3728c2ecf20Sopenharmony_ci if (err) 3738c2ecf20Sopenharmony_ci return err; 3748c2ecf20Sopenharmony_ci data_ptr += le32_to_cpu(code_h.len); 3758c2ecf20Sopenharmony_ci } 3768c2ecf20Sopenharmony_ci /* main microcode block */ 3778c2ecf20Sopenharmony_ci if (copy_from_user(&code_h, data_ptr, sizeof(code_h))) 3788c2ecf20Sopenharmony_ci return -EFAULT; 3798c2ecf20Sopenharmony_ci 3808c2ecf20Sopenharmony_ci if (le32_to_cpu(code_h.name) != MAIN_HEADER) { 3818c2ecf20Sopenharmony_ci snd_printd("%s: Missing 'main' microcode\n", __func__); 3828c2ecf20Sopenharmony_ci return -EINVAL; 3838c2ecf20Sopenharmony_ci } 3848c2ecf20Sopenharmony_ci data_ptr += sizeof(code_h); 3858c2ecf20Sopenharmony_ci err = snd_sb_csp_load_user(p, data_ptr, 3868c2ecf20Sopenharmony_ci le32_to_cpu(code_h.len), 0); 3878c2ecf20Sopenharmony_ci if (err) 3888c2ecf20Sopenharmony_ci return err; 3898c2ecf20Sopenharmony_ci 3908c2ecf20Sopenharmony_ci /* fill in codec header */ 3918c2ecf20Sopenharmony_ci strlcpy(p->codec_name, info.codec_name, sizeof(p->codec_name)); 3928c2ecf20Sopenharmony_ci p->func_nr = func_nr; 3938c2ecf20Sopenharmony_ci p->mode = le16_to_cpu(funcdesc_h.flags_play_rec); 3948c2ecf20Sopenharmony_ci switch (le16_to_cpu(funcdesc_h.VOC_type)) { 3958c2ecf20Sopenharmony_ci case 0x0001: /* QSound decoder */ 3968c2ecf20Sopenharmony_ci if (le16_to_cpu(funcdesc_h.flags_play_rec) == SNDRV_SB_CSP_MODE_DSP_WRITE) { 3978c2ecf20Sopenharmony_ci if (snd_sb_qsound_build(p) == 0) 3988c2ecf20Sopenharmony_ci /* set QSound flag and clear all other mode flags */ 3998c2ecf20Sopenharmony_ci p->mode = SNDRV_SB_CSP_MODE_QSOUND; 4008c2ecf20Sopenharmony_ci } 4018c2ecf20Sopenharmony_ci p->acc_format = 0; 4028c2ecf20Sopenharmony_ci break; 4038c2ecf20Sopenharmony_ci case 0x0006: /* A Law codec */ 4048c2ecf20Sopenharmony_ci p->acc_format = SNDRV_PCM_FMTBIT_A_LAW; 4058c2ecf20Sopenharmony_ci break; 4068c2ecf20Sopenharmony_ci case 0x0007: /* Mu Law codec */ 4078c2ecf20Sopenharmony_ci p->acc_format = SNDRV_PCM_FMTBIT_MU_LAW; 4088c2ecf20Sopenharmony_ci break; 4098c2ecf20Sopenharmony_ci case 0x0011: /* what Creative thinks is IMA ADPCM codec */ 4108c2ecf20Sopenharmony_ci case 0x0200: /* Creative ADPCM codec */ 4118c2ecf20Sopenharmony_ci p->acc_format = SNDRV_PCM_FMTBIT_IMA_ADPCM; 4128c2ecf20Sopenharmony_ci break; 4138c2ecf20Sopenharmony_ci case 201: /* Text 2 Speech decoder */ 4148c2ecf20Sopenharmony_ci /* TODO: Text2Speech handling routines */ 4158c2ecf20Sopenharmony_ci p->acc_format = 0; 4168c2ecf20Sopenharmony_ci break; 4178c2ecf20Sopenharmony_ci case 0x0202: /* Fast Speech 8 codec */ 4188c2ecf20Sopenharmony_ci case 0x0203: /* Fast Speech 10 codec */ 4198c2ecf20Sopenharmony_ci p->acc_format = SNDRV_PCM_FMTBIT_SPECIAL; 4208c2ecf20Sopenharmony_ci break; 4218c2ecf20Sopenharmony_ci default: /* other codecs are unsupported */ 4228c2ecf20Sopenharmony_ci p->acc_format = p->acc_width = p->acc_rates = 0; 4238c2ecf20Sopenharmony_ci p->mode = 0; 4248c2ecf20Sopenharmony_ci snd_printd("%s: Unsupported CSP codec type: 0x%04x\n", 4258c2ecf20Sopenharmony_ci __func__, 4268c2ecf20Sopenharmony_ci le16_to_cpu(funcdesc_h.VOC_type)); 4278c2ecf20Sopenharmony_ci return -EINVAL; 4288c2ecf20Sopenharmony_ci } 4298c2ecf20Sopenharmony_ci p->acc_channels = le16_to_cpu(funcdesc_h.flags_stereo_mono); 4308c2ecf20Sopenharmony_ci p->acc_width = le16_to_cpu(funcdesc_h.flags_16bit_8bit); 4318c2ecf20Sopenharmony_ci p->acc_rates = le16_to_cpu(funcdesc_h.flags_rates); 4328c2ecf20Sopenharmony_ci 4338c2ecf20Sopenharmony_ci /* Decouple CSP from IRQ and DMAREQ lines */ 4348c2ecf20Sopenharmony_ci spin_lock_irqsave(&p->chip->reg_lock, flags); 4358c2ecf20Sopenharmony_ci set_mode_register(p->chip, 0xfc); 4368c2ecf20Sopenharmony_ci set_mode_register(p->chip, 0x00); 4378c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&p->chip->reg_lock, flags); 4388c2ecf20Sopenharmony_ci 4398c2ecf20Sopenharmony_ci /* finished loading successfully */ 4408c2ecf20Sopenharmony_ci p->running = SNDRV_SB_CSP_ST_LOADED; /* set LOADED flag */ 4418c2ecf20Sopenharmony_ci return 0; 4428c2ecf20Sopenharmony_ci } 4438c2ecf20Sopenharmony_ci } 4448c2ecf20Sopenharmony_ci snd_printd("%s: Function #%d not found\n", __func__, info.func_req); 4458c2ecf20Sopenharmony_ci return -EINVAL; 4468c2ecf20Sopenharmony_ci} 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_ci/* 4498c2ecf20Sopenharmony_ci * unload CSP microcode 4508c2ecf20Sopenharmony_ci */ 4518c2ecf20Sopenharmony_cistatic int snd_sb_csp_unload(struct snd_sb_csp * p) 4528c2ecf20Sopenharmony_ci{ 4538c2ecf20Sopenharmony_ci if (p->running & SNDRV_SB_CSP_ST_RUNNING) 4548c2ecf20Sopenharmony_ci return -EBUSY; 4558c2ecf20Sopenharmony_ci if (!(p->running & SNDRV_SB_CSP_ST_LOADED)) 4568c2ecf20Sopenharmony_ci return -ENXIO; 4578c2ecf20Sopenharmony_ci 4588c2ecf20Sopenharmony_ci /* clear supported formats */ 4598c2ecf20Sopenharmony_ci p->acc_format = 0; 4608c2ecf20Sopenharmony_ci p->acc_channels = p->acc_width = p->acc_rates = 0; 4618c2ecf20Sopenharmony_ci /* destroy QSound mixer element */ 4628c2ecf20Sopenharmony_ci if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) { 4638c2ecf20Sopenharmony_ci snd_sb_qsound_destroy(p); 4648c2ecf20Sopenharmony_ci } 4658c2ecf20Sopenharmony_ci /* clear all flags */ 4668c2ecf20Sopenharmony_ci p->running = 0; 4678c2ecf20Sopenharmony_ci p->mode = 0; 4688c2ecf20Sopenharmony_ci return 0; 4698c2ecf20Sopenharmony_ci} 4708c2ecf20Sopenharmony_ci 4718c2ecf20Sopenharmony_ci/* 4728c2ecf20Sopenharmony_ci * send command sequence to DSP 4738c2ecf20Sopenharmony_ci */ 4748c2ecf20Sopenharmony_cistatic inline int command_seq(struct snd_sb *chip, const unsigned char *seq, int size) 4758c2ecf20Sopenharmony_ci{ 4768c2ecf20Sopenharmony_ci int i; 4778c2ecf20Sopenharmony_ci for (i = 0; i < size; i++) { 4788c2ecf20Sopenharmony_ci if (!snd_sbdsp_command(chip, seq[i])) 4798c2ecf20Sopenharmony_ci return -EIO; 4808c2ecf20Sopenharmony_ci } 4818c2ecf20Sopenharmony_ci return 0; 4828c2ecf20Sopenharmony_ci} 4838c2ecf20Sopenharmony_ci 4848c2ecf20Sopenharmony_ci/* 4858c2ecf20Sopenharmony_ci * set CSP codec parameter 4868c2ecf20Sopenharmony_ci */ 4878c2ecf20Sopenharmony_cistatic int set_codec_parameter(struct snd_sb *chip, unsigned char par, unsigned char val) 4888c2ecf20Sopenharmony_ci{ 4898c2ecf20Sopenharmony_ci unsigned char dsp_cmd[3]; 4908c2ecf20Sopenharmony_ci 4918c2ecf20Sopenharmony_ci dsp_cmd[0] = 0x05; /* CSP set codec parameter */ 4928c2ecf20Sopenharmony_ci dsp_cmd[1] = val; /* Parameter value */ 4938c2ecf20Sopenharmony_ci dsp_cmd[2] = par; /* Parameter */ 4948c2ecf20Sopenharmony_ci command_seq(chip, dsp_cmd, 3); 4958c2ecf20Sopenharmony_ci snd_sbdsp_command(chip, 0x03); /* DSP read? */ 4968c2ecf20Sopenharmony_ci if (snd_sbdsp_get_byte(chip) != par) 4978c2ecf20Sopenharmony_ci return -EIO; 4988c2ecf20Sopenharmony_ci return 0; 4998c2ecf20Sopenharmony_ci} 5008c2ecf20Sopenharmony_ci 5018c2ecf20Sopenharmony_ci/* 5028c2ecf20Sopenharmony_ci * set CSP register 5038c2ecf20Sopenharmony_ci */ 5048c2ecf20Sopenharmony_cistatic int set_register(struct snd_sb *chip, unsigned char reg, unsigned char val) 5058c2ecf20Sopenharmony_ci{ 5068c2ecf20Sopenharmony_ci unsigned char dsp_cmd[3]; 5078c2ecf20Sopenharmony_ci 5088c2ecf20Sopenharmony_ci dsp_cmd[0] = 0x0e; /* CSP set register */ 5098c2ecf20Sopenharmony_ci dsp_cmd[1] = reg; /* CSP Register */ 5108c2ecf20Sopenharmony_ci dsp_cmd[2] = val; /* value */ 5118c2ecf20Sopenharmony_ci return command_seq(chip, dsp_cmd, 3); 5128c2ecf20Sopenharmony_ci} 5138c2ecf20Sopenharmony_ci 5148c2ecf20Sopenharmony_ci/* 5158c2ecf20Sopenharmony_ci * read CSP register 5168c2ecf20Sopenharmony_ci * return < 0 -> error 5178c2ecf20Sopenharmony_ci */ 5188c2ecf20Sopenharmony_cistatic int read_register(struct snd_sb *chip, unsigned char reg) 5198c2ecf20Sopenharmony_ci{ 5208c2ecf20Sopenharmony_ci unsigned char dsp_cmd[2]; 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_ci dsp_cmd[0] = 0x0f; /* CSP read register */ 5238c2ecf20Sopenharmony_ci dsp_cmd[1] = reg; /* CSP Register */ 5248c2ecf20Sopenharmony_ci command_seq(chip, dsp_cmd, 2); 5258c2ecf20Sopenharmony_ci return snd_sbdsp_get_byte(chip); /* Read DSP value */ 5268c2ecf20Sopenharmony_ci} 5278c2ecf20Sopenharmony_ci 5288c2ecf20Sopenharmony_ci/* 5298c2ecf20Sopenharmony_ci * set CSP mode register 5308c2ecf20Sopenharmony_ci */ 5318c2ecf20Sopenharmony_cistatic int set_mode_register(struct snd_sb *chip, unsigned char mode) 5328c2ecf20Sopenharmony_ci{ 5338c2ecf20Sopenharmony_ci unsigned char dsp_cmd[2]; 5348c2ecf20Sopenharmony_ci 5358c2ecf20Sopenharmony_ci dsp_cmd[0] = 0x04; /* CSP set mode register */ 5368c2ecf20Sopenharmony_ci dsp_cmd[1] = mode; /* mode */ 5378c2ecf20Sopenharmony_ci return command_seq(chip, dsp_cmd, 2); 5388c2ecf20Sopenharmony_ci} 5398c2ecf20Sopenharmony_ci 5408c2ecf20Sopenharmony_ci/* 5418c2ecf20Sopenharmony_ci * Detect CSP 5428c2ecf20Sopenharmony_ci * return 0 if CSP exists. 5438c2ecf20Sopenharmony_ci */ 5448c2ecf20Sopenharmony_cistatic int csp_detect(struct snd_sb *chip, int *version) 5458c2ecf20Sopenharmony_ci{ 5468c2ecf20Sopenharmony_ci unsigned char csp_test1, csp_test2; 5478c2ecf20Sopenharmony_ci unsigned long flags; 5488c2ecf20Sopenharmony_ci int result = -ENODEV; 5498c2ecf20Sopenharmony_ci 5508c2ecf20Sopenharmony_ci spin_lock_irqsave(&chip->reg_lock, flags); 5518c2ecf20Sopenharmony_ci 5528c2ecf20Sopenharmony_ci set_codec_parameter(chip, 0x00, 0x00); 5538c2ecf20Sopenharmony_ci set_mode_register(chip, 0xfc); /* 0xfc = ?? */ 5548c2ecf20Sopenharmony_ci 5558c2ecf20Sopenharmony_ci csp_test1 = read_register(chip, 0x83); 5568c2ecf20Sopenharmony_ci set_register(chip, 0x83, ~csp_test1); 5578c2ecf20Sopenharmony_ci csp_test2 = read_register(chip, 0x83); 5588c2ecf20Sopenharmony_ci if (csp_test2 != (csp_test1 ^ 0xff)) 5598c2ecf20Sopenharmony_ci goto __fail; 5608c2ecf20Sopenharmony_ci 5618c2ecf20Sopenharmony_ci set_register(chip, 0x83, csp_test1); 5628c2ecf20Sopenharmony_ci csp_test2 = read_register(chip, 0x83); 5638c2ecf20Sopenharmony_ci if (csp_test2 != csp_test1) 5648c2ecf20Sopenharmony_ci goto __fail; 5658c2ecf20Sopenharmony_ci 5668c2ecf20Sopenharmony_ci set_mode_register(chip, 0x00); /* 0x00 = ? */ 5678c2ecf20Sopenharmony_ci 5688c2ecf20Sopenharmony_ci *version = get_version(chip); 5698c2ecf20Sopenharmony_ci snd_sbdsp_reset(chip); /* reset DSP after getversion! */ 5708c2ecf20Sopenharmony_ci if (*version >= 0x10 && *version <= 0x1f) 5718c2ecf20Sopenharmony_ci result = 0; /* valid version id */ 5728c2ecf20Sopenharmony_ci 5738c2ecf20Sopenharmony_ci __fail: 5748c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&chip->reg_lock, flags); 5758c2ecf20Sopenharmony_ci return result; 5768c2ecf20Sopenharmony_ci} 5778c2ecf20Sopenharmony_ci 5788c2ecf20Sopenharmony_ci/* 5798c2ecf20Sopenharmony_ci * get CSP version number 5808c2ecf20Sopenharmony_ci */ 5818c2ecf20Sopenharmony_cistatic int get_version(struct snd_sb *chip) 5828c2ecf20Sopenharmony_ci{ 5838c2ecf20Sopenharmony_ci unsigned char dsp_cmd[2]; 5848c2ecf20Sopenharmony_ci 5858c2ecf20Sopenharmony_ci dsp_cmd[0] = 0x08; /* SB_DSP_!something! */ 5868c2ecf20Sopenharmony_ci dsp_cmd[1] = 0x03; /* get chip version id? */ 5878c2ecf20Sopenharmony_ci command_seq(chip, dsp_cmd, 2); 5888c2ecf20Sopenharmony_ci 5898c2ecf20Sopenharmony_ci return (snd_sbdsp_get_byte(chip)); 5908c2ecf20Sopenharmony_ci} 5918c2ecf20Sopenharmony_ci 5928c2ecf20Sopenharmony_ci/* 5938c2ecf20Sopenharmony_ci * check if the CSP version is valid 5948c2ecf20Sopenharmony_ci */ 5958c2ecf20Sopenharmony_cistatic int snd_sb_csp_check_version(struct snd_sb_csp * p) 5968c2ecf20Sopenharmony_ci{ 5978c2ecf20Sopenharmony_ci if (p->version < 0x10 || p->version > 0x1f) { 5988c2ecf20Sopenharmony_ci snd_printd("%s: Invalid CSP version: 0x%x\n", __func__, p->version); 5998c2ecf20Sopenharmony_ci return 1; 6008c2ecf20Sopenharmony_ci } 6018c2ecf20Sopenharmony_ci return 0; 6028c2ecf20Sopenharmony_ci} 6038c2ecf20Sopenharmony_ci 6048c2ecf20Sopenharmony_ci/* 6058c2ecf20Sopenharmony_ci * download microcode to CSP (microcode should have one "main" block). 6068c2ecf20Sopenharmony_ci */ 6078c2ecf20Sopenharmony_cistatic int snd_sb_csp_load(struct snd_sb_csp * p, const unsigned char *buf, int size, int load_flags) 6088c2ecf20Sopenharmony_ci{ 6098c2ecf20Sopenharmony_ci int status, i; 6108c2ecf20Sopenharmony_ci int err; 6118c2ecf20Sopenharmony_ci int result = -EIO; 6128c2ecf20Sopenharmony_ci unsigned long flags; 6138c2ecf20Sopenharmony_ci 6148c2ecf20Sopenharmony_ci spin_lock_irqsave(&p->chip->reg_lock, flags); 6158c2ecf20Sopenharmony_ci snd_sbdsp_command(p->chip, 0x01); /* CSP download command */ 6168c2ecf20Sopenharmony_ci if (snd_sbdsp_get_byte(p->chip)) { 6178c2ecf20Sopenharmony_ci snd_printd("%s: Download command failed\n", __func__); 6188c2ecf20Sopenharmony_ci goto __fail; 6198c2ecf20Sopenharmony_ci } 6208c2ecf20Sopenharmony_ci /* Send CSP low byte (size - 1) */ 6218c2ecf20Sopenharmony_ci snd_sbdsp_command(p->chip, (unsigned char)(size - 1)); 6228c2ecf20Sopenharmony_ci /* Send high byte */ 6238c2ecf20Sopenharmony_ci snd_sbdsp_command(p->chip, (unsigned char)((size - 1) >> 8)); 6248c2ecf20Sopenharmony_ci /* send microcode sequence */ 6258c2ecf20Sopenharmony_ci /* load from kernel space */ 6268c2ecf20Sopenharmony_ci while (size--) { 6278c2ecf20Sopenharmony_ci if (!snd_sbdsp_command(p->chip, *buf++)) 6288c2ecf20Sopenharmony_ci goto __fail; 6298c2ecf20Sopenharmony_ci } 6308c2ecf20Sopenharmony_ci if (snd_sbdsp_get_byte(p->chip)) 6318c2ecf20Sopenharmony_ci goto __fail; 6328c2ecf20Sopenharmony_ci 6338c2ecf20Sopenharmony_ci if (load_flags & SNDRV_SB_CSP_LOAD_INITBLOCK) { 6348c2ecf20Sopenharmony_ci i = 0; 6358c2ecf20Sopenharmony_ci /* some codecs (FastSpeech) take some time to initialize */ 6368c2ecf20Sopenharmony_ci while (1) { 6378c2ecf20Sopenharmony_ci snd_sbdsp_command(p->chip, 0x03); 6388c2ecf20Sopenharmony_ci status = snd_sbdsp_get_byte(p->chip); 6398c2ecf20Sopenharmony_ci if (status == 0x55 || ++i >= 10) 6408c2ecf20Sopenharmony_ci break; 6418c2ecf20Sopenharmony_ci udelay (10); 6428c2ecf20Sopenharmony_ci } 6438c2ecf20Sopenharmony_ci if (status != 0x55) { 6448c2ecf20Sopenharmony_ci snd_printd("%s: Microcode initialization failed\n", __func__); 6458c2ecf20Sopenharmony_ci goto __fail; 6468c2ecf20Sopenharmony_ci } 6478c2ecf20Sopenharmony_ci } else { 6488c2ecf20Sopenharmony_ci /* 6498c2ecf20Sopenharmony_ci * Read mixer register SB_DSP4_DMASETUP after loading 'main' code. 6508c2ecf20Sopenharmony_ci * Start CSP chip if no 16bit DMA channel is set - some kind 6518c2ecf20Sopenharmony_ci * of autorun or perhaps a bugfix? 6528c2ecf20Sopenharmony_ci */ 6538c2ecf20Sopenharmony_ci spin_lock(&p->chip->mixer_lock); 6548c2ecf20Sopenharmony_ci status = snd_sbmixer_read(p->chip, SB_DSP4_DMASETUP); 6558c2ecf20Sopenharmony_ci spin_unlock(&p->chip->mixer_lock); 6568c2ecf20Sopenharmony_ci if (!(status & (SB_DMASETUP_DMA7 | SB_DMASETUP_DMA6 | SB_DMASETUP_DMA5))) { 6578c2ecf20Sopenharmony_ci err = (set_codec_parameter(p->chip, 0xaa, 0x00) || 6588c2ecf20Sopenharmony_ci set_codec_parameter(p->chip, 0xff, 0x00)); 6598c2ecf20Sopenharmony_ci snd_sbdsp_reset(p->chip); /* really! */ 6608c2ecf20Sopenharmony_ci if (err) 6618c2ecf20Sopenharmony_ci goto __fail; 6628c2ecf20Sopenharmony_ci set_mode_register(p->chip, 0xc0); /* c0 = STOP */ 6638c2ecf20Sopenharmony_ci set_mode_register(p->chip, 0x70); /* 70 = RUN */ 6648c2ecf20Sopenharmony_ci } 6658c2ecf20Sopenharmony_ci } 6668c2ecf20Sopenharmony_ci result = 0; 6678c2ecf20Sopenharmony_ci 6688c2ecf20Sopenharmony_ci __fail: 6698c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&p->chip->reg_lock, flags); 6708c2ecf20Sopenharmony_ci return result; 6718c2ecf20Sopenharmony_ci} 6728c2ecf20Sopenharmony_ci 6738c2ecf20Sopenharmony_cistatic int snd_sb_csp_load_user(struct snd_sb_csp * p, const unsigned char __user *buf, int size, int load_flags) 6748c2ecf20Sopenharmony_ci{ 6758c2ecf20Sopenharmony_ci int err; 6768c2ecf20Sopenharmony_ci unsigned char *kbuf; 6778c2ecf20Sopenharmony_ci 6788c2ecf20Sopenharmony_ci kbuf = memdup_user(buf, size); 6798c2ecf20Sopenharmony_ci if (IS_ERR(kbuf)) 6808c2ecf20Sopenharmony_ci return PTR_ERR(kbuf); 6818c2ecf20Sopenharmony_ci 6828c2ecf20Sopenharmony_ci err = snd_sb_csp_load(p, kbuf, size, load_flags); 6838c2ecf20Sopenharmony_ci 6848c2ecf20Sopenharmony_ci kfree(kbuf); 6858c2ecf20Sopenharmony_ci return err; 6868c2ecf20Sopenharmony_ci} 6878c2ecf20Sopenharmony_ci 6888c2ecf20Sopenharmony_cistatic int snd_sb_csp_firmware_load(struct snd_sb_csp *p, int index, int flags) 6898c2ecf20Sopenharmony_ci{ 6908c2ecf20Sopenharmony_ci static const char *const names[] = { 6918c2ecf20Sopenharmony_ci "sb16/mulaw_main.csp", 6928c2ecf20Sopenharmony_ci "sb16/alaw_main.csp", 6938c2ecf20Sopenharmony_ci "sb16/ima_adpcm_init.csp", 6948c2ecf20Sopenharmony_ci "sb16/ima_adpcm_playback.csp", 6958c2ecf20Sopenharmony_ci "sb16/ima_adpcm_capture.csp", 6968c2ecf20Sopenharmony_ci }; 6978c2ecf20Sopenharmony_ci const struct firmware *program; 6988c2ecf20Sopenharmony_ci 6998c2ecf20Sopenharmony_ci BUILD_BUG_ON(ARRAY_SIZE(names) != CSP_PROGRAM_COUNT); 7008c2ecf20Sopenharmony_ci program = p->csp_programs[index]; 7018c2ecf20Sopenharmony_ci if (!program) { 7028c2ecf20Sopenharmony_ci int err = request_firmware(&program, names[index], 7038c2ecf20Sopenharmony_ci p->chip->card->dev); 7048c2ecf20Sopenharmony_ci if (err < 0) 7058c2ecf20Sopenharmony_ci return err; 7068c2ecf20Sopenharmony_ci p->csp_programs[index] = program; 7078c2ecf20Sopenharmony_ci } 7088c2ecf20Sopenharmony_ci return snd_sb_csp_load(p, program->data, program->size, flags); 7098c2ecf20Sopenharmony_ci} 7108c2ecf20Sopenharmony_ci 7118c2ecf20Sopenharmony_ci/* 7128c2ecf20Sopenharmony_ci * autoload hardware codec if necessary 7138c2ecf20Sopenharmony_ci * return 0 if CSP is loaded and ready to run (p->running != 0) 7148c2ecf20Sopenharmony_ci */ 7158c2ecf20Sopenharmony_cistatic int snd_sb_csp_autoload(struct snd_sb_csp * p, snd_pcm_format_t pcm_sfmt, int play_rec_mode) 7168c2ecf20Sopenharmony_ci{ 7178c2ecf20Sopenharmony_ci unsigned long flags; 7188c2ecf20Sopenharmony_ci int err = 0; 7198c2ecf20Sopenharmony_ci 7208c2ecf20Sopenharmony_ci /* if CSP is running or manually loaded then exit */ 7218c2ecf20Sopenharmony_ci if (p->running & (SNDRV_SB_CSP_ST_RUNNING | SNDRV_SB_CSP_ST_LOADED)) 7228c2ecf20Sopenharmony_ci return -EBUSY; 7238c2ecf20Sopenharmony_ci 7248c2ecf20Sopenharmony_ci /* autoload microcode only if requested hardware codec is not already loaded */ 7258c2ecf20Sopenharmony_ci if (((1U << (__force int)pcm_sfmt) & p->acc_format) && (play_rec_mode & p->mode)) { 7268c2ecf20Sopenharmony_ci p->running = SNDRV_SB_CSP_ST_AUTO; 7278c2ecf20Sopenharmony_ci } else { 7288c2ecf20Sopenharmony_ci switch (pcm_sfmt) { 7298c2ecf20Sopenharmony_ci case SNDRV_PCM_FORMAT_MU_LAW: 7308c2ecf20Sopenharmony_ci err = snd_sb_csp_firmware_load(p, CSP_PROGRAM_MULAW, 0); 7318c2ecf20Sopenharmony_ci p->acc_format = SNDRV_PCM_FMTBIT_MU_LAW; 7328c2ecf20Sopenharmony_ci p->mode = SNDRV_SB_CSP_MODE_DSP_READ | SNDRV_SB_CSP_MODE_DSP_WRITE; 7338c2ecf20Sopenharmony_ci break; 7348c2ecf20Sopenharmony_ci case SNDRV_PCM_FORMAT_A_LAW: 7358c2ecf20Sopenharmony_ci err = snd_sb_csp_firmware_load(p, CSP_PROGRAM_ALAW, 0); 7368c2ecf20Sopenharmony_ci p->acc_format = SNDRV_PCM_FMTBIT_A_LAW; 7378c2ecf20Sopenharmony_ci p->mode = SNDRV_SB_CSP_MODE_DSP_READ | SNDRV_SB_CSP_MODE_DSP_WRITE; 7388c2ecf20Sopenharmony_ci break; 7398c2ecf20Sopenharmony_ci case SNDRV_PCM_FORMAT_IMA_ADPCM: 7408c2ecf20Sopenharmony_ci err = snd_sb_csp_firmware_load(p, CSP_PROGRAM_ADPCM_INIT, 7418c2ecf20Sopenharmony_ci SNDRV_SB_CSP_LOAD_INITBLOCK); 7428c2ecf20Sopenharmony_ci if (err) 7438c2ecf20Sopenharmony_ci break; 7448c2ecf20Sopenharmony_ci if (play_rec_mode == SNDRV_SB_CSP_MODE_DSP_WRITE) { 7458c2ecf20Sopenharmony_ci err = snd_sb_csp_firmware_load 7468c2ecf20Sopenharmony_ci (p, CSP_PROGRAM_ADPCM_PLAYBACK, 0); 7478c2ecf20Sopenharmony_ci p->mode = SNDRV_SB_CSP_MODE_DSP_WRITE; 7488c2ecf20Sopenharmony_ci } else { 7498c2ecf20Sopenharmony_ci err = snd_sb_csp_firmware_load 7508c2ecf20Sopenharmony_ci (p, CSP_PROGRAM_ADPCM_CAPTURE, 0); 7518c2ecf20Sopenharmony_ci p->mode = SNDRV_SB_CSP_MODE_DSP_READ; 7528c2ecf20Sopenharmony_ci } 7538c2ecf20Sopenharmony_ci p->acc_format = SNDRV_PCM_FMTBIT_IMA_ADPCM; 7548c2ecf20Sopenharmony_ci break; 7558c2ecf20Sopenharmony_ci default: 7568c2ecf20Sopenharmony_ci /* Decouple CSP from IRQ and DMAREQ lines */ 7578c2ecf20Sopenharmony_ci if (p->running & SNDRV_SB_CSP_ST_AUTO) { 7588c2ecf20Sopenharmony_ci spin_lock_irqsave(&p->chip->reg_lock, flags); 7598c2ecf20Sopenharmony_ci set_mode_register(p->chip, 0xfc); 7608c2ecf20Sopenharmony_ci set_mode_register(p->chip, 0x00); 7618c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&p->chip->reg_lock, flags); 7628c2ecf20Sopenharmony_ci p->running = 0; /* clear autoloaded flag */ 7638c2ecf20Sopenharmony_ci } 7648c2ecf20Sopenharmony_ci return -EINVAL; 7658c2ecf20Sopenharmony_ci } 7668c2ecf20Sopenharmony_ci if (err) { 7678c2ecf20Sopenharmony_ci p->acc_format = 0; 7688c2ecf20Sopenharmony_ci p->acc_channels = p->acc_width = p->acc_rates = 0; 7698c2ecf20Sopenharmony_ci 7708c2ecf20Sopenharmony_ci p->running = 0; /* clear autoloaded flag */ 7718c2ecf20Sopenharmony_ci p->mode = 0; 7728c2ecf20Sopenharmony_ci return (err); 7738c2ecf20Sopenharmony_ci } else { 7748c2ecf20Sopenharmony_ci p->running = SNDRV_SB_CSP_ST_AUTO; /* set autoloaded flag */ 7758c2ecf20Sopenharmony_ci p->acc_width = SNDRV_SB_CSP_SAMPLE_16BIT; /* only 16 bit data */ 7768c2ecf20Sopenharmony_ci p->acc_channels = SNDRV_SB_CSP_MONO | SNDRV_SB_CSP_STEREO; 7778c2ecf20Sopenharmony_ci p->acc_rates = SNDRV_SB_CSP_RATE_ALL; /* HW codecs accept all rates */ 7788c2ecf20Sopenharmony_ci } 7798c2ecf20Sopenharmony_ci 7808c2ecf20Sopenharmony_ci } 7818c2ecf20Sopenharmony_ci return (p->running & SNDRV_SB_CSP_ST_AUTO) ? 0 : -ENXIO; 7828c2ecf20Sopenharmony_ci} 7838c2ecf20Sopenharmony_ci 7848c2ecf20Sopenharmony_ci/* 7858c2ecf20Sopenharmony_ci * start CSP 7868c2ecf20Sopenharmony_ci */ 7878c2ecf20Sopenharmony_cistatic int snd_sb_csp_start(struct snd_sb_csp * p, int sample_width, int channels) 7888c2ecf20Sopenharmony_ci{ 7898c2ecf20Sopenharmony_ci unsigned char s_type; /* sample type */ 7908c2ecf20Sopenharmony_ci unsigned char mixL, mixR; 7918c2ecf20Sopenharmony_ci int result = -EIO; 7928c2ecf20Sopenharmony_ci unsigned long flags; 7938c2ecf20Sopenharmony_ci 7948c2ecf20Sopenharmony_ci if (!(p->running & (SNDRV_SB_CSP_ST_LOADED | SNDRV_SB_CSP_ST_AUTO))) { 7958c2ecf20Sopenharmony_ci snd_printd("%s: Microcode not loaded\n", __func__); 7968c2ecf20Sopenharmony_ci return -ENXIO; 7978c2ecf20Sopenharmony_ci } 7988c2ecf20Sopenharmony_ci if (p->running & SNDRV_SB_CSP_ST_RUNNING) { 7998c2ecf20Sopenharmony_ci snd_printd("%s: CSP already running\n", __func__); 8008c2ecf20Sopenharmony_ci return -EBUSY; 8018c2ecf20Sopenharmony_ci } 8028c2ecf20Sopenharmony_ci if (!(sample_width & p->acc_width)) { 8038c2ecf20Sopenharmony_ci snd_printd("%s: Unsupported PCM sample width\n", __func__); 8048c2ecf20Sopenharmony_ci return -EINVAL; 8058c2ecf20Sopenharmony_ci } 8068c2ecf20Sopenharmony_ci if (!(channels & p->acc_channels)) { 8078c2ecf20Sopenharmony_ci snd_printd("%s: Invalid number of channels\n", __func__); 8088c2ecf20Sopenharmony_ci return -EINVAL; 8098c2ecf20Sopenharmony_ci } 8108c2ecf20Sopenharmony_ci 8118c2ecf20Sopenharmony_ci /* Mute PCM volume */ 8128c2ecf20Sopenharmony_ci spin_lock_irqsave(&p->chip->mixer_lock, flags); 8138c2ecf20Sopenharmony_ci mixL = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV); 8148c2ecf20Sopenharmony_ci mixR = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV + 1); 8158c2ecf20Sopenharmony_ci snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL & 0x7); 8168c2ecf20Sopenharmony_ci snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR & 0x7); 8178c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&p->chip->mixer_lock, flags); 8188c2ecf20Sopenharmony_ci 8198c2ecf20Sopenharmony_ci spin_lock(&p->chip->reg_lock); 8208c2ecf20Sopenharmony_ci set_mode_register(p->chip, 0xc0); /* c0 = STOP */ 8218c2ecf20Sopenharmony_ci set_mode_register(p->chip, 0x70); /* 70 = RUN */ 8228c2ecf20Sopenharmony_ci 8238c2ecf20Sopenharmony_ci s_type = 0x00; 8248c2ecf20Sopenharmony_ci if (channels == SNDRV_SB_CSP_MONO) 8258c2ecf20Sopenharmony_ci s_type = 0x11; /* 000n 000n (n = 1 if mono) */ 8268c2ecf20Sopenharmony_ci if (sample_width == SNDRV_SB_CSP_SAMPLE_8BIT) 8278c2ecf20Sopenharmony_ci s_type |= 0x22; /* 00dX 00dX (d = 1 if 8 bit samples) */ 8288c2ecf20Sopenharmony_ci 8298c2ecf20Sopenharmony_ci if (set_codec_parameter(p->chip, 0x81, s_type)) { 8308c2ecf20Sopenharmony_ci snd_printd("%s: Set sample type command failed\n", __func__); 8318c2ecf20Sopenharmony_ci goto __fail; 8328c2ecf20Sopenharmony_ci } 8338c2ecf20Sopenharmony_ci if (set_codec_parameter(p->chip, 0x80, 0x00)) { 8348c2ecf20Sopenharmony_ci snd_printd("%s: Codec start command failed\n", __func__); 8358c2ecf20Sopenharmony_ci goto __fail; 8368c2ecf20Sopenharmony_ci } 8378c2ecf20Sopenharmony_ci p->run_width = sample_width; 8388c2ecf20Sopenharmony_ci p->run_channels = channels; 8398c2ecf20Sopenharmony_ci 8408c2ecf20Sopenharmony_ci p->running |= SNDRV_SB_CSP_ST_RUNNING; 8418c2ecf20Sopenharmony_ci 8428c2ecf20Sopenharmony_ci if (p->mode & SNDRV_SB_CSP_MODE_QSOUND) { 8438c2ecf20Sopenharmony_ci set_codec_parameter(p->chip, 0xe0, 0x01); 8448c2ecf20Sopenharmony_ci /* enable QSound decoder */ 8458c2ecf20Sopenharmony_ci set_codec_parameter(p->chip, 0x00, 0xff); 8468c2ecf20Sopenharmony_ci set_codec_parameter(p->chip, 0x01, 0xff); 8478c2ecf20Sopenharmony_ci p->running |= SNDRV_SB_CSP_ST_QSOUND; 8488c2ecf20Sopenharmony_ci /* set QSound startup value */ 8498c2ecf20Sopenharmony_ci snd_sb_csp_qsound_transfer(p); 8508c2ecf20Sopenharmony_ci } 8518c2ecf20Sopenharmony_ci result = 0; 8528c2ecf20Sopenharmony_ci 8538c2ecf20Sopenharmony_ci __fail: 8548c2ecf20Sopenharmony_ci spin_unlock(&p->chip->reg_lock); 8558c2ecf20Sopenharmony_ci 8568c2ecf20Sopenharmony_ci /* restore PCM volume */ 8578c2ecf20Sopenharmony_ci spin_lock_irqsave(&p->chip->mixer_lock, flags); 8588c2ecf20Sopenharmony_ci snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL); 8598c2ecf20Sopenharmony_ci snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR); 8608c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&p->chip->mixer_lock, flags); 8618c2ecf20Sopenharmony_ci 8628c2ecf20Sopenharmony_ci return result; 8638c2ecf20Sopenharmony_ci} 8648c2ecf20Sopenharmony_ci 8658c2ecf20Sopenharmony_ci/* 8668c2ecf20Sopenharmony_ci * stop CSP 8678c2ecf20Sopenharmony_ci */ 8688c2ecf20Sopenharmony_cistatic int snd_sb_csp_stop(struct snd_sb_csp * p) 8698c2ecf20Sopenharmony_ci{ 8708c2ecf20Sopenharmony_ci int result; 8718c2ecf20Sopenharmony_ci unsigned char mixL, mixR; 8728c2ecf20Sopenharmony_ci unsigned long flags; 8738c2ecf20Sopenharmony_ci 8748c2ecf20Sopenharmony_ci if (!(p->running & SNDRV_SB_CSP_ST_RUNNING)) 8758c2ecf20Sopenharmony_ci return 0; 8768c2ecf20Sopenharmony_ci 8778c2ecf20Sopenharmony_ci /* Mute PCM volume */ 8788c2ecf20Sopenharmony_ci spin_lock_irqsave(&p->chip->mixer_lock, flags); 8798c2ecf20Sopenharmony_ci mixL = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV); 8808c2ecf20Sopenharmony_ci mixR = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV + 1); 8818c2ecf20Sopenharmony_ci snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL & 0x7); 8828c2ecf20Sopenharmony_ci snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR & 0x7); 8838c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&p->chip->mixer_lock, flags); 8848c2ecf20Sopenharmony_ci 8858c2ecf20Sopenharmony_ci spin_lock(&p->chip->reg_lock); 8868c2ecf20Sopenharmony_ci if (p->running & SNDRV_SB_CSP_ST_QSOUND) { 8878c2ecf20Sopenharmony_ci set_codec_parameter(p->chip, 0xe0, 0x01); 8888c2ecf20Sopenharmony_ci /* disable QSound decoder */ 8898c2ecf20Sopenharmony_ci set_codec_parameter(p->chip, 0x00, 0x00); 8908c2ecf20Sopenharmony_ci set_codec_parameter(p->chip, 0x01, 0x00); 8918c2ecf20Sopenharmony_ci 8928c2ecf20Sopenharmony_ci p->running &= ~SNDRV_SB_CSP_ST_QSOUND; 8938c2ecf20Sopenharmony_ci } 8948c2ecf20Sopenharmony_ci result = set_mode_register(p->chip, 0xc0); /* c0 = STOP */ 8958c2ecf20Sopenharmony_ci spin_unlock(&p->chip->reg_lock); 8968c2ecf20Sopenharmony_ci 8978c2ecf20Sopenharmony_ci /* restore PCM volume */ 8988c2ecf20Sopenharmony_ci spin_lock_irqsave(&p->chip->mixer_lock, flags); 8998c2ecf20Sopenharmony_ci snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL); 9008c2ecf20Sopenharmony_ci snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR); 9018c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&p->chip->mixer_lock, flags); 9028c2ecf20Sopenharmony_ci 9038c2ecf20Sopenharmony_ci if (!(result)) 9048c2ecf20Sopenharmony_ci p->running &= ~(SNDRV_SB_CSP_ST_PAUSED | SNDRV_SB_CSP_ST_RUNNING); 9058c2ecf20Sopenharmony_ci return result; 9068c2ecf20Sopenharmony_ci} 9078c2ecf20Sopenharmony_ci 9088c2ecf20Sopenharmony_ci/* 9098c2ecf20Sopenharmony_ci * pause CSP codec and hold DMA transfer 9108c2ecf20Sopenharmony_ci */ 9118c2ecf20Sopenharmony_cistatic int snd_sb_csp_pause(struct snd_sb_csp * p) 9128c2ecf20Sopenharmony_ci{ 9138c2ecf20Sopenharmony_ci int result; 9148c2ecf20Sopenharmony_ci unsigned long flags; 9158c2ecf20Sopenharmony_ci 9168c2ecf20Sopenharmony_ci if (!(p->running & SNDRV_SB_CSP_ST_RUNNING)) 9178c2ecf20Sopenharmony_ci return -EBUSY; 9188c2ecf20Sopenharmony_ci 9198c2ecf20Sopenharmony_ci spin_lock_irqsave(&p->chip->reg_lock, flags); 9208c2ecf20Sopenharmony_ci result = set_codec_parameter(p->chip, 0x80, 0xff); 9218c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&p->chip->reg_lock, flags); 9228c2ecf20Sopenharmony_ci if (!(result)) 9238c2ecf20Sopenharmony_ci p->running |= SNDRV_SB_CSP_ST_PAUSED; 9248c2ecf20Sopenharmony_ci 9258c2ecf20Sopenharmony_ci return result; 9268c2ecf20Sopenharmony_ci} 9278c2ecf20Sopenharmony_ci 9288c2ecf20Sopenharmony_ci/* 9298c2ecf20Sopenharmony_ci * restart CSP codec and resume DMA transfer 9308c2ecf20Sopenharmony_ci */ 9318c2ecf20Sopenharmony_cistatic int snd_sb_csp_restart(struct snd_sb_csp * p) 9328c2ecf20Sopenharmony_ci{ 9338c2ecf20Sopenharmony_ci int result; 9348c2ecf20Sopenharmony_ci unsigned long flags; 9358c2ecf20Sopenharmony_ci 9368c2ecf20Sopenharmony_ci if (!(p->running & SNDRV_SB_CSP_ST_PAUSED)) 9378c2ecf20Sopenharmony_ci return -EBUSY; 9388c2ecf20Sopenharmony_ci 9398c2ecf20Sopenharmony_ci spin_lock_irqsave(&p->chip->reg_lock, flags); 9408c2ecf20Sopenharmony_ci result = set_codec_parameter(p->chip, 0x80, 0x00); 9418c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&p->chip->reg_lock, flags); 9428c2ecf20Sopenharmony_ci if (!(result)) 9438c2ecf20Sopenharmony_ci p->running &= ~SNDRV_SB_CSP_ST_PAUSED; 9448c2ecf20Sopenharmony_ci 9458c2ecf20Sopenharmony_ci return result; 9468c2ecf20Sopenharmony_ci} 9478c2ecf20Sopenharmony_ci 9488c2ecf20Sopenharmony_ci/* ------------------------------ */ 9498c2ecf20Sopenharmony_ci 9508c2ecf20Sopenharmony_ci/* 9518c2ecf20Sopenharmony_ci * QSound mixer control for PCM 9528c2ecf20Sopenharmony_ci */ 9538c2ecf20Sopenharmony_ci 9548c2ecf20Sopenharmony_ci#define snd_sb_qsound_switch_info snd_ctl_boolean_mono_info 9558c2ecf20Sopenharmony_ci 9568c2ecf20Sopenharmony_cistatic int snd_sb_qsound_switch_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) 9578c2ecf20Sopenharmony_ci{ 9588c2ecf20Sopenharmony_ci struct snd_sb_csp *p = snd_kcontrol_chip(kcontrol); 9598c2ecf20Sopenharmony_ci 9608c2ecf20Sopenharmony_ci ucontrol->value.integer.value[0] = p->q_enabled ? 1 : 0; 9618c2ecf20Sopenharmony_ci return 0; 9628c2ecf20Sopenharmony_ci} 9638c2ecf20Sopenharmony_ci 9648c2ecf20Sopenharmony_cistatic int snd_sb_qsound_switch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) 9658c2ecf20Sopenharmony_ci{ 9668c2ecf20Sopenharmony_ci struct snd_sb_csp *p = snd_kcontrol_chip(kcontrol); 9678c2ecf20Sopenharmony_ci unsigned long flags; 9688c2ecf20Sopenharmony_ci int change; 9698c2ecf20Sopenharmony_ci unsigned char nval; 9708c2ecf20Sopenharmony_ci 9718c2ecf20Sopenharmony_ci nval = ucontrol->value.integer.value[0] & 0x01; 9728c2ecf20Sopenharmony_ci spin_lock_irqsave(&p->q_lock, flags); 9738c2ecf20Sopenharmony_ci change = p->q_enabled != nval; 9748c2ecf20Sopenharmony_ci p->q_enabled = nval; 9758c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&p->q_lock, flags); 9768c2ecf20Sopenharmony_ci return change; 9778c2ecf20Sopenharmony_ci} 9788c2ecf20Sopenharmony_ci 9798c2ecf20Sopenharmony_cistatic int snd_sb_qsound_space_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) 9808c2ecf20Sopenharmony_ci{ 9818c2ecf20Sopenharmony_ci uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; 9828c2ecf20Sopenharmony_ci uinfo->count = 2; 9838c2ecf20Sopenharmony_ci uinfo->value.integer.min = 0; 9848c2ecf20Sopenharmony_ci uinfo->value.integer.max = SNDRV_SB_CSP_QSOUND_MAX_RIGHT; 9858c2ecf20Sopenharmony_ci return 0; 9868c2ecf20Sopenharmony_ci} 9878c2ecf20Sopenharmony_ci 9888c2ecf20Sopenharmony_cistatic int snd_sb_qsound_space_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) 9898c2ecf20Sopenharmony_ci{ 9908c2ecf20Sopenharmony_ci struct snd_sb_csp *p = snd_kcontrol_chip(kcontrol); 9918c2ecf20Sopenharmony_ci unsigned long flags; 9928c2ecf20Sopenharmony_ci 9938c2ecf20Sopenharmony_ci spin_lock_irqsave(&p->q_lock, flags); 9948c2ecf20Sopenharmony_ci ucontrol->value.integer.value[0] = p->qpos_left; 9958c2ecf20Sopenharmony_ci ucontrol->value.integer.value[1] = p->qpos_right; 9968c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&p->q_lock, flags); 9978c2ecf20Sopenharmony_ci return 0; 9988c2ecf20Sopenharmony_ci} 9998c2ecf20Sopenharmony_ci 10008c2ecf20Sopenharmony_cistatic int snd_sb_qsound_space_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) 10018c2ecf20Sopenharmony_ci{ 10028c2ecf20Sopenharmony_ci struct snd_sb_csp *p = snd_kcontrol_chip(kcontrol); 10038c2ecf20Sopenharmony_ci unsigned long flags; 10048c2ecf20Sopenharmony_ci int change; 10058c2ecf20Sopenharmony_ci unsigned char nval1, nval2; 10068c2ecf20Sopenharmony_ci 10078c2ecf20Sopenharmony_ci nval1 = ucontrol->value.integer.value[0]; 10088c2ecf20Sopenharmony_ci if (nval1 > SNDRV_SB_CSP_QSOUND_MAX_RIGHT) 10098c2ecf20Sopenharmony_ci nval1 = SNDRV_SB_CSP_QSOUND_MAX_RIGHT; 10108c2ecf20Sopenharmony_ci nval2 = ucontrol->value.integer.value[1]; 10118c2ecf20Sopenharmony_ci if (nval2 > SNDRV_SB_CSP_QSOUND_MAX_RIGHT) 10128c2ecf20Sopenharmony_ci nval2 = SNDRV_SB_CSP_QSOUND_MAX_RIGHT; 10138c2ecf20Sopenharmony_ci spin_lock_irqsave(&p->q_lock, flags); 10148c2ecf20Sopenharmony_ci change = p->qpos_left != nval1 || p->qpos_right != nval2; 10158c2ecf20Sopenharmony_ci p->qpos_left = nval1; 10168c2ecf20Sopenharmony_ci p->qpos_right = nval2; 10178c2ecf20Sopenharmony_ci p->qpos_changed = change; 10188c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&p->q_lock, flags); 10198c2ecf20Sopenharmony_ci return change; 10208c2ecf20Sopenharmony_ci} 10218c2ecf20Sopenharmony_ci 10228c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new snd_sb_qsound_switch = { 10238c2ecf20Sopenharmony_ci .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 10248c2ecf20Sopenharmony_ci .name = "3D Control - Switch", 10258c2ecf20Sopenharmony_ci .info = snd_sb_qsound_switch_info, 10268c2ecf20Sopenharmony_ci .get = snd_sb_qsound_switch_get, 10278c2ecf20Sopenharmony_ci .put = snd_sb_qsound_switch_put 10288c2ecf20Sopenharmony_ci}; 10298c2ecf20Sopenharmony_ci 10308c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new snd_sb_qsound_space = { 10318c2ecf20Sopenharmony_ci .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 10328c2ecf20Sopenharmony_ci .name = "3D Control - Space", 10338c2ecf20Sopenharmony_ci .info = snd_sb_qsound_space_info, 10348c2ecf20Sopenharmony_ci .get = snd_sb_qsound_space_get, 10358c2ecf20Sopenharmony_ci .put = snd_sb_qsound_space_put 10368c2ecf20Sopenharmony_ci}; 10378c2ecf20Sopenharmony_ci 10388c2ecf20Sopenharmony_cistatic int snd_sb_qsound_build(struct snd_sb_csp * p) 10398c2ecf20Sopenharmony_ci{ 10408c2ecf20Sopenharmony_ci struct snd_card *card; 10418c2ecf20Sopenharmony_ci int err; 10428c2ecf20Sopenharmony_ci 10438c2ecf20Sopenharmony_ci if (snd_BUG_ON(!p)) 10448c2ecf20Sopenharmony_ci return -EINVAL; 10458c2ecf20Sopenharmony_ci 10468c2ecf20Sopenharmony_ci card = p->chip->card; 10478c2ecf20Sopenharmony_ci p->qpos_left = p->qpos_right = SNDRV_SB_CSP_QSOUND_MAX_RIGHT / 2; 10488c2ecf20Sopenharmony_ci p->qpos_changed = 0; 10498c2ecf20Sopenharmony_ci 10508c2ecf20Sopenharmony_ci spin_lock_init(&p->q_lock); 10518c2ecf20Sopenharmony_ci 10528c2ecf20Sopenharmony_ci if ((err = snd_ctl_add(card, p->qsound_switch = snd_ctl_new1(&snd_sb_qsound_switch, p))) < 0) { 10538c2ecf20Sopenharmony_ci p->qsound_switch = NULL; 10548c2ecf20Sopenharmony_ci goto __error; 10558c2ecf20Sopenharmony_ci } 10568c2ecf20Sopenharmony_ci if ((err = snd_ctl_add(card, p->qsound_space = snd_ctl_new1(&snd_sb_qsound_space, p))) < 0) { 10578c2ecf20Sopenharmony_ci p->qsound_space = NULL; 10588c2ecf20Sopenharmony_ci goto __error; 10598c2ecf20Sopenharmony_ci } 10608c2ecf20Sopenharmony_ci 10618c2ecf20Sopenharmony_ci return 0; 10628c2ecf20Sopenharmony_ci 10638c2ecf20Sopenharmony_ci __error: 10648c2ecf20Sopenharmony_ci snd_sb_qsound_destroy(p); 10658c2ecf20Sopenharmony_ci return err; 10668c2ecf20Sopenharmony_ci} 10678c2ecf20Sopenharmony_ci 10688c2ecf20Sopenharmony_cistatic void snd_sb_qsound_destroy(struct snd_sb_csp * p) 10698c2ecf20Sopenharmony_ci{ 10708c2ecf20Sopenharmony_ci struct snd_card *card; 10718c2ecf20Sopenharmony_ci unsigned long flags; 10728c2ecf20Sopenharmony_ci 10738c2ecf20Sopenharmony_ci if (snd_BUG_ON(!p)) 10748c2ecf20Sopenharmony_ci return; 10758c2ecf20Sopenharmony_ci 10768c2ecf20Sopenharmony_ci card = p->chip->card; 10778c2ecf20Sopenharmony_ci 10788c2ecf20Sopenharmony_ci down_write(&card->controls_rwsem); 10798c2ecf20Sopenharmony_ci if (p->qsound_switch) { 10808c2ecf20Sopenharmony_ci snd_ctl_remove(card, p->qsound_switch); 10818c2ecf20Sopenharmony_ci p->qsound_switch = NULL; 10828c2ecf20Sopenharmony_ci } 10838c2ecf20Sopenharmony_ci if (p->qsound_space) { 10848c2ecf20Sopenharmony_ci snd_ctl_remove(card, p->qsound_space); 10858c2ecf20Sopenharmony_ci p->qsound_space = NULL; 10868c2ecf20Sopenharmony_ci } 10878c2ecf20Sopenharmony_ci up_write(&card->controls_rwsem); 10888c2ecf20Sopenharmony_ci 10898c2ecf20Sopenharmony_ci /* cancel pending transfer of QSound parameters */ 10908c2ecf20Sopenharmony_ci spin_lock_irqsave (&p->q_lock, flags); 10918c2ecf20Sopenharmony_ci p->qpos_changed = 0; 10928c2ecf20Sopenharmony_ci spin_unlock_irqrestore (&p->q_lock, flags); 10938c2ecf20Sopenharmony_ci} 10948c2ecf20Sopenharmony_ci 10958c2ecf20Sopenharmony_ci/* 10968c2ecf20Sopenharmony_ci * Transfer qsound parameters to CSP, 10978c2ecf20Sopenharmony_ci * function should be called from interrupt routine 10988c2ecf20Sopenharmony_ci */ 10998c2ecf20Sopenharmony_cistatic int snd_sb_csp_qsound_transfer(struct snd_sb_csp * p) 11008c2ecf20Sopenharmony_ci{ 11018c2ecf20Sopenharmony_ci int err = -ENXIO; 11028c2ecf20Sopenharmony_ci 11038c2ecf20Sopenharmony_ci spin_lock(&p->q_lock); 11048c2ecf20Sopenharmony_ci if (p->running & SNDRV_SB_CSP_ST_QSOUND) { 11058c2ecf20Sopenharmony_ci set_codec_parameter(p->chip, 0xe0, 0x01); 11068c2ecf20Sopenharmony_ci /* left channel */ 11078c2ecf20Sopenharmony_ci set_codec_parameter(p->chip, 0x00, p->qpos_left); 11088c2ecf20Sopenharmony_ci set_codec_parameter(p->chip, 0x02, 0x00); 11098c2ecf20Sopenharmony_ci /* right channel */ 11108c2ecf20Sopenharmony_ci set_codec_parameter(p->chip, 0x00, p->qpos_right); 11118c2ecf20Sopenharmony_ci set_codec_parameter(p->chip, 0x03, 0x00); 11128c2ecf20Sopenharmony_ci err = 0; 11138c2ecf20Sopenharmony_ci } 11148c2ecf20Sopenharmony_ci p->qpos_changed = 0; 11158c2ecf20Sopenharmony_ci spin_unlock(&p->q_lock); 11168c2ecf20Sopenharmony_ci return err; 11178c2ecf20Sopenharmony_ci} 11188c2ecf20Sopenharmony_ci 11198c2ecf20Sopenharmony_ci/* ------------------------------ */ 11208c2ecf20Sopenharmony_ci 11218c2ecf20Sopenharmony_ci/* 11228c2ecf20Sopenharmony_ci * proc interface 11238c2ecf20Sopenharmony_ci */ 11248c2ecf20Sopenharmony_cistatic int init_proc_entry(struct snd_sb_csp * p, int device) 11258c2ecf20Sopenharmony_ci{ 11268c2ecf20Sopenharmony_ci char name[16]; 11278c2ecf20Sopenharmony_ci 11288c2ecf20Sopenharmony_ci sprintf(name, "cspD%d", device); 11298c2ecf20Sopenharmony_ci snd_card_ro_proc_new(p->chip->card, name, p, info_read); 11308c2ecf20Sopenharmony_ci return 0; 11318c2ecf20Sopenharmony_ci} 11328c2ecf20Sopenharmony_ci 11338c2ecf20Sopenharmony_cistatic void info_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) 11348c2ecf20Sopenharmony_ci{ 11358c2ecf20Sopenharmony_ci struct snd_sb_csp *p = entry->private_data; 11368c2ecf20Sopenharmony_ci 11378c2ecf20Sopenharmony_ci snd_iprintf(buffer, "Creative Signal Processor [v%d.%d]\n", (p->version >> 4), (p->version & 0x0f)); 11388c2ecf20Sopenharmony_ci snd_iprintf(buffer, "State: %cx%c%c%c\n", ((p->running & SNDRV_SB_CSP_ST_QSOUND) ? 'Q' : '-'), 11398c2ecf20Sopenharmony_ci ((p->running & SNDRV_SB_CSP_ST_PAUSED) ? 'P' : '-'), 11408c2ecf20Sopenharmony_ci ((p->running & SNDRV_SB_CSP_ST_RUNNING) ? 'R' : '-'), 11418c2ecf20Sopenharmony_ci ((p->running & SNDRV_SB_CSP_ST_LOADED) ? 'L' : '-')); 11428c2ecf20Sopenharmony_ci if (p->running & SNDRV_SB_CSP_ST_LOADED) { 11438c2ecf20Sopenharmony_ci snd_iprintf(buffer, "Codec: %s [func #%d]\n", p->codec_name, p->func_nr); 11448c2ecf20Sopenharmony_ci snd_iprintf(buffer, "Sample rates: "); 11458c2ecf20Sopenharmony_ci if (p->acc_rates == SNDRV_SB_CSP_RATE_ALL) { 11468c2ecf20Sopenharmony_ci snd_iprintf(buffer, "All\n"); 11478c2ecf20Sopenharmony_ci } else { 11488c2ecf20Sopenharmony_ci snd_iprintf(buffer, "%s%s%s%s\n", 11498c2ecf20Sopenharmony_ci ((p->acc_rates & SNDRV_SB_CSP_RATE_8000) ? "8000Hz " : ""), 11508c2ecf20Sopenharmony_ci ((p->acc_rates & SNDRV_SB_CSP_RATE_11025) ? "11025Hz " : ""), 11518c2ecf20Sopenharmony_ci ((p->acc_rates & SNDRV_SB_CSP_RATE_22050) ? "22050Hz " : ""), 11528c2ecf20Sopenharmony_ci ((p->acc_rates & SNDRV_SB_CSP_RATE_44100) ? "44100Hz" : "")); 11538c2ecf20Sopenharmony_ci } 11548c2ecf20Sopenharmony_ci if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) { 11558c2ecf20Sopenharmony_ci snd_iprintf(buffer, "QSound decoder %sabled\n", 11568c2ecf20Sopenharmony_ci p->q_enabled ? "en" : "dis"); 11578c2ecf20Sopenharmony_ci } else { 11588c2ecf20Sopenharmony_ci snd_iprintf(buffer, "PCM format ID: 0x%x (%s/%s) [%s/%s] [%s/%s]\n", 11598c2ecf20Sopenharmony_ci p->acc_format, 11608c2ecf20Sopenharmony_ci ((p->acc_width & SNDRV_SB_CSP_SAMPLE_16BIT) ? "16bit" : "-"), 11618c2ecf20Sopenharmony_ci ((p->acc_width & SNDRV_SB_CSP_SAMPLE_8BIT) ? "8bit" : "-"), 11628c2ecf20Sopenharmony_ci ((p->acc_channels & SNDRV_SB_CSP_MONO) ? "mono" : "-"), 11638c2ecf20Sopenharmony_ci ((p->acc_channels & SNDRV_SB_CSP_STEREO) ? "stereo" : "-"), 11648c2ecf20Sopenharmony_ci ((p->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) ? "playback" : "-"), 11658c2ecf20Sopenharmony_ci ((p->mode & SNDRV_SB_CSP_MODE_DSP_READ) ? "capture" : "-")); 11668c2ecf20Sopenharmony_ci } 11678c2ecf20Sopenharmony_ci } 11688c2ecf20Sopenharmony_ci if (p->running & SNDRV_SB_CSP_ST_AUTO) { 11698c2ecf20Sopenharmony_ci snd_iprintf(buffer, "Autoloaded Mu-Law, A-Law or Ima-ADPCM hardware codec\n"); 11708c2ecf20Sopenharmony_ci } 11718c2ecf20Sopenharmony_ci if (p->running & SNDRV_SB_CSP_ST_RUNNING) { 11728c2ecf20Sopenharmony_ci snd_iprintf(buffer, "Processing %dbit %s PCM samples\n", 11738c2ecf20Sopenharmony_ci ((p->run_width & SNDRV_SB_CSP_SAMPLE_16BIT) ? 16 : 8), 11748c2ecf20Sopenharmony_ci ((p->run_channels & SNDRV_SB_CSP_MONO) ? "mono" : "stereo")); 11758c2ecf20Sopenharmony_ci } 11768c2ecf20Sopenharmony_ci if (p->running & SNDRV_SB_CSP_ST_QSOUND) { 11778c2ecf20Sopenharmony_ci snd_iprintf(buffer, "Qsound position: left = 0x%x, right = 0x%x\n", 11788c2ecf20Sopenharmony_ci p->qpos_left, p->qpos_right); 11798c2ecf20Sopenharmony_ci } 11808c2ecf20Sopenharmony_ci} 11818c2ecf20Sopenharmony_ci 11828c2ecf20Sopenharmony_ci/* */ 11838c2ecf20Sopenharmony_ci 11848c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_sb_csp_new); 1185