|  | /* | 
|  | *  Asihpi soundcard | 
|  | *  Copyright (c) by AudioScience Inc <alsa@audioscience.com> | 
|  | * | 
|  | *   This program is free software; you can redistribute it and/or modify | 
|  | *   it under the terms of version 2 of the GNU General Public License as | 
|  | *   published by the Free Software Foundation; | 
|  | * | 
|  | *   This program is distributed in the hope that it will be useful, | 
|  | *   but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | *   GNU General Public License for more details. | 
|  | * | 
|  | *   You should have received a copy of the GNU General Public License | 
|  | *   along with this program; if not, write to the Free Software | 
|  | *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA | 
|  | * | 
|  | * | 
|  | *  The following is not a condition of use, merely a request: | 
|  | *  If you modify this program, particularly if you fix errors, AudioScience Inc | 
|  | *  would appreciate it if you grant us the right to use those modifications | 
|  | *  for any purpose including commercial applications. | 
|  | */ | 
|  |  | 
|  | #include "hpi_internal.h" | 
|  | #include "hpi_version.h" | 
|  | #include "hpimsginit.h" | 
|  | #include "hpioctl.h" | 
|  | #include "hpicmn.h" | 
|  |  | 
|  |  | 
|  | #include <linux/pci.h> | 
|  | #include <linux/init.h> | 
|  | #include <linux/jiffies.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/time.h> | 
|  | #include <linux/wait.h> | 
|  | #include <linux/module.h> | 
|  | #include <sound/core.h> | 
|  | #include <sound/control.h> | 
|  | #include <sound/pcm.h> | 
|  | #include <sound/pcm_params.h> | 
|  | #include <sound/info.h> | 
|  | #include <sound/initval.h> | 
|  | #include <sound/tlv.h> | 
|  | #include <sound/hwdep.h> | 
|  |  | 
|  | MODULE_LICENSE("GPL"); | 
|  | MODULE_AUTHOR("AudioScience inc. <support@audioscience.com>"); | 
|  | MODULE_DESCRIPTION("AudioScience ALSA ASI5000 ASI6000 ASI87xx ASI89xx " | 
|  | HPI_VER_STRING); | 
|  |  | 
|  | #if defined CONFIG_SND_DEBUG_VERBOSE | 
|  | /** | 
|  | * snd_printddd - very verbose debug printk | 
|  | * @format: format string | 
|  | * | 
|  | * Works like snd_printk() for debugging purposes. | 
|  | * Ignored when CONFIG_SND_DEBUG_VERBOSE is not set. | 
|  | * Must set snd module debug parameter to 3 to enable at runtime. | 
|  | */ | 
|  | #define snd_printddd(format, args...) \ | 
|  | __snd_printk(3, __FILE__, __LINE__, format, ##args) | 
|  | #else | 
|  | #define snd_printddd(format, args...) do { } while (0) | 
|  | #endif | 
|  |  | 
|  | static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* index 0-MAX */ | 
|  | static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */ | 
|  | static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; | 
|  | static bool enable_hpi_hwdep = 1; | 
|  |  | 
|  | module_param_array(index, int, NULL, S_IRUGO); | 
|  | MODULE_PARM_DESC(index, "ALSA index value for AudioScience soundcard."); | 
|  |  | 
|  | module_param_array(id, charp, NULL, S_IRUGO); | 
|  | MODULE_PARM_DESC(id, "ALSA ID string for AudioScience soundcard."); | 
|  |  | 
|  | module_param_array(enable, bool, NULL, S_IRUGO); | 
|  | MODULE_PARM_DESC(enable, "ALSA enable AudioScience soundcard."); | 
|  |  | 
|  | module_param(enable_hpi_hwdep, bool, S_IRUGO|S_IWUSR); | 
|  | MODULE_PARM_DESC(enable_hpi_hwdep, | 
|  | "ALSA enable HPI hwdep for AudioScience soundcard "); | 
|  |  | 
|  | /* identify driver */ | 
|  | #ifdef KERNEL_ALSA_BUILD | 
|  | static char *build_info = "Built using headers from kernel source"; | 
|  | module_param(build_info, charp, S_IRUGO); | 
|  | MODULE_PARM_DESC(build_info, "built using headers from kernel source"); | 
|  | #else | 
|  | static char *build_info = "Built within ALSA source"; | 
|  | module_param(build_info, charp, S_IRUGO); | 
|  | MODULE_PARM_DESC(build_info, "built within ALSA source"); | 
|  | #endif | 
|  |  | 
|  | /* set to 1 to dump every control from adapter to log */ | 
|  | static const int mixer_dump; | 
|  |  | 
|  | #define DEFAULT_SAMPLERATE 44100 | 
|  | static int adapter_fs = DEFAULT_SAMPLERATE; | 
|  |  | 
|  | /* defaults */ | 
|  | #define PERIODS_MIN 2 | 
|  | #define PERIOD_BYTES_MIN  2048 | 
|  | #define BUFFER_BYTES_MAX (512 * 1024) | 
|  |  | 
|  | #define MAX_CLOCKSOURCES (HPI_SAMPLECLOCK_SOURCE_LAST + 1 + 7) | 
|  |  | 
|  | struct clk_source { | 
|  | int source; | 
|  | int index; | 
|  | char *name; | 
|  | }; | 
|  |  | 
|  | struct clk_cache { | 
|  | int count; | 
|  | int has_local; | 
|  | struct clk_source s[MAX_CLOCKSOURCES]; | 
|  | }; | 
|  |  | 
|  | /* Per card data */ | 
|  | struct snd_card_asihpi { | 
|  | struct snd_card *card; | 
|  | struct pci_dev *pci; | 
|  | struct hpi_adapter *hpi; | 
|  |  | 
|  | u32 h_mixer; | 
|  | struct clk_cache cc; | 
|  |  | 
|  | u16 can_dma; | 
|  | u16 support_grouping; | 
|  | u16 support_mrx; | 
|  | u16 update_interval_frames; | 
|  | u16 in_max_chans; | 
|  | u16 out_max_chans; | 
|  | u16 in_min_chans; | 
|  | u16 out_min_chans; | 
|  | }; | 
|  |  | 
|  | /* Per stream data */ | 
|  | struct snd_card_asihpi_pcm { | 
|  | struct timer_list timer; | 
|  | unsigned int respawn_timer; | 
|  | unsigned int hpi_buffer_attached; | 
|  | unsigned int buffer_bytes; | 
|  | unsigned int period_bytes; | 
|  | unsigned int bytes_per_sec; | 
|  | unsigned int pcm_buf_host_rw_ofs; /* Host R/W pos */ | 
|  | unsigned int pcm_buf_dma_ofs;	/* DMA R/W offset in buffer */ | 
|  | unsigned int pcm_buf_elapsed_dma_ofs;	/* DMA R/W offset in buffer */ | 
|  | unsigned int drained_count; | 
|  | struct snd_pcm_substream *substream; | 
|  | u32 h_stream; | 
|  | struct hpi_format format; | 
|  | }; | 
|  |  | 
|  | /* universal stream verbs work with out or in stream handles */ | 
|  |  | 
|  | /* Functions to allow driver to give a buffer to HPI for busmastering */ | 
|  |  | 
|  | static u16 hpi_stream_host_buffer_attach( | 
|  | u32 h_stream,   /* handle to outstream. */ | 
|  | u32 size_in_bytes, /* size in bytes of bus mastering buffer */ | 
|  | u32 pci_address | 
|  | ) | 
|  | { | 
|  | struct hpi_message hm; | 
|  | struct hpi_response hr; | 
|  | unsigned int obj = hpi_handle_object(h_stream); | 
|  |  | 
|  | if (!h_stream) | 
|  | return HPI_ERROR_INVALID_OBJ; | 
|  | hpi_init_message_response(&hm, &hr, obj, | 
|  | obj == HPI_OBJ_OSTREAM ? | 
|  | HPI_OSTREAM_HOSTBUFFER_ALLOC : | 
|  | HPI_ISTREAM_HOSTBUFFER_ALLOC); | 
|  |  | 
|  | hpi_handle_to_indexes(h_stream, &hm.adapter_index, | 
|  | &hm.obj_index); | 
|  |  | 
|  | hm.u.d.u.buffer.buffer_size = size_in_bytes; | 
|  | hm.u.d.u.buffer.pci_address = pci_address; | 
|  | hm.u.d.u.buffer.command = HPI_BUFFER_CMD_INTERNAL_GRANTADAPTER; | 
|  | hpi_send_recv(&hm, &hr); | 
|  | return hr.error; | 
|  | } | 
|  |  | 
|  | static u16 hpi_stream_host_buffer_detach(u32  h_stream) | 
|  | { | 
|  | struct hpi_message hm; | 
|  | struct hpi_response hr; | 
|  | unsigned int obj = hpi_handle_object(h_stream); | 
|  |  | 
|  | if (!h_stream) | 
|  | return HPI_ERROR_INVALID_OBJ; | 
|  |  | 
|  | hpi_init_message_response(&hm, &hr,  obj, | 
|  | obj == HPI_OBJ_OSTREAM ? | 
|  | HPI_OSTREAM_HOSTBUFFER_FREE : | 
|  | HPI_ISTREAM_HOSTBUFFER_FREE); | 
|  |  | 
|  | hpi_handle_to_indexes(h_stream, &hm.adapter_index, | 
|  | &hm.obj_index); | 
|  | hm.u.d.u.buffer.command = HPI_BUFFER_CMD_INTERNAL_REVOKEADAPTER; | 
|  | hpi_send_recv(&hm, &hr); | 
|  | return hr.error; | 
|  | } | 
|  |  | 
|  | static inline u16 hpi_stream_start(u32 h_stream) | 
|  | { | 
|  | if (hpi_handle_object(h_stream) ==  HPI_OBJ_OSTREAM) | 
|  | return hpi_outstream_start(h_stream); | 
|  | else | 
|  | return hpi_instream_start(h_stream); | 
|  | } | 
|  |  | 
|  | static inline u16 hpi_stream_stop(u32 h_stream) | 
|  | { | 
|  | if (hpi_handle_object(h_stream) ==  HPI_OBJ_OSTREAM) | 
|  | return hpi_outstream_stop(h_stream); | 
|  | else | 
|  | return hpi_instream_stop(h_stream); | 
|  | } | 
|  |  | 
|  | static inline u16 hpi_stream_get_info_ex( | 
|  | u32 h_stream, | 
|  | u16        *pw_state, | 
|  | u32        *pbuffer_size, | 
|  | u32        *pdata_in_buffer, | 
|  | u32        *psample_count, | 
|  | u32        *pauxiliary_data | 
|  | ) | 
|  | { | 
|  | u16 e; | 
|  | if (hpi_handle_object(h_stream)  ==  HPI_OBJ_OSTREAM) | 
|  | e = hpi_outstream_get_info_ex(h_stream, pw_state, | 
|  | pbuffer_size, pdata_in_buffer, | 
|  | psample_count, pauxiliary_data); | 
|  | else | 
|  | e = hpi_instream_get_info_ex(h_stream, pw_state, | 
|  | pbuffer_size, pdata_in_buffer, | 
|  | psample_count, pauxiliary_data); | 
|  | return e; | 
|  | } | 
|  |  | 
|  | static inline u16 hpi_stream_group_add( | 
|  | u32 h_master, | 
|  | u32 h_stream) | 
|  | { | 
|  | if (hpi_handle_object(h_master) ==  HPI_OBJ_OSTREAM) | 
|  | return hpi_outstream_group_add(h_master, h_stream); | 
|  | else | 
|  | return hpi_instream_group_add(h_master, h_stream); | 
|  | } | 
|  |  | 
|  | static inline u16 hpi_stream_group_reset(u32 h_stream) | 
|  | { | 
|  | if (hpi_handle_object(h_stream) ==  HPI_OBJ_OSTREAM) | 
|  | return hpi_outstream_group_reset(h_stream); | 
|  | else | 
|  | return hpi_instream_group_reset(h_stream); | 
|  | } | 
|  |  | 
|  | static inline u16 hpi_stream_group_get_map( | 
|  | u32 h_stream, u32 *mo, u32 *mi) | 
|  | { | 
|  | if (hpi_handle_object(h_stream) ==  HPI_OBJ_OSTREAM) | 
|  | return hpi_outstream_group_get_map(h_stream, mo, mi); | 
|  | else | 
|  | return hpi_instream_group_get_map(h_stream, mo, mi); | 
|  | } | 
|  |  | 
|  | static u16 handle_error(u16 err, int line, char *filename) | 
|  | { | 
|  | if (err) | 
|  | printk(KERN_WARNING | 
|  | "in file %s, line %d: HPI error %d\n", | 
|  | filename, line, err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | #define hpi_handle_error(x)  handle_error(x, __LINE__, __FILE__) | 
|  |  | 
|  | /***************************** GENERAL PCM ****************/ | 
|  |  | 
|  | static void print_hwparams(struct snd_pcm_substream *substream, | 
|  | struct snd_pcm_hw_params *p) | 
|  | { | 
|  | char name[16]; | 
|  | snd_pcm_debug_name(substream, name, sizeof(name)); | 
|  | snd_printd("%s HWPARAMS\n", name); | 
|  | snd_printd(" samplerate %d Hz\n", params_rate(p)); | 
|  | snd_printd(" channels %d\n", params_channels(p)); | 
|  | snd_printd(" format %d\n", params_format(p)); | 
|  | snd_printd(" subformat %d\n", params_subformat(p)); | 
|  | snd_printd(" buffer %d B\n", params_buffer_bytes(p)); | 
|  | snd_printd(" period %d B\n", params_period_bytes(p)); | 
|  | snd_printd(" access %d\n", params_access(p)); | 
|  | snd_printd(" period_size %d\n", params_period_size(p)); | 
|  | snd_printd(" periods %d\n", params_periods(p)); | 
|  | snd_printd(" buffer_size %d\n", params_buffer_size(p)); | 
|  | snd_printd(" %d B/s\n", params_rate(p) * | 
|  | params_channels(p) * | 
|  | snd_pcm_format_width(params_format(p)) / 8); | 
|  |  | 
|  | } | 
|  |  | 
|  | static snd_pcm_format_t hpi_to_alsa_formats[] = { | 
|  | -1,			/* INVALID */ | 
|  | SNDRV_PCM_FORMAT_U8,	/* HPI_FORMAT_PCM8_UNSIGNED        1 */ | 
|  | SNDRV_PCM_FORMAT_S16,	/* HPI_FORMAT_PCM16_SIGNED         2 */ | 
|  | -1,			/* HPI_FORMAT_MPEG_L1              3 */ | 
|  | SNDRV_PCM_FORMAT_MPEG,	/* HPI_FORMAT_MPEG_L2              4 */ | 
|  | SNDRV_PCM_FORMAT_MPEG,	/* HPI_FORMAT_MPEG_L3              5 */ | 
|  | -1,			/* HPI_FORMAT_DOLBY_AC2            6 */ | 
|  | -1,			/* HPI_FORMAT_DOLBY_AC3            7 */ | 
|  | SNDRV_PCM_FORMAT_S16_BE,/* HPI_FORMAT_PCM16_BIGENDIAN      8 */ | 
|  | -1,			/* HPI_FORMAT_AA_TAGIT1_HITS       9 */ | 
|  | -1,			/* HPI_FORMAT_AA_TAGIT1_INSERTS   10 */ | 
|  | SNDRV_PCM_FORMAT_S32,	/* HPI_FORMAT_PCM32_SIGNED        11 */ | 
|  | -1,			/* HPI_FORMAT_RAW_BITSTREAM       12 */ | 
|  | -1,			/* HPI_FORMAT_AA_TAGIT1_HITS_EX1  13 */ | 
|  | SNDRV_PCM_FORMAT_FLOAT,	/* HPI_FORMAT_PCM32_FLOAT         14 */ | 
|  | #if 1 | 
|  | /* ALSA can't handle 3 byte sample size together with power-of-2 | 
|  | *  constraint on buffer_bytes, so disable this format | 
|  | */ | 
|  | -1 | 
|  | #else | 
|  | /* SNDRV_PCM_FORMAT_S24_3LE */ /* HPI_FORMAT_PCM24_SIGNED 15 */ | 
|  | #endif | 
|  | }; | 
|  |  | 
|  |  | 
|  | static int snd_card_asihpi_format_alsa2hpi(snd_pcm_format_t alsa_format, | 
|  | u16 *hpi_format) | 
|  | { | 
|  | u16 format; | 
|  |  | 
|  | for (format = HPI_FORMAT_PCM8_UNSIGNED; | 
|  | format <= HPI_FORMAT_PCM24_SIGNED; format++) { | 
|  | if (hpi_to_alsa_formats[format] == alsa_format) { | 
|  | *hpi_format = format; | 
|  | return 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | snd_printd(KERN_WARNING "failed match for alsa format %d\n", | 
|  | alsa_format); | 
|  | *hpi_format = 0; | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | static void snd_card_asihpi_pcm_samplerates(struct snd_card_asihpi *asihpi, | 
|  | struct snd_pcm_hardware *pcmhw) | 
|  | { | 
|  | u16 err; | 
|  | u32 h_control; | 
|  | u32 sample_rate; | 
|  | int idx; | 
|  | unsigned int rate_min = 200000; | 
|  | unsigned int rate_max = 0; | 
|  | unsigned int rates = 0; | 
|  |  | 
|  | if (asihpi->support_mrx) { | 
|  | rates |= SNDRV_PCM_RATE_CONTINUOUS; | 
|  | rates |= SNDRV_PCM_RATE_8000_96000; | 
|  | rate_min = 8000; | 
|  | rate_max = 100000; | 
|  | } else { | 
|  | /* on cards without SRC, | 
|  | valid rates are determined by sampleclock */ | 
|  | err = hpi_mixer_get_control(asihpi->h_mixer, | 
|  | HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0, | 
|  | HPI_CONTROL_SAMPLECLOCK, &h_control); | 
|  | if (err) { | 
|  | snd_printk(KERN_ERR | 
|  | "No local sampleclock, err %d\n", err); | 
|  | } | 
|  |  | 
|  | for (idx = -1; idx < 100; idx++) { | 
|  | if (idx == -1) { | 
|  | if (hpi_sample_clock_get_sample_rate(h_control, | 
|  | &sample_rate)) | 
|  | continue; | 
|  | } else if (hpi_sample_clock_query_local_rate(h_control, | 
|  | idx, &sample_rate)) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | rate_min = min(rate_min, sample_rate); | 
|  | rate_max = max(rate_max, sample_rate); | 
|  |  | 
|  | switch (sample_rate) { | 
|  | case 5512: | 
|  | rates |= SNDRV_PCM_RATE_5512; | 
|  | break; | 
|  | case 8000: | 
|  | rates |= SNDRV_PCM_RATE_8000; | 
|  | break; | 
|  | case 11025: | 
|  | rates |= SNDRV_PCM_RATE_11025; | 
|  | break; | 
|  | case 16000: | 
|  | rates |= SNDRV_PCM_RATE_16000; | 
|  | break; | 
|  | case 22050: | 
|  | rates |= SNDRV_PCM_RATE_22050; | 
|  | break; | 
|  | case 32000: | 
|  | rates |= SNDRV_PCM_RATE_32000; | 
|  | break; | 
|  | case 44100: | 
|  | rates |= SNDRV_PCM_RATE_44100; | 
|  | break; | 
|  | case 48000: | 
|  | rates |= SNDRV_PCM_RATE_48000; | 
|  | break; | 
|  | case 64000: | 
|  | rates |= SNDRV_PCM_RATE_64000; | 
|  | break; | 
|  | case 88200: | 
|  | rates |= SNDRV_PCM_RATE_88200; | 
|  | break; | 
|  | case 96000: | 
|  | rates |= SNDRV_PCM_RATE_96000; | 
|  | break; | 
|  | case 176400: | 
|  | rates |= SNDRV_PCM_RATE_176400; | 
|  | break; | 
|  | case 192000: | 
|  | rates |= SNDRV_PCM_RATE_192000; | 
|  | break; | 
|  | default: /* some other rate */ | 
|  | rates |= SNDRV_PCM_RATE_KNOT; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | pcmhw->rates = rates; | 
|  | pcmhw->rate_min = rate_min; | 
|  | pcmhw->rate_max = rate_max; | 
|  | } | 
|  |  | 
|  | static int snd_card_asihpi_pcm_hw_params(struct snd_pcm_substream *substream, | 
|  | struct snd_pcm_hw_params *params) | 
|  | { | 
|  | struct snd_pcm_runtime *runtime = substream->runtime; | 
|  | struct snd_card_asihpi_pcm *dpcm = runtime->private_data; | 
|  | struct snd_card_asihpi *card = snd_pcm_substream_chip(substream); | 
|  | int err; | 
|  | u16 format; | 
|  | int width; | 
|  | unsigned int bytes_per_sec; | 
|  |  | 
|  | print_hwparams(substream, params); | 
|  | err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); | 
|  | if (err < 0) | 
|  | return err; | 
|  | err = snd_card_asihpi_format_alsa2hpi(params_format(params), &format); | 
|  | if (err) | 
|  | return err; | 
|  |  | 
|  | hpi_handle_error(hpi_format_create(&dpcm->format, | 
|  | params_channels(params), | 
|  | format, params_rate(params), 0, 0)); | 
|  |  | 
|  | if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { | 
|  | if (hpi_instream_reset(dpcm->h_stream) != 0) | 
|  | return -EINVAL; | 
|  |  | 
|  | if (hpi_instream_set_format( | 
|  | dpcm->h_stream, &dpcm->format) != 0) | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | dpcm->hpi_buffer_attached = 0; | 
|  | if (card->can_dma) { | 
|  | err = hpi_stream_host_buffer_attach(dpcm->h_stream, | 
|  | params_buffer_bytes(params),  runtime->dma_addr); | 
|  | if (err == 0) { | 
|  | snd_printdd( | 
|  | "stream_host_buffer_attach succeeded %u %lu\n", | 
|  | params_buffer_bytes(params), | 
|  | (unsigned long)runtime->dma_addr); | 
|  | } else { | 
|  | snd_printd("stream_host_buffer_attach error %d\n", | 
|  | err); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | err = hpi_stream_get_info_ex(dpcm->h_stream, NULL, | 
|  | &dpcm->hpi_buffer_attached, | 
|  | NULL, NULL, NULL); | 
|  |  | 
|  | snd_printdd("stream_host_buffer_attach status 0x%x\n", | 
|  | dpcm->hpi_buffer_attached); | 
|  |  | 
|  | } | 
|  | bytes_per_sec = params_rate(params) * params_channels(params); | 
|  | width = snd_pcm_format_width(params_format(params)); | 
|  | bytes_per_sec *= width; | 
|  | bytes_per_sec /= 8; | 
|  | if (width < 0 || bytes_per_sec == 0) | 
|  | return -EINVAL; | 
|  |  | 
|  | dpcm->bytes_per_sec = bytes_per_sec; | 
|  | dpcm->buffer_bytes = params_buffer_bytes(params); | 
|  | dpcm->period_bytes = params_period_bytes(params); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int | 
|  | snd_card_asihpi_hw_free(struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct snd_pcm_runtime *runtime = substream->runtime; | 
|  | struct snd_card_asihpi_pcm *dpcm = runtime->private_data; | 
|  | if (dpcm->hpi_buffer_attached) | 
|  | hpi_stream_host_buffer_detach(dpcm->h_stream); | 
|  |  | 
|  | snd_pcm_lib_free_pages(substream); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void snd_card_asihpi_runtime_free(struct snd_pcm_runtime *runtime) | 
|  | { | 
|  | struct snd_card_asihpi_pcm *dpcm = runtime->private_data; | 
|  | kfree(dpcm); | 
|  | } | 
|  |  | 
|  | static void snd_card_asihpi_pcm_timer_start(struct snd_pcm_substream * | 
|  | substream) | 
|  | { | 
|  | struct snd_pcm_runtime *runtime = substream->runtime; | 
|  | struct snd_card_asihpi_pcm *dpcm = runtime->private_data; | 
|  | int expiry; | 
|  |  | 
|  | expiry = HZ / 200; | 
|  | /*? (dpcm->period_bytes * HZ / dpcm->bytes_per_sec); */ | 
|  | expiry = max(expiry, 1); /* don't let it be zero! */ | 
|  | dpcm->timer.expires = jiffies + expiry; | 
|  | dpcm->respawn_timer = 1; | 
|  | add_timer(&dpcm->timer); | 
|  | } | 
|  |  | 
|  | static void snd_card_asihpi_pcm_timer_stop(struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct snd_pcm_runtime *runtime = substream->runtime; | 
|  | struct snd_card_asihpi_pcm *dpcm = runtime->private_data; | 
|  |  | 
|  | dpcm->respawn_timer = 0; | 
|  | del_timer(&dpcm->timer); | 
|  | } | 
|  |  | 
|  | static int snd_card_asihpi_trigger(struct snd_pcm_substream *substream, | 
|  | int cmd) | 
|  | { | 
|  | struct snd_card_asihpi_pcm *dpcm = substream->runtime->private_data; | 
|  | struct snd_card_asihpi *card = snd_pcm_substream_chip(substream); | 
|  | struct snd_pcm_substream *s; | 
|  | u16 e; | 
|  | char name[16]; | 
|  |  | 
|  | snd_pcm_debug_name(substream, name, sizeof(name)); | 
|  | snd_printdd("%s trigger\n", name); | 
|  |  | 
|  | switch (cmd) { | 
|  | case SNDRV_PCM_TRIGGER_START: | 
|  | snd_pcm_group_for_each_entry(s, substream) { | 
|  | struct snd_pcm_runtime *runtime = s->runtime; | 
|  | struct snd_card_asihpi_pcm *ds = runtime->private_data; | 
|  |  | 
|  | if (snd_pcm_substream_chip(s) != card) | 
|  | continue; | 
|  |  | 
|  | /* don't link Cap and Play */ | 
|  | if (substream->stream != s->stream) | 
|  | continue; | 
|  |  | 
|  | ds->drained_count = 0; | 
|  | if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) { | 
|  | /* How do I know how much valid data is present | 
|  | * in buffer? Must be at least one period! | 
|  | * Guessing 2 periods, but if | 
|  | * buffer is bigger it may contain even more | 
|  | * data?? | 
|  | */ | 
|  | unsigned int preload = ds->period_bytes * 1; | 
|  | snd_printddd("%d preload x%x\n", s->number, preload); | 
|  | hpi_handle_error(hpi_outstream_write_buf( | 
|  | ds->h_stream, | 
|  | &runtime->dma_area[0], | 
|  | preload, | 
|  | &ds->format)); | 
|  | ds->pcm_buf_host_rw_ofs = preload; | 
|  | } | 
|  |  | 
|  | if (card->support_grouping) { | 
|  | snd_printdd("%d group\n", s->number); | 
|  | e = hpi_stream_group_add( | 
|  | dpcm->h_stream, | 
|  | ds->h_stream); | 
|  | if (!e) { | 
|  | snd_pcm_trigger_done(s, substream); | 
|  | } else { | 
|  | hpi_handle_error(e); | 
|  | break; | 
|  | } | 
|  | } else | 
|  | break; | 
|  | } | 
|  | snd_printdd("start\n"); | 
|  | /* start the master stream */ | 
|  | snd_card_asihpi_pcm_timer_start(substream); | 
|  | if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) || | 
|  | !card->can_dma) | 
|  | hpi_handle_error(hpi_stream_start(dpcm->h_stream)); | 
|  | break; | 
|  |  | 
|  | case SNDRV_PCM_TRIGGER_STOP: | 
|  | snd_card_asihpi_pcm_timer_stop(substream); | 
|  | snd_pcm_group_for_each_entry(s, substream) { | 
|  | if (snd_pcm_substream_chip(s) != card) | 
|  | continue; | 
|  | /* don't link Cap and Play */ | 
|  | if (substream->stream != s->stream) | 
|  | continue; | 
|  |  | 
|  | /*? workaround linked streams don't | 
|  | transition to SETUP 20070706*/ | 
|  | s->runtime->status->state = SNDRV_PCM_STATE_SETUP; | 
|  |  | 
|  | if (card->support_grouping) { | 
|  | snd_printdd("%d group\n", s->number); | 
|  | snd_pcm_trigger_done(s, substream); | 
|  | } else | 
|  | break; | 
|  | } | 
|  | snd_printdd("stop\n"); | 
|  |  | 
|  | /* _prepare and _hwparams reset the stream */ | 
|  | hpi_handle_error(hpi_stream_stop(dpcm->h_stream)); | 
|  | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | 
|  | hpi_handle_error( | 
|  | hpi_outstream_reset(dpcm->h_stream)); | 
|  |  | 
|  | if (card->support_grouping) | 
|  | hpi_handle_error(hpi_stream_group_reset(dpcm->h_stream)); | 
|  | break; | 
|  |  | 
|  | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | 
|  | snd_printdd("pause release\n"); | 
|  | hpi_handle_error(hpi_stream_start(dpcm->h_stream)); | 
|  | snd_card_asihpi_pcm_timer_start(substream); | 
|  | break; | 
|  | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | 
|  | snd_printdd("pause\n"); | 
|  | snd_card_asihpi_pcm_timer_stop(substream); | 
|  | hpi_handle_error(hpi_stream_stop(dpcm->h_stream)); | 
|  | break; | 
|  | default: | 
|  | snd_printd(KERN_ERR "\tINVALID\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /*algorithm outline | 
|  | Without linking degenerates to getting single stream pos etc | 
|  | Without mmap 2nd loop degenerates to snd_pcm_period_elapsed | 
|  | */ | 
|  | /* | 
|  | pcm_buf_dma_ofs=get_buf_pos(s); | 
|  | for_each_linked_stream(s) { | 
|  | pcm_buf_dma_ofs=get_buf_pos(s); | 
|  | min_buf_pos = modulo_min(min_buf_pos, pcm_buf_dma_ofs, buffer_bytes) | 
|  | new_data = min(new_data, calc_new_data(pcm_buf_dma_ofs,irq_pos) | 
|  | } | 
|  | timer.expires = jiffies + predict_next_period_ready(min_buf_pos); | 
|  | for_each_linked_stream(s) { | 
|  | s->pcm_buf_dma_ofs = min_buf_pos; | 
|  | if (new_data > period_bytes) { | 
|  | if (mmap) { | 
|  | irq_pos = (irq_pos + period_bytes) % buffer_bytes; | 
|  | if (playback) { | 
|  | write(period_bytes); | 
|  | } else { | 
|  | read(period_bytes); | 
|  | } | 
|  | } | 
|  | snd_pcm_period_elapsed(s); | 
|  | } | 
|  | } | 
|  | */ | 
|  |  | 
|  | /** Minimum of 2 modulo values.  Works correctly when the difference between | 
|  | * the values is less than half the modulus | 
|  | */ | 
|  | static inline unsigned int modulo_min(unsigned int a, unsigned int b, | 
|  | unsigned long int modulus) | 
|  | { | 
|  | unsigned int result; | 
|  | if (((a-b) % modulus) < (modulus/2)) | 
|  | result = b; | 
|  | else | 
|  | result = a; | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /** Timer function, equivalent to interrupt service routine for cards | 
|  | */ | 
|  | static void snd_card_asihpi_timer_function(unsigned long data) | 
|  | { | 
|  | struct snd_card_asihpi_pcm *dpcm = (struct snd_card_asihpi_pcm *)data; | 
|  | struct snd_pcm_substream *substream = dpcm->substream; | 
|  | struct snd_card_asihpi *card = snd_pcm_substream_chip(substream); | 
|  | struct snd_pcm_runtime *runtime; | 
|  | struct snd_pcm_substream *s; | 
|  | unsigned int newdata = 0; | 
|  | unsigned int pcm_buf_dma_ofs, min_buf_pos = 0; | 
|  | unsigned int remdata, xfercount, next_jiffies; | 
|  | int first = 1; | 
|  | int loops = 0; | 
|  | u16 state; | 
|  | u32 buffer_size, bytes_avail, samples_played, on_card_bytes; | 
|  | char name[16]; | 
|  |  | 
|  | snd_pcm_debug_name(substream, name, sizeof(name)); | 
|  |  | 
|  | snd_printdd("%s snd_card_asihpi_timer_function\n", name); | 
|  |  | 
|  | /* find minimum newdata and buffer pos in group */ | 
|  | snd_pcm_group_for_each_entry(s, substream) { | 
|  | struct snd_card_asihpi_pcm *ds = s->runtime->private_data; | 
|  | runtime = s->runtime; | 
|  |  | 
|  | if (snd_pcm_substream_chip(s) != card) | 
|  | continue; | 
|  |  | 
|  | /* don't link Cap and Play */ | 
|  | if (substream->stream != s->stream) | 
|  | continue; | 
|  |  | 
|  | hpi_handle_error(hpi_stream_get_info_ex( | 
|  | ds->h_stream, &state, | 
|  | &buffer_size, &bytes_avail, | 
|  | &samples_played, &on_card_bytes)); | 
|  |  | 
|  | /* number of bytes in on-card buffer */ | 
|  | runtime->delay = on_card_bytes; | 
|  |  | 
|  | if (!card->can_dma) | 
|  | on_card_bytes = bytes_avail; | 
|  |  | 
|  | if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) { | 
|  | pcm_buf_dma_ofs = ds->pcm_buf_host_rw_ofs - bytes_avail; | 
|  | if (state == HPI_STATE_STOPPED) { | 
|  | if (bytes_avail == 0) { | 
|  | hpi_handle_error(hpi_stream_start(ds->h_stream)); | 
|  | snd_printdd("P%d start\n", s->number); | 
|  | ds->drained_count = 0; | 
|  | } | 
|  | } else if (state == HPI_STATE_DRAINED) { | 
|  | snd_printd(KERN_WARNING "P%d drained\n", | 
|  | s->number); | 
|  | ds->drained_count++; | 
|  | if (ds->drained_count > 20) { | 
|  | snd_pcm_stop(s, SNDRV_PCM_STATE_XRUN); | 
|  | continue; | 
|  | } | 
|  | } else { | 
|  | ds->drained_count = 0; | 
|  | } | 
|  | } else | 
|  | pcm_buf_dma_ofs = bytes_avail + ds->pcm_buf_host_rw_ofs; | 
|  |  | 
|  | if (first) { | 
|  | /* can't statically init min when wrap is involved */ | 
|  | min_buf_pos = pcm_buf_dma_ofs; | 
|  | newdata = (pcm_buf_dma_ofs - ds->pcm_buf_elapsed_dma_ofs) % ds->buffer_bytes; | 
|  | first = 0; | 
|  | } else { | 
|  | min_buf_pos = | 
|  | modulo_min(min_buf_pos, pcm_buf_dma_ofs, UINT_MAX+1L); | 
|  | newdata = min( | 
|  | (pcm_buf_dma_ofs - ds->pcm_buf_elapsed_dma_ofs) % ds->buffer_bytes, | 
|  | newdata); | 
|  | } | 
|  |  | 
|  | snd_printdd("hw_ptr 0x%04lX, appl_ptr 0x%04lX\n", | 
|  | (unsigned long)frames_to_bytes(runtime, | 
|  | runtime->status->hw_ptr), | 
|  | (unsigned long)frames_to_bytes(runtime, | 
|  | runtime->control->appl_ptr)); | 
|  |  | 
|  | snd_printdd("%d S=%d, " | 
|  | "rw=0x%04X, dma=0x%04X, left=0x%04X, " | 
|  | "aux=0x%04X space=0x%04X\n", | 
|  | s->number, state, | 
|  | ds->pcm_buf_host_rw_ofs, pcm_buf_dma_ofs, | 
|  | (int)bytes_avail, | 
|  | (int)on_card_bytes, buffer_size-bytes_avail); | 
|  | loops++; | 
|  | } | 
|  | pcm_buf_dma_ofs = min_buf_pos; | 
|  |  | 
|  | remdata = newdata % dpcm->period_bytes; | 
|  | xfercount = newdata - remdata; /* a multiple of period_bytes */ | 
|  | /* come back when on_card_bytes has decreased enough to allow | 
|  | write to happen, or when data has been consumed to make another | 
|  | period | 
|  | */ | 
|  | if (xfercount && (on_card_bytes  > dpcm->period_bytes)) | 
|  | next_jiffies = ((on_card_bytes - dpcm->period_bytes) * HZ / dpcm->bytes_per_sec); | 
|  | else | 
|  | next_jiffies = ((dpcm->period_bytes - remdata) * HZ / dpcm->bytes_per_sec); | 
|  |  | 
|  | next_jiffies = max(next_jiffies, 1U); | 
|  | dpcm->timer.expires = jiffies + next_jiffies; | 
|  | snd_printdd("jif %d buf pos 0x%04X newdata 0x%04X xfer 0x%04X\n", | 
|  | next_jiffies, pcm_buf_dma_ofs, newdata, xfercount); | 
|  |  | 
|  | snd_pcm_group_for_each_entry(s, substream) { | 
|  | struct snd_card_asihpi_pcm *ds = s->runtime->private_data; | 
|  |  | 
|  | /* don't link Cap and Play */ | 
|  | if (substream->stream != s->stream) | 
|  | continue; | 
|  |  | 
|  | ds->pcm_buf_dma_ofs = pcm_buf_dma_ofs; | 
|  |  | 
|  | if (xfercount && | 
|  | /* Limit use of on card fifo for playback */ | 
|  | ((on_card_bytes <= ds->period_bytes) || | 
|  | (s->stream == SNDRV_PCM_STREAM_CAPTURE))) | 
|  |  | 
|  | { | 
|  |  | 
|  | unsigned int buf_ofs = ds->pcm_buf_host_rw_ofs % ds->buffer_bytes; | 
|  | unsigned int xfer1, xfer2; | 
|  | char *pd = &s->runtime->dma_area[buf_ofs]; | 
|  |  | 
|  | if (card->can_dma) { /* buffer wrap is handled at lower level */ | 
|  | xfer1 = xfercount; | 
|  | xfer2 = 0; | 
|  | } else { | 
|  | xfer1 = min(xfercount, ds->buffer_bytes - buf_ofs); | 
|  | xfer2 = xfercount - xfer1; | 
|  | } | 
|  |  | 
|  | if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) { | 
|  | snd_printddd("P%d write1 0x%04X 0x%04X\n", | 
|  | s->number, xfer1, buf_ofs); | 
|  | hpi_handle_error( | 
|  | hpi_outstream_write_buf( | 
|  | ds->h_stream, pd, xfer1, | 
|  | &ds->format)); | 
|  |  | 
|  | if (xfer2) { | 
|  | pd = s->runtime->dma_area; | 
|  |  | 
|  | snd_printddd("P%d write2 0x%04X 0x%04X\n", | 
|  | s->number, | 
|  | xfercount - xfer1, buf_ofs); | 
|  | hpi_handle_error( | 
|  | hpi_outstream_write_buf( | 
|  | ds->h_stream, pd, | 
|  | xfercount - xfer1, | 
|  | &ds->format)); | 
|  | } | 
|  | } else { | 
|  | snd_printddd("C%d read1 0x%04x\n", | 
|  | s->number, xfer1); | 
|  | hpi_handle_error( | 
|  | hpi_instream_read_buf( | 
|  | ds->h_stream, | 
|  | pd, xfer1)); | 
|  | if (xfer2) { | 
|  | pd = s->runtime->dma_area; | 
|  | snd_printddd("C%d read2 0x%04x\n", | 
|  | s->number, xfer2); | 
|  | hpi_handle_error( | 
|  | hpi_instream_read_buf( | 
|  | ds->h_stream, | 
|  | pd, xfer2)); | 
|  | } | 
|  | } | 
|  | ds->pcm_buf_host_rw_ofs += xfercount; | 
|  | ds->pcm_buf_elapsed_dma_ofs += xfercount; | 
|  | snd_pcm_period_elapsed(s); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (dpcm->respawn_timer) | 
|  | add_timer(&dpcm->timer); | 
|  | } | 
|  |  | 
|  | /***************************** PLAYBACK OPS ****************/ | 
|  | static int snd_card_asihpi_playback_ioctl(struct snd_pcm_substream *substream, | 
|  | unsigned int cmd, void *arg) | 
|  | { | 
|  | char name[16]; | 
|  | snd_pcm_debug_name(substream, name, sizeof(name)); | 
|  | snd_printddd(KERN_INFO "%s ioctl %d\n", name, cmd); | 
|  | return snd_pcm_lib_ioctl(substream, cmd, arg); | 
|  | } | 
|  |  | 
|  | static int snd_card_asihpi_playback_prepare(struct snd_pcm_substream * | 
|  | substream) | 
|  | { | 
|  | struct snd_pcm_runtime *runtime = substream->runtime; | 
|  | struct snd_card_asihpi_pcm *dpcm = runtime->private_data; | 
|  |  | 
|  | snd_printdd("P%d prepare\n", substream->number); | 
|  |  | 
|  | hpi_handle_error(hpi_outstream_reset(dpcm->h_stream)); | 
|  | dpcm->pcm_buf_host_rw_ofs = 0; | 
|  | dpcm->pcm_buf_dma_ofs = 0; | 
|  | dpcm->pcm_buf_elapsed_dma_ofs = 0; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static snd_pcm_uframes_t | 
|  | snd_card_asihpi_playback_pointer(struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct snd_pcm_runtime *runtime = substream->runtime; | 
|  | struct snd_card_asihpi_pcm *dpcm = runtime->private_data; | 
|  | snd_pcm_uframes_t ptr; | 
|  | char name[16]; | 
|  | snd_pcm_debug_name(substream, name, sizeof(name)); | 
|  |  | 
|  | ptr = bytes_to_frames(runtime, dpcm->pcm_buf_dma_ofs  % dpcm->buffer_bytes); | 
|  | snd_printddd("%s pointer = 0x%04lx\n", name, (unsigned long)ptr); | 
|  | return ptr; | 
|  | } | 
|  |  | 
|  | static u64 snd_card_asihpi_playback_formats(struct snd_card_asihpi *asihpi, | 
|  | u32 h_stream) | 
|  | { | 
|  | struct hpi_format hpi_format; | 
|  | u16 format; | 
|  | u16 err; | 
|  | u32 h_control; | 
|  | u32 sample_rate = 48000; | 
|  | u64 formats = 0; | 
|  |  | 
|  | /* on cards without SRC, must query at valid rate, | 
|  | * maybe set by external sync | 
|  | */ | 
|  | err = hpi_mixer_get_control(asihpi->h_mixer, | 
|  | HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0, | 
|  | HPI_CONTROL_SAMPLECLOCK, &h_control); | 
|  |  | 
|  | if (!err) | 
|  | err = hpi_sample_clock_get_sample_rate(h_control, | 
|  | &sample_rate); | 
|  |  | 
|  | for (format = HPI_FORMAT_PCM8_UNSIGNED; | 
|  | format <= HPI_FORMAT_PCM24_SIGNED; format++) { | 
|  | err = hpi_format_create(&hpi_format, asihpi->out_max_chans, | 
|  | format, sample_rate, 128000, 0); | 
|  | if (!err) | 
|  | err = hpi_outstream_query_format(h_stream, &hpi_format); | 
|  | if (!err && (hpi_to_alsa_formats[format] != -1)) | 
|  | formats |= (1ULL << hpi_to_alsa_formats[format]); | 
|  | } | 
|  | return formats; | 
|  | } | 
|  |  | 
|  | static int snd_card_asihpi_playback_open(struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct snd_pcm_runtime *runtime = substream->runtime; | 
|  | struct snd_card_asihpi_pcm *dpcm; | 
|  | struct snd_card_asihpi *card = snd_pcm_substream_chip(substream); | 
|  | struct snd_pcm_hardware snd_card_asihpi_playback; | 
|  | int err; | 
|  |  | 
|  | dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL); | 
|  | if (dpcm == NULL) | 
|  | return -ENOMEM; | 
|  |  | 
|  | err = hpi_outstream_open(card->hpi->adapter->index, | 
|  | substream->number, &dpcm->h_stream); | 
|  | hpi_handle_error(err); | 
|  | if (err) | 
|  | kfree(dpcm); | 
|  | if (err == HPI_ERROR_OBJ_ALREADY_OPEN) | 
|  | return -EBUSY; | 
|  | if (err) | 
|  | return -EIO; | 
|  |  | 
|  | /*? also check ASI5000 samplerate source | 
|  | If external, only support external rate. | 
|  | If internal and other stream playing, can't switch | 
|  | */ | 
|  |  | 
|  | init_timer(&dpcm->timer); | 
|  | dpcm->timer.data = (unsigned long) dpcm; | 
|  | dpcm->timer.function = snd_card_asihpi_timer_function; | 
|  | dpcm->substream = substream; | 
|  | runtime->private_data = dpcm; | 
|  | runtime->private_free = snd_card_asihpi_runtime_free; | 
|  |  | 
|  | memset(&snd_card_asihpi_playback, 0, sizeof(snd_card_asihpi_playback)); | 
|  | snd_card_asihpi_playback.buffer_bytes_max = BUFFER_BYTES_MAX; | 
|  | snd_card_asihpi_playback.period_bytes_min = PERIOD_BYTES_MIN; | 
|  | /*?snd_card_asihpi_playback.period_bytes_min = | 
|  | card->out_max_chans * 4096; */ | 
|  | snd_card_asihpi_playback.period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN; | 
|  | snd_card_asihpi_playback.periods_min = PERIODS_MIN; | 
|  | snd_card_asihpi_playback.periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN; | 
|  | /* snd_card_asihpi_playback.fifo_size = 0; */ | 
|  | snd_card_asihpi_playback.channels_max = card->out_max_chans; | 
|  | snd_card_asihpi_playback.channels_min = card->out_min_chans; | 
|  | snd_card_asihpi_playback.formats = | 
|  | snd_card_asihpi_playback_formats(card, dpcm->h_stream); | 
|  |  | 
|  | snd_card_asihpi_pcm_samplerates(card,  &snd_card_asihpi_playback); | 
|  |  | 
|  | snd_card_asihpi_playback.info = SNDRV_PCM_INFO_INTERLEAVED | | 
|  | SNDRV_PCM_INFO_DOUBLE | | 
|  | SNDRV_PCM_INFO_BATCH | | 
|  | SNDRV_PCM_INFO_BLOCK_TRANSFER | | 
|  | SNDRV_PCM_INFO_PAUSE | | 
|  | SNDRV_PCM_INFO_MMAP | | 
|  | SNDRV_PCM_INFO_MMAP_VALID; | 
|  |  | 
|  | if (card->support_grouping) { | 
|  | snd_card_asihpi_playback.info |= SNDRV_PCM_INFO_SYNC_START; | 
|  | snd_pcm_set_sync(substream); | 
|  | } | 
|  |  | 
|  | /* struct is copied, so can create initializer dynamically */ | 
|  | runtime->hw = snd_card_asihpi_playback; | 
|  |  | 
|  | if (card->can_dma) | 
|  | err = snd_pcm_hw_constraint_pow2(runtime, 0, | 
|  | SNDRV_PCM_HW_PARAM_BUFFER_BYTES); | 
|  | if (err < 0) | 
|  | return err; | 
|  |  | 
|  | snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, | 
|  | card->update_interval_frames); | 
|  |  | 
|  | snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, | 
|  | card->update_interval_frames * 2, UINT_MAX); | 
|  |  | 
|  | snd_printdd("playback open\n"); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_card_asihpi_playback_close(struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct snd_pcm_runtime *runtime = substream->runtime; | 
|  | struct snd_card_asihpi_pcm *dpcm = runtime->private_data; | 
|  |  | 
|  | hpi_handle_error(hpi_outstream_close(dpcm->h_stream)); | 
|  | snd_printdd("playback close\n"); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct snd_pcm_ops snd_card_asihpi_playback_mmap_ops = { | 
|  | .open = snd_card_asihpi_playback_open, | 
|  | .close = snd_card_asihpi_playback_close, | 
|  | .ioctl = snd_card_asihpi_playback_ioctl, | 
|  | .hw_params = snd_card_asihpi_pcm_hw_params, | 
|  | .hw_free = snd_card_asihpi_hw_free, | 
|  | .prepare = snd_card_asihpi_playback_prepare, | 
|  | .trigger = snd_card_asihpi_trigger, | 
|  | .pointer = snd_card_asihpi_playback_pointer, | 
|  | }; | 
|  |  | 
|  | /***************************** CAPTURE OPS ****************/ | 
|  | static snd_pcm_uframes_t | 
|  | snd_card_asihpi_capture_pointer(struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct snd_pcm_runtime *runtime = substream->runtime; | 
|  | struct snd_card_asihpi_pcm *dpcm = runtime->private_data; | 
|  |  | 
|  | snd_printddd("capture pointer %d=%d\n", | 
|  | substream->number, dpcm->pcm_buf_dma_ofs); | 
|  | /* NOTE Unlike playback can't use actual samples_played | 
|  | for the capture position, because those samples aren't yet in | 
|  | the local buffer available for reading. | 
|  | */ | 
|  | return bytes_to_frames(runtime, dpcm->pcm_buf_dma_ofs % dpcm->buffer_bytes); | 
|  | } | 
|  |  | 
|  | static int snd_card_asihpi_capture_ioctl(struct snd_pcm_substream *substream, | 
|  | unsigned int cmd, void *arg) | 
|  | { | 
|  | return snd_pcm_lib_ioctl(substream, cmd, arg); | 
|  | } | 
|  |  | 
|  | static int snd_card_asihpi_capture_prepare(struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct snd_pcm_runtime *runtime = substream->runtime; | 
|  | struct snd_card_asihpi_pcm *dpcm = runtime->private_data; | 
|  |  | 
|  | hpi_handle_error(hpi_instream_reset(dpcm->h_stream)); | 
|  | dpcm->pcm_buf_host_rw_ofs = 0; | 
|  | dpcm->pcm_buf_dma_ofs = 0; | 
|  | dpcm->pcm_buf_elapsed_dma_ofs = 0; | 
|  |  | 
|  | snd_printdd("Capture Prepare %d\n", substream->number); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | static u64 snd_card_asihpi_capture_formats(struct snd_card_asihpi *asihpi, | 
|  | u32 h_stream) | 
|  | { | 
|  | struct hpi_format hpi_format; | 
|  | u16 format; | 
|  | u16 err; | 
|  | u32 h_control; | 
|  | u32 sample_rate = 48000; | 
|  | u64 formats = 0; | 
|  |  | 
|  | /* on cards without SRC, must query at valid rate, | 
|  | maybe set by external sync */ | 
|  | err = hpi_mixer_get_control(asihpi->h_mixer, | 
|  | HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0, | 
|  | HPI_CONTROL_SAMPLECLOCK, &h_control); | 
|  |  | 
|  | if (!err) | 
|  | err = hpi_sample_clock_get_sample_rate(h_control, | 
|  | &sample_rate); | 
|  |  | 
|  | for (format = HPI_FORMAT_PCM8_UNSIGNED; | 
|  | format <= HPI_FORMAT_PCM24_SIGNED; format++) { | 
|  |  | 
|  | err = hpi_format_create(&hpi_format, asihpi->in_max_chans, | 
|  | format, sample_rate, 128000, 0); | 
|  | if (!err) | 
|  | err = hpi_instream_query_format(h_stream, &hpi_format); | 
|  | if (!err) | 
|  | formats |= (1ULL << hpi_to_alsa_formats[format]); | 
|  | } | 
|  | return formats; | 
|  | } | 
|  |  | 
|  | static int snd_card_asihpi_capture_open(struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct snd_pcm_runtime *runtime = substream->runtime; | 
|  | struct snd_card_asihpi *card = snd_pcm_substream_chip(substream); | 
|  | struct snd_card_asihpi_pcm *dpcm; | 
|  | struct snd_pcm_hardware snd_card_asihpi_capture; | 
|  | int err; | 
|  |  | 
|  | dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL); | 
|  | if (dpcm == NULL) | 
|  | return -ENOMEM; | 
|  |  | 
|  | snd_printdd("capture open adapter %d stream %d\n", | 
|  | card->hpi->adapter->index, substream->number); | 
|  |  | 
|  | err = hpi_handle_error( | 
|  | hpi_instream_open(card->hpi->adapter->index, | 
|  | substream->number, &dpcm->h_stream)); | 
|  | if (err) | 
|  | kfree(dpcm); | 
|  | if (err == HPI_ERROR_OBJ_ALREADY_OPEN) | 
|  | return -EBUSY; | 
|  | if (err) | 
|  | return -EIO; | 
|  |  | 
|  | init_timer(&dpcm->timer); | 
|  | dpcm->timer.data = (unsigned long) dpcm; | 
|  | dpcm->timer.function = snd_card_asihpi_timer_function; | 
|  | dpcm->substream = substream; | 
|  | runtime->private_data = dpcm; | 
|  | runtime->private_free = snd_card_asihpi_runtime_free; | 
|  |  | 
|  | memset(&snd_card_asihpi_capture, 0, sizeof(snd_card_asihpi_capture)); | 
|  | snd_card_asihpi_capture.buffer_bytes_max = BUFFER_BYTES_MAX; | 
|  | snd_card_asihpi_capture.period_bytes_min = PERIOD_BYTES_MIN; | 
|  | snd_card_asihpi_capture.period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN; | 
|  | snd_card_asihpi_capture.periods_min = PERIODS_MIN; | 
|  | snd_card_asihpi_capture.periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN; | 
|  | /* snd_card_asihpi_capture.fifo_size = 0; */ | 
|  | snd_card_asihpi_capture.channels_max = card->in_max_chans; | 
|  | snd_card_asihpi_capture.channels_min = card->in_min_chans; | 
|  | snd_card_asihpi_capture.formats = | 
|  | snd_card_asihpi_capture_formats(card, dpcm->h_stream); | 
|  | snd_card_asihpi_pcm_samplerates(card,  &snd_card_asihpi_capture); | 
|  | snd_card_asihpi_capture.info = SNDRV_PCM_INFO_INTERLEAVED | | 
|  | SNDRV_PCM_INFO_MMAP | | 
|  | SNDRV_PCM_INFO_MMAP_VALID; | 
|  |  | 
|  | if (card->support_grouping) | 
|  | snd_card_asihpi_capture.info |= SNDRV_PCM_INFO_SYNC_START; | 
|  |  | 
|  | runtime->hw = snd_card_asihpi_capture; | 
|  |  | 
|  | if (card->can_dma) | 
|  | err = snd_pcm_hw_constraint_pow2(runtime, 0, | 
|  | SNDRV_PCM_HW_PARAM_BUFFER_BYTES); | 
|  | if (err < 0) | 
|  | return err; | 
|  |  | 
|  | snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, | 
|  | card->update_interval_frames); | 
|  | snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, | 
|  | card->update_interval_frames * 2, UINT_MAX); | 
|  |  | 
|  | snd_pcm_set_sync(substream); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_card_asihpi_capture_close(struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct snd_card_asihpi_pcm *dpcm = substream->runtime->private_data; | 
|  |  | 
|  | hpi_handle_error(hpi_instream_close(dpcm->h_stream)); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct snd_pcm_ops snd_card_asihpi_capture_mmap_ops = { | 
|  | .open = snd_card_asihpi_capture_open, | 
|  | .close = snd_card_asihpi_capture_close, | 
|  | .ioctl = snd_card_asihpi_capture_ioctl, | 
|  | .hw_params = snd_card_asihpi_pcm_hw_params, | 
|  | .hw_free = snd_card_asihpi_hw_free, | 
|  | .prepare = snd_card_asihpi_capture_prepare, | 
|  | .trigger = snd_card_asihpi_trigger, | 
|  | .pointer = snd_card_asihpi_capture_pointer, | 
|  | }; | 
|  |  | 
|  | static int __devinit snd_card_asihpi_pcm_new( | 
|  | struct snd_card_asihpi *asihpi, int device) | 
|  | { | 
|  | struct snd_pcm *pcm; | 
|  | int err; | 
|  | u16 num_instreams, num_outstreams, x16; | 
|  | u32 x32; | 
|  |  | 
|  | err = hpi_adapter_get_info(asihpi->hpi->adapter->index, | 
|  | &num_outstreams, &num_instreams, | 
|  | &x16, &x32, &x16); | 
|  |  | 
|  | err = snd_pcm_new(asihpi->card, "Asihpi PCM", device, | 
|  | num_outstreams,	num_instreams, &pcm); | 
|  | if (err < 0) | 
|  | return err; | 
|  | /* pointer to ops struct is stored, dont change ops afterwards! */ | 
|  | snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, | 
|  | &snd_card_asihpi_playback_mmap_ops); | 
|  | snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, | 
|  | &snd_card_asihpi_capture_mmap_ops); | 
|  |  | 
|  | pcm->private_data = asihpi; | 
|  | pcm->info_flags = 0; | 
|  | strcpy(pcm->name, "Asihpi PCM"); | 
|  |  | 
|  | /*? do we want to emulate MMAP for non-BBM cards? | 
|  | Jack doesn't work with ALSAs MMAP emulation - WHY NOT? */ | 
|  | snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, | 
|  | snd_dma_pci_data(asihpi->pci), | 
|  | 64*1024, BUFFER_BYTES_MAX); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /***************************** MIXER CONTROLS ****************/ | 
|  | struct hpi_control { | 
|  | u32 h_control; | 
|  | u16 control_type; | 
|  | u16 src_node_type; | 
|  | u16 src_node_index; | 
|  | u16 dst_node_type; | 
|  | u16 dst_node_index; | 
|  | u16 band; | 
|  | char name[44]; /* copied to snd_ctl_elem_id.name[44]; */ | 
|  | }; | 
|  |  | 
|  | static const char * const asihpi_tuner_band_names[] = { | 
|  | "invalid", | 
|  | "AM", | 
|  | "FM mono", | 
|  | "TV NTSC-M", | 
|  | "FM stereo", | 
|  | "AUX", | 
|  | "TV PAL BG", | 
|  | "TV PAL I", | 
|  | "TV PAL DK", | 
|  | "TV SECAM", | 
|  | }; | 
|  |  | 
|  | compile_time_assert( | 
|  | (ARRAY_SIZE(asihpi_tuner_band_names) == | 
|  | (HPI_TUNER_BAND_LAST+1)), | 
|  | assert_tuner_band_names_size); | 
|  |  | 
|  | static const char * const asihpi_src_names[] = { | 
|  | "no source", | 
|  | "PCM", | 
|  | "Line", | 
|  | "Digital", | 
|  | "Tuner", | 
|  | "RF", | 
|  | "Clock", | 
|  | "Bitstream", | 
|  | "Mic", | 
|  | "Net", | 
|  | "Analog", | 
|  | "Adapter", | 
|  | "RTP", | 
|  | "Internal" | 
|  | }; | 
|  |  | 
|  | compile_time_assert( | 
|  | (ARRAY_SIZE(asihpi_src_names) == | 
|  | (HPI_SOURCENODE_LAST_INDEX-HPI_SOURCENODE_NONE+1)), | 
|  | assert_src_names_size); | 
|  |  | 
|  | static const char * const asihpi_dst_names[] = { | 
|  | "no destination", | 
|  | "PCM", | 
|  | "Line", | 
|  | "Digital", | 
|  | "RF", | 
|  | "Speaker", | 
|  | "Net", | 
|  | "Analog", | 
|  | "RTP", | 
|  | }; | 
|  |  | 
|  | compile_time_assert( | 
|  | (ARRAY_SIZE(asihpi_dst_names) == | 
|  | (HPI_DESTNODE_LAST_INDEX-HPI_DESTNODE_NONE+1)), | 
|  | assert_dst_names_size); | 
|  |  | 
|  | static inline int ctl_add(struct snd_card *card, struct snd_kcontrol_new *ctl, | 
|  | struct snd_card_asihpi *asihpi) | 
|  | { | 
|  | int err; | 
|  |  | 
|  | err = snd_ctl_add(card, snd_ctl_new1(ctl, asihpi)); | 
|  | if (err < 0) | 
|  | return err; | 
|  | else if (mixer_dump) | 
|  | snd_printk(KERN_INFO "added %s(%d)\n", ctl->name, ctl->index); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Convert HPI control name and location into ALSA control name */ | 
|  | static void asihpi_ctl_init(struct snd_kcontrol_new *snd_control, | 
|  | struct hpi_control *hpi_ctl, | 
|  | char *name) | 
|  | { | 
|  | char *dir; | 
|  | memset(snd_control, 0, sizeof(*snd_control)); | 
|  | snd_control->name = hpi_ctl->name; | 
|  | snd_control->private_value = hpi_ctl->h_control; | 
|  | snd_control->iface = SNDRV_CTL_ELEM_IFACE_MIXER; | 
|  | snd_control->index = 0; | 
|  |  | 
|  | if (hpi_ctl->src_node_type + HPI_SOURCENODE_NONE == HPI_SOURCENODE_CLOCK_SOURCE) | 
|  | dir = ""; /* clock is neither capture nor playback */ | 
|  | else if (hpi_ctl->dst_node_type + HPI_DESTNODE_NONE == HPI_DESTNODE_ISTREAM) | 
|  | dir = "Capture ";  /* On or towards a PCM capture destination*/ | 
|  | else if ((hpi_ctl->src_node_type + HPI_SOURCENODE_NONE != HPI_SOURCENODE_OSTREAM) && | 
|  | (!hpi_ctl->dst_node_type)) | 
|  | dir = "Capture "; /* On a source node that is not PCM playback */ | 
|  | else if (hpi_ctl->src_node_type && | 
|  | (hpi_ctl->src_node_type + HPI_SOURCENODE_NONE != HPI_SOURCENODE_OSTREAM) && | 
|  | (hpi_ctl->dst_node_type)) | 
|  | dir = "Monitor Playback "; /* Between an input and an output */ | 
|  | else | 
|  | dir = "Playback "; /* PCM Playback source, or  output node */ | 
|  |  | 
|  | if (hpi_ctl->src_node_type && hpi_ctl->dst_node_type) | 
|  | sprintf(hpi_ctl->name, "%s %d %s %d %s%s", | 
|  | asihpi_src_names[hpi_ctl->src_node_type], | 
|  | hpi_ctl->src_node_index, | 
|  | asihpi_dst_names[hpi_ctl->dst_node_type], | 
|  | hpi_ctl->dst_node_index, | 
|  | dir, name); | 
|  | else if (hpi_ctl->dst_node_type) { | 
|  | sprintf(hpi_ctl->name, "%s %d %s%s", | 
|  | asihpi_dst_names[hpi_ctl->dst_node_type], | 
|  | hpi_ctl->dst_node_index, | 
|  | dir, name); | 
|  | } else { | 
|  | sprintf(hpi_ctl->name, "%s %d %s%s", | 
|  | asihpi_src_names[hpi_ctl->src_node_type], | 
|  | hpi_ctl->src_node_index, | 
|  | dir, name); | 
|  | } | 
|  | /* printk(KERN_INFO "Adding %s %d to %d ",  hpi_ctl->name, | 
|  | hpi_ctl->wSrcNodeType, hpi_ctl->wDstNodeType); */ | 
|  | } | 
|  |  | 
|  | /*------------------------------------------------------------ | 
|  | Volume controls | 
|  | ------------------------------------------------------------*/ | 
|  | #define VOL_STEP_mB 1 | 
|  | static int snd_asihpi_volume_info(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_info *uinfo) | 
|  | { | 
|  | u32 h_control = kcontrol->private_value; | 
|  | u32 count; | 
|  | u16 err; | 
|  | /* native gains are in millibels */ | 
|  | short min_gain_mB; | 
|  | short max_gain_mB; | 
|  | short step_gain_mB; | 
|  |  | 
|  | err = hpi_volume_query_range(h_control, | 
|  | &min_gain_mB, &max_gain_mB, &step_gain_mB); | 
|  | if (err) { | 
|  | max_gain_mB = 0; | 
|  | min_gain_mB = -10000; | 
|  | step_gain_mB = VOL_STEP_mB; | 
|  | } | 
|  |  | 
|  | err = hpi_meter_query_channels(h_control, &count); | 
|  | if (err) | 
|  | count = HPI_MAX_CHANNELS; | 
|  |  | 
|  | uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | 
|  | uinfo->count = count; | 
|  | uinfo->value.integer.min = min_gain_mB / VOL_STEP_mB; | 
|  | uinfo->value.integer.max = max_gain_mB / VOL_STEP_mB; | 
|  | uinfo->value.integer.step = step_gain_mB / VOL_STEP_mB; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_volume_get(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | u32 h_control = kcontrol->private_value; | 
|  | short an_gain_mB[HPI_MAX_CHANNELS]; | 
|  |  | 
|  | hpi_handle_error(hpi_volume_get_gain(h_control, an_gain_mB)); | 
|  | ucontrol->value.integer.value[0] = an_gain_mB[0] / VOL_STEP_mB; | 
|  | ucontrol->value.integer.value[1] = an_gain_mB[1] / VOL_STEP_mB; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_volume_put(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | int change; | 
|  | u32 h_control = kcontrol->private_value; | 
|  | short an_gain_mB[HPI_MAX_CHANNELS]; | 
|  |  | 
|  | an_gain_mB[0] = | 
|  | (ucontrol->value.integer.value[0]) * VOL_STEP_mB; | 
|  | an_gain_mB[1] = | 
|  | (ucontrol->value.integer.value[1]) * VOL_STEP_mB; | 
|  | /*  change = asihpi->mixer_volume[addr][0] != left || | 
|  | asihpi->mixer_volume[addr][1] != right; | 
|  | */ | 
|  | change = 1; | 
|  | hpi_handle_error(hpi_volume_set_gain(h_control, an_gain_mB)); | 
|  | return change; | 
|  | } | 
|  |  | 
|  | static const DECLARE_TLV_DB_SCALE(db_scale_100, -10000, VOL_STEP_mB, 0); | 
|  |  | 
|  | #define snd_asihpi_volume_mute_info	snd_ctl_boolean_mono_info | 
|  |  | 
|  | static int snd_asihpi_volume_mute_get(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | u32 h_control = kcontrol->private_value; | 
|  | u32 mute; | 
|  |  | 
|  | hpi_handle_error(hpi_volume_get_mute(h_control, &mute)); | 
|  | ucontrol->value.integer.value[0] = mute ? 0 : 1; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_volume_mute_put(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | u32 h_control = kcontrol->private_value; | 
|  | int change = 1; | 
|  | /* HPI currently only supports all or none muting of multichannel volume | 
|  | ALSA Switch element has opposite sense to HPI mute: on==unmuted, off=muted | 
|  | */ | 
|  | int mute =  ucontrol->value.integer.value[0] ? 0 : HPI_BITMASK_ALL_CHANNELS; | 
|  | hpi_handle_error(hpi_volume_set_mute(h_control, mute)); | 
|  | return change; | 
|  | } | 
|  |  | 
|  | static int __devinit snd_asihpi_volume_add(struct snd_card_asihpi *asihpi, | 
|  | struct hpi_control *hpi_ctl) | 
|  | { | 
|  | struct snd_card *card = asihpi->card; | 
|  | struct snd_kcontrol_new snd_control; | 
|  | int err; | 
|  | u32 mute; | 
|  |  | 
|  | asihpi_ctl_init(&snd_control, hpi_ctl, "Volume"); | 
|  | snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | | 
|  | SNDRV_CTL_ELEM_ACCESS_TLV_READ; | 
|  | snd_control.info = snd_asihpi_volume_info; | 
|  | snd_control.get = snd_asihpi_volume_get; | 
|  | snd_control.put = snd_asihpi_volume_put; | 
|  | snd_control.tlv.p = db_scale_100; | 
|  |  | 
|  | err = ctl_add(card, &snd_control, asihpi); | 
|  | if (err) | 
|  | return err; | 
|  |  | 
|  | if (hpi_volume_get_mute(hpi_ctl->h_control, &mute) == 0) { | 
|  | asihpi_ctl_init(&snd_control, hpi_ctl, "Switch"); | 
|  | snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; | 
|  | snd_control.info = snd_asihpi_volume_mute_info; | 
|  | snd_control.get = snd_asihpi_volume_mute_get; | 
|  | snd_control.put = snd_asihpi_volume_mute_put; | 
|  | err = ctl_add(card, &snd_control, asihpi); | 
|  | } | 
|  | return err; | 
|  | } | 
|  |  | 
|  | /*------------------------------------------------------------ | 
|  | Level controls | 
|  | ------------------------------------------------------------*/ | 
|  | static int snd_asihpi_level_info(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_info *uinfo) | 
|  | { | 
|  | u32 h_control = kcontrol->private_value; | 
|  | u16 err; | 
|  | short min_gain_mB; | 
|  | short max_gain_mB; | 
|  | short step_gain_mB; | 
|  |  | 
|  | err = | 
|  | hpi_level_query_range(h_control, &min_gain_mB, | 
|  | &max_gain_mB, &step_gain_mB); | 
|  | if (err) { | 
|  | max_gain_mB = 2400; | 
|  | min_gain_mB = -1000; | 
|  | step_gain_mB = 100; | 
|  | } | 
|  |  | 
|  | uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | 
|  | uinfo->count = 2; | 
|  | uinfo->value.integer.min = min_gain_mB / HPI_UNITS_PER_dB; | 
|  | uinfo->value.integer.max = max_gain_mB / HPI_UNITS_PER_dB; | 
|  | uinfo->value.integer.step = step_gain_mB / HPI_UNITS_PER_dB; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_level_get(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | u32 h_control = kcontrol->private_value; | 
|  | short an_gain_mB[HPI_MAX_CHANNELS]; | 
|  |  | 
|  | hpi_handle_error(hpi_level_get_gain(h_control, an_gain_mB)); | 
|  | ucontrol->value.integer.value[0] = | 
|  | an_gain_mB[0] / HPI_UNITS_PER_dB; | 
|  | ucontrol->value.integer.value[1] = | 
|  | an_gain_mB[1] / HPI_UNITS_PER_dB; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_level_put(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | int change; | 
|  | u32 h_control = kcontrol->private_value; | 
|  | short an_gain_mB[HPI_MAX_CHANNELS]; | 
|  |  | 
|  | an_gain_mB[0] = | 
|  | (ucontrol->value.integer.value[0]) * HPI_UNITS_PER_dB; | 
|  | an_gain_mB[1] = | 
|  | (ucontrol->value.integer.value[1]) * HPI_UNITS_PER_dB; | 
|  | /*  change = asihpi->mixer_level[addr][0] != left || | 
|  | asihpi->mixer_level[addr][1] != right; | 
|  | */ | 
|  | change = 1; | 
|  | hpi_handle_error(hpi_level_set_gain(h_control, an_gain_mB)); | 
|  | return change; | 
|  | } | 
|  |  | 
|  | static const DECLARE_TLV_DB_SCALE(db_scale_level, -1000, 100, 0); | 
|  |  | 
|  | static int __devinit snd_asihpi_level_add(struct snd_card_asihpi *asihpi, | 
|  | struct hpi_control *hpi_ctl) | 
|  | { | 
|  | struct snd_card *card = asihpi->card; | 
|  | struct snd_kcontrol_new snd_control; | 
|  |  | 
|  | /* can't use 'volume' cos some nodes have volume as well */ | 
|  | asihpi_ctl_init(&snd_control, hpi_ctl, "Level"); | 
|  | snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | | 
|  | SNDRV_CTL_ELEM_ACCESS_TLV_READ; | 
|  | snd_control.info = snd_asihpi_level_info; | 
|  | snd_control.get = snd_asihpi_level_get; | 
|  | snd_control.put = snd_asihpi_level_put; | 
|  | snd_control.tlv.p = db_scale_level; | 
|  |  | 
|  | return ctl_add(card, &snd_control, asihpi); | 
|  | } | 
|  |  | 
|  | /*------------------------------------------------------------ | 
|  | AESEBU controls | 
|  | ------------------------------------------------------------*/ | 
|  |  | 
|  | /* AESEBU format */ | 
|  | static const char * const asihpi_aesebu_format_names[] = { | 
|  | "N/A", "S/PDIF", "AES/EBU" }; | 
|  |  | 
|  | static int snd_asihpi_aesebu_format_info(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_info *uinfo) | 
|  | { | 
|  | uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; | 
|  | uinfo->count = 1; | 
|  | uinfo->value.enumerated.items = 3; | 
|  |  | 
|  | if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items) | 
|  | uinfo->value.enumerated.item = | 
|  | uinfo->value.enumerated.items - 1; | 
|  |  | 
|  | strcpy(uinfo->value.enumerated.name, | 
|  | asihpi_aesebu_format_names[uinfo->value.enumerated.item]); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_aesebu_format_get(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol, | 
|  | u16 (*func)(u32, u16 *)) | 
|  | { | 
|  | u32 h_control = kcontrol->private_value; | 
|  | u16 source, err; | 
|  |  | 
|  | err = func(h_control, &source); | 
|  |  | 
|  | /* default to N/A */ | 
|  | ucontrol->value.enumerated.item[0] = 0; | 
|  | /* return success but set the control to N/A */ | 
|  | if (err) | 
|  | return 0; | 
|  | if (source == HPI_AESEBU_FORMAT_SPDIF) | 
|  | ucontrol->value.enumerated.item[0] = 1; | 
|  | if (source == HPI_AESEBU_FORMAT_AESEBU) | 
|  | ucontrol->value.enumerated.item[0] = 2; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_aesebu_format_put(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol, | 
|  | u16 (*func)(u32, u16)) | 
|  | { | 
|  | u32 h_control = kcontrol->private_value; | 
|  |  | 
|  | /* default to S/PDIF */ | 
|  | u16 source = HPI_AESEBU_FORMAT_SPDIF; | 
|  |  | 
|  | if (ucontrol->value.enumerated.item[0] == 1) | 
|  | source = HPI_AESEBU_FORMAT_SPDIF; | 
|  | if (ucontrol->value.enumerated.item[0] == 2) | 
|  | source = HPI_AESEBU_FORMAT_AESEBU; | 
|  |  | 
|  | if (func(h_control, source) != 0) | 
|  | return -EINVAL; | 
|  |  | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_aesebu_rx_format_get(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) { | 
|  | return snd_asihpi_aesebu_format_get(kcontrol, ucontrol, | 
|  | hpi_aesebu_receiver_get_format); | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_aesebu_rx_format_put(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) { | 
|  | return snd_asihpi_aesebu_format_put(kcontrol, ucontrol, | 
|  | hpi_aesebu_receiver_set_format); | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_aesebu_rxstatus_info(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_info *uinfo) | 
|  | { | 
|  | uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | 
|  | uinfo->count = 1; | 
|  |  | 
|  | uinfo->value.integer.min = 0; | 
|  | uinfo->value.integer.max = 0X1F; | 
|  | uinfo->value.integer.step = 1; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_aesebu_rxstatus_get(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) { | 
|  |  | 
|  | u32 h_control = kcontrol->private_value; | 
|  | u16 status; | 
|  |  | 
|  | hpi_handle_error(hpi_aesebu_receiver_get_error_status( | 
|  | h_control, &status)); | 
|  | ucontrol->value.integer.value[0] = status; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int __devinit snd_asihpi_aesebu_rx_add(struct snd_card_asihpi *asihpi, | 
|  | struct hpi_control *hpi_ctl) | 
|  | { | 
|  | struct snd_card *card = asihpi->card; | 
|  | struct snd_kcontrol_new snd_control; | 
|  |  | 
|  | asihpi_ctl_init(&snd_control, hpi_ctl, "Format"); | 
|  | snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; | 
|  | snd_control.info = snd_asihpi_aesebu_format_info; | 
|  | snd_control.get = snd_asihpi_aesebu_rx_format_get; | 
|  | snd_control.put = snd_asihpi_aesebu_rx_format_put; | 
|  |  | 
|  |  | 
|  | if (ctl_add(card, &snd_control, asihpi) < 0) | 
|  | return -EINVAL; | 
|  |  | 
|  | asihpi_ctl_init(&snd_control, hpi_ctl, "Status"); | 
|  | snd_control.access = | 
|  | SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_READ; | 
|  | snd_control.info = snd_asihpi_aesebu_rxstatus_info; | 
|  | snd_control.get = snd_asihpi_aesebu_rxstatus_get; | 
|  |  | 
|  | return ctl_add(card, &snd_control, asihpi); | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_aesebu_tx_format_get(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) { | 
|  | return snd_asihpi_aesebu_format_get(kcontrol, ucontrol, | 
|  | hpi_aesebu_transmitter_get_format); | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_aesebu_tx_format_put(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) { | 
|  | return snd_asihpi_aesebu_format_put(kcontrol, ucontrol, | 
|  | hpi_aesebu_transmitter_set_format); | 
|  | } | 
|  |  | 
|  |  | 
|  | static int __devinit snd_asihpi_aesebu_tx_add(struct snd_card_asihpi *asihpi, | 
|  | struct hpi_control *hpi_ctl) | 
|  | { | 
|  | struct snd_card *card = asihpi->card; | 
|  | struct snd_kcontrol_new snd_control; | 
|  |  | 
|  | asihpi_ctl_init(&snd_control, hpi_ctl, "Format"); | 
|  | snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; | 
|  | snd_control.info = snd_asihpi_aesebu_format_info; | 
|  | snd_control.get = snd_asihpi_aesebu_tx_format_get; | 
|  | snd_control.put = snd_asihpi_aesebu_tx_format_put; | 
|  |  | 
|  | return ctl_add(card, &snd_control, asihpi); | 
|  | } | 
|  |  | 
|  | /*------------------------------------------------------------ | 
|  | Tuner controls | 
|  | ------------------------------------------------------------*/ | 
|  |  | 
|  | /* Gain */ | 
|  |  | 
|  | static int snd_asihpi_tuner_gain_info(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_info *uinfo) | 
|  | { | 
|  | u32 h_control = kcontrol->private_value; | 
|  | u16 err; | 
|  | short idx; | 
|  | u16 gain_range[3]; | 
|  |  | 
|  | for (idx = 0; idx < 3; idx++) { | 
|  | err = hpi_tuner_query_gain(h_control, | 
|  | idx, &gain_range[idx]); | 
|  | if (err != 0) | 
|  | return err; | 
|  | } | 
|  |  | 
|  | uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | 
|  | uinfo->count = 1; | 
|  | uinfo->value.integer.min = ((int)gain_range[0]) / HPI_UNITS_PER_dB; | 
|  | uinfo->value.integer.max = ((int)gain_range[1]) / HPI_UNITS_PER_dB; | 
|  | uinfo->value.integer.step = ((int) gain_range[2]) / HPI_UNITS_PER_dB; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_tuner_gain_get(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | /* | 
|  | struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol); | 
|  | */ | 
|  | u32 h_control = kcontrol->private_value; | 
|  | short gain; | 
|  |  | 
|  | hpi_handle_error(hpi_tuner_get_gain(h_control, &gain)); | 
|  | ucontrol->value.integer.value[0] = gain / HPI_UNITS_PER_dB; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_tuner_gain_put(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | /* | 
|  | struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol); | 
|  | */ | 
|  | u32 h_control = kcontrol->private_value; | 
|  | short gain; | 
|  |  | 
|  | gain = (ucontrol->value.integer.value[0]) * HPI_UNITS_PER_dB; | 
|  | hpi_handle_error(hpi_tuner_set_gain(h_control, gain)); | 
|  |  | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | /* Band  */ | 
|  |  | 
|  | static int asihpi_tuner_band_query(struct snd_kcontrol *kcontrol, | 
|  | u16 *band_list, u32 len) { | 
|  | u32 h_control = kcontrol->private_value; | 
|  | u16 err = 0; | 
|  | u32 i; | 
|  |  | 
|  | for (i = 0; i < len; i++) { | 
|  | err = hpi_tuner_query_band( | 
|  | h_control, i, &band_list[i]); | 
|  | if (err != 0) | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (err && (err != HPI_ERROR_INVALID_OBJ_INDEX)) | 
|  | return -EIO; | 
|  |  | 
|  | return i; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_tuner_band_info(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_info *uinfo) | 
|  | { | 
|  | u16 tuner_bands[HPI_TUNER_BAND_LAST]; | 
|  | int num_bands = 0; | 
|  |  | 
|  | num_bands = asihpi_tuner_band_query(kcontrol, tuner_bands, | 
|  | HPI_TUNER_BAND_LAST); | 
|  |  | 
|  | if (num_bands < 0) | 
|  | return num_bands; | 
|  |  | 
|  | uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; | 
|  | uinfo->count = 1; | 
|  | uinfo->value.enumerated.items = num_bands; | 
|  |  | 
|  | if (num_bands > 0) { | 
|  | if (uinfo->value.enumerated.item >= | 
|  | uinfo->value.enumerated.items) | 
|  | uinfo->value.enumerated.item = | 
|  | uinfo->value.enumerated.items - 1; | 
|  |  | 
|  | strcpy(uinfo->value.enumerated.name, | 
|  | asihpi_tuner_band_names[ | 
|  | tuner_bands[uinfo->value.enumerated.item]]); | 
|  |  | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_tuner_band_get(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | u32 h_control = kcontrol->private_value; | 
|  | /* | 
|  | struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol); | 
|  | */ | 
|  | u16 band, idx; | 
|  | u16 tuner_bands[HPI_TUNER_BAND_LAST]; | 
|  | u32 num_bands = 0; | 
|  |  | 
|  | num_bands = asihpi_tuner_band_query(kcontrol, tuner_bands, | 
|  | HPI_TUNER_BAND_LAST); | 
|  |  | 
|  | hpi_handle_error(hpi_tuner_get_band(h_control, &band)); | 
|  |  | 
|  | ucontrol->value.enumerated.item[0] = -1; | 
|  | for (idx = 0; idx < HPI_TUNER_BAND_LAST; idx++) | 
|  | if (tuner_bands[idx] == band) { | 
|  | ucontrol->value.enumerated.item[0] = idx; | 
|  | break; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_tuner_band_put(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | /* | 
|  | struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol); | 
|  | */ | 
|  | u32 h_control = kcontrol->private_value; | 
|  | u16 band; | 
|  | u16 tuner_bands[HPI_TUNER_BAND_LAST]; | 
|  | u32 num_bands = 0; | 
|  |  | 
|  | num_bands = asihpi_tuner_band_query(kcontrol, tuner_bands, | 
|  | HPI_TUNER_BAND_LAST); | 
|  |  | 
|  | band = tuner_bands[ucontrol->value.enumerated.item[0]]; | 
|  | hpi_handle_error(hpi_tuner_set_band(h_control, band)); | 
|  |  | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | /* Freq */ | 
|  |  | 
|  | static int snd_asihpi_tuner_freq_info(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_info *uinfo) | 
|  | { | 
|  | u32 h_control = kcontrol->private_value; | 
|  | u16 err; | 
|  | u16 tuner_bands[HPI_TUNER_BAND_LAST]; | 
|  | u16 num_bands = 0, band_iter, idx; | 
|  | u32 freq_range[3], temp_freq_range[3]; | 
|  |  | 
|  | num_bands = asihpi_tuner_band_query(kcontrol, tuner_bands, | 
|  | HPI_TUNER_BAND_LAST); | 
|  |  | 
|  | freq_range[0] = INT_MAX; | 
|  | freq_range[1] = 0; | 
|  | freq_range[2] = INT_MAX; | 
|  |  | 
|  | for (band_iter = 0; band_iter < num_bands; band_iter++) { | 
|  | for (idx = 0; idx < 3; idx++) { | 
|  | err = hpi_tuner_query_frequency(h_control, | 
|  | idx, tuner_bands[band_iter], | 
|  | &temp_freq_range[idx]); | 
|  | if (err != 0) | 
|  | return err; | 
|  | } | 
|  |  | 
|  | /* skip band with bogus stepping */ | 
|  | if (temp_freq_range[2] <= 0) | 
|  | continue; | 
|  |  | 
|  | if (temp_freq_range[0] < freq_range[0]) | 
|  | freq_range[0] = temp_freq_range[0]; | 
|  | if (temp_freq_range[1] > freq_range[1]) | 
|  | freq_range[1] = temp_freq_range[1]; | 
|  | if (temp_freq_range[2] < freq_range[2]) | 
|  | freq_range[2] = temp_freq_range[2]; | 
|  | } | 
|  |  | 
|  | uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | 
|  | uinfo->count = 1; | 
|  | uinfo->value.integer.min = ((int)freq_range[0]); | 
|  | uinfo->value.integer.max = ((int)freq_range[1]); | 
|  | uinfo->value.integer.step = ((int)freq_range[2]); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_tuner_freq_get(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | u32 h_control = kcontrol->private_value; | 
|  | u32 freq; | 
|  |  | 
|  | hpi_handle_error(hpi_tuner_get_frequency(h_control, &freq)); | 
|  | ucontrol->value.integer.value[0] = freq; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_tuner_freq_put(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | u32 h_control = kcontrol->private_value; | 
|  | u32 freq; | 
|  |  | 
|  | freq = ucontrol->value.integer.value[0]; | 
|  | hpi_handle_error(hpi_tuner_set_frequency(h_control, freq)); | 
|  |  | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | /* Tuner control group initializer  */ | 
|  | static int __devinit snd_asihpi_tuner_add(struct snd_card_asihpi *asihpi, | 
|  | struct hpi_control *hpi_ctl) | 
|  | { | 
|  | struct snd_card *card = asihpi->card; | 
|  | struct snd_kcontrol_new snd_control; | 
|  |  | 
|  | snd_control.private_value = hpi_ctl->h_control; | 
|  | snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; | 
|  |  | 
|  | if (!hpi_tuner_get_gain(hpi_ctl->h_control, NULL)) { | 
|  | asihpi_ctl_init(&snd_control, hpi_ctl, "Gain"); | 
|  | snd_control.info = snd_asihpi_tuner_gain_info; | 
|  | snd_control.get = snd_asihpi_tuner_gain_get; | 
|  | snd_control.put = snd_asihpi_tuner_gain_put; | 
|  |  | 
|  | if (ctl_add(card, &snd_control, asihpi) < 0) | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | asihpi_ctl_init(&snd_control, hpi_ctl, "Band"); | 
|  | snd_control.info = snd_asihpi_tuner_band_info; | 
|  | snd_control.get = snd_asihpi_tuner_band_get; | 
|  | snd_control.put = snd_asihpi_tuner_band_put; | 
|  |  | 
|  | if (ctl_add(card, &snd_control, asihpi) < 0) | 
|  | return -EINVAL; | 
|  |  | 
|  | asihpi_ctl_init(&snd_control, hpi_ctl, "Freq"); | 
|  | snd_control.info = snd_asihpi_tuner_freq_info; | 
|  | snd_control.get = snd_asihpi_tuner_freq_get; | 
|  | snd_control.put = snd_asihpi_tuner_freq_put; | 
|  |  | 
|  | return ctl_add(card, &snd_control, asihpi); | 
|  | } | 
|  |  | 
|  | /*------------------------------------------------------------ | 
|  | Meter controls | 
|  | ------------------------------------------------------------*/ | 
|  | static int snd_asihpi_meter_info(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_info *uinfo) | 
|  | { | 
|  | u32 h_control = kcontrol->private_value; | 
|  | u32 count; | 
|  | u16 err; | 
|  | err = hpi_meter_query_channels(h_control, &count); | 
|  | if (err) | 
|  | count = HPI_MAX_CHANNELS; | 
|  |  | 
|  | uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | 
|  | uinfo->count = count; | 
|  | uinfo->value.integer.min = 0; | 
|  | uinfo->value.integer.max = 0x7FFFFFFF; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* linear values for 10dB steps */ | 
|  | static int log2lin[] = { | 
|  | 0x7FFFFFFF, /* 0dB */ | 
|  | 679093956, | 
|  | 214748365, | 
|  | 67909396, | 
|  | 21474837, | 
|  | 6790940, | 
|  | 2147484, /* -60dB */ | 
|  | 679094, | 
|  | 214748, /* -80 */ | 
|  | 67909, | 
|  | 21475, /* -100 */ | 
|  | 6791, | 
|  | 2147, | 
|  | 679, | 
|  | 214, | 
|  | 68, | 
|  | 21, | 
|  | 7, | 
|  | 2 | 
|  | }; | 
|  |  | 
|  | static int snd_asihpi_meter_get(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | u32 h_control = kcontrol->private_value; | 
|  | short an_gain_mB[HPI_MAX_CHANNELS], i; | 
|  | u16 err; | 
|  |  | 
|  | err = hpi_meter_get_peak(h_control, an_gain_mB); | 
|  |  | 
|  | for (i = 0; i < HPI_MAX_CHANNELS; i++) { | 
|  | if (err) { | 
|  | ucontrol->value.integer.value[i] = 0; | 
|  | } else if (an_gain_mB[i] >= 0) { | 
|  | ucontrol->value.integer.value[i] = | 
|  | an_gain_mB[i] << 16; | 
|  | } else { | 
|  | /* -ve is log value in millibels < -60dB, | 
|  | * convert to (roughly!) linear, | 
|  | */ | 
|  | ucontrol->value.integer.value[i] = | 
|  | log2lin[an_gain_mB[i] / -1000]; | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int __devinit snd_asihpi_meter_add(struct snd_card_asihpi *asihpi, | 
|  | struct hpi_control *hpi_ctl, int subidx) | 
|  | { | 
|  | struct snd_card *card = asihpi->card; | 
|  | struct snd_kcontrol_new snd_control; | 
|  |  | 
|  | asihpi_ctl_init(&snd_control, hpi_ctl, "Meter"); | 
|  | snd_control.access = | 
|  | SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_READ; | 
|  | snd_control.info = snd_asihpi_meter_info; | 
|  | snd_control.get = snd_asihpi_meter_get; | 
|  |  | 
|  | snd_control.index = subidx; | 
|  |  | 
|  | return ctl_add(card, &snd_control, asihpi); | 
|  | } | 
|  |  | 
|  | /*------------------------------------------------------------ | 
|  | Multiplexer controls | 
|  | ------------------------------------------------------------*/ | 
|  | static int snd_card_asihpi_mux_count_sources(struct snd_kcontrol *snd_control) | 
|  | { | 
|  | u32 h_control = snd_control->private_value; | 
|  | struct hpi_control hpi_ctl; | 
|  | int s, err; | 
|  | for (s = 0; s < 32; s++) { | 
|  | err = hpi_multiplexer_query_source(h_control, s, | 
|  | &hpi_ctl. | 
|  | src_node_type, | 
|  | &hpi_ctl. | 
|  | src_node_index); | 
|  | if (err) | 
|  | break; | 
|  | } | 
|  | return s; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_mux_info(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_info *uinfo) | 
|  | { | 
|  | int err; | 
|  | u16 src_node_type, src_node_index; | 
|  | u32 h_control = kcontrol->private_value; | 
|  |  | 
|  | uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; | 
|  | uinfo->count = 1; | 
|  | uinfo->value.enumerated.items = | 
|  | snd_card_asihpi_mux_count_sources(kcontrol); | 
|  |  | 
|  | if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items) | 
|  | uinfo->value.enumerated.item = | 
|  | uinfo->value.enumerated.items - 1; | 
|  |  | 
|  | err = | 
|  | hpi_multiplexer_query_source(h_control, | 
|  | uinfo->value.enumerated.item, | 
|  | &src_node_type, &src_node_index); | 
|  |  | 
|  | sprintf(uinfo->value.enumerated.name, "%s %d", | 
|  | asihpi_src_names[src_node_type - HPI_SOURCENODE_NONE], | 
|  | src_node_index); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_mux_get(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | u32 h_control = kcontrol->private_value; | 
|  | u16 source_type, source_index; | 
|  | u16 src_node_type, src_node_index; | 
|  | int s; | 
|  |  | 
|  | hpi_handle_error(hpi_multiplexer_get_source(h_control, | 
|  | &source_type, &source_index)); | 
|  | /* Should cache this search result! */ | 
|  | for (s = 0; s < 256; s++) { | 
|  | if (hpi_multiplexer_query_source(h_control, s, | 
|  | &src_node_type, &src_node_index)) | 
|  | break; | 
|  |  | 
|  | if ((source_type == src_node_type) | 
|  | && (source_index == src_node_index)) { | 
|  | ucontrol->value.enumerated.item[0] = s; | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | snd_printd(KERN_WARNING | 
|  | "Control %x failed to match mux source %hu %hu\n", | 
|  | h_control, source_type, source_index); | 
|  | ucontrol->value.enumerated.item[0] = 0; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_mux_put(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | int change; | 
|  | u32 h_control = kcontrol->private_value; | 
|  | u16 source_type, source_index; | 
|  | u16 e; | 
|  |  | 
|  | change = 1; | 
|  |  | 
|  | e = hpi_multiplexer_query_source(h_control, | 
|  | ucontrol->value.enumerated.item[0], | 
|  | &source_type, &source_index); | 
|  | if (!e) | 
|  | hpi_handle_error( | 
|  | hpi_multiplexer_set_source(h_control, | 
|  | source_type, source_index)); | 
|  | return change; | 
|  | } | 
|  |  | 
|  |  | 
|  | static int  __devinit snd_asihpi_mux_add(struct snd_card_asihpi *asihpi, | 
|  | struct hpi_control *hpi_ctl) | 
|  | { | 
|  | struct snd_card *card = asihpi->card; | 
|  | struct snd_kcontrol_new snd_control; | 
|  |  | 
|  | asihpi_ctl_init(&snd_control, hpi_ctl, "Route"); | 
|  | snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; | 
|  | snd_control.info = snd_asihpi_mux_info; | 
|  | snd_control.get = snd_asihpi_mux_get; | 
|  | snd_control.put = snd_asihpi_mux_put; | 
|  |  | 
|  | return ctl_add(card, &snd_control, asihpi); | 
|  |  | 
|  | } | 
|  |  | 
|  | /*------------------------------------------------------------ | 
|  | Channel mode controls | 
|  | ------------------------------------------------------------*/ | 
|  | static int snd_asihpi_cmode_info(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_info *uinfo) | 
|  | { | 
|  | static const char * const mode_names[HPI_CHANNEL_MODE_LAST + 1] = { | 
|  | "invalid", | 
|  | "Normal", "Swap", | 
|  | "From Left", "From Right", | 
|  | "To Left", "To Right" | 
|  | }; | 
|  |  | 
|  | u32 h_control = kcontrol->private_value; | 
|  | u16 mode; | 
|  | int i; | 
|  | u16 mode_map[6]; | 
|  | int valid_modes = 0; | 
|  |  | 
|  | /* HPI channel mode values can be from 1 to 6 | 
|  | Some adapters only support a contiguous subset | 
|  | */ | 
|  | for (i = 0; i < HPI_CHANNEL_MODE_LAST; i++) | 
|  | if (!hpi_channel_mode_query_mode( | 
|  | h_control, i, &mode)) { | 
|  | mode_map[valid_modes] = mode; | 
|  | valid_modes++; | 
|  | } | 
|  |  | 
|  | if (!valid_modes) | 
|  | return -EINVAL; | 
|  |  | 
|  | uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; | 
|  | uinfo->count = 1; | 
|  | uinfo->value.enumerated.items = valid_modes; | 
|  |  | 
|  | if (uinfo->value.enumerated.item >= valid_modes) | 
|  | uinfo->value.enumerated.item = valid_modes - 1; | 
|  |  | 
|  | strcpy(uinfo->value.enumerated.name, | 
|  | mode_names[mode_map[uinfo->value.enumerated.item]]); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_cmode_get(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | u32 h_control = kcontrol->private_value; | 
|  | u16 mode; | 
|  |  | 
|  | if (hpi_channel_mode_get(h_control, &mode)) | 
|  | mode = 1; | 
|  |  | 
|  | ucontrol->value.enumerated.item[0] = mode - 1; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_cmode_put(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | int change; | 
|  | u32 h_control = kcontrol->private_value; | 
|  |  | 
|  | change = 1; | 
|  |  | 
|  | hpi_handle_error(hpi_channel_mode_set(h_control, | 
|  | ucontrol->value.enumerated.item[0] + 1)); | 
|  | return change; | 
|  | } | 
|  |  | 
|  |  | 
|  | static int __devinit snd_asihpi_cmode_add(struct snd_card_asihpi *asihpi, | 
|  | struct hpi_control *hpi_ctl) | 
|  | { | 
|  | struct snd_card *card = asihpi->card; | 
|  | struct snd_kcontrol_new snd_control; | 
|  |  | 
|  | asihpi_ctl_init(&snd_control, hpi_ctl, "Mode"); | 
|  | snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; | 
|  | snd_control.info = snd_asihpi_cmode_info; | 
|  | snd_control.get = snd_asihpi_cmode_get; | 
|  | snd_control.put = snd_asihpi_cmode_put; | 
|  |  | 
|  | return ctl_add(card, &snd_control, asihpi); | 
|  | } | 
|  |  | 
|  | /*------------------------------------------------------------ | 
|  | Sampleclock source  controls | 
|  | ------------------------------------------------------------*/ | 
|  | static char *sampleclock_sources[MAX_CLOCKSOURCES] = { | 
|  | "N/A", "Local PLL", "Digital Sync", "Word External", "Word Header", | 
|  | "SMPTE", "Digital1", "Auto", "Network", "Invalid", | 
|  | "Prev Module", | 
|  | "Digital2", "Digital3", "Digital4", "Digital5", | 
|  | "Digital6", "Digital7", "Digital8"}; | 
|  |  | 
|  | static int snd_asihpi_clksrc_info(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_info *uinfo) | 
|  | { | 
|  | struct snd_card_asihpi *asihpi = | 
|  | (struct snd_card_asihpi *)(kcontrol->private_data); | 
|  | struct clk_cache *clkcache = &asihpi->cc; | 
|  | uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; | 
|  | uinfo->count = 1; | 
|  | uinfo->value.enumerated.items = clkcache->count; | 
|  |  | 
|  | if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items) | 
|  | uinfo->value.enumerated.item = | 
|  | uinfo->value.enumerated.items - 1; | 
|  |  | 
|  | strcpy(uinfo->value.enumerated.name, | 
|  | clkcache->s[uinfo->value.enumerated.item].name); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_clksrc_get(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | struct snd_card_asihpi *asihpi = | 
|  | (struct snd_card_asihpi *)(kcontrol->private_data); | 
|  | struct clk_cache *clkcache = &asihpi->cc; | 
|  | u32 h_control = kcontrol->private_value; | 
|  | u16 source, srcindex = 0; | 
|  | int i; | 
|  |  | 
|  | ucontrol->value.enumerated.item[0] = 0; | 
|  | if (hpi_sample_clock_get_source(h_control, &source)) | 
|  | source = 0; | 
|  |  | 
|  | if (source == HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT) | 
|  | if (hpi_sample_clock_get_source_index(h_control, &srcindex)) | 
|  | srcindex = 0; | 
|  |  | 
|  | for (i = 0; i < clkcache->count; i++) | 
|  | if ((clkcache->s[i].source == source) && | 
|  | (clkcache->s[i].index == srcindex)) | 
|  | break; | 
|  |  | 
|  | ucontrol->value.enumerated.item[0] = i; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_clksrc_put(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | struct snd_card_asihpi *asihpi = | 
|  | (struct snd_card_asihpi *)(kcontrol->private_data); | 
|  | struct clk_cache *clkcache = &asihpi->cc; | 
|  | int change, item; | 
|  | u32 h_control = kcontrol->private_value; | 
|  |  | 
|  | change = 1; | 
|  | item = ucontrol->value.enumerated.item[0]; | 
|  | if (item >= clkcache->count) | 
|  | item = clkcache->count-1; | 
|  |  | 
|  | hpi_handle_error(hpi_sample_clock_set_source( | 
|  | h_control, clkcache->s[item].source)); | 
|  |  | 
|  | if (clkcache->s[item].source == HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT) | 
|  | hpi_handle_error(hpi_sample_clock_set_source_index( | 
|  | h_control, clkcache->s[item].index)); | 
|  | return change; | 
|  | } | 
|  |  | 
|  | /*------------------------------------------------------------ | 
|  | Clkrate controls | 
|  | ------------------------------------------------------------*/ | 
|  | /* Need to change this to enumerated control with list of rates */ | 
|  | static int snd_asihpi_clklocal_info(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_info *uinfo) | 
|  | { | 
|  | uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | 
|  | uinfo->count = 1; | 
|  | uinfo->value.integer.min = 8000; | 
|  | uinfo->value.integer.max = 192000; | 
|  | uinfo->value.integer.step = 100; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_clklocal_get(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | u32 h_control = kcontrol->private_value; | 
|  | u32 rate; | 
|  | u16 e; | 
|  |  | 
|  | e = hpi_sample_clock_get_local_rate(h_control, &rate); | 
|  | if (!e) | 
|  | ucontrol->value.integer.value[0] = rate; | 
|  | else | 
|  | ucontrol->value.integer.value[0] = 0; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_clklocal_put(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | int change; | 
|  | u32 h_control = kcontrol->private_value; | 
|  |  | 
|  | /*  change = asihpi->mixer_clkrate[addr][0] != left || | 
|  | asihpi->mixer_clkrate[addr][1] != right; | 
|  | */ | 
|  | change = 1; | 
|  | hpi_handle_error(hpi_sample_clock_set_local_rate(h_control, | 
|  | ucontrol->value.integer.value[0])); | 
|  | return change; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_clkrate_info(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_info *uinfo) | 
|  | { | 
|  | uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | 
|  | uinfo->count = 1; | 
|  | uinfo->value.integer.min = 8000; | 
|  | uinfo->value.integer.max = 192000; | 
|  | uinfo->value.integer.step = 100; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_clkrate_get(struct snd_kcontrol *kcontrol, | 
|  | struct snd_ctl_elem_value *ucontrol) | 
|  | { | 
|  | u32 h_control = kcontrol->private_value; | 
|  | u32 rate; | 
|  | u16 e; | 
|  |  | 
|  | e = hpi_sample_clock_get_sample_rate(h_control, &rate); | 
|  | if (!e) | 
|  | ucontrol->value.integer.value[0] = rate; | 
|  | else | 
|  | ucontrol->value.integer.value[0] = 0; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int __devinit snd_asihpi_sampleclock_add(struct snd_card_asihpi *asihpi, | 
|  | struct hpi_control *hpi_ctl) | 
|  | { | 
|  | struct snd_card *card = asihpi->card; | 
|  | struct snd_kcontrol_new snd_control; | 
|  |  | 
|  | struct clk_cache *clkcache = &asihpi->cc; | 
|  | u32 hSC =  hpi_ctl->h_control; | 
|  | int has_aes_in = 0; | 
|  | int i, j; | 
|  | u16 source; | 
|  |  | 
|  | snd_control.private_value = hpi_ctl->h_control; | 
|  |  | 
|  | clkcache->has_local = 0; | 
|  |  | 
|  | for (i = 0; i <= HPI_SAMPLECLOCK_SOURCE_LAST; i++) { | 
|  | if  (hpi_sample_clock_query_source(hSC, | 
|  | i, &source)) | 
|  | break; | 
|  | clkcache->s[i].source = source; | 
|  | clkcache->s[i].index = 0; | 
|  | clkcache->s[i].name = sampleclock_sources[source]; | 
|  | if (source == HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT) | 
|  | has_aes_in = 1; | 
|  | if (source == HPI_SAMPLECLOCK_SOURCE_LOCAL) | 
|  | clkcache->has_local = 1; | 
|  | } | 
|  | if (has_aes_in) | 
|  | /* already will have picked up index 0 above */ | 
|  | for (j = 1; j < 8; j++) { | 
|  | if (hpi_sample_clock_query_source_index(hSC, | 
|  | j, HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT, | 
|  | &source)) | 
|  | break; | 
|  | clkcache->s[i].source = | 
|  | HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT; | 
|  | clkcache->s[i].index = j; | 
|  | clkcache->s[i].name = sampleclock_sources[ | 
|  | j+HPI_SAMPLECLOCK_SOURCE_LAST]; | 
|  | i++; | 
|  | } | 
|  | clkcache->count = i; | 
|  |  | 
|  | asihpi_ctl_init(&snd_control, hpi_ctl, "Source"); | 
|  | snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE ; | 
|  | snd_control.info = snd_asihpi_clksrc_info; | 
|  | snd_control.get = snd_asihpi_clksrc_get; | 
|  | snd_control.put = snd_asihpi_clksrc_put; | 
|  | if (ctl_add(card, &snd_control, asihpi) < 0) | 
|  | return -EINVAL; | 
|  |  | 
|  |  | 
|  | if (clkcache->has_local) { | 
|  | asihpi_ctl_init(&snd_control, hpi_ctl, "Localrate"); | 
|  | snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE ; | 
|  | snd_control.info = snd_asihpi_clklocal_info; | 
|  | snd_control.get = snd_asihpi_clklocal_get; | 
|  | snd_control.put = snd_asihpi_clklocal_put; | 
|  |  | 
|  |  | 
|  | if (ctl_add(card, &snd_control, asihpi) < 0) | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | asihpi_ctl_init(&snd_control, hpi_ctl, "Rate"); | 
|  | snd_control.access = | 
|  | SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_READ; | 
|  | snd_control.info = snd_asihpi_clkrate_info; | 
|  | snd_control.get = snd_asihpi_clkrate_get; | 
|  |  | 
|  | return ctl_add(card, &snd_control, asihpi); | 
|  | } | 
|  | /*------------------------------------------------------------ | 
|  | Mixer | 
|  | ------------------------------------------------------------*/ | 
|  |  | 
|  | static int __devinit snd_card_asihpi_mixer_new(struct snd_card_asihpi *asihpi) | 
|  | { | 
|  | struct snd_card *card = asihpi->card; | 
|  | unsigned int idx = 0; | 
|  | unsigned int subindex = 0; | 
|  | int err; | 
|  | struct hpi_control hpi_ctl, prev_ctl; | 
|  |  | 
|  | if (snd_BUG_ON(!asihpi)) | 
|  | return -EINVAL; | 
|  | strcpy(card->mixername, "Asihpi Mixer"); | 
|  |  | 
|  | err = | 
|  | hpi_mixer_open(asihpi->hpi->adapter->index, | 
|  | &asihpi->h_mixer); | 
|  | hpi_handle_error(err); | 
|  | if (err) | 
|  | return -err; | 
|  |  | 
|  | memset(&prev_ctl, 0, sizeof(prev_ctl)); | 
|  | prev_ctl.control_type = -1; | 
|  |  | 
|  | for (idx = 0; idx < 2000; idx++) { | 
|  | err = hpi_mixer_get_control_by_index( | 
|  | asihpi->h_mixer, | 
|  | idx, | 
|  | &hpi_ctl.src_node_type, | 
|  | &hpi_ctl.src_node_index, | 
|  | &hpi_ctl.dst_node_type, | 
|  | &hpi_ctl.dst_node_index, | 
|  | &hpi_ctl.control_type, | 
|  | &hpi_ctl.h_control); | 
|  | if (err) { | 
|  | if (err == HPI_ERROR_CONTROL_DISABLED) { | 
|  | if (mixer_dump) | 
|  | snd_printk(KERN_INFO | 
|  | "Disabled HPI Control(%d)\n", | 
|  | idx); | 
|  | continue; | 
|  | } else | 
|  | break; | 
|  |  | 
|  | } | 
|  |  | 
|  | hpi_ctl.src_node_type -= HPI_SOURCENODE_NONE; | 
|  | hpi_ctl.dst_node_type -= HPI_DESTNODE_NONE; | 
|  |  | 
|  | /* ASI50xx in SSX mode has multiple meters on the same node. | 
|  | Use subindex to create distinct ALSA controls | 
|  | for any duplicated controls. | 
|  | */ | 
|  | if ((hpi_ctl.control_type == prev_ctl.control_type) && | 
|  | (hpi_ctl.src_node_type == prev_ctl.src_node_type) && | 
|  | (hpi_ctl.src_node_index == prev_ctl.src_node_index) && | 
|  | (hpi_ctl.dst_node_type == prev_ctl.dst_node_type) && | 
|  | (hpi_ctl.dst_node_index == prev_ctl.dst_node_index)) | 
|  | subindex++; | 
|  | else | 
|  | subindex = 0; | 
|  |  | 
|  | prev_ctl = hpi_ctl; | 
|  |  | 
|  | switch (hpi_ctl.control_type) { | 
|  | case HPI_CONTROL_VOLUME: | 
|  | err = snd_asihpi_volume_add(asihpi, &hpi_ctl); | 
|  | break; | 
|  | case HPI_CONTROL_LEVEL: | 
|  | err = snd_asihpi_level_add(asihpi, &hpi_ctl); | 
|  | break; | 
|  | case HPI_CONTROL_MULTIPLEXER: | 
|  | err = snd_asihpi_mux_add(asihpi, &hpi_ctl); | 
|  | break; | 
|  | case HPI_CONTROL_CHANNEL_MODE: | 
|  | err = snd_asihpi_cmode_add(asihpi, &hpi_ctl); | 
|  | break; | 
|  | case HPI_CONTROL_METER: | 
|  | err = snd_asihpi_meter_add(asihpi, &hpi_ctl, subindex); | 
|  | break; | 
|  | case HPI_CONTROL_SAMPLECLOCK: | 
|  | err = snd_asihpi_sampleclock_add( | 
|  | asihpi, &hpi_ctl); | 
|  | break; | 
|  | case HPI_CONTROL_CONNECTION:	/* ignore these */ | 
|  | continue; | 
|  | case HPI_CONTROL_TUNER: | 
|  | err = snd_asihpi_tuner_add(asihpi, &hpi_ctl); | 
|  | break; | 
|  | case HPI_CONTROL_AESEBU_TRANSMITTER: | 
|  | err = snd_asihpi_aesebu_tx_add(asihpi, &hpi_ctl); | 
|  | break; | 
|  | case HPI_CONTROL_AESEBU_RECEIVER: | 
|  | err = snd_asihpi_aesebu_rx_add(asihpi, &hpi_ctl); | 
|  | break; | 
|  | case HPI_CONTROL_VOX: | 
|  | case HPI_CONTROL_BITSTREAM: | 
|  | case HPI_CONTROL_MICROPHONE: | 
|  | case HPI_CONTROL_PARAMETRIC_EQ: | 
|  | case HPI_CONTROL_COMPANDER: | 
|  | default: | 
|  | if (mixer_dump) | 
|  | snd_printk(KERN_INFO | 
|  | "Untranslated HPI Control" | 
|  | "(%d) %d %d %d %d %d\n", | 
|  | idx, | 
|  | hpi_ctl.control_type, | 
|  | hpi_ctl.src_node_type, | 
|  | hpi_ctl.src_node_index, | 
|  | hpi_ctl.dst_node_type, | 
|  | hpi_ctl.dst_node_index); | 
|  | continue; | 
|  | }; | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | if (HPI_ERROR_INVALID_OBJ_INDEX != err) | 
|  | hpi_handle_error(err); | 
|  |  | 
|  | snd_printk(KERN_INFO "%d mixer controls found\n", idx); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /*------------------------------------------------------------ | 
|  | /proc interface | 
|  | ------------------------------------------------------------*/ | 
|  |  | 
|  | static void | 
|  | snd_asihpi_proc_read(struct snd_info_entry *entry, | 
|  | struct snd_info_buffer *buffer) | 
|  | { | 
|  | struct snd_card_asihpi *asihpi = entry->private_data; | 
|  | u32 h_control; | 
|  | u32 rate = 0; | 
|  | u16 source = 0; | 
|  |  | 
|  | u16 num_outstreams; | 
|  | u16 num_instreams; | 
|  | u16 version; | 
|  | u32 serial_number; | 
|  | u16 type; | 
|  |  | 
|  | int err; | 
|  |  | 
|  | snd_iprintf(buffer, "ASIHPI driver proc file\n"); | 
|  |  | 
|  | hpi_handle_error(hpi_adapter_get_info(asihpi->hpi->adapter->index, | 
|  | &num_outstreams, &num_instreams, | 
|  | &version, &serial_number, &type)); | 
|  |  | 
|  | snd_iprintf(buffer, | 
|  | "Adapter type ASI%4X\nHardware Index %d\n" | 
|  | "%d outstreams\n%d instreams\n", | 
|  | type, asihpi->hpi->adapter->index, | 
|  | num_outstreams, num_instreams); | 
|  |  | 
|  | snd_iprintf(buffer, | 
|  | "Serial#%d\nHardware version %c%d\nDSP code version %03d\n", | 
|  | serial_number, ((version >> 3) & 0xf) + 'A', version & 0x7, | 
|  | ((version >> 13) * 100) + ((version >> 7) & 0x3f)); | 
|  |  | 
|  | err = hpi_mixer_get_control(asihpi->h_mixer, | 
|  | HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0, | 
|  | HPI_CONTROL_SAMPLECLOCK, &h_control); | 
|  |  | 
|  | if (!err) { | 
|  | err = hpi_sample_clock_get_sample_rate(h_control, &rate); | 
|  | err += hpi_sample_clock_get_source(h_control, &source); | 
|  |  | 
|  | if (!err) | 
|  | snd_iprintf(buffer, "Sample Clock %dHz, source %s\n", | 
|  | rate, sampleclock_sources[source]); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void __devinit snd_asihpi_proc_init(struct snd_card_asihpi *asihpi) | 
|  | { | 
|  | struct snd_info_entry *entry; | 
|  |  | 
|  | if (!snd_card_proc_new(asihpi->card, "info", &entry)) | 
|  | snd_info_set_text_ops(entry, asihpi, snd_asihpi_proc_read); | 
|  | } | 
|  |  | 
|  | /*------------------------------------------------------------ | 
|  | HWDEP | 
|  | ------------------------------------------------------------*/ | 
|  |  | 
|  | static int snd_asihpi_hpi_open(struct snd_hwdep *hw, struct file *file) | 
|  | { | 
|  | if (enable_hpi_hwdep) | 
|  | return 0; | 
|  | else | 
|  | return -ENODEV; | 
|  |  | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_hpi_release(struct snd_hwdep *hw, struct file *file) | 
|  | { | 
|  | if (enable_hpi_hwdep) | 
|  | return asihpi_hpi_release(file); | 
|  | else | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | static int snd_asihpi_hpi_ioctl(struct snd_hwdep *hw, struct file *file, | 
|  | unsigned int cmd, unsigned long arg) | 
|  | { | 
|  | if (enable_hpi_hwdep) | 
|  | return asihpi_hpi_ioctl(file, cmd, arg); | 
|  | else | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* results in /dev/snd/hwC#D0 file for each card with index # | 
|  | also /proc/asound/hwdep will contain '#-00: asihpi (HPI) for each card' | 
|  | */ | 
|  | static int __devinit snd_asihpi_hpi_new(struct snd_card_asihpi *asihpi, | 
|  | int device, struct snd_hwdep **rhwdep) | 
|  | { | 
|  | struct snd_hwdep *hw; | 
|  | int err; | 
|  |  | 
|  | if (rhwdep) | 
|  | *rhwdep = NULL; | 
|  | err = snd_hwdep_new(asihpi->card, "HPI", device, &hw); | 
|  | if (err < 0) | 
|  | return err; | 
|  | strcpy(hw->name, "asihpi (HPI)"); | 
|  | hw->iface = SNDRV_HWDEP_IFACE_LAST; | 
|  | hw->ops.open = snd_asihpi_hpi_open; | 
|  | hw->ops.ioctl = snd_asihpi_hpi_ioctl; | 
|  | hw->ops.release = snd_asihpi_hpi_release; | 
|  | hw->private_data = asihpi; | 
|  | if (rhwdep) | 
|  | *rhwdep = hw; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /*------------------------------------------------------------ | 
|  | CARD | 
|  | ------------------------------------------------------------*/ | 
|  | static int __devinit snd_asihpi_probe(struct pci_dev *pci_dev, | 
|  | const struct pci_device_id *pci_id) | 
|  | { | 
|  | int err; | 
|  | struct hpi_adapter *hpi; | 
|  | struct snd_card *card; | 
|  | struct snd_card_asihpi *asihpi; | 
|  |  | 
|  | u32 h_control; | 
|  | u32 h_stream; | 
|  | u32 adapter_index; | 
|  |  | 
|  | static int dev; | 
|  | if (dev >= SNDRV_CARDS) | 
|  | return -ENODEV; | 
|  |  | 
|  | /* Should this be enable[hpi->index] ? */ | 
|  | if (!enable[dev]) { | 
|  | dev++; | 
|  | return -ENOENT; | 
|  | } | 
|  |  | 
|  | /* Initialise low-level HPI driver */ | 
|  | err = asihpi_adapter_probe(pci_dev, pci_id); | 
|  | if (err < 0) | 
|  | return err; | 
|  |  | 
|  | hpi = pci_get_drvdata(pci_dev); | 
|  | adapter_index = hpi->adapter->index; | 
|  | /* first try to give the card the same index as its hardware index */ | 
|  | err = snd_card_create(adapter_index, | 
|  | id[adapter_index], THIS_MODULE, | 
|  | sizeof(struct snd_card_asihpi), | 
|  | &card); | 
|  | if (err < 0) { | 
|  | /* if that fails, try the default index==next available */ | 
|  | err = | 
|  | snd_card_create(index[dev], id[dev], | 
|  | THIS_MODULE, | 
|  | sizeof(struct snd_card_asihpi), | 
|  | &card); | 
|  | if (err < 0) | 
|  | return err; | 
|  | snd_printk(KERN_WARNING | 
|  | "**** WARNING **** Adapter index %d->ALSA index %d\n", | 
|  | adapter_index, card->number); | 
|  | } | 
|  |  | 
|  | snd_card_set_dev(card, &pci_dev->dev); | 
|  |  | 
|  | asihpi = card->private_data; | 
|  | asihpi->card = card; | 
|  | asihpi->pci = pci_dev; | 
|  | asihpi->hpi = hpi; | 
|  |  | 
|  | snd_printk(KERN_INFO "adapter ID=%4X index=%d\n", | 
|  | asihpi->hpi->adapter->type, adapter_index); | 
|  |  | 
|  | err = hpi_adapter_get_property(adapter_index, | 
|  | HPI_ADAPTER_PROPERTY_CAPS1, | 
|  | NULL, &asihpi->support_grouping); | 
|  | if (err) | 
|  | asihpi->support_grouping = 0; | 
|  |  | 
|  | err = hpi_adapter_get_property(adapter_index, | 
|  | HPI_ADAPTER_PROPERTY_CAPS2, | 
|  | &asihpi->support_mrx, NULL); | 
|  | if (err) | 
|  | asihpi->support_mrx = 0; | 
|  |  | 
|  | err = hpi_adapter_get_property(adapter_index, | 
|  | HPI_ADAPTER_PROPERTY_INTERVAL, | 
|  | NULL, &asihpi->update_interval_frames); | 
|  | if (err) | 
|  | asihpi->update_interval_frames = 512; | 
|  |  | 
|  | if (!asihpi->can_dma) | 
|  | asihpi->update_interval_frames *= 2; | 
|  |  | 
|  | hpi_handle_error(hpi_instream_open(adapter_index, | 
|  | 0, &h_stream)); | 
|  |  | 
|  | err = hpi_instream_host_buffer_free(h_stream); | 
|  | asihpi->can_dma = (!err); | 
|  |  | 
|  | hpi_handle_error(hpi_instream_close(h_stream)); | 
|  |  | 
|  | err = hpi_adapter_get_property(adapter_index, | 
|  | HPI_ADAPTER_PROPERTY_CURCHANNELS, | 
|  | &asihpi->in_max_chans, &asihpi->out_max_chans); | 
|  | if (err) { | 
|  | asihpi->in_max_chans = 2; | 
|  | asihpi->out_max_chans = 2; | 
|  | } | 
|  |  | 
|  | if (asihpi->out_max_chans > 2) { /* assume LL mode */ | 
|  | asihpi->out_min_chans = asihpi->out_max_chans; | 
|  | asihpi->in_min_chans = asihpi->in_max_chans; | 
|  | asihpi->support_grouping = 0; | 
|  | } else { | 
|  | asihpi->out_min_chans = 1; | 
|  | asihpi->in_min_chans = 1; | 
|  | } | 
|  |  | 
|  | snd_printk(KERN_INFO "Has dma:%d, grouping:%d, mrx:%d\n", | 
|  | asihpi->can_dma, | 
|  | asihpi->support_grouping, | 
|  | asihpi->support_mrx | 
|  | ); | 
|  |  | 
|  | err = snd_card_asihpi_pcm_new(asihpi, 0); | 
|  | if (err < 0) { | 
|  | snd_printk(KERN_ERR "pcm_new failed\n"); | 
|  | goto __nodev; | 
|  | } | 
|  | err = snd_card_asihpi_mixer_new(asihpi); | 
|  | if (err < 0) { | 
|  | snd_printk(KERN_ERR "mixer_new failed\n"); | 
|  | goto __nodev; | 
|  | } | 
|  |  | 
|  | err = hpi_mixer_get_control(asihpi->h_mixer, | 
|  | HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0, | 
|  | HPI_CONTROL_SAMPLECLOCK, &h_control); | 
|  |  | 
|  | if (!err) | 
|  | err = hpi_sample_clock_set_local_rate( | 
|  | h_control, adapter_fs); | 
|  |  | 
|  | snd_asihpi_proc_init(asihpi); | 
|  |  | 
|  | /* always create, can be enabled or disabled dynamically | 
|  | by enable_hwdep  module param*/ | 
|  | snd_asihpi_hpi_new(asihpi, 0, NULL); | 
|  |  | 
|  | strcpy(card->driver, "ASIHPI"); | 
|  |  | 
|  | sprintf(card->shortname, "AudioScience ASI%4X", | 
|  | asihpi->hpi->adapter->type); | 
|  | sprintf(card->longname, "%s %i", | 
|  | card->shortname, adapter_index); | 
|  | err = snd_card_register(card); | 
|  |  | 
|  | if (!err) { | 
|  | hpi->snd_card = card; | 
|  | dev++; | 
|  | return 0; | 
|  | } | 
|  | __nodev: | 
|  | snd_card_free(card); | 
|  | snd_printk(KERN_ERR "snd_asihpi_probe error %d\n", err); | 
|  | return err; | 
|  |  | 
|  | } | 
|  |  | 
|  | static void __devexit snd_asihpi_remove(struct pci_dev *pci_dev) | 
|  | { | 
|  | struct hpi_adapter *hpi = pci_get_drvdata(pci_dev); | 
|  | snd_card_free(hpi->snd_card); | 
|  | hpi->snd_card = NULL; | 
|  | asihpi_adapter_remove(pci_dev); | 
|  | } | 
|  |  | 
|  | static DEFINE_PCI_DEVICE_TABLE(asihpi_pci_tbl) = { | 
|  | {HPI_PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_DSP6205, | 
|  | HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0, | 
|  | (kernel_ulong_t)HPI_6205}, | 
|  | {HPI_PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_PCI2040, | 
|  | HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0, | 
|  | (kernel_ulong_t)HPI_6000}, | 
|  | {0,} | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(pci, asihpi_pci_tbl); | 
|  |  | 
|  | static struct pci_driver driver = { | 
|  | .name = KBUILD_MODNAME, | 
|  | .id_table = asihpi_pci_tbl, | 
|  | .probe = snd_asihpi_probe, | 
|  | .remove = __devexit_p(snd_asihpi_remove), | 
|  | #ifdef CONFIG_PM | 
|  | /*	.suspend = snd_asihpi_suspend, | 
|  | .resume = snd_asihpi_resume, */ | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | static int __init snd_asihpi_init(void) | 
|  | { | 
|  | asihpi_init(); | 
|  | return pci_register_driver(&driver); | 
|  | } | 
|  |  | 
|  | static void __exit snd_asihpi_exit(void) | 
|  | { | 
|  |  | 
|  | pci_unregister_driver(&driver); | 
|  | asihpi_exit(); | 
|  | } | 
|  |  | 
|  | module_init(snd_asihpi_init) | 
|  | module_exit(snd_asihpi_exit) | 
|  |  |