diff options
71 files changed, 4534 insertions, 1242 deletions
diff --git a/Documentation/sound/alsa-configuration.rst b/Documentation/sound/alsa-configuration.rst index 4a3cecc8ad38..02aacd69ab96 100644 --- a/Documentation/sound/alsa-configuration.rst +++ b/Documentation/sound/alsa-configuration.rst @@ -1001,6 +1001,8 @@ position_fix 2 = POSBUF: use position buffer, 3 = VIACOMBO: VIA-specific workaround for capture, 4 = COMBO: use LPIB for playback, auto for capture stream + 5 = SKL+: apply the delay calculation available on recent Intel chips + 6 = FIFO: correct the position with the fixed FIFO size, for recent AMD chips probe_mask Bitmask to probe codecs (default = -1, meaning all slots); When the bit 8 (0x100) is set, the lower 8 bits are used diff --git a/Documentation/sound/hd-audio/models.rst b/Documentation/sound/hd-audio/models.rst index 7d7c191102a7..11298f0ce44d 100644 --- a/Documentation/sound/hd-audio/models.rst +++ b/Documentation/sound/hd-audio/models.rst @@ -260,6 +260,9 @@ alc295-hp-x360 HP Spectre X360 fixups alc-sense-combo Headset button support for Chrome platform +huawei-mbx-stereo + Enable initialization verbs for Huawei MBX stereo speakers; + might be risky, try this at your own risk ALC66x/67x/892 ============== diff --git a/Documentation/sound/hd-audio/notes.rst b/Documentation/sound/hd-audio/notes.rst index 9f7347830ba4..0f3109d9abc8 100644 --- a/Documentation/sound/hd-audio/notes.rst +++ b/Documentation/sound/hd-audio/notes.rst @@ -66,6 +66,11 @@ by comparing both LPIB and position-buffer values. ``position_fix=4`` is another combination available for all controllers, and uses LPIB for the playback and the position-buffer for the capture streams. +``position_fix=5`` is specific to Intel platforms, so far, for Skylake +and onward. It applies the delay calculation for the precise position +reporting. +``position_fix=6`` is to correct the position with the fixed FIFO +size, mainly targeted for the recent AMD controllers. 0 is the default value for all other controllers, the automatic check and fallback to LPIB as described in the above. If you get a problem of repeated sounds, this option might diff --git a/MAINTAINERS b/MAINTAINERS index e7a47b5210fd..1b39e187d756 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6268,12 +6268,14 @@ S: Maintained F: drivers/hwmon/f75375s.c F: include/linux/f75375s.h -FIREWIRE AUDIO DRIVERS +FIREWIRE AUDIO DRIVERS and IEC 61883-1/6 PACKET STREAMING ENGINE M: Clemens Ladisch <clemens@ladisch.de> +M: Takashi Sakamoto <o-takashi@sakamocchi.jp> L: alsa-devel@alsa-project.org (moderated for non-subscribers) T: git git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git S: Maintained F: sound/firewire/ +F: include/uapi/sound/firewire.h FIREWIRE MEDIA DRIVERS (firedtv) M: Stefan Richter <stefanr@s5r6.in-berlin.de> diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index c842735a4f45..901c5b2f76de 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -1951,6 +1951,8 @@ #define PCI_VENDOR_ID_DIGIGRAM 0x1369 #define PCI_SUBDEVICE_ID_DIGIGRAM_LX6464ES_SERIAL_SUBSYSTEM 0xc001 #define PCI_SUBDEVICE_ID_DIGIGRAM_LX6464ES_CAE_SERIAL_SUBSYSTEM 0xc002 +#define PCI_SUBDEVICE_ID_DIGIGRAM_LX6464ESE_SERIAL_SUBSYSTEM 0xc021 +#define PCI_SUBDEVICE_ID_DIGIGRAM_LX6464ESE_CAE_SERIAL_SUBSYSTEM 0xc022 #define PCI_VENDOR_ID_KAWASAKI 0x136b #define PCI_DEVICE_ID_MCHIP_KL5A72002 0xff01 diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h index 871993696c5f..9a0393cf024c 100644 --- a/include/sound/hda_codec.h +++ b/include/sound/hda_codec.h @@ -59,6 +59,7 @@ struct hda_bus { unsigned int in_reset:1; /* during reset operation */ unsigned int no_response_fallback:1; /* don't fallback at RIRB error */ unsigned int bus_probing :1; /* during probing process */ + unsigned int keep_power:1; /* keep power up for notification */ int primary_dig_out_type; /* primary digital out PCM type */ unsigned int mixer_assigned; /* codec addr for mixer name */ diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 4af4af55e854..b260c5fd2337 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -122,10 +122,6 @@ int snd_hdac_codec_modalias(struct hdac_device *hdac, char *buf, size_t size); int snd_hdac_refresh_widgets(struct hdac_device *codec); -unsigned int snd_hdac_make_cmd(struct hdac_device *codec, hda_nid_t nid, - unsigned int verb, unsigned int parm); -int snd_hdac_exec_verb(struct hdac_device *codec, unsigned int cmd, - unsigned int flags, unsigned int *res); int snd_hdac_read(struct hdac_device *codec, hda_nid_t nid, unsigned int verb, unsigned int parm, unsigned int *res); int _snd_hdac_read_parm(struct hdac_device *codec, hda_nid_t nid, int parm, @@ -374,11 +370,6 @@ int snd_hdac_bus_exec_verb_unlocked(struct hdac_bus *bus, unsigned int addr, unsigned int cmd, unsigned int *res); void snd_hdac_bus_queue_event(struct hdac_bus *bus, u32 res, u32 res_ex); -int snd_hdac_bus_add_device(struct hdac_bus *bus, struct hdac_device *codec); -void snd_hdac_bus_remove_device(struct hdac_bus *bus, - struct hdac_device *codec); -void snd_hdac_bus_process_unsol_events(struct work_struct *work); - static inline void snd_hdac_codec_link_up(struct hdac_device *codec) { set_bit(codec->addr, &codec->bus->codec_powered); diff --git a/sound/aoa/codecs/onyx.c b/sound/aoa/codecs/onyx.c index db917546965d..9827bee109c1 100644 --- a/sound/aoa/codecs/onyx.c +++ b/sound/aoa/codecs/onyx.c @@ -71,8 +71,10 @@ static int onyx_read_register(struct onyx *onyx, u8 reg, u8 *value) return 0; } v = i2c_smbus_read_byte_data(onyx->i2c, reg); - if (v < 0) + if (v < 0) { + *value = 0; return -1; + } *value = (u8)v; onyx->cache[ONYX_REG_CONTROL-FIRSTREGISTER] = *value; return 0; diff --git a/sound/firewire/amdtp-am824.c b/sound/firewire/amdtp-am824.c index fd5d6b8ac557..67d735e9a6a4 100644 --- a/sound/firewire/amdtp-am824.c +++ b/sound/firewire/amdtp-am824.c @@ -146,19 +146,24 @@ void amdtp_am824_set_midi_position(struct amdtp_stream *s, } EXPORT_SYMBOL_GPL(amdtp_am824_set_midi_position); -static void write_pcm_s32(struct amdtp_stream *s, - struct snd_pcm_substream *pcm, - __be32 *buffer, unsigned int frames) +static void write_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames, + unsigned int pcm_frames) { struct amdtp_am824 *p = s->protocol; + unsigned int channels = p->pcm_channels; struct snd_pcm_runtime *runtime = pcm->runtime; - unsigned int channels, remaining_frames, i, c; + unsigned int pcm_buffer_pointer; + int remaining_frames; const u32 *src; + int i, c; + + pcm_buffer_pointer = s->pcm_buffer_pointer + pcm_frames; + pcm_buffer_pointer %= runtime->buffer_size; - channels = p->pcm_channels; src = (void *)runtime->dma_area + - frames_to_bytes(runtime, s->pcm_buffer_pointer); - remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + frames_to_bytes(runtime, pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - pcm_buffer_pointer; for (i = 0; i < frames; ++i) { for (c = 0; c < channels; ++c) { @@ -172,19 +177,24 @@ static void write_pcm_s32(struct amdtp_stream *s, } } -static void read_pcm_s32(struct amdtp_stream *s, - struct snd_pcm_substream *pcm, - __be32 *buffer, unsigned int frames) +static void read_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames, + unsigned int pcm_frames) { struct amdtp_am824 *p = s->protocol; + unsigned int channels = p->pcm_channels; struct snd_pcm_runtime *runtime = pcm->runtime; - unsigned int channels, remaining_frames, i, c; + unsigned int pcm_buffer_pointer; + int remaining_frames; u32 *dst; + int i, c; + + pcm_buffer_pointer = s->pcm_buffer_pointer + pcm_frames; + pcm_buffer_pointer %= runtime->buffer_size; - channels = p->pcm_channels; dst = (void *)runtime->dma_area + - frames_to_bytes(runtime, s->pcm_buffer_pointer); - remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + frames_to_bytes(runtime, pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - pcm_buffer_pointer; for (i = 0; i < frames; ++i) { for (c = 0; c < channels; ++c) { @@ -284,7 +294,7 @@ static void midi_rate_use_one_byte(struct amdtp_stream *s, unsigned int port) } static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, - unsigned int frames) + unsigned int frames, unsigned int data_block_counter) { struct amdtp_am824 *p = s->protocol; unsigned int f, port; @@ -293,7 +303,7 @@ static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, for (f = 0; f < frames; f++) { b = (u8 *)&buffer[p->midi_position]; - port = (s->data_block_counter + f) % 8; + port = (data_block_counter + f) % 8; if (f < MAX_MIDI_RX_BLOCKS && midi_ratelimit_per_packet(s, port) && p->midi[port] != NULL && @@ -311,16 +321,20 @@ static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, } } -static void read_midi_messages(struct amdtp_stream *s, - __be32 *buffer, unsigned int frames) +static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int frames, unsigned int data_block_counter) { struct amdtp_am824 *p = s->protocol; - unsigned int f, port; int len; u8 *b; + int f; for (f = 0; f < frames; f++) { - port = (8 - s->ctx_data.tx.first_dbc + s->data_block_counter + f) % 8; + unsigned int port = f; + + if (!(s->flags & CIP_UNALIGHED_DBC)) + port += data_block_counter; + port %= 8; b = (u8 *)&buffer[p->midi_position]; len = b[0] - 0x80; @@ -331,43 +345,60 @@ static void read_midi_messages(struct amdtp_stream *s, } } -static unsigned int process_rx_data_blocks(struct amdtp_stream *s, __be32 *buffer, - unsigned int data_blocks, unsigned int *syt) +static unsigned int process_it_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) { struct amdtp_am824 *p = s->protocol; - struct snd_pcm_substream *pcm = READ_ONCE(s->pcm); - unsigned int pcm_frames; - - if (pcm) { - write_pcm_s32(s, pcm, buffer, data_blocks); - pcm_frames = data_blocks * p->frame_multiplier; - } else { - write_pcm_silence(s, buffer, data_blocks); - pcm_frames = 0; - } + unsigned int pcm_frames = 0; + int i; - if (p->midi_ports) - write_midi_messages(s, buffer, data_blocks); + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buf = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; + + if (pcm) { + write_pcm_s32(s, pcm, buf, data_blocks, pcm_frames); + pcm_frames += data_blocks * p->frame_multiplier; + } else { + write_pcm_silence(s, buf, data_blocks); + } + + if (p->midi_ports) { + write_midi_messages(s, buf, data_blocks, + desc->data_block_counter); + } + } return pcm_frames; } -static unsigned int process_tx_data_blocks(struct amdtp_stream *s, __be32 *buffer, - unsigned int data_blocks, unsigned int *syt) +static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) { struct amdtp_am824 *p = s->protocol; - struct snd_pcm_substream *pcm = READ_ONCE(s->pcm); - unsigned int pcm_frames; - - if (pcm) { - read_pcm_s32(s, pcm, buffer, data_blocks); - pcm_frames = data_blocks * p->frame_multiplier; - } else { - pcm_frames = 0; - } + unsigned int pcm_frames = 0; + int i; + + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buf = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; - if (p->midi_ports) - read_midi_messages(s, buffer, data_blocks); + if (pcm) { + read_pcm_s32(s, pcm, buf, data_blocks, pcm_frames); + pcm_frames += data_blocks * p->frame_multiplier; + } + + if (p->midi_ports) { + read_midi_messages(s, buf, data_blocks, + desc->data_block_counter); + } + } return pcm_frames; } @@ -383,15 +414,14 @@ static unsigned int process_tx_data_blocks(struct amdtp_stream *s, __be32 *buffe int amdtp_am824_init(struct amdtp_stream *s, struct fw_unit *unit, enum amdtp_stream_direction dir, enum cip_flags flags) { - amdtp_stream_process_data_blocks_t process_data_blocks; + amdtp_stream_process_ctx_payloads_t process_ctx_payloads; if (dir == AMDTP_IN_STREAM) - process_data_blocks = process_tx_data_blocks; + process_ctx_payloads = process_ir_ctx_payloads; else - process_data_blocks = process_rx_data_blocks; + process_ctx_payloads = process_it_ctx_payloads; return amdtp_stream_init(s, unit, dir, flags, CIP_FMT_AM, - process_data_blocks, - sizeof(struct amdtp_am824)); + process_ctx_payloads, sizeof(struct amdtp_am824)); } EXPORT_SYMBOL_GPL(amdtp_am824_init); diff --git a/sound/firewire/amdtp-stream-trace.h b/sound/firewire/amdtp-stream-trace.h index 4adbbf789cbe..16c7f6605511 100644 --- a/sound/firewire/amdtp-stream-trace.h +++ b/sound/firewire/amdtp-stream-trace.h @@ -14,8 +14,8 @@ #include <linux/tracepoint.h> TRACE_EVENT(amdtp_packet, - TP_PROTO(const struct amdtp_stream *s, u32 cycles, const __be32 *cip_header, unsigned int payload_length, unsigned int data_blocks, unsigned int index), - TP_ARGS(s, cycles, cip_header, payload_length, data_blocks, index), + TP_PROTO(const struct amdtp_stream *s, u32 cycles, const __be32 *cip_header, unsigned int payload_length, unsigned int data_blocks, unsigned int data_block_counter, unsigned int index), + TP_ARGS(s, cycles, cip_header, payload_length, data_blocks, data_block_counter, index), TP_STRUCT__entry( __field(unsigned int, second) __field(unsigned int, cycle) @@ -47,7 +47,7 @@ TRACE_EVENT(amdtp_packet, } __entry->payload_quadlets = payload_length / sizeof(__be32); __entry->data_blocks = data_blocks; - __entry->data_block_counter = s->data_block_counter, + __entry->data_block_counter = data_block_counter, __entry->packet_index = s->packet_index; __entry->irq = !!in_interrupt(); __entry->index = index; diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c index 4d71d74707cf..e50e28f77e74 100644 --- a/sound/firewire/amdtp-stream.c +++ b/sound/firewire/amdtp-stream.c @@ -74,16 +74,16 @@ static void pcm_period_tasklet(unsigned long data); * @dir: the direction of stream * @flags: the packet transmission method to use * @fmt: the value of fmt field in CIP header - * @process_data_blocks: callback handler to process data blocks + * @process_ctx_payloads: callback handler to process payloads of isoc context * @protocol_size: the size to allocate newly for protocol */ int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit, enum amdtp_stream_direction dir, enum cip_flags flags, unsigned int fmt, - amdtp_stream_process_data_blocks_t process_data_blocks, + amdtp_stream_process_ctx_payloads_t process_ctx_payloads, unsigned int protocol_size) { - if (process_data_blocks == NULL) + if (process_ctx_payloads == NULL) return -EINVAL; s->protocol = kzalloc(protocol_size, GFP_KERNEL); @@ -102,7 +102,10 @@ int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit, s->callbacked = false; s->fmt = fmt; - s->process_data_blocks = process_data_blocks; + s->process_ctx_payloads = process_ctx_payloads; + + if (dir == AMDTP_OUT_STREAM) + s->ctx_data.rx.syt_override = -1; return 0; } @@ -473,12 +476,12 @@ static inline int queue_in_packet(struct amdtp_stream *s, } static void generate_cip_header(struct amdtp_stream *s, __be32 cip_header[2], - unsigned int syt) + unsigned int data_block_counter, unsigned int syt) { cip_header[0] = cpu_to_be32(READ_ONCE(s->source_node_id_field) | (s->data_block_quadlets << CIP_DBS_SHIFT) | ((s->sph << CIP_SPH_SHIFT) & CIP_SPH_MASK) | - s->data_block_counter); + data_block_counter); cip_header[1] = cpu_to_be32(CIP_EOH | ((s->fmt << CIP_FMT_SHIFT) & CIP_FMT_MASK) | ((s->ctx_data.rx.fdf << CIP_FDF_SHIFT) & CIP_FDF_MASK) | @@ -487,8 +490,9 @@ static void generate_cip_header(struct amdtp_stream *s, __be32 cip_header[2], static void build_it_pkt_header(struct amdtp_stream *s, unsigned int cycle, struct fw_iso_packet *params, - unsigned int data_blocks, unsigned int syt, - unsigned int index) + unsigned int data_blocks, + unsigned int data_block_counter, + unsigned int syt, unsigned int index) { unsigned int payload_length; __be32 *cip_header; @@ -496,14 +500,9 @@ static void build_it_pkt_header(struct amdtp_stream *s, unsigned int cycle, payload_length = data_blocks * sizeof(__be32) * s->data_block_quadlets; params->payload_length = payload_length; - if (s->flags & CIP_DBC_IS_END_EVENT) { - s->data_block_counter = - (s->data_block_counter + data_blocks) & 0xff; - } - if (!(s->flags & CIP_NO_HEADER)) { cip_header = (__be32 *)params->header; - generate_cip_header(s, cip_header, syt); + generate_cip_header(s, cip_header, data_block_counter, syt); params->header_length = 2 * sizeof(__be32); payload_length += params->header_length; } else { @@ -511,23 +510,19 @@ static void build_it_pkt_header(struct amdtp_stream *s, unsigned int cycle, } trace_amdtp_packet(s, cycle, cip_header, payload_length, data_blocks, - index); - - if (!(s->flags & CIP_DBC_IS_END_EVENT)) { - s->data_block_counter = - (s->data_block_counter + data_blocks) & 0xff; - } + data_block_counter, index); } static int check_cip_header(struct amdtp_stream *s, const __be32 *buf, unsigned int payload_length, - unsigned int *data_blocks, unsigned int *dbc, - unsigned int *syt) + unsigned int *data_blocks, + unsigned int *data_block_counter, unsigned int *syt) { u32 cip_header[2]; unsigned int sph; unsigned int fmt; unsigned int fdf; + unsigned int dbc; bool lost; cip_header[0] = be32_to_cpu(buf[0]); @@ -579,17 +574,16 @@ static int check_cip_header(struct amdtp_stream *s, const __be32 *buf, } /* Check data block counter continuity */ - *dbc = cip_header[0] & CIP_DBC_MASK; + dbc = cip_header[0] & CIP_DBC_MASK; if (*data_blocks == 0 && (s->flags & CIP_EMPTY_HAS_WRONG_DBC) && - s->data_block_counter != UINT_MAX) - *dbc = s->data_block_counter; + *data_block_counter != UINT_MAX) + dbc = *data_block_counter; - if (((s->flags & CIP_SKIP_DBC_ZERO_CHECK) && - *dbc == s->ctx_data.tx.first_dbc) || - s->data_block_counter == UINT_MAX) { + if ((dbc == 0x00 && (s->flags & CIP_SKIP_DBC_ZERO_CHECK)) || + *data_block_counter == UINT_MAX) { lost = false; } else if (!(s->flags & CIP_DBC_IS_END_EVENT)) { - lost = *dbc != s->data_block_counter; + lost = dbc != *data_block_counter; } else { unsigned int dbc_interval; @@ -598,16 +592,18 @@ static int check_cip_header(struct amdtp_stream *s, const __be32 *buf, else dbc_interval = *data_blocks; - lost = *dbc != ((s->data_block_counter + dbc_interval) & 0xff); + lost = dbc != ((*data_block_counter + dbc_interval) & 0xff); } if (lost) { dev_err(&s->unit->device, "Detect discontinuity of CIP: %02X %02X\n", - s->data_block_counter, *dbc); + *data_block_counter, dbc); return -EIO; } + *data_block_counter = dbc; + *syt = cip_header[1] & CIP_SYT_MASK; return 0; @@ -616,10 +612,10 @@ static int check_cip_header(struct amdtp_stream *s, const __be32 *buf, static int parse_ir_ctx_header(struct amdtp_stream *s, unsigned int cycle, const __be32 *ctx_header, unsigned int *payload_length, - unsigned int *data_blocks, unsigned int *syt, - unsigned int index) + unsigned int *data_blocks, + unsigned int *data_block_counter, + unsigned int *syt, unsigned int index) { - unsigned int dbc; const __be32 *cip_header; int err; @@ -635,7 +631,7 @@ static int parse_ir_ctx_header(struct amdtp_stream *s, unsigned int cycle, if (!(s->flags & CIP_NO_HEADER)) { cip_header = ctx_header + 2; err = check_cip_header(s, cip_header, *payload_length, - data_blocks, &dbc, syt); + data_blocks, data_block_counter, syt); if (err < 0) return err; } else { @@ -645,16 +641,12 @@ static int parse_ir_ctx_header(struct amdtp_stream *s, unsigned int cycle, s->data_block_quadlets; *syt = 0; - if (s->data_block_counter != UINT_MAX) - dbc = s->data_block_counter; - else - dbc = 0; + if (*data_block_counter == UINT_MAX) + *data_block_counter = 0; } - s->data_block_counter = dbc; - trace_amdtp_packet(s, cycle, cip_header, *payload_length, *data_blocks, - index); + *data_block_counter, index); return err; } @@ -686,6 +678,80 @@ static inline u32 compute_it_cycle(const __be32 ctx_header_tstamp) return increment_cycle_count(cycle, QUEUE_LENGTH); } +static int generate_device_pkt_descs(struct amdtp_stream *s, + struct pkt_desc *descs, + const __be32 *ctx_header, + unsigned int packets) +{ + unsigned int dbc = s->data_block_counter; + int i; + int err; + + for (i = 0; i < packets; ++i) { + struct pkt_desc *desc = descs + i; + unsigned int index = (s->packet_index + i) % QUEUE_LENGTH; + unsigned int cycle; + unsigned int payload_length; + unsigned int data_blocks; + unsigned int syt; + + cycle = compute_cycle_count(ctx_header[1]); + + err = parse_ir_ctx_header(s, cycle, ctx_header, &payload_length, + &data_blocks, &dbc, &syt, i); + if (err < 0) + return err; + + desc->cycle = cycle; + desc->syt = syt; + desc->data_blocks = data_blocks; + desc->data_block_counter = dbc; + desc->ctx_payload = s->buffer.packets[index].buffer; + + if (!(s->flags & CIP_DBC_IS_END_EVENT)) + dbc = (dbc + desc->data_blocks) & 0xff; + + ctx_header += + s->ctx_data.tx.ctx_header_size / sizeof(*ctx_header); + } + + s->data_block_counter = dbc; + + return 0; +} + +static void generate_ideal_pkt_descs(struct amdtp_stream *s, + struct pkt_desc *descs, + const __be32 *ctx_header, + unsigned int packets) +{ + unsigned int dbc = s->data_block_counter; + int i; + + for (i = 0; i < packets; ++i) { + struct pkt_desc *desc = descs + i; + unsigned int index = (s->packet_index + i) % QUEUE_LENGTH; + + desc->cycle = compute_it_cycle(*ctx_header); + desc->syt = calculate_syt(s, desc->cycle); + desc->data_blocks = calculate_data_blocks(s, desc->syt); + + if (s->flags & CIP_DBC_IS_END_EVENT) + dbc = (dbc + desc->data_blocks) & 0xff; + + desc->data_block_counter = dbc; + + if (!(s->flags & CIP_DBC_IS_END_EVENT)) + dbc = (dbc + desc->data_blocks) & 0xff; + + desc->ctx_payload = s->buffer.packets[index].buffer; + + ++ctx_header; + } + + s->data_block_counter = dbc; +} + static inline void cancel_stream(struct amdtp_stream *s) { s->packet_index = -1; @@ -694,6 +760,19 @@ static inline void cancel_stream(struct amdtp_stream *s) WRITE_ONCE(s->pcm_buffer_pointer, SNDRV_PCM_POS_XRUN); } +static void process_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets) +{ + struct snd_pcm_substream *pcm; + unsigned int pcm_frames; + + pcm = READ_ONCE(s->pcm); + pcm_frames = s->process_ctx_payloads(s, descs, packets, pcm); + if (pcm) + update_pcm_pointers(s, pcm, pcm_frames); +} + static void out_stream_callback(struct fw_iso_context *context, u32 tstamp, size_t header_length, void *header, void *private_data) @@ -706,38 +785,31 @@ static void out_stream_callback(struct fw_iso_context *context, u32 tstamp, if (s->packet_index < 0) return; + generate_ideal_pkt_descs(s, s->pkt_descs, ctx_header, packets); + + process_ctx_payloads(s, s->pkt_descs, packets); + for (i = 0; i < packets; ++i) { - u32 cycle; + const struct pkt_desc *desc = s->pkt_descs + i; unsigned int syt; - unsigned int data_blocks; - __be32 *buffer; - unsigned int pcm_frames; struct { struct fw_iso_packet params; __be32 header[IT_PKT_HEADER_SIZE_CIP / sizeof(__be32)]; } template = { {0}, {0} }; - struct snd_pcm_substream *pcm; - cycle = compute_it_cycle(*ctx_header); - syt = calculate_syt(s, cycle); - data_blocks = calculate_data_blocks(s, syt); - buffer = s->buffer.packets[s->packet_index].buffer; - pcm_frames = s->process_data_blocks(s, buffer, data_blocks, - &syt); + if (s->ctx_data.rx.syt_override < 0) + syt = desc->syt; + else + syt = s->ctx_data.rx.syt_override; - build_it_pkt_header(s, cycle, &template.params, data_blocks, + build_it_pkt_header(s, desc->cycle, &template.params, + desc->data_blocks, desc->data_block_counter, syt, i); if (queue_out_packet(s, &template.params) < 0) { cancel_stream(s); return; } - - pcm = READ_ONCE(s->pcm); - if (pcm && pcm_frames > 0) - update_pcm_pointers(s, pcm, pcm_frames); - - ++ctx_header; } fw_iso_context_queue_flush(s->context); @@ -748,8 +820,10 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp, void *private_data) { struct amdtp_stream *s = private_data; - unsigned int i, packets; + unsigned int packets; __be32 *ctx_header = header; + int i; + int err; if (s->packet_index < 0) return; @@ -757,48 +831,23 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp, // The number of packets in buffer. packets = header_length / s->ctx_data.tx.ctx_header_size; - for (i = 0; i < packets; i++) { - u32 cycle; - unsigned int payload_length; - unsigned int data_blocks; - unsigned int syt; - __be32 *buffer; - unsigned int pcm_frames = 0; - struct fw_iso_packet params = {0}; - struct snd_pcm_substream *pcm; - int err; - - cycle = compute_cycle_count(ctx_header[1]); - err = parse_ir_ctx_header(s, cycle, ctx_header, &payload_length, - &data_blocks, &syt, i); - if (err < 0 && err != -EAGAIN) - break; - - if (err >= 0) { - buffer = s->buffer.packets[s->packet_index].buffer; - pcm_frames = s->process_data_blocks(s, buffer, - data_blocks, &syt); - - if (!(s->flags & CIP_DBC_IS_END_EVENT)) { - s->data_block_counter += data_blocks; - s->data_block_counter &= 0xff; - } + err = generate_device_pkt_descs(s, s->pkt_descs, ctx_header, packets); + if (err < 0) { + if (err != -EAGAIN) { + cancel_stream(s); + return; } - - if (queue_in_packet(s, ¶ms) < 0) - break; - - pcm = READ_ONCE(s->pcm); - if (pcm && pcm_frames > 0) - update_pcm_pointers(s, pcm, pcm_frames); - - ctx_header += s->ctx_data.tx.ctx_header_size / sizeof(*ctx_header); + } else { + process_ctx_payloads(s, s->pkt_descs, packets); } - /* Queueing error or detecting invalid payload. */ - if (i < packets) { - cancel_stream(s); - return; + for (i = 0; i < packets; ++i) { + struct fw_iso_packet params = {0}; + + if (queue_in_packet(s, ¶ms) < 0) { + cancel_stream(s); + return; + } } fw_iso_context_queue_flush(s->context); @@ -845,7 +894,7 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context, * amdtp_stream_set_parameters() and it must be started before any PCM or MIDI * device can be started. */ -int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) +static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) { static const struct { unsigned int data_block; @@ -932,6 +981,13 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) else s->tag = TAG_CIP; + s->pkt_descs = kcalloc(INTERRUPT_INTERVAL, sizeof(*s->pkt_descs), + GFP_KERNEL); + if (!s->pkt_descs) { + err = -ENOMEM; + goto err_context; + } + s->packet_index = 0; do { struct fw_iso_packet params; @@ -943,7 +999,7 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) err = queue_out_packet(s, ¶ms); } if (err < 0) - goto err_context; + goto err_pkt_descs; } while (s->packet_index > 0); /* NOTE: TAG1 matches CIP. This just affects in stream. */ @@ -954,12 +1010,13 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) s->callbacked = false; err = fw_iso_context_start(s->context, -1, 0, tag); if (err < 0) - goto err_context; + goto err_pkt_descs; mutex_unlock(&s->mutex); return 0; - +err_pkt_descs: + kfree(s->pkt_descs); err_context: fw_iso_context_destroy(s->context); s->context = ERR_PTR(-1); @@ -970,7 +1027,6 @@ err_unlock: return err; } -EXPORT_SYMBOL(amdtp_stream_start); /** * amdtp_stream_pcm_pointer - get the PCM buffer position @@ -1041,7 +1097,7 @@ EXPORT_SYMBOL(amdtp_stream_update); * All PCM and MIDI devices of the stream must be stopped before the stream * itself can be stopped. */ -void amdtp_stream_stop(struct amdtp_stream *s) +static void amdtp_stream_stop(struct amdtp_stream *s) { mutex_lock(&s->mutex); @@ -1055,12 +1111,12 @@ void amdtp_stream_stop(struct amdtp_stream *s) fw_iso_context_destroy(s->context); s->context = ERR_PTR(-1); iso_packets_buffer_destroy(&s->buffer, s->unit); + kfree(s->pkt_descs); s->callbacked = false; mutex_unlock(&s->mutex); } -EXPORT_SYMBOL(amdtp_stream_stop); /** * amdtp_stream_pcm_abort - abort the running PCM device @@ -1078,3 +1134,92 @@ void amdtp_stream_pcm_abort(struct amdtp_stream *s) snd_pcm_stop_xrun(pcm); } EXPORT_SYMBOL(amdtp_stream_pcm_abort); + +/** + * amdtp_domain_init - initialize an AMDTP domain structure + * @d: the AMDTP domain to initialize. + */ +int amdtp_domain_init(struct amdtp_domain *d) +{ + INIT_LIST_HEAD(&d->streams); + + return 0; +} +EXPORT_SYMBOL_GPL(amdtp_domain_init); + +/** + * amdtp_domain_destroy - destroy an AMDTP domain structure + * @d: the AMDTP domain to destroy. + */ +void amdtp_domain_destroy(struct amdtp_domain *d) +{ + // At present nothing to do. + return; +} +EXPORT_SYMBOL_GPL(amdtp_domain_destroy); + +/** + * amdtp_domain_add_stream - register isoc context into the domain. + * @d: the AMDTP domain. + * @s: the AMDTP stream. + * @channel: the isochronous channel on the bus. + * @speed: firewire speed code. + */ +int amdtp_domain_add_stream(struct amdtp_domain *d, struct amdtp_stream *s, + int channel, int speed) +{ + struct amdtp_stream *tmp; + + list_for_each_entry(tmp, &d->streams, list) { + if (s == tmp) + return -EBUSY; + } + + list_add(&s->list, &d->streams); + + s->channel = channel; + s->speed = speed; + + return 0; +} +EXPORT_SYMBOL_GPL(amdtp_domain_add_stream); + +/** + * amdtp_domain_start - start sending packets for isoc context in the domain. + * @d: the AMDTP domain. + */ +int amdtp_domain_start(struct amdtp_domain *d) +{ + struct amdtp_stream *s; + int err = 0; + + list_for_each_entry(s, &d->streams, list) { + err = amdtp_stream_start(s, s->channel, s->speed); + if (err < 0) + break; + } + + if (err < 0) { + list_for_each_entry(s, &d->streams, list) + amdtp_stream_stop(s); + } + + return err; +} +EXPORT_SYMBOL_GPL(amdtp_domain_start); + +/** + * amdtp_domain_stop - stop sending packets for isoc context in the same domain. + * @d: the AMDTP domain to which the isoc contexts belong. + */ +void amdtp_domain_stop(struct amdtp_domain *d) +{ + struct amdtp_stream *s, *next; + + list_for_each_entry_safe(s, next, &d->streams, list) { + list_del(&s->list); + + amdtp_stream_stop(s); + } +} +EXPORT_SYMBOL_GPL(amdtp_domain_stop); diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h index 3942894c11ac..bbbca964b9b4 100644 --- a/sound/firewire/amdtp-stream.h +++ b/sound/firewire/amdtp-stream.h @@ -33,6 +33,8 @@ * @CIP_HEADER_WITHOUT_EOH: Only for in-stream. CIP Header doesn't include * valid EOH. * @CIP_NO_HEADERS: a lack of headers in packets + * @CIP_UNALIGHED_DBC: Only for in-stream. The value of dbc is not alighed to + * the value of current SYT_INTERVAL; e.g. initial value is not zero. */ enum cip_flags { CIP_NONBLOCKING = 0x00, @@ -45,6 +47,7 @@ enum cip_flags { CIP_JUMBO_PAYLOAD = 0x40, CIP_HEADER_WITHOUT_EOH = 0x80, CIP_NO_HEADER = 0x100, + CIP_UNALIGHED_DBC = 0x200, }; /** @@ -91,12 +94,20 @@ enum amdtp_stream_direction { AMDTP_IN_STREAM }; +struct pkt_desc { + u32 cycle; + u32 syt; + unsigned int data_blocks; + unsigned int data_block_counter; + __be32 *ctx_payload; +}; + struct amdtp_stream; -typedef unsigned int (*amdtp_stream_process_data_blocks_t)( +typedef unsigned int (*amdtp_stream_process_ctx_payloads_t)( struct amdtp_stream *s, - __be32 *buffer, - unsigned int data_blocks, - unsigned int *syt); + const struct pkt_desc *desc, + unsigned int packets, + struct snd_pcm_substream *pcm); struct amdtp_stream { struct fw_unit *unit; enum cip_flags flags; @@ -107,6 +118,7 @@ struct amdtp_stream { struct fw_iso_context *context; struct iso_packets_buffer buffer; int packet_index; + struct pkt_desc *pkt_descs; int tag; union { struct { @@ -119,8 +131,6 @@ struct amdtp_stream { // Fixed interval of dbc between previos/current // packets. unsigned int dbc_interval; - // Indicate the value of dbc field in a first packet. - unsigned int first_dbc; } tx; struct { // To calculate CIP data blocks and tstamp. @@ -131,6 +141,7 @@ struct amdtp_stream { // To generate CIP header. unsigned int fdf; + int syt_override; } rx; } ctx_data; @@ -158,13 +169,18 @@ struct amdtp_stream { /* For backends to process data blocks. */ void *protocol; - amdtp_stream_process_data_blocks_t process_data_blocks; + amdtp_stream_process_ctx_payloads_t process_ctx_payloads; + + // For domain. + int channel; + int speed; + struct list_head list; }; int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit, enum amdtp_stream_direction dir, enum cip_flags flags, unsigned int fmt, - amdtp_stream_process_data_blocks_t process_data_blocks, + amdtp_stream_process_ctx_payloads_t process_ctx_payloads, unsigned int protocol_size); void amdtp_stream_destroy(struct amdtp_stream *s); @@ -172,9 +188,7 @@ int amdtp_stream_set_parameters(struct amdtp_stream *s, unsigned int rate, unsigned int data_block_quadlets); unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s); -int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed); void amdtp_stream_update(struct amdtp_stream *s); -void amdtp_stream_stop(struct amdtp_stream *s); int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s, struct snd_pcm_runtime *runtime); @@ -256,4 +270,17 @@ static inline bool amdtp_stream_wait_callback(struct amdtp_stream *s, msecs_to_jiffies(timeout)) > 0; } +struct amdtp_domain { + struct list_head streams; +}; + +int amdtp_domain_init(struct amdtp_domain *d); +void amdtp_domain_destroy(struct amdtp_domain *d); + +int amdtp_domain_add_stream(struct amdtp_domain *d, struct amdtp_stream *s, + int channel, int speed); + +int amdtp_domain_start(struct amdtp_domain *d); +void amdtp_domain_stop(struct amdtp_domain *d); + #endif diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 9e0b689fe34a..356d6ba60959 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -115,6 +115,8 @@ struct snd_bebob { /* For BeBoB version quirk. */ unsigned int version; + + struct amdtp_domain domain; }; static inline int diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c index 334dc7c96e1d..73fee991bd75 100644 --- a/sound/firewire/bebob/bebob_stream.c +++ b/sound/firewire/bebob/bebob_stream.c @@ -445,10 +445,9 @@ start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream) goto end; } - /* start amdtp stream */ - err = amdtp_stream_start(stream, - conn->resources.channel, - conn->speed); + // start amdtp stream. + err = amdtp_domain_add_stream(&bebob->domain, stream, + conn->resources.channel, conn->speed); end: return err; } @@ -523,7 +522,13 @@ int snd_bebob_stream_init_duplex(struct snd_bebob *bebob) return err; } - return 0; + err = amdtp_domain_init(&bebob->domain); + if (err < 0) { + destroy_stream(bebob, &bebob->tx_stream); + destroy_stream(bebob, &bebob->rx_stream); + } + + return err; } static int keep_resources(struct snd_bebob *bebob, struct amdtp_stream *stream, @@ -566,9 +571,7 @@ int snd_bebob_stream_reserve_duplex(struct snd_bebob *bebob, unsigned int rate) if (rate == 0) rate = curr_rate; if (curr_rate != rate) { - amdtp_stream_stop(&bebob->tx_stream); - amdtp_stream_stop(&bebob->rx_stream); - + amdtp_domain_stop(&bebob->domain); break_both_connections(bebob); cmp_connection_release(&bebob->out_conn); @@ -620,9 +623,7 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob) // packet queueing error or detecting discontinuity if (amdtp_streaming_error(&bebob->rx_stream) || amdtp_streaming_error(&bebob->tx_stream)) { - amdtp_stream_stop(&bebob->rx_stream); - amdtp_stream_stop(&bebob->tx_stream); - + amdtp_domain_stop(&bebob->domain); break_both_connections(bebob); } @@ -640,11 +641,16 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob) return err; err = start_stream(bebob, &bebob->rx_stream); - if (err < 0) { - dev_err(&bebob->unit->device, - "fail to run AMDTP master stream:%d\n", err); + if (err < 0) + goto error; + + err = start_stream(bebob, &bebob->tx_stream); + if (err < 0) + goto error; + + err = amdtp_domain_start(&bebob->domain); + if (err < 0) goto error; - } // NOTE: // The firmware customized by M-Audio uses these commands to @@ -660,21 +666,8 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob) } if (!amdtp_stream_wait_callback(&bebob->rx_stream, - CALLBACK_TIMEOUT)) { - err = -ETIMEDOUT; - goto error; - } - } - - if (!amdtp_stream_running(&bebob->tx_stream)) { - err = start_stream(bebob, &bebob->tx_stream); - if (err < 0) { - dev_err(&bebob->unit->device, - "fail to run AMDTP slave stream:%d\n", err); - goto error; - } - - if (!amdtp_stream_wait_callback(&bebob->tx_stream, + CALLBACK_TIMEOUT) || + !amdtp_stream_wait_callback(&bebob->tx_stream, CALLBACK_TIMEOUT)) { err = -ETIMEDOUT; goto error; @@ -683,8 +676,7 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob) return 0; error: - amdtp_stream_stop(&bebob->tx_stream); - amdtp_stream_stop(&bebob->rx_stream); + amdtp_domain_stop(&bebob->domain); break_both_connections(bebob); return err; } @@ -692,9 +684,7 @@ error: void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob) { if (bebob->substreams_counter == 0) { - amdtp_stream_stop(&bebob->rx_stream); - amdtp_stream_stop(&bebob->tx_stream); - + amdtp_domain_stop(&bebob->domain); break_both_connections(bebob); cmp_connection_release(&bebob->out_conn); @@ -708,6 +698,8 @@ void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob) */ void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob) { + amdtp_domain_destroy(&bebob->domain); + destroy_stream(bebob, &bebob->tx_stream); destroy_stream(bebob, &bebob->rx_stream); } diff --git a/sound/firewire/dice/dice-stream.c b/sound/firewire/dice/dice-stream.c index a9f0c77734c3..f6a8627ae5a2 100644 --- a/sound/firewire/dice/dice-stream.c +++ b/sound/firewire/dice/dice-stream.c @@ -154,14 +154,10 @@ static void stop_streams(struct snd_dice *dice, enum amdtp_stream_direction dir, for (i = 0; i < params->count; i++) { reg = cpu_to_be32((u32)-1); if (dir == AMDTP_IN_STREAM) { - amdtp_stream_stop(&dice->tx_stream[i]); - snd_dice_transaction_write_tx(dice, params->size * i + TX_ISOCHRONOUS, ®, sizeof(reg)); } else { - amdtp_stream_stop(&dice->rx_stream[i]); - snd_dice_transaction_write_rx(dice, params->size * i + RX_ISOCHRONOUS, ®, sizeof(reg)); @@ -297,10 +293,11 @@ int snd_dice_stream_reserve_duplex(struct snd_dice *dice, unsigned int rate) if (dice->substreams_counter == 0 || curr_rate != rate) { struct reg_params tx_params, rx_params; + amdtp_domain_stop(&dice->domain); + err = get_register_params(dice, &tx_params, &rx_params); if (err < 0) return err; - finish_session(dice, &tx_params, &rx_params); release_resources(dice); @@ -377,7 +374,8 @@ static int start_streams(struct snd_dice *dice, enum amdtp_stream_direction dir, return err; } - err = amdtp_stream_start(stream, resources->channel, max_speed); + err = amdtp_domain_add_stream(&dice->domain, stream, + resources->channel, max_speed); if (err < 0) return err; } @@ -410,6 +408,7 @@ int snd_dice_stream_start_duplex(struct snd_dice *dice) for (i = 0; i < MAX_STREAMS; ++i) { if (amdtp_streaming_error(&dice->tx_stream[i]) || amdtp_streaming_error(&dice->rx_stream[i])) { + amdtp_domain_stop(&dice->domain); finish_session(dice, &tx_params, &rx_params); break; } @@ -456,6 +455,10 @@ int snd_dice_stream_start_duplex(struct snd_dice *dice) goto error; } + err = amdtp_domain_start(&dice->domain); + if (err < 0) + goto error; + for (i = 0; i < MAX_STREAMS; i++) { if ((i < tx_params.count && !amdtp_stream_wait_callback(&dice->tx_stream[i], @@ -471,6 +474,7 @@ int snd_dice_stream_start_duplex(struct snd_dice *dice) return 0; error: + amdtp_domain_stop(&dice->domain); finish_session(dice, &tx_params, &rx_params); return err; } @@ -485,8 +489,10 @@ void snd_dice_stream_stop_duplex(struct snd_dice *dice) struct reg_params tx_params, rx_params; if (dice->substreams_counter == 0) { - if (get_register_params(dice, &tx_params, &rx_params) >= 0) + if (get_register_params(dice, &tx_params, &rx_params) >= 0) { + amdtp_domain_stop(&dice->domain); finish_session(dice, &tx_params, &rx_params); + } release_resources(dice); } @@ -564,7 +570,15 @@ int snd_dice_stream_init_duplex(struct snd_dice *dice) destroy_stream(dice, AMDTP_OUT_STREAM, i); for (i = 0; i < MAX_STREAMS; i++) destroy_stream(dice, AMDTP_IN_STREAM, i); - break; + goto end; + } + } + + err = amdtp_domain_init(&dice->domain); + if (err < 0) { + for (i = 0; i < MAX_STREAMS; ++i) { + destroy_stream(dice, AMDTP_OUT_STREAM, i); + destroy_stream(dice, AMDTP_IN_STREAM, i); } } end: @@ -579,6 +593,8 @@ void snd_dice_stream_destroy_duplex(struct snd_dice *dice) destroy_stream(dice, AMDTP_IN_STREAM, i); destroy_stream(dice, AMDTP_OUT_STREAM, i); } + + amdtp_domain_destroy(&dice->domain); } void snd_dice_stream_update_duplex(struct snd_dice *dice) @@ -596,6 +612,8 @@ void snd_dice_stream_update_duplex(struct snd_dice *dice) dice->global_enabled = false; if (get_register_params(dice, &tx_params, &rx_params) == 0) { + amdtp_domain_stop(&dice->domain); + stop_streams(dice, AMDTP_IN_STREAM, &tx_params); stop_streams(dice, AMDTP_OUT_STREAM, &rx_params); } diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h index c6304e5e9fc4..fa6d74303f54 100644 --- a/sound/firewire/dice/dice.h +++ b/sound/firewire/dice/dice.h @@ -112,6 +112,8 @@ struct snd_dice { bool global_enabled; struct completion clock_accepted; unsigned int substreams_counter; + + struct amdtp_domain domain; }; enum snd_dice_addr_type { diff --git a/sound/firewire/digi00x/amdtp-dot.c b/sound/firewire/digi00x/amdtp-dot.c index 45ff73d16074..d613642a2ce3 100644 --- a/sound/firewire/digi00x/amdtp-dot.c +++ b/sound/firewire/digi00x/amdtp-dot.c @@ -143,17 +143,23 @@ int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate, } static void write_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, - __be32 *buffer, unsigned int frames) + __be32 *buffer, unsigned int frames, + unsigned int pcm_frames) { struct amdtp_dot *p = s->protocol; + unsigned int channels = p->pcm_channels; struct snd_pcm_runtime *runtime = pcm->runtime; - unsigned int channels, remaining_frames, i, c; + unsigned int pcm_buffer_pointer; + int remaining_frames; const u32 *src; + int i, c; + + pcm_buffer_pointer = s->pcm_buffer_pointer + pcm_frames; + pcm_buffer_pointer %= runtime->buffer_size; - channels = p->pcm_channels; src = (void *)runtime->dma_area + - frames_to_bytes(runtime, s->pcm_buffer_pointer); - remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + frames_to_bytes(runtime, pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - pcm_buffer_pointer; buffer++; for (i = 0; i < frames; ++i) { @@ -169,17 +175,23 @@ static void write_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, } static void read_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, - __be32 *buffer, unsigned int frames) + __be32 *buffer, unsigned int frames, + unsigned int pcm_frames) { struct amdtp_dot *p = s->protocol; + unsigned int channels = p->pcm_channels; struct snd_pcm_runtime *runtime = pcm->runtime; - unsigned int channels, remaining_frames, i, c; + unsigned int pcm_buffer_pointer; + int remaining_frames; u32 *dst; + int i, c; + + pcm_buffer_pointer = s->pcm_buffer_pointer + pcm_frames; + pcm_buffer_pointer %= runtime->buffer_size; - channels = p->pcm_channels; dst = (void *)runtime->dma_area + - frames_to_bytes(runtime, s->pcm_buffer_pointer); - remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + frames_to_bytes(runtime, pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - pcm_buffer_pointer; buffer++; for (i = 0; i < frames; ++i) { @@ -234,7 +246,7 @@ static inline void midi_use_bytes(struct amdtp_stream *s, } static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, - unsigned int data_blocks) + unsigned int data_blocks, unsigned int data_block_counter) { struct amdtp_dot *p = s->protocol; unsigned int f, port; @@ -242,7 +254,7 @@ static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, u8 *b; for (f = 0; f < data_blocks; f++) { - port = (s->data_block_counter + f) % 8; + port = (data_block_counter + f) % 8; b = (u8 *)&buffer[0]; len = 0; @@ -329,45 +341,53 @@ void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port, WRITE_ONCE(p->midi[port], midi); } -static unsigned int process_tx_data_blocks(struct amdtp_stream *s, - __be32 *buffer, - unsigned int data_blocks, - unsigned int *syt) +static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) { - struct snd_pcm_substream *pcm; - unsigned int pcm_frames; + unsigned int pcm_frames = 0; + int i; - pcm = READ_ONCE(s->pcm); - if (pcm) { - read_pcm_s32(s, pcm, buffer, data_blocks); - pcm_frames = data_blocks; - } else { - pcm_frames = 0; - } + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buf = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; - read_midi_messages(s, buffer, data_blocks); + if (pcm) { + read_pcm_s32(s, pcm, buf, data_blocks, pcm_frames); + pcm_frames += data_blocks; + } + + read_midi_messages(s, buf, data_blocks); + } return pcm_frames; } -static unsigned int process_rx_data_blocks(struct amdtp_stream *s, - __be32 *buffer, - unsigned int data_blocks, - unsigned int *syt) +static unsigned int process_it_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) { - struct snd_pcm_substream *pcm; - unsigned int pcm_frames; + unsigned int pcm_frames = 0; + int i; - pcm = READ_ONCE(s->pcm); - if (pcm) { - write_pcm_s32(s, pcm, buffer, data_blocks); - pcm_frames = data_blocks; - } else { - write_pcm_silence(s, buffer, data_blocks); - pcm_frames = 0; - } + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buf = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; - write_midi_messages(s, buffer, data_blocks); + if (pcm) { + write_pcm_s32(s, pcm, buf, data_blocks, pcm_frames); + pcm_frames += data_blocks; + } else { + write_pcm_silence(s, buf, data_blocks); + } + + write_midi_messages(s, buf, data_blocks, + desc->data_block_counter); + } return pcm_frames; } @@ -375,20 +395,20 @@ static unsigned int process_rx_data_blocks(struct amdtp_stream *s, int amdtp_dot_init(struct amdtp_stream *s, struct fw_unit *unit, enum amdtp_stream_direction dir) { - amdtp_stream_process_data_blocks_t process_data_blocks; + amdtp_stream_process_ctx_payloads_t process_ctx_payloads; enum cip_flags flags; - /* Use different mode between incoming/outgoing. */ + // Use different mode between incoming/outgoing. if (dir == AMDTP_IN_STREAM) { flags = CIP_NONBLOCKING; - process_data_blocks = process_tx_data_blocks; + process_ctx_payloads = process_ir_ctx_payloads; } else { flags = CIP_BLOCKING; - process_data_blocks = process_rx_data_blocks; + process_ctx_payloads = process_it_ctx_payloads; } return amdtp_stream_init(s, unit, dir, flags, CIP_FMT_AM, - process_data_blocks, sizeof(struct amdtp_dot)); + process_ctx_payloads, sizeof(struct amdtp_dot)); } void amdtp_dot_reset(struct amdtp_stream *s) diff --git a/sound/firewire/digi00x/digi00x-stream.c b/sound/firewire/digi00x/digi00x-stream.c index 3e77dbd3ee22..d6a92460060f 100644 --- a/sound/firewire/digi00x/digi00x-stream.c +++ b/sound/firewire/digi00x/digi00x-stream.c @@ -126,9 +126,6 @@ static void finish_session(struct snd_dg00x *dg00x) { __be32 data; - amdtp_stream_stop(&dg00x->tx_stream); - amdtp_stream_stop(&dg00x->rx_stream); - data = cpu_to_be32(0x00000003); snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST, DG00X_ADDR_BASE + DG00X_OFFSET_STREAMING_SET, @@ -218,29 +215,59 @@ static int keep_resources(struct snd_dg00x *dg00x, struct amdtp_stream *stream, fw_parent_device(dg00x->unit)->max_speed); } -int snd_dg00x_stream_init_duplex(struct snd_dg00x *dg00x) +static int init_stream(struct snd_dg00x *dg00x, struct amdtp_stream *s) { + struct fw_iso_resources *resources; + enum amdtp_stream_direction dir; int err; - /* For out-stream. */ - err = fw_iso_resources_init(&dg00x->rx_resources, dg00x->unit); + if (s == &dg00x->tx_stream) { + resources = &dg00x->tx_resources; + dir = AMDTP_IN_STREAM; + } else { + resources = &dg00x->rx_resources; + dir = AMDTP_OUT_STREAM; + } + + err = fw_iso_resources_init(resources, dg00x->unit); if (err < 0) - goto error; - err = amdtp_dot_init(&dg00x->rx_stream, dg00x->unit, AMDTP_OUT_STREAM); + return err; + + err = amdtp_dot_init(s, dg00x->unit, dir); if (err < 0) - goto error; + fw_iso_resources_destroy(resources); + + return err; +} + +static void destroy_stream(struct snd_dg00x *dg00x, struct amdtp_stream *s) +{ + amdtp_stream_destroy(s); + + if (s == &dg00x->tx_stream) + fw_iso_resources_destroy(&dg00x->tx_resources); + else + fw_iso_resources_destroy(&dg00x->rx_resources); +} + +int snd_dg00x_stream_init_duplex(struct snd_dg00x *dg00x) +{ + int err; - /* For in-stream. */ - err = fw_iso_resources_init(&dg00x->tx_resources, dg00x->unit); + err = init_stream(dg00x, &dg00x->rx_stream); if (err < 0) - goto error; - err = amdtp_dot_init(&dg00x->tx_stream, dg00x->unit, AMDTP_IN_STREAM); + return err; + + err = init_stream(dg00x, &dg00x->tx_stream); if (err < 0) - goto error; + destroy_stream(dg00x, &dg00x->rx_stream); + + err = amdtp_domain_init(&dg00x->domain); + if (err < 0) { + destroy_stream(dg00x, &dg00x->rx_stream); + destroy_stream(dg00x, &dg00x->tx_stream); + } - return 0; -error: - snd_dg00x_stream_destroy_duplex(dg00x); return err; } @@ -250,11 +277,10 @@ error: */ void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x) { - amdtp_stream_destroy(&dg00x->rx_stream); - fw_iso_resources_destroy(&dg00x->rx_resources); + amdtp_domain_destroy(&dg00x->domain); - amdtp_stream_destroy(&dg00x->tx_stream); - fw_iso_resources_destroy(&dg00x->tx_resources); + destroy_stream(dg00x, &dg00x->rx_stream); + destroy_stream(dg00x, &dg00x->tx_stream); } int snd_dg00x_stream_reserve_duplex(struct snd_dg00x *dg00x, unsigned int rate) @@ -269,6 +295,8 @@ int snd_dg00x_stream_reserve_duplex(struct snd_dg00x *dg00x, unsigned int rate) rate = curr_rate; if (dg00x->substreams_counter == 0 || curr_rate != rate) { + amdtp_domain_stop(&dg00x->domain); + finish_session(dg00x); fw_iso_resources_free(&dg00x->tx_resources); @@ -301,8 +329,10 @@ int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x) return 0; if (amdtp_streaming_error(&dg00x->tx_stream) || - amdtp_streaming_error(&dg00x->rx_stream)) + amdtp_streaming_error(&dg00x->rx_stream)) { + amdtp_domain_stop(&dg00x->domain); finish_session(dg00x); + } if (generation != fw_parent_device(dg00x->unit)->card->generation) { err = fw_iso_resources_update(&dg00x->tx_resources); @@ -319,36 +349,30 @@ int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x) * which source of clock is used. */ if (!amdtp_stream_running(&dg00x->rx_stream)) { + int spd = fw_parent_device(dg00x->unit)->max_speed; + err = begin_session(dg00x); if (err < 0) goto error; - err = amdtp_stream_start(&dg00x->rx_stream, - dg00x->rx_resources.channel, - fw_parent_device(dg00x->unit)->max_speed); + err = amdtp_domain_add_stream(&dg00x->domain, &dg00x->rx_stream, + dg00x->rx_resources.channel, spd); if (err < 0) goto error; - if (!amdtp_stream_wait_callback(&dg00x->rx_stream, - CALLBACK_TIMEOUT)) { - err = -ETIMEDOUT; + err = amdtp_domain_add_stream(&dg00x->domain, &dg00x->tx_stream, + dg00x->tx_resources.channel, spd); + if (err < 0) goto error; - } - } - /* - * The value of SYT field in transmitted packets is always 0x0000. Thus, - * duplex streams with timestamp synchronization cannot be built. - */ - if (!amdtp_stream_running(&dg00x->tx_stream)) { - err = amdtp_stream_start(&dg00x->tx_stream, - dg00x->tx_resources.channel, - fw_parent_device(dg00x->unit)->max_speed); + err = amdtp_domain_start(&dg00x->domain); if (err < 0) goto error; - if (!amdtp_stream_wait_callback(&dg00x->tx_stream, - CALLBACK_TIMEOUT)) { + if (!amdtp_stream_wait_callback(&dg00x->rx_stream, + CALLBACK_TIMEOUT) || + !amdtp_stream_wait_callback(&dg00x->tx_stream, + CALLBACK_TIMEOUT)) { err = -ETIMEDOUT; goto error; } @@ -356,6 +380,7 @@ int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x) return 0; error: + amdtp_domain_stop(&dg00x->domain); finish_session(dg00x); return err; @@ -364,6 +389,7 @@ error: void snd_dg00x_stream_stop_duplex(struct snd_dg00x *dg00x) { if (dg00x->substreams_counter == 0) { + amdtp_domain_stop(&dg00x->domain); finish_session(dg00x); fw_iso_resources_free(&dg00x->tx_resources); diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h index 0994d191ccda..8041c65f2736 100644 --- a/sound/firewire/digi00x/digi00x.h +++ b/sound/firewire/digi00x/digi00x.h @@ -59,6 +59,8 @@ struct snd_dg00x { /* Console models have additional MIDI ports for control surface. */ bool is_console; + + struct amdtp_domain domain; }; #define DG00X_ADDR_BASE 0xffffe0000000ull diff --git a/sound/firewire/fireface/amdtp-ff.c b/sound/firewire/fireface/amdtp-ff.c index 2938489740b4..119c0076b17a 100644 --- a/sound/firewire/fireface/amdtp-ff.c +++ b/sound/firewire/fireface/amdtp-ff.c @@ -27,19 +27,24 @@ int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate, return amdtp_stream_set_parameters(s, rate, data_channels); } -static void write_pcm_s32(struct amdtp_stream *s, - struct snd_pcm_substream *pcm, - __le32 *buffer, unsigned int frames) +static void write_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, + __le32 *buffer, unsigned int frames, + unsigned int pcm_frames) { struct amdtp_ff *p = s->protocol; + unsigned int channels = p->pcm_channels; struct snd_pcm_runtime *runtime = pcm->runtime; - unsigned int channels, remaining_frames, i, c; + unsigned int pcm_buffer_pointer; + int remaining_frames; const u32 *src; + int i, c; + + pcm_buffer_pointer = s->pcm_buffer_pointer + pcm_frames; + pcm_buffer_pointer %= runtime->buffer_size; - channels = p->pcm_channels; src = (void *)runtime->dma_area + - frames_to_bytes(runtime, s->pcm_buffer_pointer); - remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + frames_to_bytes(runtime, pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - pcm_buffer_pointer; for (i = 0; i < frames; ++i) { for (c = 0; c < channels; ++c) { @@ -52,19 +57,24 @@ static void write_pcm_s32(struct amdtp_stream *s, } } -static void read_pcm_s32(struct amdtp_stream *s, - struct snd_pcm_substream *pcm, - __le32 *buffer, unsigned int frames) +static void read_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, + __le32 *buffer, unsigned int frames, + unsigned int pcm_frames) { struct amdtp_ff *p = s->protocol; + unsigned int channels = p->pcm_channels; struct snd_pcm_runtime *runtime = pcm->runtime; - unsigned int channels, remaining_frames, i, c; + unsigned int pcm_buffer_pointer; + int remaining_frames; u32 *dst; + int i, c; + + pcm_buffer_pointer = s->pcm_buffer_pointer + pcm_frames; + pcm_buffer_pointer %= runtime->buffer_size; - channels = p->pcm_channels; dst = (void *)runtime->dma_area + - frames_to_bytes(runtime, s->pcm_buffer_pointer); - remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + frames_to_bytes(runtime, pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - pcm_buffer_pointer; for (i = 0; i < frames; ++i) { for (c = 0; c < channels; ++c) { @@ -102,38 +112,47 @@ int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s, return amdtp_stream_add_pcm_hw_constraints(s, runtime); } -static unsigned int process_rx_data_blocks(struct amdtp_stream *s, - __be32 *buffer, - unsigned int data_blocks, - unsigned int *syt) +static unsigned int process_it_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) { - struct snd_pcm_substream *pcm = READ_ONCE(s->pcm); - unsigned int pcm_frames; - - if (pcm) { - write_pcm_s32(s, pcm, (__le32 *)buffer, data_blocks); - pcm_frames = data_blocks; - } else { - write_pcm_silence(s, (__le32 *)buffer, data_blocks); - pcm_frames = 0; + unsigned int pcm_frames = 0; + int i; + + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + i; + __le32 *buf = (__le32 *)desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; + + if (pcm) { + write_pcm_s32(s, pcm, buf, data_blocks, pcm_frames); + pcm_frames += data_blocks; + } else { + write_pcm_silence(s, buf, data_blocks); + } } return pcm_frames; } -static unsigned int process_tx_data_blocks(struct amdtp_stream *s, - __be32 *buffer, - unsigned int data_blocks, - unsigned int *syt) +static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) { - struct snd_pcm_substream *pcm = READ_ONCE(s->pcm); - unsigned int pcm_frames; - - if (pcm) { - read_pcm_s32(s, pcm, (__le32 *)buffer, data_blocks); - pcm_frames = data_blocks; - } else { - pcm_frames = 0; + unsigned int pcm_frames = 0; + int i; + + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + i; + __le32 *buf = (__le32 *)desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; + + if (pcm) { + read_pcm_s32(s, pcm, buf, data_blocks, pcm_frames); + pcm_frames += data_blocks; + } } return pcm_frames; @@ -142,13 +161,13 @@ static unsigned int process_tx_data_blocks(struct amdtp_stream *s, int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit, enum amdtp_stream_direction dir) { - amdtp_stream_process_data_blocks_t process_data_blocks; + amdtp_stream_process_ctx_payloads_t process_ctx_payloads; if (dir == AMDTP_IN_STREAM) - process_data_blocks = process_tx_data_blocks; + process_ctx_payloads = process_ir_ctx_payloads; else - process_data_blocks = process_rx_data_blocks; + process_ctx_payloads = process_it_ctx_payloads; return amdtp_stream_init(s, unit, dir, CIP_NO_HEADER, 0, - process_data_blocks, sizeof(struct amdtp_ff)); + process_ctx_payloads, sizeof(struct amdtp_ff)); } diff --git a/sound/firewire/fireface/ff-stream.c b/sound/firewire/fireface/ff-stream.c index 4208b8004d1a..e8e6f9fd6433 100644 --- a/sound/firewire/fireface/ff-stream.c +++ b/sound/firewire/fireface/ff-stream.c @@ -32,61 +32,65 @@ int snd_ff_stream_get_multiplier_mode(enum cip_sfc sfc, static inline void finish_session(struct snd_ff *ff) { - amdtp_stream_stop(&ff->tx_stream); - amdtp_stream_stop(&ff->rx_stream); - ff->spec->protocol->finish_session(ff); ff->spec->protocol->switch_fetching_mode(ff, false); } -static int init_stream(struct snd_ff *ff, enum amdtp_stream_direction dir) +static int init_stream(struct snd_ff *ff, struct amdtp_stream *s) { - int err; struct fw_iso_resources *resources; - struct amdtp_stream *stream; + enum amdtp_stream_direction dir; + int err; - if (dir == AMDTP_IN_STREAM) { + if (s == &ff->tx_stream) { resources = &ff->tx_resources; - stream = &ff->tx_stream; + dir = AMDTP_IN_STREAM; } else { resources = &ff->rx_resources; - stream = &ff->rx_stream; + dir = AMDTP_OUT_STREAM; } err = fw_iso_resources_init(resources, ff->unit); if (err < 0) return err; - err = amdtp_ff_init(stream, ff->unit, dir); + err = amdtp_ff_init(s, ff->unit, dir); if (err < 0) fw_iso_resources_destroy(resources); return err; } -static void destroy_stream(struct snd_ff *ff, enum amdtp_stream_direction dir) +static void destroy_stream(struct snd_ff *ff, struct amdtp_stream *s) { - if (dir == AMDTP_IN_STREAM) { - amdtp_stream_destroy(&ff->tx_stream); + amdtp_stream_destroy(s); + + if (s == &ff->tx_stream) fw_iso_resources_destroy(&ff->tx_resources); - } else { - amdtp_stream_destroy(&ff->rx_stream); + else fw_iso_resources_destroy(&ff->rx_resources); - } } int snd_ff_stream_init_duplex(struct snd_ff *ff) { int err; - err = init_stream(ff, AMDTP_OUT_STREAM); + err = init_stream(ff, &ff->rx_stream); if (err < 0) - goto end; + return err; + + err = init_stream(ff, &ff->tx_stream); + if (err < 0) { + destroy_stream(ff, &ff->rx_stream); + return err; + } + + err = amdtp_domain_init(&ff->domain); + if (err < 0) { + destroy_stream(ff, &ff->rx_stream); + destroy_stream(ff, &ff->tx_stream); + } - err = init_stream(ff, AMDTP_IN_STREAM); - if (err < 0) - destroy_stream(ff, AMDTP_OUT_STREAM); -end: return err; } @@ -96,8 +100,10 @@ end: */ void snd_ff_stream_destroy_duplex(struct snd_ff *ff) { - destroy_stream(ff, AMDTP_IN_STREAM); - destroy_stream(ff, AMDTP_OUT_STREAM); + amdtp_domain_destroy(&ff->domain); + + destroy_stream(ff, &ff->rx_stream); + destroy_stream(ff, &ff->tx_stream); } int snd_ff_stream_reserve_duplex(struct snd_ff *ff, unsigned int rate) @@ -114,6 +120,7 @@ int snd_ff_stream_reserve_duplex(struct snd_ff *ff, unsigned int rate) enum snd_ff_stream_mode mode; int i; + amdtp_domain_stop(&ff->domain); finish_session(ff); fw_iso_resources_free(&ff->tx_resources); @@ -156,51 +163,52 @@ int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate) return 0; if (amdtp_streaming_error(&ff->tx_stream) || - amdtp_streaming_error(&ff->rx_stream)) + amdtp_streaming_error(&ff->rx_stream)) { + amdtp_domain_stop(&ff->domain); finish_session(ff); + } /* * Regardless of current source of clock signal, drivers transfer some * packets. Then, the device transfers packets. */ if (!amdtp_stream_running(&ff->rx_stream)) { + int spd = fw_parent_device(ff->unit)->max_speed; + err = ff->spec->protocol->begin_session(ff, rate); if (err < 0) goto error; - err = amdtp_stream_start(&ff->rx_stream, - ff->rx_resources.channel, - fw_parent_device(ff->unit)->max_speed); + err = amdtp_domain_add_stream(&ff->domain, &ff->rx_stream, + ff->rx_resources.channel, spd); if (err < 0) goto error; - if (!amdtp_stream_wait_callback(&ff->rx_stream, - CALLBACK_TIMEOUT_MS)) { - err = -ETIMEDOUT; - goto error; - } - - err = ff->spec->protocol->switch_fetching_mode(ff, true); + err = amdtp_domain_add_stream(&ff->domain, &ff->tx_stream, + ff->tx_resources.channel, spd); if (err < 0) goto error; - } - if (!amdtp_stream_running(&ff->tx_stream)) { - err = amdtp_stream_start(&ff->tx_stream, - ff->tx_resources.channel, - fw_parent_device(ff->unit)->max_speed); + err = amdtp_domain_start(&ff->domain); if (err < 0) goto error; - if (!amdtp_stream_wait_callback(&ff->tx_stream, + if (!amdtp_stream_wait_callback(&ff->rx_stream, + CALLBACK_TIMEOUT_MS) || + !amdtp_stream_wait_callback(&ff->tx_stream, CALLBACK_TIMEOUT_MS)) { err = -ETIMEDOUT; goto error; } + + err = ff->spec->protocol->switch_fetching_mode(ff, true); + if (err < 0) + goto error; } return 0; error: + amdtp_domain_stop(&ff->domain); finish_session(ff); return err; @@ -209,6 +217,7 @@ error: void snd_ff_stream_stop_duplex(struct snd_ff *ff) { if (ff->substreams_counter == 0) { + amdtp_domain_stop(&ff->domain); finish_session(ff); fw_iso_resources_free(&ff->tx_resources); @@ -218,12 +227,11 @@ void snd_ff_stream_stop_duplex(struct snd_ff *ff) void snd_ff_stream_update_duplex(struct snd_ff *ff) { + amdtp_domain_stop(&ff->domain); + // The device discontinue to transfer packets. amdtp_stream_pcm_abort(&ff->tx_stream); - amdtp_stream_stop(&ff->tx_stream); - amdtp_stream_pcm_abort(&ff->rx_stream); - amdtp_stream_stop(&ff->rx_stream); } void snd_ff_stream_lock_changed(struct snd_ff *ff) diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index 36dd0c75b9f7..b4c22ca6079e 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -91,6 +91,8 @@ struct snd_ff { int dev_lock_count; bool dev_lock_changed; wait_queue_head_t hwdep_wait; + + struct amdtp_domain domain; }; enum snd_ff_clock_src { diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h index 31efd4b53b4f..4cda297f8438 100644 --- a/sound/firewire/fireworks/fireworks.h +++ b/sound/firewire/fireworks/fireworks.h @@ -107,6 +107,8 @@ struct snd_efw { u8 *resp_buf; u8 *pull_ptr; u8 *push_ptr; + + struct amdtp_domain domain; }; int snd_efw_transaction_cmd(struct fw_unit *unit, diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c index e659a0b89ba5..f2de304d2f26 100644 --- a/sound/firewire/fireworks/fireworks_stream.c +++ b/sound/firewire/fireworks/fireworks_stream.c @@ -8,8 +8,7 @@ #define CALLBACK_TIMEOUT 100 -static int -init_stream(struct snd_efw *efw, struct amdtp_stream *stream) +static int init_stream(struct snd_efw *efw, struct amdtp_stream *stream) { struct cmp_connection *conn; enum cmp_direction c_dir; @@ -28,26 +27,38 @@ init_stream(struct snd_efw *efw, struct amdtp_stream *stream) err = cmp_connection_init(conn, efw->unit, c_dir, 0); if (err < 0) - goto end; + return err; err = amdtp_am824_init(stream, efw->unit, s_dir, CIP_BLOCKING); if (err < 0) { amdtp_stream_destroy(stream); cmp_connection_destroy(conn); + return err; } -end: - return err; -} -static void -stop_stream(struct snd_efw *efw, struct amdtp_stream *stream) -{ - amdtp_stream_stop(stream); + if (stream == &efw->tx_stream) { + // Fireworks transmits NODATA packets with TAG0. + efw->tx_stream.flags |= CIP_EMPTY_WITH_TAG0; + // Fireworks has its own meaning for dbc. + efw->tx_stream.flags |= CIP_DBC_IS_END_EVENT; + // Fireworks reset dbc at bus reset. + efw->tx_stream.flags |= CIP_SKIP_DBC_ZERO_CHECK; + // But Recent firmwares starts packets with non-zero dbc. + // Driver version 5.7.6 installs firmware version 5.7.3. + if (efw->is_fireworks3 && + (efw->firmware_version == 0x5070000 || + efw->firmware_version == 0x5070300 || + efw->firmware_version == 0x5080000)) + efw->tx_stream.flags |= CIP_UNALIGHED_DBC; + // AudioFire9 always reports wrong dbs. + if (efw->is_af9) + efw->tx_stream.flags |= CIP_WRONG_DBS; + // Firmware version 5.5 reports fixed interval for dbc. + if (efw->firmware_version == 0x5050000) + efw->tx_stream.ctx_data.tx.dbc_interval = 8; + } - if (stream == &efw->tx_stream) - cmp_connection_break(&efw->out_conn); - else - cmp_connection_break(&efw->in_conn); + return err; } static int start_stream(struct snd_efw *efw, struct amdtp_stream *stream, @@ -67,38 +78,26 @@ static int start_stream(struct snd_efw *efw, struct amdtp_stream *stream, return err; // Start amdtp stream. - err = amdtp_stream_start(stream, conn->resources.channel, conn->speed); + err = amdtp_domain_add_stream(&efw->domain, stream, + conn->resources.channel, conn->speed); if (err < 0) { cmp_connection_break(conn); return err; } - // Wait first callback. - if (!amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT)) { - amdtp_stream_stop(stream); - cmp_connection_break(conn); - return -ETIMEDOUT; - } - return 0; } -/* - * This function should be called before starting the stream or after stopping - * the streams. - */ -static void -destroy_stream(struct snd_efw *efw, struct amdtp_stream *stream) +// This function should be called before starting the stream or after stopping +// the streams. +static void destroy_stream(struct snd_efw *efw, struct amdtp_stream *stream) { - struct cmp_connection *conn; + amdtp_stream_destroy(stream); if (stream == &efw->tx_stream) - conn = &efw->out_conn; + cmp_connection_destroy(&efw->out_conn); else - conn = &efw->in_conn; - - amdtp_stream_destroy(stream); - cmp_connection_destroy(conn); + cmp_connection_destroy(&efw->in_conn); } static int @@ -131,42 +130,28 @@ int snd_efw_stream_init_duplex(struct snd_efw *efw) err = init_stream(efw, &efw->tx_stream); if (err < 0) - goto end; - /* Fireworks transmits NODATA packets with TAG0. */ - efw->tx_stream.flags |= CIP_EMPTY_WITH_TAG0; - /* Fireworks has its own meaning for dbc. */ - efw->tx_stream.flags |= CIP_DBC_IS_END_EVENT; - /* Fireworks reset dbc at bus reset. */ - efw->tx_stream.flags |= CIP_SKIP_DBC_ZERO_CHECK; - /* - * But Recent firmwares starts packets with non-zero dbc. - * Driver version 5.7.6 installs firmware version 5.7.3. - */ - if (efw->is_fireworks3 && - (efw->firmware_version == 0x5070000 || - efw->firmware_version == 0x5070300 || - efw->firmware_version == 0x5080000)) - efw->tx_stream.ctx_data.tx.first_dbc = 0x02; - /* AudioFire9 always reports wrong dbs. */ - if (efw->is_af9) - efw->tx_stream.flags |= CIP_WRONG_DBS; - /* Firmware version 5.5 reports fixed interval for dbc. */ - if (efw->firmware_version == 0x5050000) - efw->tx_stream.ctx_data.tx.dbc_interval = 8; + return err; err = init_stream(efw, &efw->rx_stream); if (err < 0) { destroy_stream(efw, &efw->tx_stream); - goto end; + return err; + } + + err = amdtp_domain_init(&efw->domain); + if (err < 0) { + destroy_stream(efw, &efw->tx_stream); + destroy_stream(efw, &efw->rx_stream); + return err; } - /* set IEC61883 compliant mode (actually not fully compliant...) */ + // set IEC61883 compliant mode (actually not fully compliant...). err = snd_efw_command_set_tx_mode(efw, SND_EFW_TRANSPORT_MODE_IEC61883); if (err < 0) { destroy_stream(efw, &efw->tx_stream); destroy_stream(efw, &efw->rx_stream); } -end: + return err; } @@ -214,8 +199,10 @@ int snd_efw_stream_reserve_duplex(struct snd_efw *efw, unsigned int rate) if (rate == 0) rate = curr_rate; if (rate != curr_rate) { - stop_stream(efw, &efw->tx_stream); - stop_stream(efw, &efw->rx_stream); + amdtp_domain_stop(&efw->domain); + + cmp_connection_break(&efw->out_conn); + cmp_connection_break(&efw->in_conn); cmp_connection_release(&efw->out_conn); cmp_connection_release(&efw->in_conn); @@ -255,47 +242,57 @@ int snd_efw_stream_start_duplex(struct snd_efw *efw) if (efw->substreams_counter == 0) return -EIO; - err = snd_efw_command_get_sampling_rate(efw, &rate); - if (err < 0) - return err; - if (amdtp_streaming_error(&efw->rx_stream) || amdtp_streaming_error(&efw->tx_stream)) { - stop_stream(efw, &efw->rx_stream); - stop_stream(efw, &efw->tx_stream); + amdtp_domain_stop(&efw->domain); + cmp_connection_break(&efw->out_conn); + cmp_connection_break(&efw->in_conn); } - /* master should be always running */ + err = snd_efw_command_get_sampling_rate(efw, &rate); + if (err < 0) + return err; + if (!amdtp_stream_running(&efw->rx_stream)) { err = start_stream(efw, &efw->rx_stream, rate); - if (err < 0) { - dev_err(&efw->unit->device, - "fail to start AMDTP master stream:%d\n", err); + if (err < 0) goto error; - } - } - if (!amdtp_stream_running(&efw->tx_stream)) { err = start_stream(efw, &efw->tx_stream, rate); - if (err < 0) { - dev_err(&efw->unit->device, - "fail to start AMDTP slave stream:%d\n", err); + if (err < 0) + goto error; + + err = amdtp_domain_start(&efw->domain); + if (err < 0) + goto error; + + // Wait first callback. + if (!amdtp_stream_wait_callback(&efw->rx_stream, + CALLBACK_TIMEOUT) || + !amdtp_stream_wait_callback(&efw->tx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; goto error; } } return 0; error: - stop_stream(efw, &efw->rx_stream); - stop_stream(efw, &efw->tx_stream); + amdtp_domain_stop(&efw->domain); + + cmp_connection_break(&efw->out_conn); + cmp_connection_break(&efw->in_conn); + return err; } void snd_efw_stream_stop_duplex(struct snd_efw *efw) { if (efw->substreams_counter == 0) { - stop_stream(efw, &efw->tx_stream); - stop_stream(efw, &efw->rx_stream); + amdtp_domain_stop(&efw->domain); + + cmp_connection_break(&efw->out_conn); + cmp_connection_break(&efw->in_conn); cmp_connection_release(&efw->out_conn); cmp_connection_release(&efw->in_conn); @@ -304,18 +301,19 @@ void snd_efw_stream_stop_duplex(struct snd_efw *efw) void snd_efw_stream_update_duplex(struct snd_efw *efw) { - if (cmp_connection_update(&efw->out_conn) < 0 || - cmp_connection_update(&efw->in_conn) < 0) { - stop_stream(efw, &efw->rx_stream); - stop_stream(efw, &efw->tx_stream); - } else { - amdtp_stream_update(&efw->rx_stream); - amdtp_stream_update(&efw->tx_stream); - } + amdtp_domain_stop(&efw->domain); + + cmp_connection_break(&efw->out_conn); + cmp_connection_break(&efw->in_conn); + + amdtp_stream_pcm_abort(&efw->rx_stream); + amdtp_stream_pcm_abort(&efw->tx_stream); } void snd_efw_stream_destroy_duplex(struct snd_efw *efw) { + amdtp_domain_destroy(&efw->domain); + destroy_stream(efw, &efw->rx_stream); destroy_stream(efw, &efw->tx_stream); } diff --git a/sound/firewire/motu/amdtp-motu.c b/sound/firewire/motu/amdtp-motu.c index 7973dedd31ef..0fd36e469ad0 100644 --- a/sound/firewire/motu/amdtp-motu.c +++ b/sound/firewire/motu/amdtp-motu.c @@ -117,19 +117,25 @@ int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, return 0; } -static void read_pcm_s32(struct amdtp_stream *s, - struct snd_pcm_runtime *runtime, - __be32 *buffer, unsigned int data_blocks) +static void read_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int data_blocks, + unsigned int pcm_frames) { struct amdtp_motu *p = s->protocol; - unsigned int channels, remaining_frames, i, c; + unsigned int channels = p->pcm_chunks; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int pcm_buffer_pointer; + int remaining_frames; u8 *byte; u32 *dst; + int i, c; + + pcm_buffer_pointer = s->pcm_buffer_pointer + pcm_frames; + pcm_buffer_pointer %= runtime->buffer_size; - channels = p->pcm_chunks; dst = (void *)runtime->dma_area + - frames_to_bytes(runtime, s->pcm_buffer_pointer); - remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + frames_to_bytes(runtime, pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - pcm_buffer_pointer; for (i = 0; i < data_blocks; ++i) { byte = (u8 *)buffer + p->pcm_byte_offset; @@ -147,19 +153,25 @@ static void read_pcm_s32(struct amdtp_stream *s, } } -static void write_pcm_s32(struct amdtp_stream *s, - struct snd_pcm_runtime *runtime, - __be32 *buffer, unsigned int data_blocks) +static void write_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int data_blocks, + unsigned int pcm_frames) { struct amdtp_motu *p = s->protocol; - unsigned int channels, remaining_frames, i, c; + unsigned int channels = p->pcm_chunks; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int pcm_buffer_pointer; + int remaining_frames; u8 *byte; const u32 *src; + int i, c; + + pcm_buffer_pointer = s->pcm_buffer_pointer + pcm_frames; + pcm_buffer_pointer %= runtime->buffer_size; - channels = p->pcm_chunks; src = (void *)runtime->dma_area + - frames_to_bytes(runtime, s->pcm_buffer_pointer); - remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + frames_to_bytes(runtime, pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - pcm_buffer_pointer; for (i = 0; i < data_blocks; ++i) { byte = (u8 *)buffer + p->pcm_byte_offset; @@ -298,24 +310,52 @@ static void __maybe_unused copy_message(u64 *frames, __be32 *buffer, } } -static unsigned int process_tx_data_blocks(struct amdtp_stream *s, - __be32 *buffer, unsigned int data_blocks, - unsigned int *syt) +static void probe_tracepoints_events(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets) +{ + int i; + + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buf = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; + + trace_data_block_sph(s, data_blocks, buf); + trace_data_block_message(s, data_blocks, buf); + } +} + +static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) { struct amdtp_motu *p = s->protocol; - struct snd_pcm_substream *pcm; + unsigned int pcm_frames = 0; + int i; + + // For data block processing. + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buf = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; - trace_data_block_sph(s, data_blocks, buffer); - trace_data_block_message(s, data_blocks, buffer); + if (pcm) { + read_pcm_s32(s, pcm, buf, data_blocks, pcm_frames); + pcm_frames += data_blocks; + } - if (p->midi_ports) - read_midi_messages(s, buffer, data_blocks); + if (p->midi_ports) + read_midi_messages(s, buf, data_blocks); + } - pcm = READ_ONCE(s->pcm); - if (data_blocks > 0 && pcm) - read_pcm_s32(s, pcm->runtime, buffer, data_blocks); + // For tracepoints. + if (trace_data_block_sph_enabled() || + trace_data_block_message_enabled()) + probe_tracepoints_events(s, descs, packets); - return data_blocks; + return pcm_frames; } static inline void compute_next_elapse_from_start(struct amdtp_motu *p) @@ -360,46 +400,55 @@ static void write_sph(struct amdtp_stream *s, __be32 *buffer, } } -static unsigned int process_rx_data_blocks(struct amdtp_stream *s, - __be32 *buffer, unsigned int data_blocks, - unsigned int *syt) +static unsigned int process_it_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) { - struct amdtp_motu *p = (struct amdtp_motu *)s->protocol; - struct snd_pcm_substream *pcm; + struct amdtp_motu *p = s->protocol; + unsigned int pcm_frames = 0; + int i; - /* Not used. */ - *syt = 0xffff; + // For data block processing. + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buf = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; - /* TODO: how to interact control messages between userspace? */ + if (pcm) { + write_pcm_s32(s, pcm, buf, data_blocks, pcm_frames); + pcm_frames += data_blocks; + } else { + write_pcm_silence(s, buf, data_blocks); + } - if (p->midi_ports) - write_midi_messages(s, buffer, data_blocks); + if (p->midi_ports) + write_midi_messages(s, buf, data_blocks); - pcm = READ_ONCE(s->pcm); - if (pcm) - write_pcm_s32(s, pcm->runtime, buffer, data_blocks); - else - write_pcm_silence(s, buffer, data_blocks); + // TODO: how to interact control messages between userspace? - write_sph(s, buffer, data_blocks); + write_sph(s, buf, data_blocks); + } - trace_data_block_sph(s, data_blocks, buffer); - trace_data_block_message(s, data_blocks, buffer); + // For tracepoints. + if (trace_data_block_sph_enabled() || + trace_data_block_message_enabled()) + probe_tracepoints_events(s, descs, packets); - return data_blocks; + return pcm_frames; } int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, enum amdtp_stream_direction dir, const struct snd_motu_protocol *const protocol) { - amdtp_stream_process_data_blocks_t process_data_blocks; + amdtp_stream_process_ctx_payloads_t process_ctx_payloads; int fmt = CIP_FMT_MOTU; int flags = CIP_BLOCKING; int err; if (dir == AMDTP_IN_STREAM) { - process_data_blocks = process_tx_data_blocks; + process_ctx_payloads = process_ir_ctx_payloads; /* * Units of version 3 transmits packets with invalid CIP header @@ -418,17 +467,23 @@ int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, CIP_SKIP_DBC_ZERO_CHECK; } } else { - process_data_blocks = process_rx_data_blocks; + process_ctx_payloads = process_it_ctx_payloads; flags |= CIP_DBC_IS_END_EVENT; } - err = amdtp_stream_init(s, unit, dir, flags, fmt, process_data_blocks, + err = amdtp_stream_init(s, unit, dir, flags, fmt, process_ctx_payloads, sizeof(struct amdtp_motu)); if (err < 0) return err; s->sph = 1; - s->ctx_data.rx.fdf = MOTU_FDF_AM824; + + if (dir == AMDTP_OUT_STREAM) { + // Use fixed value for FDF field. + s->ctx_data.rx.fdf = MOTU_FDF_AM824; + // Not used. + s->ctx_data.rx.syt_override = 0xffff; + } return 0; } diff --git a/sound/firewire/motu/motu-stream.c b/sound/firewire/motu/motu-stream.c index 2bbb335e8de1..813e38e6a86e 100644 --- a/sound/firewire/motu/motu-stream.c +++ b/sound/firewire/motu/motu-stream.c @@ -92,9 +92,6 @@ static void finish_session(struct snd_motu *motu) if (err < 0) return; - amdtp_stream_stop(&motu->tx_stream); - amdtp_stream_stop(&motu->rx_stream); - err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, ®, sizeof(reg)); if (err < 0) @@ -109,27 +106,6 @@ static void finish_session(struct snd_motu *motu) sizeof(reg)); } -static int start_isoc_ctx(struct snd_motu *motu, struct amdtp_stream *stream) -{ - struct fw_iso_resources *resources; - int err; - - if (stream == &motu->rx_stream) - resources = &motu->rx_resources; - else - resources = &motu->tx_resources; - - err = amdtp_stream_start(stream, resources->channel, - fw_parent_device(motu->unit)->max_speed); - if (err < 0) - return err; - - if (!amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT)) - return -ETIMEDOUT; - - return 0; -} - int snd_motu_stream_cache_packet_formats(struct snd_motu *motu) { int err; @@ -169,6 +145,7 @@ int snd_motu_stream_reserve_duplex(struct snd_motu *motu, unsigned int rate) rate = curr_rate; if (motu->substreams_counter == 0 || curr_rate != rate) { + amdtp_domain_stop(&motu->domain); finish_session(motu); fw_iso_resources_free(&motu->tx_resources); @@ -234,8 +211,10 @@ int snd_motu_stream_start_duplex(struct snd_motu *motu) return 0; if (amdtp_streaming_error(&motu->rx_stream) || - amdtp_streaming_error(&motu->tx_stream)) + amdtp_streaming_error(&motu->tx_stream)) { + amdtp_domain_stop(&motu->domain); finish_session(motu); + } if (generation != fw_parent_device(motu->unit)->card->generation) { err = fw_iso_resources_update(&motu->rx_resources); @@ -248,6 +227,8 @@ int snd_motu_stream_start_duplex(struct snd_motu *motu) } if (!amdtp_stream_running(&motu->rx_stream)) { + int spd = fw_parent_device(motu->unit)->max_speed; + err = ensure_packet_formats(motu); if (err < 0) return err; @@ -259,26 +240,32 @@ int snd_motu_stream_start_duplex(struct snd_motu *motu) goto stop_streams; } - err = start_isoc_ctx(motu, &motu->rx_stream); - if (err < 0) { - dev_err(&motu->unit->device, - "fail to start IT context: %d\n", err); + err = amdtp_domain_add_stream(&motu->domain, &motu->tx_stream, + motu->tx_resources.channel, spd); + if (err < 0) goto stop_streams; - } - err = motu->spec->protocol->switch_fetching_mode(motu, true); - if (err < 0) { - dev_err(&motu->unit->device, - "fail to enable frame fetching: %d\n", err); + err = amdtp_domain_add_stream(&motu->domain, &motu->rx_stream, + motu->rx_resources.channel, spd); + if (err < 0) + goto stop_streams; + + err = amdtp_domain_start(&motu->domain); + if (err < 0) + goto stop_streams; + + if (!amdtp_stream_wait_callback(&motu->tx_stream, + CALLBACK_TIMEOUT) || + !amdtp_stream_wait_callback(&motu->rx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; goto stop_streams; } - } - if (!amdtp_stream_running(&motu->tx_stream)) { - err = start_isoc_ctx(motu, &motu->tx_stream); + err = motu->spec->protocol->switch_fetching_mode(motu, true); if (err < 0) { dev_err(&motu->unit->device, - "fail to start IR context: %d", err); + "fail to enable frame fetching: %d\n", err); goto stop_streams; } } @@ -286,6 +273,7 @@ int snd_motu_stream_start_duplex(struct snd_motu *motu) return 0; stop_streams: + amdtp_domain_stop(&motu->domain); finish_session(motu); return err; } @@ -293,6 +281,7 @@ stop_streams: void snd_motu_stream_stop_duplex(struct snd_motu *motu) { if (motu->substreams_counter == 0) { + amdtp_domain_stop(&motu->domain); finish_session(motu); fw_iso_resources_free(&motu->tx_resources); @@ -300,74 +289,72 @@ void snd_motu_stream_stop_duplex(struct snd_motu *motu) } } -static int init_stream(struct snd_motu *motu, enum amdtp_stream_direction dir) +static int init_stream(struct snd_motu *motu, struct amdtp_stream *s) { - int err; - struct amdtp_stream *stream; struct fw_iso_resources *resources; + enum amdtp_stream_direction dir; + int err; - if (dir == AMDTP_IN_STREAM) { - stream = &motu->tx_stream; + if (s == &motu->tx_stream) { resources = &motu->tx_resources; + dir = AMDTP_IN_STREAM; } else { - stream = &motu->rx_stream; resources = &motu->rx_resources; + dir = AMDTP_OUT_STREAM; } err = fw_iso_resources_init(resources, motu->unit); if (err < 0) return err; - err = amdtp_motu_init(stream, motu->unit, dir, motu->spec->protocol); - if (err < 0) { - amdtp_stream_destroy(stream); + err = amdtp_motu_init(s, motu->unit, dir, motu->spec->protocol); + if (err < 0) fw_iso_resources_destroy(resources); - } return err; } -static void destroy_stream(struct snd_motu *motu, - enum amdtp_stream_direction dir) +static void destroy_stream(struct snd_motu *motu, struct amdtp_stream *s) { - struct amdtp_stream *stream; - struct fw_iso_resources *resources; - - if (dir == AMDTP_IN_STREAM) { - stream = &motu->tx_stream; - resources = &motu->tx_resources; - } else { - stream = &motu->rx_stream; - resources = &motu->rx_resources; - } + amdtp_stream_destroy(s); - amdtp_stream_destroy(stream); - fw_iso_resources_destroy(resources); + if (s == &motu->tx_stream) + fw_iso_resources_destroy(&motu->tx_resources); + else + fw_iso_resources_destroy(&motu->rx_resources); } int snd_motu_stream_init_duplex(struct snd_motu *motu) { int err; - err = init_stream(motu, AMDTP_IN_STREAM); + err = init_stream(motu, &motu->tx_stream); if (err < 0) return err; - err = init_stream(motu, AMDTP_OUT_STREAM); - if (err < 0) - destroy_stream(motu, AMDTP_IN_STREAM); + err = init_stream(motu, &motu->rx_stream); + if (err < 0) { + destroy_stream(motu, &motu->tx_stream); + return err; + } + + err = amdtp_domain_init(&motu->domain); + if (err < 0) { + destroy_stream(motu, &motu->tx_stream); + destroy_stream(motu, &motu->rx_stream); + } return err; } -/* - * This function should be called before starting streams or after stopping - * streams. - */ +// This function should be called before starting streams or after stopping +// streams. void snd_motu_stream_destroy_duplex(struct snd_motu *motu) { - destroy_stream(motu, AMDTP_IN_STREAM); - destroy_stream(motu, AMDTP_OUT_STREAM); + amdtp_domain_destroy(&motu->domain); + + destroy_stream(motu, &motu->rx_stream); + destroy_stream(motu, &motu->tx_stream); motu->substreams_counter = 0; } diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index 03cda2166ea3..72908b4de77c 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -247,6 +247,17 @@ static const struct snd_motu_spec motu_audio_express = { .analog_out_ports = 4, }; +static const struct snd_motu_spec motu_4pre = { + .name = "4pre", + .protocol = &snd_motu_protocol_v3, + .flags = SND_MOTU_SPEC_SUPPORT_CLOCK_X2 | + SND_MOTU_SPEC_TX_MICINST_CHUNK | + SND_MOTU_SPEC_TX_RETURN_CHUNK | + SND_MOTU_SPEC_RX_SEPARETED_MAIN, + .analog_in_ports = 2, + .analog_out_ports = 2, +}; + #define SND_MOTU_DEV_ENTRY(model, data) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ @@ -265,6 +276,7 @@ static const struct ieee1394_device_id motu_id_table[] = { SND_MOTU_DEV_ENTRY(0x000015, &motu_828mk3), /* FireWire only. */ SND_MOTU_DEV_ENTRY(0x000035, &motu_828mk3), /* Hybrid. */ SND_MOTU_DEV_ENTRY(0x000033, &motu_audio_express), + SND_MOTU_DEV_ENTRY(0x000045, &motu_4pre), { } }; MODULE_DEVICE_TABLE(ieee1394, motu_id_table); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index 09d1451d7de4..350ee2c16f4a 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -69,6 +69,8 @@ struct snd_motu { int dev_lock_count; bool dev_lock_changed; wait_queue_head_t hwdep_wait; + + struct amdtp_domain domain; }; enum snd_motu_spec_flags { diff --git a/sound/firewire/oxfw/oxfw-stream.c b/sound/firewire/oxfw/oxfw-stream.c index 74c972d25c66..3c9a796b6526 100644 --- a/sound/firewire/oxfw/oxfw-stream.c +++ b/sound/firewire/oxfw/oxfw-stream.c @@ -114,19 +114,13 @@ static int start_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream) if (err < 0) return err; - err = amdtp_stream_start(stream, conn->resources.channel, conn->speed); + err = amdtp_domain_add_stream(&oxfw->domain, stream, + conn->resources.channel, conn->speed); if (err < 0) { cmp_connection_break(conn); return err; } - // Wait first packet. - if (!amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT)) { - amdtp_stream_stop(stream); - cmp_connection_break(conn); - return -ETIMEDOUT; - } - return 0; } @@ -280,12 +274,12 @@ int snd_oxfw_stream_reserve_duplex(struct snd_oxfw *oxfw, pcm_channels = formation.pcm; } if (formation.rate != rate || formation.pcm != pcm_channels) { - amdtp_stream_stop(&oxfw->rx_stream); + amdtp_domain_stop(&oxfw->domain); + cmp_connection_break(&oxfw->in_conn); cmp_connection_release(&oxfw->in_conn); if (oxfw->has_output) { - amdtp_stream_stop(&oxfw->tx_stream); cmp_connection_break(&oxfw->out_conn); cmp_connection_release(&oxfw->out_conn); } @@ -325,30 +319,46 @@ int snd_oxfw_stream_start_duplex(struct snd_oxfw *oxfw) if (amdtp_streaming_error(&oxfw->rx_stream) || amdtp_streaming_error(&oxfw->tx_stream)) { - amdtp_stream_stop(&oxfw->rx_stream); - cmp_connection_break(&oxfw->in_conn); + amdtp_domain_stop(&oxfw->domain); - if (oxfw->has_output) { - amdtp_stream_stop(&oxfw->tx_stream); + cmp_connection_break(&oxfw->in_conn); + if (oxfw->has_output) cmp_connection_break(&oxfw->out_conn); - } } if (!amdtp_stream_running(&oxfw->rx_stream)) { err = start_stream(oxfw, &oxfw->rx_stream); if (err < 0) { dev_err(&oxfw->unit->device, - "fail to start rx stream: %d\n", err); + "fail to prepare rx stream: %d\n", err); goto error; } - } - if (oxfw->has_output) { - if (!amdtp_stream_running(&oxfw->tx_stream)) { + if (oxfw->has_output && + !amdtp_stream_running(&oxfw->tx_stream)) { err = start_stream(oxfw, &oxfw->tx_stream); if (err < 0) { dev_err(&oxfw->unit->device, - "fail to start tx stream: %d\n", err); + "fail to prepare tx stream: %d\n", err); + goto error; + } + } + + err = amdtp_domain_start(&oxfw->domain); + if (err < 0) + goto error; + + // Wait first packet. + if (!amdtp_stream_wait_callback(&oxfw->rx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; + goto error; + } + + if (oxfw->has_output) { + if (!amdtp_stream_wait_callback(&oxfw->tx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; goto error; } } @@ -356,24 +366,24 @@ int snd_oxfw_stream_start_duplex(struct snd_oxfw *oxfw) return 0; error: - amdtp_stream_stop(&oxfw->rx_stream); + amdtp_domain_stop(&oxfw->domain); + cmp_connection_break(&oxfw->in_conn); - if (oxfw->has_output) { - amdtp_stream_stop(&oxfw->tx_stream); + if (oxfw->has_output) cmp_connection_break(&oxfw->out_conn); - } + return err; } void snd_oxfw_stream_stop_duplex(struct snd_oxfw *oxfw) { if (oxfw->substreams_count == 0) { - amdtp_stream_stop(&oxfw->rx_stream); + amdtp_domain_stop(&oxfw->domain); + cmp_connection_break(&oxfw->in_conn); cmp_connection_release(&oxfw->in_conn); if (oxfw->has_output) { - amdtp_stream_stop(&oxfw->tx_stream); cmp_connection_break(&oxfw->out_conn); cmp_connection_release(&oxfw->out_conn); } @@ -409,13 +419,22 @@ int snd_oxfw_stream_init_duplex(struct snd_oxfw *oxfw) } } - return 0; + err = amdtp_domain_init(&oxfw->domain); + if (err < 0) { + destroy_stream(oxfw, &oxfw->rx_stream); + if (oxfw->has_output) + destroy_stream(oxfw, &oxfw->tx_stream); + } + + return err; } // This function should be called before starting the stream or after stopping // the streams. void snd_oxfw_stream_destroy_duplex(struct snd_oxfw *oxfw) { + amdtp_domain_destroy(&oxfw->domain); + destroy_stream(oxfw, &oxfw->rx_stream); if (oxfw->has_output) @@ -424,13 +443,13 @@ void snd_oxfw_stream_destroy_duplex(struct snd_oxfw *oxfw) void snd_oxfw_stream_update_duplex(struct snd_oxfw *oxfw) { - amdtp_stream_stop(&oxfw->rx_stream); + amdtp_domain_stop(&oxfw->domain); + cmp_connection_break(&oxfw->in_conn); amdtp_stream_pcm_abort(&oxfw->rx_stream); if (oxfw->has_output) { - amdtp_stream_stop(&oxfw->tx_stream); cmp_connection_break(&oxfw->out_conn); amdtp_stream_pcm_abort(&oxfw->tx_stream); diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index cb69ab87bb14..c9627b8c5d6e 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -63,6 +63,8 @@ struct snd_oxfw { const struct ieee1394_device_id *entry; void *spec; + + struct amdtp_domain domain; }; /* diff --git a/sound/firewire/tascam/amdtp-tascam.c b/sound/firewire/tascam/amdtp-tascam.c index 95fb10b7a737..e80bb84c43f6 100644 --- a/sound/firewire/tascam/amdtp-tascam.c +++ b/sound/firewire/tascam/amdtp-tascam.c @@ -32,19 +32,24 @@ int amdtp_tscm_set_parameters(struct amdtp_stream *s, unsigned int rate) return amdtp_stream_set_parameters(s, rate, data_channels); } -static void write_pcm_s32(struct amdtp_stream *s, - struct snd_pcm_substream *pcm, - __be32 *buffer, unsigned int frames) +static void write_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames, + unsigned int pcm_frames) { struct amdtp_tscm *p = s->protocol; + unsigned int channels = p->pcm_channels; struct snd_pcm_runtime *runtime = pcm->runtime; - unsigned int channels, remaining_frames, i, c; + unsigned int pcm_buffer_pointer; + int remaining_frames; const u32 *src; + int i, c; + + pcm_buffer_pointer = s->pcm_buffer_pointer + pcm_frames; + pcm_buffer_pointer %= runtime->buffer_size; - channels = p->pcm_channels; src = (void *)runtime->dma_area + - frames_to_bytes(runtime, s->pcm_buffer_pointer); - remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + frames_to_bytes(runtime, pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - pcm_buffer_pointer; for (i = 0; i < frames; ++i) { for (c = 0; c < channels; ++c) { @@ -57,19 +62,24 @@ static void write_pcm_s32(struct amdtp_stream *s, } } -static void read_pcm_s32(struct amdtp_stream *s, - struct snd_pcm_substream *pcm, - __be32 *buffer, unsigned int frames) +static void read_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames, + unsigned int pcm_frames) { struct amdtp_tscm *p = s->protocol; + unsigned int channels = p->pcm_channels; struct snd_pcm_runtime *runtime = pcm->runtime; - unsigned int channels, remaining_frames, i, c; + unsigned int pcm_buffer_pointer; + int remaining_frames; u32 *dst; + int i, c; + + pcm_buffer_pointer = s->pcm_buffer_pointer + pcm_frames; + pcm_buffer_pointer %= runtime->buffer_size; - channels = p->pcm_channels; dst = (void *)runtime->dma_area + - frames_to_bytes(runtime, s->pcm_buffer_pointer); - remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + frames_to_bytes(runtime, pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - pcm_buffer_pointer; /* The first data channel is for event counter. */ buffer += 1; @@ -165,65 +175,82 @@ static void read_status_messages(struct amdtp_stream *s, } } -static unsigned int process_tx_data_blocks(struct amdtp_stream *s, - __be32 *buffer, - unsigned int data_blocks, - unsigned int *syt) +static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) { - struct snd_pcm_substream *pcm; + unsigned int pcm_frames = 0; + int i; + + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buf = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; - pcm = READ_ONCE(s->pcm); - if (data_blocks > 0 && pcm) - read_pcm_s32(s, pcm, buffer, data_blocks); + if (pcm) { + read_pcm_s32(s, pcm, buf, data_blocks, pcm_frames); + pcm_frames += data_blocks; + } - read_status_messages(s, buffer, data_blocks); + read_status_messages(s, buf, data_blocks); + } - return data_blocks; + return pcm_frames; } -static unsigned int process_rx_data_blocks(struct amdtp_stream *s, - __be32 *buffer, - unsigned int data_blocks, - unsigned int *syt) +static unsigned int process_it_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) { - struct snd_pcm_substream *pcm; + unsigned int pcm_frames = 0; + int i; - /* This field is not used. */ - *syt = 0x0000; + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buf = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; - pcm = READ_ONCE(s->pcm); - if (pcm) - write_pcm_s32(s, pcm, buffer, data_blocks); - else - write_pcm_silence(s, buffer, data_blocks); + if (pcm) { + write_pcm_s32(s, pcm, buf, data_blocks, pcm_frames); + pcm_frames += data_blocks; + } else { + write_pcm_silence(s, buf, data_blocks); + } + } - return data_blocks; + return pcm_frames; } int amdtp_tscm_init(struct amdtp_stream *s, struct fw_unit *unit, enum amdtp_stream_direction dir, unsigned int pcm_channels) { - amdtp_stream_process_data_blocks_t process_data_blocks; + amdtp_stream_process_ctx_payloads_t process_ctx_payloads; struct amdtp_tscm *p; unsigned int fmt; int err; if (dir == AMDTP_IN_STREAM) { fmt = AMDTP_FMT_TSCM_TX; - process_data_blocks = process_tx_data_blocks; + process_ctx_payloads = process_ir_ctx_payloads; } else { fmt = AMDTP_FMT_TSCM_RX; - process_data_blocks = process_rx_data_blocks; + process_ctx_payloads = process_it_ctx_payloads; } err = amdtp_stream_init(s, unit, dir, - CIP_NONBLOCKING | CIP_SKIP_DBC_ZERO_CHECK, fmt, - process_data_blocks, sizeof(struct amdtp_tscm)); + CIP_NONBLOCKING | CIP_SKIP_DBC_ZERO_CHECK, fmt, + process_ctx_payloads, sizeof(struct amdtp_tscm)); if (err < 0) return 0; - /* Use fixed value for FDF field. */ - s->ctx_data.rx.fdf = 0x00; + if (dir == AMDTP_OUT_STREAM) { + // Use fixed value for FDF field. + s->ctx_data.rx.fdf = 0x00; + // Not used. + s->ctx_data.rx.syt_override = 0x0000; + } /* This protocol uses fixed number of data channels for PCM samples. */ p = s->protocol; diff --git a/sound/firewire/tascam/tascam-stream.c b/sound/firewire/tascam/tascam-stream.c index e852e46ebe6f..9e2dc2fe3271 100644 --- a/sound/firewire/tascam/tascam-stream.c +++ b/sound/firewire/tascam/tascam-stream.c @@ -180,9 +180,6 @@ static void finish_session(struct snd_tscm *tscm) { __be32 reg; - amdtp_stream_stop(&tscm->rx_stream); - amdtp_stream_stop(&tscm->tx_stream); - reg = 0; snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING, @@ -287,38 +284,68 @@ static int keep_resources(struct snd_tscm *tscm, unsigned int rate, fw_parent_device(tscm->unit)->max_speed); } -int snd_tscm_stream_init_duplex(struct snd_tscm *tscm) +static int init_stream(struct snd_tscm *tscm, struct amdtp_stream *s) { + struct fw_iso_resources *resources; + enum amdtp_stream_direction dir; unsigned int pcm_channels; int err; - /* For out-stream. */ - err = fw_iso_resources_init(&tscm->rx_resources, tscm->unit); - if (err < 0) - return err; - pcm_channels = tscm->spec->pcm_playback_analog_channels; + if (s == &tscm->tx_stream) { + resources = &tscm->tx_resources; + dir = AMDTP_IN_STREAM; + pcm_channels = tscm->spec->pcm_capture_analog_channels; + } else { + resources = &tscm->rx_resources; + dir = AMDTP_OUT_STREAM; + pcm_channels = tscm->spec->pcm_playback_analog_channels; + } + if (tscm->spec->has_adat) pcm_channels += 8; if (tscm->spec->has_spdif) pcm_channels += 2; - err = amdtp_tscm_init(&tscm->rx_stream, tscm->unit, AMDTP_OUT_STREAM, - pcm_channels); + + err = fw_iso_resources_init(resources, tscm->unit); if (err < 0) return err; - /* For in-stream. */ - err = fw_iso_resources_init(&tscm->tx_resources, tscm->unit); + err = amdtp_tscm_init(s, tscm->unit, dir, pcm_channels); if (err < 0) - return err; - pcm_channels = tscm->spec->pcm_capture_analog_channels; - if (tscm->spec->has_adat) - pcm_channels += 8; - if (tscm->spec->has_spdif) - pcm_channels += 2; - err = amdtp_tscm_init(&tscm->tx_stream, tscm->unit, AMDTP_IN_STREAM, - pcm_channels); + fw_iso_resources_free(resources); + + return err; +} + +static void destroy_stream(struct snd_tscm *tscm, struct amdtp_stream *s) +{ + amdtp_stream_destroy(s); + + if (s == &tscm->tx_stream) + fw_iso_resources_destroy(&tscm->tx_resources); + else + fw_iso_resources_destroy(&tscm->rx_resources); +} + +int snd_tscm_stream_init_duplex(struct snd_tscm *tscm) +{ + int err; + + err = init_stream(tscm, &tscm->tx_stream); if (err < 0) - amdtp_stream_destroy(&tscm->rx_stream); + return err; + + err = init_stream(tscm, &tscm->rx_stream); + if (err < 0) { + destroy_stream(tscm, &tscm->tx_stream); + return err; + } + + err = amdtp_domain_init(&tscm->domain); + if (err < 0) { + destroy_stream(tscm, &tscm->tx_stream); + destroy_stream(tscm, &tscm->rx_stream); + } return err; } @@ -326,24 +353,20 @@ int snd_tscm_stream_init_duplex(struct snd_tscm *tscm) // At bus reset, streaming is stopped and some registers are clear. void snd_tscm_stream_update_duplex(struct snd_tscm *tscm) { - amdtp_stream_pcm_abort(&tscm->tx_stream); - amdtp_stream_stop(&tscm->tx_stream); + amdtp_domain_stop(&tscm->domain); + amdtp_stream_pcm_abort(&tscm->tx_stream); amdtp_stream_pcm_abort(&tscm->rx_stream); - amdtp_stream_stop(&tscm->rx_stream); } -/* - * This function should be called before starting streams or after stopping - * streams. - */ +// This function should be called before starting streams or after stopping +// streams. void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm) { - amdtp_stream_destroy(&tscm->rx_stream); - amdtp_stream_destroy(&tscm->tx_stream); + amdtp_domain_destroy(&tscm->domain); - fw_iso_resources_destroy(&tscm->rx_resources); - fw_iso_resources_destroy(&tscm->tx_resources); + destroy_stream(tscm, &tscm->rx_stream); + destroy_stream(tscm, &tscm->tx_stream); } int snd_tscm_stream_reserve_duplex(struct snd_tscm *tscm, unsigned int rate) @@ -356,6 +379,8 @@ int snd_tscm_stream_reserve_duplex(struct snd_tscm *tscm, unsigned int rate) return err; if (tscm->substreams_counter == 0 || rate != curr_rate) { + amdtp_domain_stop(&tscm->domain); + finish_session(tscm); fw_iso_resources_free(&tscm->tx_resources); @@ -388,8 +413,10 @@ int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate) return 0; if (amdtp_streaming_error(&tscm->rx_stream) || - amdtp_streaming_error(&tscm->tx_stream)) + amdtp_streaming_error(&tscm->tx_stream)) { + amdtp_domain_stop(&tscm->domain); finish_session(tscm); + } if (generation != fw_parent_device(tscm->unit)->card->generation) { err = fw_iso_resources_update(&tscm->tx_resources); @@ -402,6 +429,8 @@ int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate) } if (!amdtp_stream_running(&tscm->rx_stream)) { + int spd = fw_parent_device(tscm->unit)->max_speed; + err = set_stream_formats(tscm, rate); if (err < 0) goto error; @@ -410,27 +439,23 @@ int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate) if (err < 0) goto error; - err = amdtp_stream_start(&tscm->rx_stream, - tscm->rx_resources.channel, - fw_parent_device(tscm->unit)->max_speed); + err = amdtp_domain_add_stream(&tscm->domain, &tscm->rx_stream, + tscm->rx_resources.channel, spd); if (err < 0) goto error; - if (!amdtp_stream_wait_callback(&tscm->rx_stream, - CALLBACK_TIMEOUT)) { - err = -ETIMEDOUT; + err = amdtp_domain_add_stream(&tscm->domain, &tscm->tx_stream, + tscm->tx_resources.channel, spd); + if (err < 0) goto error; - } - } - if (!amdtp_stream_running(&tscm->tx_stream)) { - err = amdtp_stream_start(&tscm->tx_stream, - tscm->tx_resources.channel, - fw_parent_device(tscm->unit)->max_speed); + err = amdtp_domain_start(&tscm->domain); if (err < 0) - goto error; + return err; - if (!amdtp_stream_wait_callback(&tscm->tx_stream, + if (!amdtp_stream_wait_callback(&tscm->rx_stream, + CALLBACK_TIMEOUT) || + !amdtp_stream_wait_callback(&tscm->tx_stream, CALLBACK_TIMEOUT)) { err = -ETIMEDOUT; goto error; @@ -439,6 +464,7 @@ int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate) return 0; error: + amdtp_domain_stop(&tscm->domain); finish_session(tscm); return err; @@ -447,6 +473,7 @@ error: void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm) { if (tscm->substreams_counter == 0) { + amdtp_domain_stop(&tscm->domain); finish_session(tscm); fw_iso_resources_free(&tscm->tx_resources); diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c index 231052db5680..addc464503bc 100644 --- a/sound/firewire/tascam/tascam.c +++ b/sound/firewire/tascam/tascam.c @@ -39,6 +39,9 @@ static const struct snd_tscm_spec model_specs[] = { .midi_capture_ports = 2, .midi_playback_ports = 4, }, + // This kernel module doesn't support FE-8 because the most of features + // can be implemented in userspace without any specific support of this + // module. }; static int identify_model(struct snd_tscm *tscm) @@ -214,7 +217,6 @@ static const struct ieee1394_device_id snd_tscm_id_table[] = { .vendor_id = 0x00022e, .specifier_id = 0x00022e, }, - /* FE-08 requires reverse-engineering because it just has faders. */ {} }; MODULE_DEVICE_TABLE(ieee1394, snd_tscm_id_table); diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index 734e5bb9c3da..15bd335fa07f 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -97,6 +97,8 @@ struct snd_tscm { struct snd_firewire_tascam_change queue[SND_TSCM_QUEUE_COUNT]; unsigned int pull_pos; unsigned int push_pos; + + struct amdtp_domain domain; }; #define TSCM_ADDR_BASE 0xffff00000000ull @@ -127,6 +129,26 @@ struct snd_tscm { #define TSCM_OFFSET_MIDI_RX_QUAD 0x4000 +// Although FE-8 supports the above registers, it has no I/O interfaces for +// audio samples and music messages. Otherwise it supports another notification +// for status and control message as well as LED brightening. The message +// consists of quadlet-aligned data up to 32 quadlets. The first byte of message +// is fixed to 0x40. The second byte is between 0x00 to 0x1f and represent each +// control: +// fader: 0x00-0x07 +// button: 0x0d, 0x0e +// knob: 0x14-0x1b +// sensing: 0x0b +// +// The rest two bytes represent state of the controls; e.g. current value for +// fader and knob, bitmasks for button and sensing. +// Just after turning on, 32 quadlets messages with 0x00-0x1f are immediately +// sent in one transaction. After, several quadlets are sent in one transaction. +// +// TSCM_OFFSET_FE8_CTL_TX_ON 0x0310 +// TSCM_OFFSET_FE8_CTL_TX_ADDR_HI 0x0314 +// TSCM_OFFSET_FE8_CTL_TX_ADDR_LO 0x0318 + enum snd_tscm_clock { SND_TSCM_CLOCK_INTERNAL = 0, SND_TSCM_CLOCK_WORD = 1, diff --git a/sound/hda/hdac_bus.c b/sound/hda/hdac_bus.c index cd25e2b3f7f2..8f19876244eb 100644 --- a/sound/hda/hdac_bus.c +++ b/sound/hda/hdac_bus.c @@ -9,8 +9,11 @@ #include <linux/module.h> #include <linux/export.h> #include <sound/hdaudio.h> +#include "local.h" #include "trace.h" +static void snd_hdac_bus_process_unsol_events(struct work_struct *work); + static const struct hdac_bus_ops default_ops = { .command = snd_hdac_bus_send_cmd, .get_response = snd_hdac_bus_get_response, @@ -148,7 +151,7 @@ EXPORT_SYMBOL_GPL(snd_hdac_bus_queue_event); /* * process queued unsolicited events */ -void snd_hdac_bus_process_unsol_events(struct work_struct *work) +static void snd_hdac_bus_process_unsol_events(struct work_struct *work) { struct hdac_bus *bus = container_of(work, struct hdac_bus, unsol_work); struct hdac_device *codec; @@ -171,7 +174,6 @@ void snd_hdac_bus_process_unsol_events(struct work_struct *work) drv->unsol_event(codec, res); } } -EXPORT_SYMBOL_GPL(snd_hdac_bus_process_unsol_events); /** * snd_hdac_bus_add_device - Add a codec to bus @@ -196,7 +198,6 @@ int snd_hdac_bus_add_device(struct hdac_bus *bus, struct hdac_device *codec) bus->num_codecs++; return 0; } -EXPORT_SYMBOL_GPL(snd_hdac_bus_add_device); /** * snd_hdac_bus_remove_device - Remove a codec from bus @@ -215,7 +216,6 @@ void snd_hdac_bus_remove_device(struct hdac_bus *bus, bus->num_codecs--; flush_work(&bus->unsol_work); } -EXPORT_SYMBOL_GPL(snd_hdac_bus_remove_device); #ifdef CONFIG_SND_HDA_ALIGNED_MMIO /* Helpers for aligned read/write of mmio space, for Tegra */ diff --git a/sound/hda/hdac_controller.c b/sound/hda/hdac_controller.c index 7e7be8e4dcf9..d3999e7b0705 100644 --- a/sound/hda/hdac_controller.c +++ b/sound/hda/hdac_controller.c @@ -447,6 +447,8 @@ static void azx_int_disable(struct hdac_bus *bus) list_for_each_entry(azx_dev, &bus->stream_list, list) snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_INT_MASK, 0); + synchronize_irq(bus->irq); + /* disable SIE for all streams */ snd_hdac_chip_writeb(bus, INTCTL, 0); diff --git a/sound/hda/hdac_device.c b/sound/hda/hdac_device.c index b26cc93e7e10..9f3e37511408 100644 --- a/sound/hda/hdac_device.c +++ b/sound/hda/hdac_device.c @@ -218,8 +218,8 @@ EXPORT_SYMBOL_GPL(snd_hdac_codec_modalias); * * Return an encoded command verb or -1 for error. */ -unsigned int snd_hdac_make_cmd(struct hdac_device *codec, hda_nid_t nid, - unsigned int verb, unsigned int parm) +static unsigned int snd_hdac_make_cmd(struct hdac_device *codec, hda_nid_t nid, + unsigned int verb, unsigned int parm) { u32 val, addr; @@ -237,7 +237,6 @@ unsigned int snd_hdac_make_cmd(struct hdac_device *codec, hda_nid_t nid, val |= parm; return val; } -EXPORT_SYMBOL_GPL(snd_hdac_make_cmd); /** * snd_hdac_exec_verb - execute an encoded verb @@ -258,7 +257,6 @@ int snd_hdac_exec_verb(struct hdac_device *codec, unsigned int cmd, return codec->exec_verb(codec, cmd, flags, res); return snd_hdac_bus_exec_verb(codec->bus, codec->addr, cmd, res); } -EXPORT_SYMBOL_GPL(snd_hdac_exec_verb); /** diff --git a/sound/hda/hdac_regmap.c b/sound/hda/hdac_regmap.c index f399a1552e73..286361ecd640 100644 --- a/sound/hda/hdac_regmap.c +++ b/sound/hda/hdac_regmap.c @@ -21,6 +21,7 @@ #include <sound/core.h> #include <sound/hdaudio.h> #include <sound/hda_regmap.h> +#include "local.h" static int codec_pm_lock(struct hdac_device *codec) { diff --git a/sound/hda/hdac_stream.c b/sound/hda/hdac_stream.c index fc68d4ce0a37..d8fe7ff0cd58 100644 --- a/sound/hda/hdac_stream.c +++ b/sound/hda/hdac_stream.c @@ -229,11 +229,7 @@ int snd_hdac_stream_setup(struct hdac_stream *azx_dev) /* set the interrupt enable bits in the descriptor control register */ snd_hdac_stream_updatel(azx_dev, SD_CTL, 0, SD_INT_MASK); - if (azx_dev->direction == SNDRV_PCM_STREAM_PLAYBACK) - azx_dev->fifo_size = - snd_hdac_stream_readw(azx_dev, SD_FIFOSIZE) + 1; - else - azx_dev->fifo_size = 0; + azx_dev->fifo_size = snd_hdac_stream_readw(azx_dev, SD_FIFOSIZE) + 1; /* when LPIB delay correction gives a small negative value, * we ignore it; currently set the threshold statically to diff --git a/sound/hda/local.h b/sound/hda/local.h index 877631e39373..5b935219352f 100644 --- a/sound/hda/local.h +++ b/sound/hda/local.h @@ -33,4 +33,11 @@ int hda_widget_sysfs_reinit(struct hdac_device *codec, hda_nid_t start_nid, int num_nodes); void hda_widget_sysfs_exit(struct hdac_device *codec); +int snd_hdac_bus_add_device(struct hdac_bus *bus, struct hdac_device *codec); +void snd_hdac_bus_remove_device(struct hdac_bus *bus, + struct hdac_device *codec); + +int snd_hdac_exec_verb(struct hdac_device *codec, unsigned int cmd, + unsigned int flags, unsigned int *res); + #endif /* __HDAC_LOCAL_H */ diff --git a/sound/i2c/other/ak4xxx-adda.c b/sound/i2c/other/ak4xxx-adda.c index 5f59316f982a..7d15093844b9 100644 --- a/sound/i2c/other/ak4xxx-adda.c +++ b/sound/i2c/other/ak4xxx-adda.c @@ -775,11 +775,12 @@ static int build_adc_controls(struct snd_akm4xxx *ak) return err; memset(&knew, 0, sizeof(knew)); - knew.name = ak->adc_info[mixer_ch].selector_name; - if (!knew.name) { + if (!ak->adc_info || + !ak->adc_info[mixer_ch].selector_name) { knew.name = "Capture Channel"; knew.index = mixer_ch + ak->idx_offset * 2; - } + } else + knew.name = ak->adc_info[mixer_ch].selector_name; knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER; knew.info = ak4xxx_capture_source_info; diff --git a/sound/isa/sb/sb_common.c b/sound/isa/sb/sb_common.c index 162338f1b68a..ff031d670400 100644 --- a/sound/isa/sb/sb_common.c +++ b/sound/isa/sb/sb_common.c @@ -80,7 +80,7 @@ int snd_sbdsp_reset(struct snd_sb *chip) static int snd_sbdsp_version(struct snd_sb * chip) { - unsigned int result = -ENODEV; + unsigned int result; snd_sbdsp_command(chip, SB_DSP_GET_VERSION); result = (short) snd_sbdsp_get_byte(chip) << 8; diff --git a/sound/isa/wavefront/wavefront_synth.c b/sound/isa/wavefront/wavefront_synth.c index aec1c46e6697..c5b1d5900eed 100644 --- a/sound/isa/wavefront/wavefront_synth.c +++ b/sound/isa/wavefront/wavefront_synth.c @@ -788,7 +788,6 @@ wavefront_send_patch (snd_wavefront_t *dev, wavefront_patch_info *header) dev->patch_status[header->number] |= WF_SLOT_FILLED; - bptr = buf; bptr = munge_int32 (header->number, buf, 2); munge_buf ((unsigned char *)&header->hdr.p, bptr, WF_PATCH_BYTES); diff --git a/sound/oss/dmasound/dmasound_atari.c b/sound/oss/dmasound/dmasound_atari.c index 83653683fd68..823ccfa089b2 100644 --- a/sound/oss/dmasound/dmasound_atari.c +++ b/sound/oss/dmasound/dmasound_atari.c @@ -1432,25 +1432,25 @@ static int FalconMixerIoctl(u_int cmd, u_long arg) { int data; switch (cmd) { - case SOUND_MIXER_READ_RECMASK: + case SOUND_MIXER_READ_RECMASK: return IOCTL_OUT(arg, SOUND_MASK_MIC); - case SOUND_MIXER_READ_DEVMASK: + case SOUND_MIXER_READ_DEVMASK: return IOCTL_OUT(arg, SOUND_MASK_VOLUME | SOUND_MASK_MIC | SOUND_MASK_SPEAKER); - case SOUND_MIXER_READ_STEREODEVS: + case SOUND_MIXER_READ_STEREODEVS: return IOCTL_OUT(arg, SOUND_MASK_VOLUME | SOUND_MASK_MIC); - case SOUND_MIXER_READ_VOLUME: + case SOUND_MIXER_READ_VOLUME: return IOCTL_OUT(arg, VOLUME_ATT_TO_VOXWARE(dmasound.volume_left) | VOLUME_ATT_TO_VOXWARE(dmasound.volume_right) << 8); - case SOUND_MIXER_READ_CAPS: + case SOUND_MIXER_READ_CAPS: return IOCTL_OUT(arg, SOUND_CAP_EXCL_INPUT); - case SOUND_MIXER_WRITE_MIC: + case SOUND_MIXER_WRITE_MIC: IOCTL_IN(arg, data); tt_dmasnd.input_gain = RECLEVEL_VOXWARE_TO_GAIN(data & 0xff) << 4 | RECLEVEL_VOXWARE_TO_GAIN(data >> 8 & 0xff); - /* fall thru, return set value */ - case SOUND_MIXER_READ_MIC: + /* fall through - return set value */ + case SOUND_MIXER_READ_MIC: return IOCTL_OUT(arg, RECLEVEL_GAIN_TO_VOXWARE(tt_dmasnd.input_gain >> 4 & 0xf) | RECLEVEL_GAIN_TO_VOXWARE(tt_dmasnd.input_gain & 0xf) << 8); diff --git a/sound/pci/ac97/ac97_codec.c b/sound/pci/ac97/ac97_codec.c index 96b4601aae73..66f6c3bf08e3 100644 --- a/sound/pci/ac97/ac97_codec.c +++ b/sound/pci/ac97/ac97_codec.c @@ -596,11 +596,6 @@ static int snd_ac97_put_volsw(struct snd_kcontrol *kcontrol, return err; } -static const struct snd_kcontrol_new snd_ac97_controls_master_mono[2] = { -AC97_SINGLE("Master Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1), -AC97_SINGLE("Master Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1) -}; - static const struct snd_kcontrol_new snd_ac97_controls_tone[2] = { AC97_SINGLE("Tone Control - Bass", AC97_MASTER_TONE, 8, 15, 1), AC97_SINGLE("Tone Control - Treble", AC97_MASTER_TONE, 0, 15, 1) diff --git a/sound/pci/echoaudio/echoaudio.c b/sound/pci/echoaudio/echoaudio.c index b612a536a5a1..ca9125726be2 100644 --- a/sound/pci/echoaudio/echoaudio.c +++ b/sound/pci/echoaudio/echoaudio.c @@ -2189,11 +2189,10 @@ static int snd_echo_resume(struct device *dev) u32 pipe_alloc_mask; int err; - commpage_bak = kmalloc(sizeof(*commpage), GFP_KERNEL); + commpage = chip->comm_page; + commpage_bak = kmemdup(commpage, sizeof(*commpage), GFP_KERNEL); if (commpage_bak == NULL) return -ENOMEM; - commpage = chip->comm_page; - memcpy(commpage_bak, commpage, sizeof(*commpage)); err = init_hw(chip, chip->pci->device, chip->pci->subsystem_device); if (err < 0) { diff --git a/sound/pci/hda/hda_auto_parser.c b/sound/pci/hda/hda_auto_parser.c index 18e6546b4467..2c6d2becfe1a 100644 --- a/sound/pci/hda/hda_auto_parser.c +++ b/sound/pci/hda/hda_auto_parser.c @@ -884,7 +884,8 @@ EXPORT_SYMBOL_GPL(snd_hda_apply_fixup); #define IGNORE_SEQ_ASSOC (~(AC_DEFCFG_SEQUENCE | AC_DEFCFG_DEF_ASSOC)) static bool pin_config_match(struct hda_codec *codec, - const struct hda_pintbl *pins) + const struct hda_pintbl *pins, + bool match_all_pins) { const struct hda_pincfg *pin; int i; @@ -908,7 +909,8 @@ static bool pin_config_match(struct hda_codec *codec, return false; } } - if (!found && (cfg & 0xf0000000) != 0x40000000) + if (match_all_pins && + !found && (cfg & 0xf0000000) != 0x40000000) return false; } @@ -920,10 +922,12 @@ static bool pin_config_match(struct hda_codec *codec, * @codec: the HDA codec * @pin_quirk: zero-terminated pin quirk list * @fixlist: the fixup list + * @match_all_pins: all valid pins must match with the table entries */ void snd_hda_pick_pin_fixup(struct hda_codec *codec, const struct snd_hda_pin_quirk *pin_quirk, - const struct hda_fixup *fixlist) + const struct hda_fixup *fixlist, + bool match_all_pins) { const struct snd_hda_pin_quirk *pq; @@ -935,7 +939,7 @@ void snd_hda_pick_pin_fixup(struct hda_codec *codec, continue; if (codec->core.vendor_id != pq->codec) continue; - if (pin_config_match(codec, pq->pins)) { + if (pin_config_match(codec, pq->pins, match_all_pins)) { codec->fixup_id = pq->value; #ifdef CONFIG_SND_DEBUG_VERBOSE codec->fixup_name = pq->name; diff --git a/sound/pci/hda/hda_controller.c b/sound/pci/hda/hda_controller.c index 97a43a28b9e4..6387c7e90918 100644 --- a/sound/pci/hda/hda_controller.c +++ b/sound/pci/hda/hda_controller.c @@ -794,6 +794,7 @@ static int azx_rirb_get_response(struct hdac_bus *bus, unsigned int addr, unsigned long timeout; unsigned long loopcounter; int do_poll = 0; + bool warned = false; again: timeout = jiffies + msecs_to_jiffies(1000); @@ -813,9 +814,17 @@ static int azx_rirb_get_response(struct hdac_bus *bus, unsigned int addr, spin_unlock_irq(&bus->reg_lock); if (time_after(jiffies, timeout)) break; - if (hbus->needs_damn_long_delay || loopcounter > 3000) +#define LOOP_COUNT_MAX 3000 + if (hbus->needs_damn_long_delay || + loopcounter > LOOP_COUNT_MAX) { + if (loopcounter > LOOP_COUNT_MAX && !warned) { + dev_dbg_ratelimited(chip->card->dev, + "too slow response, last cmd=%#08x\n", + bus->last_cmd[addr]); + warned = true; + } msleep(2); /* temporary workaround */ - else { + } else { udelay(10); cond_resched(); } @@ -869,10 +878,13 @@ static int azx_rirb_get_response(struct hdac_bus *bus, unsigned int addr, */ if (hbus->allow_bus_reset && !hbus->response_reset && !hbus->in_reset) { hbus->response_reset = 1; + dev_err(chip->card->dev, + "No response from codec, resetting bus: last cmd=0x%08x\n", + bus->last_cmd[addr]); return -EAGAIN; /* give a chance to retry */ } - dev_err(chip->card->dev, + dev_WARN(chip->card->dev, "azx_get_response timeout, switching to single_cmd mode: last cmd=0x%08x\n", bus->last_cmd[addr]); chip->single_cmd = 1; diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 2d0db3c9f335..91e71be42fa4 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -85,8 +85,6 @@ enum { #define INTEL_SCH_HDA_DEVC 0x78 #define INTEL_SCH_HDA_DEVC_NOSNOOP (0x1<<11) -/* Define IN stream 0 FIFO size offset in VIA controller */ -#define VIA_IN_STREAM0_FIFO_SIZE_OFFSET 0x90 /* Define VIA HD Audio Device ID*/ #define VIA_HDAC_DEVICE_ID 0x3288 @@ -271,6 +269,7 @@ enum { AZX_DRIVER_CTX, AZX_DRIVER_CTHDA, AZX_DRIVER_CMEDIA, + AZX_DRIVER_ZHAOXIN, AZX_DRIVER_GENERIC, AZX_NUM_DRIVERS, /* keep this as last entry */ }; @@ -357,7 +356,7 @@ enum { */ #ifdef SUPPORT_VGA_SWITCHEROO #define use_vga_switcheroo(chip) ((chip)->use_vga_switcheroo) -#define needs_eld_notify_link(chip) ((chip)->need_eld_notify_link) +#define needs_eld_notify_link(chip) ((chip)->bus.keep_power) #else #define use_vga_switcheroo(chip) 0 #define needs_eld_notify_link(chip) false @@ -389,6 +388,7 @@ static char *driver_short_names[] = { [AZX_DRIVER_CTX] = "HDA Creative", [AZX_DRIVER_CTHDA] = "HDA Creative", [AZX_DRIVER_CMEDIA] = "HDA C-Media", + [AZX_DRIVER_ZHAOXIN] = "HDA Zhaoxin", [AZX_DRIVER_GENERIC] = "HD-Audio Generic", }; @@ -815,11 +815,7 @@ static unsigned int azx_via_get_position(struct azx *chip, mod_dma_pos = le32_to_cpu(*azx_dev->core.posbuf); mod_dma_pos %= azx_dev->core.period_bytes; - /* azx_dev->fifo_size can't get FIFO size of in stream. - * Get from base address + offset. - */ - fifo_size = readw(azx_bus(chip)->remap_addr + - VIA_IN_STREAM0_FIFO_SIZE_OFFSET); + fifo_size = azx_stream(azx_dev)->fifo_size - 1; if (azx_dev->insufficient) { /* Link position never gather than FIFO size */ @@ -1149,7 +1145,7 @@ static int azx_runtime_idle(struct device *dev) return -EBUSY; /* ELD notification gets broken when HD-audio bus is off */ - if (needs_eld_notify_link(hda)) + if (needs_eld_notify_link(chip)) return -EBUSY; return 0; @@ -1260,7 +1256,7 @@ static void setup_vga_switcheroo_runtime_pm(struct azx *chip) struct hda_intel *hda = container_of(chip, struct hda_intel, chip); struct hda_codec *codec; - if (hda->use_vga_switcheroo && !hda->need_eld_notify_link) { + if (hda->use_vga_switcheroo && !needs_eld_notify_link(chip)) { list_for_each_codec(codec, &chip->bus) codec->auto_runtime_pm = 1; /* reset the power save setup */ @@ -1274,10 +1270,9 @@ static void azx_vs_gpu_bound(struct pci_dev *pci, { struct snd_card *card = pci_get_drvdata(pci); struct azx *chip = card->private_data; - struct hda_intel *hda = container_of(chip, struct hda_intel, chip); if (client_id == VGA_SWITCHEROO_DIS) - hda->need_eld_notify_link = 0; + chip->bus.keep_power = 0; setup_vga_switcheroo_runtime_pm(chip); } @@ -1289,7 +1284,7 @@ static void init_vga_switcheroo(struct azx *chip) dev_info(chip->card->dev, "Handle vga_switcheroo audio client\n"); hda->use_vga_switcheroo = 1; - hda->need_eld_notify_link = 1; /* cleared in gpu_bound op */ + chip->bus.keep_power = 1; /* cleared in either gpu_bound op or codec probe */ chip->driver_caps |= AZX_DCAPS_PM_RUNTIME; pci_dev_put(p); } @@ -1353,9 +1348,9 @@ static int azx_free(struct azx *chip) } if (bus->chip_init) { + azx_stop_chip(chip); azx_clear_irq_pending(chip); azx_stop_all_streams(chip); - azx_stop_chip(chip); } if (bus->irq >= 0) @@ -2626,6 +2621,8 @@ static const struct pci_device_id azx_ids[] = { .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8, .class_mask = 0xffffff, .driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_PRESET_ATI_HDMI }, + /* Zhaoxin */ + { PCI_DEVICE(0x1d17, 0x3288), .driver_data = AZX_DRIVER_ZHAOXIN }, { 0, } }; MODULE_DEVICE_TABLE(pci, azx_ids); diff --git a/sound/pci/hda/hda_intel.h b/sound/pci/hda/hda_intel.h index 1468865e0342..2acfff3da1a0 100644 --- a/sound/pci/hda/hda_intel.h +++ b/sound/pci/hda/hda_intel.h @@ -25,7 +25,6 @@ struct hda_intel { /* vga_switcheroo setup */ unsigned int use_vga_switcheroo:1; - unsigned int need_eld_notify_link:1; unsigned int vga_switcheroo_registered:1; unsigned int init_failed:1; /* delayed init failed */ diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h index 349a8312d06a..3942e1b528d8 100644 --- a/sound/pci/hda/hda_local.h +++ b/sound/pci/hda/hda_local.h @@ -361,7 +361,8 @@ void snd_hda_pick_fixup(struct hda_codec *codec, const struct hda_fixup *fixlist); void snd_hda_pick_pin_fixup(struct hda_codec *codec, const struct snd_hda_pin_quirk *pin_quirk, - const struct hda_fixup *fixlist); + const struct hda_fixup *fixlist, + bool match_all_pins); /* helper macros to retrieve pin default-config values */ #define get_defcfg_connect(cfg) \ diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index bea7b0961080..bca5de78e9ad 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -18,6 +18,7 @@ #include <linux/init.h> #include <linux/delay.h> +#include <linux/pci.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/pm_runtime.h> @@ -119,6 +120,7 @@ struct hdmi_pcm { }; struct hdmi_spec { + struct hda_codec *codec; int num_cvts; struct snd_array cvts; /* struct hdmi_spec_per_cvt */ hda_nid_t cvt_nids[4]; /* only for haswell fix */ @@ -163,9 +165,11 @@ struct hdmi_spec { struct hda_multi_out multiout; struct hda_pcm_stream pcm_playback; - /* i915/powerwell (Haswell+/Valleyview+) specific */ - bool use_acomp_notifier; /* use i915 eld_notify callback for hotplug */ + bool use_jack_detect; /* jack detection enabled */ + bool use_acomp_notifier; /* use eld_notify callback for hotplug */ + bool acomp_registered; /* audio component registered in this driver */ struct drm_audio_component_audio_ops drm_audio_ops; + int (*port2pin)(struct hda_codec *, int); /* reverse port/pin mapping */ struct hdac_chmap chmap; hda_nid_t vendor_nid; @@ -765,6 +769,10 @@ static void check_presence_and_report(struct hda_codec *codec, hda_nid_t nid, static void jack_callback(struct hda_codec *codec, struct hda_jack_callback *jack) { + /* stop polling when notification is enabled */ + if (codec_has_acomp(codec)) + return; + /* hda_jack don't support DP MST */ check_presence_and_report(codec, jack->nid, 0); } @@ -823,6 +831,9 @@ static void hdmi_unsol_event(struct hda_codec *codec, unsigned int res) int tag = res >> AC_UNSOL_RES_TAG_SHIFT; int subtag = (res & AC_UNSOL_RES_SUBTAG) >> AC_UNSOL_RES_SUBTAG_SHIFT; + if (codec_has_acomp(codec)) + return; + if (!snd_hda_jack_tbl_get_from_tag(codec, tag)) { codec_dbg(codec, "Unexpected HDMI event tag 0x%x\n", tag); return; @@ -1421,7 +1432,7 @@ static void hdmi_pcm_reset_pin(struct hdmi_spec *spec, /* update per_pin ELD from the given new ELD; * setup info frame and notification accordingly */ -static void update_eld(struct hda_codec *codec, +static bool update_eld(struct hda_codec *codec, struct hdmi_spec_per_pin *per_pin, struct hdmi_eld *eld) { @@ -1429,7 +1440,7 @@ static void update_eld(struct hda_codec *codec, struct hdmi_spec *spec = codec->spec; bool old_eld_valid = pin_eld->eld_valid; bool eld_changed; - int pcm_idx = -1; + int pcm_idx; /* for monitor disconnection, save pcm_idx firstly */ pcm_idx = per_pin->pcm_idx; @@ -1452,18 +1463,22 @@ static void update_eld(struct hda_codec *codec, snd_hdmi_show_eld(codec, &eld->info); eld_changed = (pin_eld->eld_valid != eld->eld_valid); - if (eld->eld_valid && pin_eld->eld_valid) + eld_changed |= (pin_eld->monitor_present != eld->monitor_present); + if (!eld_changed && eld->eld_valid && pin_eld->eld_valid) if (pin_eld->eld_size != eld->eld_size || memcmp(pin_eld->eld_buffer, eld->eld_buffer, eld->eld_size) != 0) eld_changed = true; - pin_eld->monitor_present = eld->monitor_present; - pin_eld->eld_valid = eld->eld_valid; - pin_eld->eld_size = eld->eld_size; - if (eld->eld_valid) - memcpy(pin_eld->eld_buffer, eld->eld_buffer, eld->eld_size); - pin_eld->info = eld->info; + if (eld_changed) { + pin_eld->monitor_present = eld->monitor_present; + pin_eld->eld_valid = eld->eld_valid; + pin_eld->eld_size = eld->eld_size; + if (eld->eld_valid) + memcpy(pin_eld->eld_buffer, eld->eld_buffer, + eld->eld_size); + pin_eld->info = eld->info; + } /* * Re-setup pin and infoframe. This is needed e.g. when @@ -1481,6 +1496,7 @@ static void update_eld(struct hda_codec *codec, SNDRV_CTL_EVENT_MASK_VALUE | SNDRV_CTL_EVENT_MASK_INFO, &get_hdmi_pcm(spec, pcm_idx)->eld_ctl->id); + return eld_changed; } /* update ELD and jack state via HD-audio verbs */ @@ -1582,6 +1598,7 @@ static void sync_eld_via_acomp(struct hda_codec *codec, struct hdmi_spec *spec = codec->spec; struct hdmi_eld *eld = &spec->temp_eld; struct snd_jack *jack = NULL; + bool changed; int size; mutex_lock(&per_pin->lock); @@ -1608,15 +1625,13 @@ static void sync_eld_via_acomp(struct hda_codec *codec, * disconnected event. Jack must be fetched before update_eld() */ jack = pin_idx_to_jack(codec, per_pin); - update_eld(codec, per_pin, eld); + changed = update_eld(codec, per_pin, eld); if (jack == NULL) jack = pin_idx_to_jack(codec, per_pin); - if (jack == NULL) - goto unlock; - snd_jack_report(jack, - (eld->monitor_present && eld->eld_valid) ? + if (changed && jack) + snd_jack_report(jack, + (eld->monitor_present && eld->eld_valid) ? SND_JACK_AVOUT : 0); - unlock: mutex_unlock(&per_pin->lock); } @@ -1632,18 +1647,13 @@ static bool hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll) snd_hda_power_down_pm(codec); return false; } - } - - if (codec_has_acomp(codec)) { + ret = hdmi_present_sense_via_verbs(per_pin, repoll); + snd_hda_power_down_pm(codec); + } else { sync_eld_via_acomp(codec, per_pin); ret = false; /* don't call snd_hda_jack_report_sync() */ - } else { - ret = hdmi_present_sense_via_verbs(per_pin, repoll); } - if (!codec_has_acomp(codec)) - snd_hda_power_down_pm(codec); - return ret; } @@ -2248,6 +2258,8 @@ static int generic_hdmi_init(struct hda_codec *codec) struct hdmi_spec *spec = codec->spec; int pin_idx; + mutex_lock(&spec->pcm_lock); + spec->use_jack_detect = !codec->jackpoll_interval; for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx); hda_nid_t pin_nid = per_pin->pin_nid; @@ -2255,11 +2267,15 @@ static int generic_hdmi_init(struct hda_codec *codec) snd_hda_set_dev_select(codec, pin_nid, dev_id); hdmi_init_pin(codec, pin_nid); - if (!codec_has_acomp(codec)) + if (codec_has_acomp(codec)) + continue; + if (spec->use_jack_detect) + snd_hda_jack_detect_enable(codec, pin_nid); + else snd_hda_jack_detect_enable_callback(codec, pin_nid, - codec->jackpoll_interval > 0 ? - jack_callback : NULL); + jack_callback); } + mutex_unlock(&spec->pcm_lock); return 0; } @@ -2292,7 +2308,9 @@ static void generic_hdmi_free(struct hda_codec *codec) struct hdmi_spec *spec = codec->spec; int pin_idx, pcm_idx; - if (codec_has_acomp(codec)) { + if (spec->acomp_registered) { + snd_hdac_acomp_exit(&codec->bus->core); + } else if (codec_has_acomp(codec)) { snd_hdac_acomp_register_notifier(&codec->bus->core, NULL); codec->relaxed_resume = 0; } @@ -2360,6 +2378,7 @@ static int alloc_generic_hdmi(struct hda_codec *codec) if (!spec) return -ENOMEM; + spec->codec = codec; spec->ops = generic_standard_hdmi_ops; spec->dev_num = 1; /* initialize to 1 */ mutex_init(&spec->pcm_lock); @@ -2398,6 +2417,138 @@ static int patch_generic_hdmi(struct hda_codec *codec) } /* + * generic audio component binding + */ + +/* turn on / off the unsol event jack detection dynamically */ +static void reprogram_jack_detect(struct hda_codec *codec, hda_nid_t nid, + bool use_acomp) +{ + struct hda_jack_tbl *tbl; + + tbl = snd_hda_jack_tbl_get(codec, nid); + if (tbl) { + /* clear unsol even if component notifier is used, or re-enable + * if notifier is cleared + */ + unsigned int val = use_acomp ? 0 : (AC_USRSP_EN | tbl->tag); + snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_UNSOLICITED_ENABLE, val); + } else { + /* if no jack entry was defined beforehand, create a new one + * at need (i.e. only when notifier is cleared) + */ + if (!use_acomp) + snd_hda_jack_detect_enable(codec, nid); + } +} + +/* set up / clear component notifier dynamically */ +static void generic_acomp_notifier_set(struct drm_audio_component *acomp, + bool use_acomp) +{ + struct hdmi_spec *spec; + int i; + + spec = container_of(acomp->audio_ops, struct hdmi_spec, drm_audio_ops); + mutex_lock(&spec->pcm_lock); + spec->use_acomp_notifier = use_acomp; + spec->codec->relaxed_resume = use_acomp; + /* reprogram each jack detection logic depending on the notifier */ + if (spec->use_jack_detect) { + for (i = 0; i < spec->num_pins; i++) + reprogram_jack_detect(spec->codec, + get_pin(spec, i)->pin_nid, + use_acomp); + } + mutex_unlock(&spec->pcm_lock); +} + +/* enable / disable the notifier via master bind / unbind */ +static int generic_acomp_master_bind(struct device *dev, + struct drm_audio_component *acomp) +{ + generic_acomp_notifier_set(acomp, true); + return 0; +} + +static void generic_acomp_master_unbind(struct device *dev, + struct drm_audio_component *acomp) +{ + generic_acomp_notifier_set(acomp, false); +} + +/* check whether both HD-audio and DRM PCI devices belong to the same bus */ +static int match_bound_vga(struct device *dev, int subtype, void *data) +{ + struct hdac_bus *bus = data; + struct pci_dev *pci, *master; + + if (!dev_is_pci(dev) || !dev_is_pci(bus->dev)) + return 0; + master = to_pci_dev(bus->dev); + pci = to_pci_dev(dev); + return master->bus == pci->bus; +} + +/* audio component notifier for AMD/Nvidia HDMI codecs */ +static void generic_acomp_pin_eld_notify(void *audio_ptr, int port, int dev_id) +{ + struct hda_codec *codec = audio_ptr; + struct hdmi_spec *spec = codec->spec; + hda_nid_t pin_nid = spec->port2pin(codec, port); + + if (!pin_nid) + return; + if (get_wcaps_type(get_wcaps(codec, pin_nid)) != AC_WID_PIN) + return; + /* skip notification during system suspend (but not in runtime PM); + * the state will be updated at resume + */ + if (snd_power_get_state(codec->card) != SNDRV_CTL_POWER_D0) + return; + /* ditto during suspend/resume process itself */ + if (snd_hdac_is_in_pm(&codec->core)) + return; + + check_presence_and_report(codec, pin_nid, dev_id); +} + +/* set up the private drm_audio_ops from the template */ +static void setup_drm_audio_ops(struct hda_codec *codec, + const struct drm_audio_component_audio_ops *ops) +{ + struct hdmi_spec *spec = codec->spec; + + spec->drm_audio_ops.audio_ptr = codec; + /* intel_audio_codec_enable() or intel_audio_codec_disable() + * will call pin_eld_notify with using audio_ptr pointer + * We need make sure audio_ptr is really setup + */ + wmb(); + spec->drm_audio_ops.pin2port = ops->pin2port; + spec->drm_audio_ops.pin_eld_notify = ops->pin_eld_notify; + spec->drm_audio_ops.master_bind = ops->master_bind; + spec->drm_audio_ops.master_unbind = ops->master_unbind; +} + +/* initialize the generic HDMI audio component */ +static void generic_acomp_init(struct hda_codec *codec, + const struct drm_audio_component_audio_ops *ops, + int (*port2pin)(struct hda_codec *, int)) +{ + struct hdmi_spec *spec = codec->spec; + + spec->port2pin = port2pin; + setup_drm_audio_ops(codec, ops); + if (!snd_hdac_acomp_init(&codec->bus->core, &spec->drm_audio_ops, + match_bound_vga, 0)) { + spec->acomp_registered = true; + codec->bus->keep_power = 0; + } +} + +/* * Intel codec parsers and helpers */ @@ -2565,20 +2716,19 @@ static void intel_pin_eld_notify(void *audio_ptr, int port, int pipe) check_presence_and_report(codec, pin_nid, dev_id); } +static const struct drm_audio_component_audio_ops intel_audio_ops = { + .pin2port = intel_pin2port, + .pin_eld_notify = intel_pin_eld_notify, +}; + /* register i915 component pin_eld_notify callback */ static void register_i915_notifier(struct hda_codec *codec) { struct hdmi_spec *spec = codec->spec; spec->use_acomp_notifier = true; - spec->drm_audio_ops.audio_ptr = codec; - /* intel_audio_codec_enable() or intel_audio_codec_disable() - * will call pin_eld_notify with using audio_ptr pointer - * We need make sure audio_ptr is really setup - */ - wmb(); - spec->drm_audio_ops.pin2port = intel_pin2port; - spec->drm_audio_ops.pin_eld_notify = intel_pin_eld_notify; + spec->port2pin = intel_port2pin; + setup_drm_audio_ops(codec, &intel_audio_ops); snd_hdac_acomp_register_notifier(&codec->bus->core, &spec->drm_audio_ops); /* no need for forcible resume for jack check thanks to notifier */ @@ -2612,6 +2762,8 @@ static void i915_pin_cvt_fixup(struct hda_codec *codec, /* precondition and allocation for Intel codecs */ static int alloc_intel_hdmi(struct hda_codec *codec) { + int err; + /* requires i915 binding */ if (!codec->bus->core.audio_component) { codec_info(codec, "No i915 binding for Intel HDMI/DP codec\n"); @@ -2620,7 +2772,12 @@ static int alloc_intel_hdmi(struct hda_codec *codec) return -ENODEV; } - return alloc_generic_hdmi(codec); + err = alloc_generic_hdmi(codec); + if (err < 0) + return err; + /* no need to handle unsol events */ + codec->patch_ops.unsol_event = NULL; + return 0; } /* parse and post-process for Intel codecs */ @@ -2976,6 +3133,7 @@ static int patch_simple_hdmi(struct hda_codec *codec, if (!spec) return -ENOMEM; + spec->codec = codec; codec->spec = spec; hdmi_array_init(spec, 1); @@ -3280,6 +3438,26 @@ static int nvhdmi_chmap_validate(struct hdac_chmap *chmap, return 0; } +/* map from pin NID to port; port is 0-based */ +/* for Nvidia: assume widget NID starting from 4, with step 1 (4, 5, 6, ...) */ +static int nvhdmi_pin2port(void *audio_ptr, int pin_nid) +{ + return pin_nid - 4; +} + +/* reverse-map from port to pin NID: see above */ +static int nvhdmi_port2pin(struct hda_codec *codec, int port) +{ + return port + 4; +} + +static const struct drm_audio_component_audio_ops nvhdmi_audio_ops = { + .pin2port = nvhdmi_pin2port, + .pin_eld_notify = generic_acomp_pin_eld_notify, + .master_bind = generic_acomp_master_bind, + .master_unbind = generic_acomp_master_unbind, +}; + static int patch_nvhdmi(struct hda_codec *codec) { struct hdmi_spec *spec; @@ -3296,6 +3474,8 @@ static int patch_nvhdmi(struct hda_codec *codec) nvhdmi_chmap_cea_alloc_validate_get_type; spec->chmap.ops.chmap_validate = nvhdmi_chmap_validate; + generic_acomp_init(codec, &nvhdmi_audio_ops, nvhdmi_port2pin); + return 0; } @@ -3783,6 +3963,26 @@ static int atihdmi_init(struct hda_codec *codec) return 0; } +/* map from pin NID to port; port is 0-based */ +/* for AMD: assume widget NID starting from 3, with step 2 (3, 5, 7, ...) */ +static int atihdmi_pin2port(void *audio_ptr, int pin_nid) +{ + return pin_nid / 2 - 1; +} + +/* reverse-map from port to pin NID: see above */ +static int atihdmi_port2pin(struct hda_codec *codec, int port) +{ + return port * 2 + 3; +} + +static const struct drm_audio_component_audio_ops atihdmi_audio_ops = { + .pin2port = atihdmi_pin2port, + .pin_eld_notify = generic_acomp_pin_eld_notify, + .master_bind = generic_acomp_master_bind, + .master_unbind = generic_acomp_master_unbind, +}; + static int patch_atihdmi(struct hda_codec *codec) { struct hdmi_spec *spec; @@ -3831,6 +4031,8 @@ static int patch_atihdmi(struct hda_codec *codec) */ codec->link_down_at_suspend = 1; + generic_acomp_init(codec, &atihdmi_audio_ops, atihdmi_port2pin); + return 0; } diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index c1ddfd2fac52..da1695418731 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -1058,6 +1058,9 @@ static const struct snd_pci_quirk beep_white_list[] = { SND_PCI_QUIRK(0x1043, 0x834a, "EeePC", 1), SND_PCI_QUIRK(0x1458, 0xa002, "GA-MA790X", 1), SND_PCI_QUIRK(0x8086, 0xd613, "Intel", 1), + /* blacklist -- no beep available */ + SND_PCI_QUIRK(0x17aa, 0x309e, "Lenovo ThinkCentre M73", 0), + SND_PCI_QUIRK(0x17aa, 0x30a3, "Lenovo ThinkCentre M93", 0), {} }; @@ -2841,7 +2844,8 @@ static int patch_alc268(struct hda_codec *codec) return err; spec = codec->spec; - spec->gen.beep_nid = 0x01; + if (has_cdefine_beep(codec)) + spec->gen.beep_nid = 0x01; spec->shutup = alc_eapd_shutup; @@ -3755,6 +3759,72 @@ static void alc269_x101_hp_automute_hook(struct hda_codec *codec, vref); } +/* + * Magic sequence to make Huawei Matebook X right speaker working (bko#197801) + */ +struct hda_alc298_mbxinit { + unsigned char value_0x23; + unsigned char value_0x25; +}; + +static void alc298_huawei_mbx_stereo_seq(struct hda_codec *codec, + const struct hda_alc298_mbxinit *initval, + bool first) +{ + snd_hda_codec_write(codec, 0x06, 0, AC_VERB_SET_DIGI_CONVERT_3, 0x0); + alc_write_coef_idx(codec, 0x26, 0xb000); + + if (first) + snd_hda_codec_write(codec, 0x21, 0, AC_VERB_GET_PIN_SENSE, 0x0); + + snd_hda_codec_write(codec, 0x6, 0, AC_VERB_SET_DIGI_CONVERT_3, 0x80); + alc_write_coef_idx(codec, 0x26, 0xf000); + alc_write_coef_idx(codec, 0x23, initval->value_0x23); + + if (initval->value_0x23 != 0x1e) + alc_write_coef_idx(codec, 0x25, initval->value_0x25); + + snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_COEF_INDEX, 0x26); + snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, 0xb010); +} + +static void alc298_fixup_huawei_mbx_stereo(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + /* Initialization magic */ + static const struct hda_alc298_mbxinit dac_init[] = { + {0x0c, 0x00}, {0x0d, 0x00}, {0x0e, 0x00}, {0x0f, 0x00}, + {0x10, 0x00}, {0x1a, 0x40}, {0x1b, 0x82}, {0x1c, 0x00}, + {0x1d, 0x00}, {0x1e, 0x00}, {0x1f, 0x00}, + {0x20, 0xc2}, {0x21, 0xc8}, {0x22, 0x26}, {0x23, 0x24}, + {0x27, 0xff}, {0x28, 0xff}, {0x29, 0xff}, {0x2a, 0x8f}, + {0x2b, 0x02}, {0x2c, 0x48}, {0x2d, 0x34}, {0x2e, 0x00}, + {0x2f, 0x00}, + {0x30, 0x00}, {0x31, 0x00}, {0x32, 0x00}, {0x33, 0x00}, + {0x34, 0x00}, {0x35, 0x01}, {0x36, 0x93}, {0x37, 0x0c}, + {0x38, 0x00}, {0x39, 0x00}, {0x3a, 0xf8}, {0x38, 0x80}, + {} + }; + const struct hda_alc298_mbxinit *seq; + + if (action != HDA_FIXUP_ACT_INIT) + return; + + /* Start */ + snd_hda_codec_write(codec, 0x06, 0, AC_VERB_SET_DIGI_CONVERT_3, 0x00); + snd_hda_codec_write(codec, 0x06, 0, AC_VERB_SET_DIGI_CONVERT_3, 0x80); + alc_write_coef_idx(codec, 0x26, 0xf000); + alc_write_coef_idx(codec, 0x22, 0x31); + alc_write_coef_idx(codec, 0x23, 0x0b); + alc_write_coef_idx(codec, 0x25, 0x00); + snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_COEF_INDEX, 0x26); + snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, 0xb010); + + for (seq = dac_init; seq->value_0x23; seq++) + alc298_huawei_mbx_stereo_seq(codec, seq, seq == dac_init); +} + static void alc269_fixup_x101_headset_mic(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -5780,6 +5850,7 @@ enum { ALC255_FIXUP_DUMMY_LINEOUT_VERB, ALC255_FIXUP_DELL_HEADSET_MIC, ALC256_FIXUP_HUAWEI_MACH_WX9_PINS, + ALC298_FIXUP_HUAWEI_MBX_STEREO, ALC295_FIXUP_HP_X360, ALC221_FIXUP_HP_HEADSET_MIC, ALC285_FIXUP_LENOVO_HEADPHONE_NOISE, @@ -6089,6 +6160,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC255_FIXUP_MIC_MUTE_LED }, + [ALC298_FIXUP_HUAWEI_MBX_STEREO] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc298_fixup_huawei_mbx_stereo, + .chained = true, + .chain_id = ALC255_FIXUP_MIC_MUTE_LED + }, [ALC269_FIXUP_ASUS_X101_FUNC] = { .type = HDA_FIXUP_FUNC, .v.func = alc269_fixup_x101_headset_mic, @@ -7280,6 +7357,7 @@ static const struct hda_model_fixup alc269_fixup_models[] = { {.id = ALC225_FIXUP_HEADSET_JACK, .name = "alc-headset-jack"}, {.id = ALC295_FIXUP_CHROME_BOOK, .name = "alc-chrome-book"}, {.id = ALC299_FIXUP_PREDATOR_SPK, .name = "predator-spk"}, + {.id = ALC298_FIXUP_HUAWEI_MBX_STEREO, .name = "huawei-mbx-stereo"}, {} }; #define ALC225_STANDARD_PINS \ @@ -7590,10 +7668,6 @@ static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = { {0x12, 0x90a60120}, {0x14, 0x90170110}, {0x21, 0x0321101f}), - SND_HDA_PIN_QUIRK(0x10ec0289, 0x1028, "Dell", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE, - {0x12, 0xb7a60130}, - {0x14, 0x90170110}, - {0x21, 0x04211020}), SND_HDA_PIN_QUIRK(0x10ec0290, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1, ALC290_STANDARD_PINS, {0x15, 0x04211040}, @@ -7703,6 +7777,19 @@ static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = { {} }; +/* This is the fallback pin_fixup_tbl for alc269 family, to make the tbl match + * more machines, don't need to match all valid pins, just need to match + * all the pins defined in the tbl. Just because of this reason, it is possible + * that a single machine matches multiple tbls, so there is one limitation: + * at most one tbl is allowed to define for the same vendor and same codec + */ +static const struct snd_hda_pin_quirk alc269_fallback_pin_fixup_tbl[] = { + SND_HDA_PIN_QUIRK(0x10ec0289, 0x1028, "Dell", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE, + {0x19, 0x40000000}, + {0x1b, 0x40000000}), + {} +}; + static void alc269_fill_coef(struct hda_codec *codec) { struct alc_spec *spec = codec->spec; @@ -7892,7 +7979,8 @@ static int patch_alc269(struct hda_codec *codec) snd_hda_pick_fixup(codec, alc269_fixup_models, alc269_fixup_tbl, alc269_fixups); - snd_hda_pick_pin_fixup(codec, alc269_pin_fixup_tbl, alc269_fixups); + snd_hda_pick_pin_fixup(codec, alc269_pin_fixup_tbl, alc269_fixups, true); + snd_hda_pick_pin_fixup(codec, alc269_fallback_pin_fixup_tbl, alc269_fixups, false); snd_hda_pick_fixup(codec, NULL, alc269_fixup_vendor_tbl, alc269_fixups); snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); @@ -8026,7 +8114,8 @@ static int patch_alc861(struct hda_codec *codec) return err; spec = codec->spec; - spec->gen.beep_nid = 0x23; + if (has_cdefine_beep(codec)) + spec->gen.beep_nid = 0x23; #ifdef CONFIG_PM spec->power_hook = alc_power_eapd; @@ -8127,7 +8216,8 @@ static int patch_alc861vd(struct hda_codec *codec) return err; spec = codec->spec; - spec->gen.beep_nid = 0x23; + if (has_cdefine_beep(codec)) + spec->gen.beep_nid = 0x23; spec->shutup = alc_eapd_shutup; @@ -8267,6 +8357,45 @@ static void alc662_fixup_usi_headset_mic(struct hda_codec *codec, } } +static void alc662_aspire_ethos_mute_speakers(struct hda_codec *codec, + struct hda_jack_callback *cb) +{ + /* surround speakers at 0x1b already get muted automatically when + * headphones are plugged in, but we have to mute/unmute the remaining + * channels manually: + * 0x15 - front left/front right + * 0x18 - front center/ LFE + */ + if (snd_hda_jack_detect_state(codec, 0x1b) == HDA_JACK_PRESENT) { + snd_hda_set_pin_ctl_cache(codec, 0x15, 0); + snd_hda_set_pin_ctl_cache(codec, 0x18, 0); + } else { + snd_hda_set_pin_ctl_cache(codec, 0x15, PIN_OUT); + snd_hda_set_pin_ctl_cache(codec, 0x18, PIN_OUT); + } +} + +static void alc662_fixup_aspire_ethos_hp(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + /* Pin 0x1b: shared headphones jack and surround speakers */ + if (!is_jack_detectable(codec, 0x1b)) + return; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + snd_hda_jack_detect_enable_callback(codec, 0x1b, + alc662_aspire_ethos_mute_speakers); + break; + case HDA_FIXUP_ACT_INIT: + /* Make sure to start in a correct state, i.e. if + * headphones have been plugged in before powering up the system + */ + alc662_aspire_ethos_mute_speakers(codec, NULL); + break; + } +} + static struct coef_fw alc668_coefs[] = { WRITE_COEF(0x01, 0xbebe), WRITE_COEF(0x02, 0xaaaa), WRITE_COEF(0x03, 0x0), WRITE_COEF(0x04, 0x0180), WRITE_COEF(0x06, 0x0), WRITE_COEF(0x07, 0x0f80), @@ -8338,6 +8467,9 @@ enum { ALC662_FIXUP_USI_FUNC, ALC662_FIXUP_USI_HEADSET_MODE, ALC662_FIXUP_LENOVO_MULTI_CODECS, + ALC669_FIXUP_ACER_ASPIRE_ETHOS, + ALC669_FIXUP_ACER_ASPIRE_ETHOS_SUBWOOFER, + ALC669_FIXUP_ACER_ASPIRE_ETHOS_HEADSET, }; static const struct hda_fixup alc662_fixups[] = { @@ -8664,6 +8796,33 @@ static const struct hda_fixup alc662_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc233_alc662_fixup_lenovo_dual_codecs, }, + [ALC669_FIXUP_ACER_ASPIRE_ETHOS_HEADSET] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc662_fixup_aspire_ethos_hp, + }, + [ALC669_FIXUP_ACER_ASPIRE_ETHOS_SUBWOOFER] = { + .type = HDA_FIXUP_VERBS, + /* subwoofer needs an extra GPIO setting to become audible */ + .v.verbs = (const struct hda_verb[]) { + {0x01, AC_VERB_SET_GPIO_MASK, 0x02}, + {0x01, AC_VERB_SET_GPIO_DIRECTION, 0x02}, + {0x01, AC_VERB_SET_GPIO_DATA, 0x00}, + { } + }, + .chained = true, + .chain_id = ALC669_FIXUP_ACER_ASPIRE_ETHOS_HEADSET + }, + [ALC669_FIXUP_ACER_ASPIRE_ETHOS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x15, 0x92130110 }, /* front speakers */ + { 0x18, 0x99130111 }, /* center/subwoofer */ + { 0x1b, 0x11130012 }, /* surround plus jack for HP */ + { } + }, + .chained = true, + .chain_id = ALC669_FIXUP_ACER_ASPIRE_ETHOS_SUBWOOFER + }, }; static const struct snd_pci_quirk alc662_fixup_tbl[] = { @@ -8709,6 +8868,7 @@ static const struct snd_pci_quirk alc662_fixup_tbl[] = { SND_PCI_QUIRK(0x19da, 0xa130, "Zotac Z68", ALC662_FIXUP_ZOTAC_Z68), SND_PCI_QUIRK(0x1b0a, 0x01b8, "ACER Veriton", ALC662_FIXUP_ACER_VERITON), SND_PCI_QUIRK(0x1b35, 0x2206, "CZC P10T", ALC662_FIXUP_CZC_P10T), + SND_PCI_QUIRK(0x1025, 0x0566, "Acer Aspire Ethos 8951G", ALC669_FIXUP_ACER_ASPIRE_ETHOS), #if 0 /* Below is a quirk table taken from the old code. @@ -8802,6 +8962,7 @@ static const struct hda_model_fixup alc662_fixup_models[] = { {.id = ALC892_FIXUP_ASROCK_MOBO, .name = "asrock-mobo"}, {.id = ALC662_FIXUP_USI_HEADSET_MODE, .name = "usi-headset"}, {.id = ALC662_FIXUP_LENOVO_MULTI_CODECS, .name = "dual-codecs"}, + {.id = ALC669_FIXUP_ACER_ASPIRE_ETHOS, .name = "aspire-ethos"}, {} }; @@ -8877,7 +9038,7 @@ static int patch_alc662(struct hda_codec *codec) snd_hda_pick_fixup(codec, alc662_fixup_models, alc662_fixup_tbl, alc662_fixups); - snd_hda_pick_pin_fixup(codec, alc662_pin_fixup_tbl, alc662_fixups); + snd_hda_pick_pin_fixup(codec, alc662_pin_fixup_tbl, alc662_fixups, true); snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); alc_auto_parse_customize_define(codec); diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c index 0d9b62768241..894f3f509e76 100644 --- a/sound/pci/hda/patch_sigmatel.c +++ b/sound/pci/hda/patch_sigmatel.c @@ -975,15 +975,6 @@ static int stac_create_spdif_mux_ctls(struct hda_codec *codec) return 0; } -/* - */ - -static const struct hda_verb stac9200_core_init[] = { - /* set dac0mux for dac converter */ - { 0x07, AC_VERB_SET_CONNECT_SEL, 0x00}, - {} -}; - static const struct hda_verb stac9200_eapd_init[] = { /* set dac0mux for dac converter */ {0x07, AC_VERB_SET_CONNECT_SEL, 0x00}, diff --git a/sound/pci/lx6464es/lx6464es.c b/sound/pci/lx6464es/lx6464es.c index 583ca7384d83..fe10714380f2 100644 --- a/sound/pci/lx6464es/lx6464es.c +++ b/sound/pci/lx6464es/lx6464es.c @@ -49,6 +49,14 @@ static const struct pci_device_id snd_lx6464es_ids[] = { PCI_VENDOR_ID_DIGIGRAM, PCI_SUBDEVICE_ID_DIGIGRAM_LX6464ES_CAE_SERIAL_SUBSYSTEM), }, /* LX6464ES-CAE */ + { PCI_DEVICE_SUB(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_LX6464ES, + PCI_VENDOR_ID_DIGIGRAM, + PCI_SUBDEVICE_ID_DIGIGRAM_LX6464ESE_SERIAL_SUBSYSTEM), + }, /* LX6464ESe */ + { PCI_DEVICE_SUB(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_LX6464ES, + PCI_VENDOR_ID_DIGIGRAM, + PCI_SUBDEVICE_ID_DIGIGRAM_LX6464ESE_CAE_SERIAL_SUBSYSTEM), + }, /* LX6464ESe-CAE */ { 0, }, }; diff --git a/sound/sparc/dbri.c b/sound/sparc/dbri.c index 010113156239..6e065d44060e 100644 --- a/sound/sparc/dbri.c +++ b/sound/sparc/dbri.c @@ -580,12 +580,16 @@ static __u32 reverse_bytes(__u32 b, int len) switch (len) { case 32: b = ((b & 0xffff0000) >> 16) | ((b & 0x0000ffff) << 16); + /* fall through */ case 16: b = ((b & 0xff00ff00) >> 8) | ((b & 0x00ff00ff) << 8); + /* fall through */ case 8: b = ((b & 0xf0f0f0f0) >> 4) | ((b & 0x0f0f0f0f) << 4); + /* fall through */ case 4: b = ((b & 0xcccccccc) >> 2) | ((b & 0x33333333) << 2); + /* fall through */ case 2: b = ((b & 0xaaaaaaaa) >> 1) | ((b & 0x55555555) << 1); case 1: diff --git a/sound/usb/Makefile b/sound/usb/Makefile index e1ce257ab705..78edd7d2f418 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -11,12 +11,14 @@ snd-usb-audio-objs := card.o \ mixer.o \ mixer_quirks.o \ mixer_scarlett.o \ + mixer_scarlett_gen2.o \ mixer_us16x08.o \ pcm.o \ power.o \ proc.o \ quirks.o \ - stream.o + stream.o \ + validate.o snd-usb-audio-$(CONFIG_SND_USB_AUDIO_USE_MEDIA_CONTROLLER) += media.o diff --git a/sound/usb/clock.c b/sound/usb/clock.c index 72e9bdf76115..6b8c14f9b5d4 100644 --- a/sound/usb/clock.c +++ b/sound/usb/clock.c @@ -38,39 +38,37 @@ static void *find_uac_clock_desc(struct usb_host_interface *iface, int id, static bool validate_clock_source_v2(void *p, int id) { struct uac_clock_source_descriptor *cs = p; - return cs->bLength == sizeof(*cs) && cs->bClockID == id; + return cs->bClockID == id; } static bool validate_clock_source_v3(void *p, int id) { struct uac3_clock_source_descriptor *cs = p; - return cs->bLength == sizeof(*cs) && cs->bClockID == id; + return cs->bClockID == id; } static bool validate_clock_selector_v2(void *p, int id) { struct uac_clock_selector_descriptor *cs = p; - return cs->bLength >= sizeof(*cs) && cs->bClockID == id && - cs->bLength == 7 + cs->bNrInPins; + return cs->bClockID == id; } static bool validate_clock_selector_v3(void *p, int id) { struct uac3_clock_selector_descriptor *cs = p; - return cs->bLength >= sizeof(*cs) && cs->bClockID == id && - cs->bLength == 11 + cs->bNrInPins; + return cs->bClockID == id; } static bool validate_clock_multiplier_v2(void *p, int id) { struct uac_clock_multiplier_descriptor *cs = p; - return cs->bLength == sizeof(*cs) && cs->bClockID == id; + return cs->bClockID == id; } static bool validate_clock_multiplier_v3(void *p, int id) { struct uac3_clock_multiplier_descriptor *cs = p; - return cs->bLength == sizeof(*cs) && cs->bClockID == id; + return cs->bClockID == id; } #define DEFINE_FIND_HELPER(name, obj, validator, type) \ diff --git a/sound/usb/helper.h b/sound/usb/helper.h index 6afb70156ec4..5e8a18b4e7b9 100644 --- a/sound/usb/helper.h +++ b/sound/usb/helper.h @@ -31,4 +31,8 @@ static inline int snd_usb_ctrl_intf(struct snd_usb_audio *chip) return get_iface_desc(chip->ctrl_intf)->bInterfaceNumber; } +/* in validate.c */ +bool snd_usb_validate_audio_desc(void *p, int protocol); +bool snd_usb_validate_midi_desc(void *p); + #endif /* __USBAUDIO_HELPER_H */ diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index ab2ec896f49c..b5a3f754a4f1 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -342,7 +342,7 @@ int line6_read_data(struct usb_line6 *line6, unsigned address, void *data, if (address > 0xffff || datalen > 0xff) return -EINVAL; - len = kmalloc(sizeof(*len), GFP_KERNEL); + len = kmalloc(1, GFP_KERNEL); if (!len) return -ENOMEM; @@ -418,7 +418,7 @@ int line6_write_data(struct usb_line6 *line6, unsigned address, void *data, if (address > 0xffff || datalen > 0xffff) return -EINVAL; - status = kmalloc(sizeof(*status), GFP_KERNEL); + status = kmalloc(1, GFP_KERNEL); if (!status) return -ENOMEM; diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index eceab19766db..3fd1d1749edf 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -740,13 +740,6 @@ static int uac_mixer_unit_get_channels(struct mixer_build *state, { int mu_channels; - if (desc->bLength < sizeof(*desc)) - return -EINVAL; - if (!desc->bNrInPins) - return -EINVAL; - if (desc->bLength < sizeof(*desc) + desc->bNrInPins) - return -EINVAL; - switch (state->mixer->protocol) { case UAC_VERSION_1: case UAC_VERSION_2: @@ -765,222 +758,242 @@ static int uac_mixer_unit_get_channels(struct mixer_build *state, } /* - * parse the source unit recursively until it reaches to a terminal - * or a branched unit. + * Parse Input Terminal Unit */ static int __check_input_term(struct mixer_build *state, int id, - struct usb_audio_term *term) + struct usb_audio_term *term); + +static int parse_term_uac1_iterm_unit(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) { - int protocol = state->mixer->protocol; + struct uac_input_terminal_descriptor *d = p1; + + term->type = le16_to_cpu(d->wTerminalType); + term->channels = d->bNrChannels; + term->chconfig = le16_to_cpu(d->wChannelConfig); + term->name = d->iTerminal; + return 0; +} + +static int parse_term_uac2_iterm_unit(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + struct uac2_input_terminal_descriptor *d = p1; int err; - void *p1; - unsigned char *hdr; - memset(term, 0, sizeof(*term)); - for (;;) { - /* a loop in the terminal chain? */ - if (test_and_set_bit(id, state->termbitmap)) - return -EINVAL; + /* call recursively to verify the referenced clock entity */ + err = __check_input_term(state, d->bCSourceID, term); + if (err < 0) + return err; - p1 = find_audio_control_unit(state, id); - if (!p1) - break; + /* save input term properties after recursion, + * to ensure they are not overriden by the recursion calls + */ + term->id = id; + term->type = le16_to_cpu(d->wTerminalType); + term->channels = d->bNrChannels; + term->chconfig = le32_to_cpu(d->bmChannelConfig); + term->name = d->iTerminal; + return 0; +} - hdr = p1; - term->id = id; +static int parse_term_uac3_iterm_unit(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + struct uac3_input_terminal_descriptor *d = p1; + int err; - if (protocol == UAC_VERSION_1 || protocol == UAC_VERSION_2) { - switch (hdr[2]) { - case UAC_INPUT_TERMINAL: - if (protocol == UAC_VERSION_1) { - struct uac_input_terminal_descriptor *d = p1; - - term->type = le16_to_cpu(d->wTerminalType); - term->channels = d->bNrChannels; - term->chconfig = le16_to_cpu(d->wChannelConfig); - term->name = d->iTerminal; - } else { /* UAC_VERSION_2 */ - struct uac2_input_terminal_descriptor *d = p1; - - /* call recursively to verify that the - * referenced clock entity is valid */ - err = __check_input_term(state, d->bCSourceID, term); - if (err < 0) - return err; + /* call recursively to verify the referenced clock entity */ + err = __check_input_term(state, d->bCSourceID, term); + if (err < 0) + return err; - /* save input term properties after recursion, - * to ensure they are not overriden by the - * recursion calls */ - term->id = id; - term->type = le16_to_cpu(d->wTerminalType); - term->channels = d->bNrChannels; - term->chconfig = le32_to_cpu(d->bmChannelConfig); - term->name = d->iTerminal; - } - return 0; - case UAC_FEATURE_UNIT: { - /* the header is the same for v1 and v2 */ - struct uac_feature_unit_descriptor *d = p1; + /* save input term properties after recursion, + * to ensure they are not overriden by the recursion calls + */ + term->id = id; + term->type = le16_to_cpu(d->wTerminalType); - id = d->bSourceID; - break; /* continue to parse */ - } - case UAC_MIXER_UNIT: { - struct uac_mixer_unit_descriptor *d = p1; - - term->type = UAC3_MIXER_UNIT << 16; /* virtual type */ - term->channels = uac_mixer_unit_bNrChannels(d); - term->chconfig = uac_mixer_unit_wChannelConfig(d, protocol); - term->name = uac_mixer_unit_iMixer(d); - return 0; - } - case UAC_SELECTOR_UNIT: - case UAC2_CLOCK_SELECTOR: { - struct uac_selector_unit_descriptor *d = p1; - /* call recursively to retrieve the channel info */ - err = __check_input_term(state, d->baSourceID[0], term); - if (err < 0) - return err; - term->type = UAC3_SELECTOR_UNIT << 16; /* virtual type */ - term->id = id; - term->name = uac_selector_unit_iSelector(d); - return 0; - } - case UAC1_PROCESSING_UNIT: - /* UAC2_EFFECT_UNIT */ - if (protocol == UAC_VERSION_1) - term->type = UAC3_PROCESSING_UNIT << 16; /* virtual type */ - else /* UAC_VERSION_2 */ - term->type = UAC3_EFFECT_UNIT << 16; /* virtual type */ - /* fall through */ - case UAC1_EXTENSION_UNIT: - /* UAC2_PROCESSING_UNIT_V2 */ - if (protocol == UAC_VERSION_1 && !term->type) - term->type = UAC3_EXTENSION_UNIT << 16; /* virtual type */ - else if (protocol == UAC_VERSION_2 && !term->type) - term->type = UAC3_PROCESSING_UNIT << 16; /* virtual type */ - /* fall through */ - case UAC2_EXTENSION_UNIT_V2: { - struct uac_processing_unit_descriptor *d = p1; - - if (protocol == UAC_VERSION_2 && - hdr[2] == UAC2_EFFECT_UNIT) { - /* UAC2/UAC1 unit IDs overlap here in an - * uncompatible way. Ignore this unit for now. - */ - return 0; - } + err = get_cluster_channels_v3(state, le16_to_cpu(d->wClusterDescrID)); + if (err < 0) + return err; + term->channels = err; - if (d->bNrInPins) { - id = d->baSourceID[0]; - break; /* continue to parse */ - } - if (!term->type) - term->type = UAC3_EXTENSION_UNIT << 16; /* virtual type */ + /* REVISIT: UAC3 IT doesn't have channels cfg */ + term->chconfig = 0; - term->channels = uac_processing_unit_bNrChannels(d); - term->chconfig = uac_processing_unit_wChannelConfig(d, protocol); - term->name = uac_processing_unit_iProcessing(d, protocol); - return 0; - } - case UAC2_CLOCK_SOURCE: { - struct uac_clock_source_descriptor *d = p1; + term->name = le16_to_cpu(d->wTerminalDescrStr); + return 0; +} - term->type = UAC3_CLOCK_SOURCE << 16; /* virtual type */ - term->id = id; - term->name = d->iClockSource; - return 0; - } - default: - return -ENODEV; - } - } else { /* UAC_VERSION_3 */ - switch (hdr[2]) { - case UAC_INPUT_TERMINAL: { - struct uac3_input_terminal_descriptor *d = p1; - - /* call recursively to verify that the - * referenced clock entity is valid */ - err = __check_input_term(state, d->bCSourceID, term); - if (err < 0) - return err; +static int parse_term_mixer_unit(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + struct uac_mixer_unit_descriptor *d = p1; + int protocol = state->mixer->protocol; + int err; - /* save input term properties after recursion, - * to ensure they are not overriden by the - * recursion calls */ - term->id = id; - term->type = le16_to_cpu(d->wTerminalType); + err = uac_mixer_unit_get_channels(state, d); + if (err <= 0) + return err; - err = get_cluster_channels_v3(state, le16_to_cpu(d->wClusterDescrID)); - if (err < 0) - return err; - term->channels = err; + term->type = UAC3_MIXER_UNIT << 16; /* virtual type */ + term->channels = err; + if (protocol != UAC_VERSION_3) { + term->chconfig = uac_mixer_unit_wChannelConfig(d, protocol); + term->name = uac_mixer_unit_iMixer(d); + } + return 0; +} - /* REVISIT: UAC3 IT doesn't have channels cfg */ - term->chconfig = 0; +static int parse_term_selector_unit(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + struct uac_selector_unit_descriptor *d = p1; + int err; - term->name = le16_to_cpu(d->wTerminalDescrStr); - return 0; - } - case UAC3_FEATURE_UNIT: { - struct uac3_feature_unit_descriptor *d = p1; + /* call recursively to retrieve the channel info */ + err = __check_input_term(state, d->baSourceID[0], term); + if (err < 0) + return err; + term->type = UAC3_SELECTOR_UNIT << 16; /* virtual type */ + term->id = id; + if (state->mixer->protocol != UAC_VERSION_3) + term->name = uac_selector_unit_iSelector(d); + return 0; +} - id = d->bSourceID; - break; /* continue to parse */ - } - case UAC3_CLOCK_SOURCE: { - struct uac3_clock_source_descriptor *d = p1; +static int parse_term_proc_unit(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id, int vtype) +{ + struct uac_processing_unit_descriptor *d = p1; + int protocol = state->mixer->protocol; + int err; - term->type = UAC3_CLOCK_SOURCE << 16; /* virtual type */ - term->id = id; - term->name = le16_to_cpu(d->wClockSourceStr); - return 0; - } - case UAC3_MIXER_UNIT: { - struct uac_mixer_unit_descriptor *d = p1; + if (d->bNrInPins) { + /* call recursively to retrieve the channel info */ + err = __check_input_term(state, d->baSourceID[0], term); + if (err < 0) + return err; + } - err = uac_mixer_unit_get_channels(state, d); - if (err <= 0) - return err; + term->type = vtype << 16; /* virtual type */ + term->id = id; - term->channels = err; - term->type = UAC3_MIXER_UNIT << 16; /* virtual type */ + if (protocol == UAC_VERSION_3) + return 0; - return 0; - } - case UAC3_SELECTOR_UNIT: - case UAC3_CLOCK_SELECTOR: { - struct uac_selector_unit_descriptor *d = p1; - /* call recursively to retrieve the channel info */ - err = __check_input_term(state, d->baSourceID[0], term); - if (err < 0) - return err; - term->type = UAC3_SELECTOR_UNIT << 16; /* virtual type */ - term->id = id; - term->name = 0; /* TODO: UAC3 Class-specific strings */ + if (!term->channels) { + term->channels = uac_processing_unit_bNrChannels(d); + term->chconfig = uac_processing_unit_wChannelConfig(d, protocol); + } + term->name = uac_processing_unit_iProcessing(d, protocol); + return 0; +} - return 0; - } - case UAC3_PROCESSING_UNIT: { - struct uac_processing_unit_descriptor *d = p1; +static int parse_term_uac2_clock_source(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + struct uac_clock_source_descriptor *d = p1; - if (!d->bNrInPins) - return -EINVAL; + term->type = UAC3_CLOCK_SOURCE << 16; /* virtual type */ + term->id = id; + term->name = d->iClockSource; + return 0; +} - /* call recursively to retrieve the channel info */ - err = __check_input_term(state, d->baSourceID[0], term); - if (err < 0) - return err; +static int parse_term_uac3_clock_source(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + struct uac3_clock_source_descriptor *d = p1; + + term->type = UAC3_CLOCK_SOURCE << 16; /* virtual type */ + term->id = id; + term->name = le16_to_cpu(d->wClockSourceStr); + return 0; +} - term->type = UAC3_PROCESSING_UNIT << 16; /* virtual type */ - term->id = id; - term->name = 0; /* TODO: UAC3 Class-specific strings */ +#define PTYPE(a, b) ((a) << 8 | (b)) - return 0; - } - default: - return -ENODEV; - } +/* + * parse the source unit recursively until it reaches to a terminal + * or a branched unit. + */ +static int __check_input_term(struct mixer_build *state, int id, + struct usb_audio_term *term) +{ + int protocol = state->mixer->protocol; + void *p1; + unsigned char *hdr; + + for (;;) { + /* a loop in the terminal chain? */ + if (test_and_set_bit(id, state->termbitmap)) + return -EINVAL; + + p1 = find_audio_control_unit(state, id); + if (!p1) + break; + if (!snd_usb_validate_audio_desc(p1, protocol)) + break; /* bad descriptor */ + + hdr = p1; + term->id = id; + + switch (PTYPE(protocol, hdr[2])) { + case PTYPE(UAC_VERSION_1, UAC_FEATURE_UNIT): + case PTYPE(UAC_VERSION_2, UAC_FEATURE_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_FEATURE_UNIT): { + /* the header is the same for all versions */ + struct uac_feature_unit_descriptor *d = p1; + + id = d->bSourceID; + break; /* continue to parse */ + } + case PTYPE(UAC_VERSION_1, UAC_INPUT_TERMINAL): + return parse_term_uac1_iterm_unit(state, term, p1, id); + case PTYPE(UAC_VERSION_2, UAC_INPUT_TERMINAL): + return parse_term_uac2_iterm_unit(state, term, p1, id); + case PTYPE(UAC_VERSION_3, UAC_INPUT_TERMINAL): + return parse_term_uac3_iterm_unit(state, term, p1, id); + case PTYPE(UAC_VERSION_1, UAC_MIXER_UNIT): + case PTYPE(UAC_VERSION_2, UAC_MIXER_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_MIXER_UNIT): + return parse_term_mixer_unit(state, term, p1, id); + case PTYPE(UAC_VERSION_1, UAC_SELECTOR_UNIT): + case PTYPE(UAC_VERSION_2, UAC_SELECTOR_UNIT): + case PTYPE(UAC_VERSION_2, UAC2_CLOCK_SELECTOR): + case PTYPE(UAC_VERSION_3, UAC3_SELECTOR_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_CLOCK_SELECTOR): + return parse_term_selector_unit(state, term, p1, id); + case PTYPE(UAC_VERSION_1, UAC1_PROCESSING_UNIT): + case PTYPE(UAC_VERSION_2, UAC2_PROCESSING_UNIT_V2): + case PTYPE(UAC_VERSION_3, UAC3_PROCESSING_UNIT): + return parse_term_proc_unit(state, term, p1, id, + UAC3_PROCESSING_UNIT); + case PTYPE(UAC_VERSION_2, UAC2_EFFECT_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_EFFECT_UNIT): + return parse_term_proc_unit(state, term, p1, id, + UAC3_EFFECT_UNIT); + case PTYPE(UAC_VERSION_1, UAC1_EXTENSION_UNIT): + case PTYPE(UAC_VERSION_2, UAC2_EXTENSION_UNIT_V2): + case PTYPE(UAC_VERSION_3, UAC3_EXTENSION_UNIT): + return parse_term_proc_unit(state, term, p1, id, + UAC3_EXTENSION_UNIT); + case PTYPE(UAC_VERSION_2, UAC2_CLOCK_SOURCE): + return parse_term_uac2_clock_source(state, term, p1, id); + case PTYPE(UAC_VERSION_3, UAC3_CLOCK_SOURCE): + return parse_term_uac3_clock_source(state, term, p1, id); + default: + return -ENODEV; } } return -ENODEV; @@ -1024,10 +1037,15 @@ static struct usb_feature_control_info audio_feature_info[] = { { UAC2_FU_PHASE_INVERTER, "Phase Inverter Control", USB_MIXER_BOOLEAN, -1 }, }; +static void usb_mixer_elem_info_free(struct usb_mixer_elem_info *cval) +{ + kfree(cval); +} + /* private_free callback */ void snd_usb_mixer_elem_free(struct snd_kcontrol *kctl) { - kfree(kctl->private_data); + usb_mixer_elem_info_free(kctl->private_data); kctl->private_data = NULL; } @@ -1550,7 +1568,7 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, ctl_info = get_feature_control_info(control); if (!ctl_info) { - kfree(cval); + usb_mixer_elem_info_free(cval); return; } if (mixer->protocol == UAC_VERSION_1) @@ -1583,7 +1601,7 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, if (!kctl) { usb_audio_err(mixer->chip, "cannot malloc kcontrol\n"); - kfree(cval); + usb_mixer_elem_info_free(cval); return; } kctl->private_free = snd_usb_mixer_elem_free; @@ -1753,7 +1771,7 @@ static void build_connector_control(struct usb_mixer_interface *mixer, kctl = snd_ctl_new1(&usb_connector_ctl_ro, cval); if (!kctl) { usb_audio_err(mixer->chip, "cannot malloc kcontrol\n"); - kfree(cval); + usb_mixer_elem_info_free(cval); return; } get_connector_control_name(mixer, term, is_input, kctl->id.name, @@ -1774,13 +1792,6 @@ static int parse_clock_source_unit(struct mixer_build *state, int unitid, if (state->mixer->protocol != UAC_VERSION_2) return -EINVAL; - if (hdr->bLength != sizeof(*hdr)) { - usb_audio_dbg(state->chip, - "Bogus clock source descriptor length of %d, ignoring.\n", - hdr->bLength); - return 0; - } - /* * The only property of this unit we are interested in is the * clock source validity. If that isn't readable, just bail out. @@ -1806,7 +1817,7 @@ static int parse_clock_source_unit(struct mixer_build *state, int unitid, kctl = snd_ctl_new1(&usb_bool_master_control_ctl_ro, cval); if (!kctl) { - kfree(cval); + usb_mixer_elem_info_free(cval); return -ENOMEM; } @@ -1839,62 +1850,20 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, __u8 *bmaControls; if (state->mixer->protocol == UAC_VERSION_1) { - if (hdr->bLength < 7) { - usb_audio_err(state->chip, - "unit %u: invalid UAC_FEATURE_UNIT descriptor\n", - unitid); - return -EINVAL; - } csize = hdr->bControlSize; - if (!csize) { - usb_audio_dbg(state->chip, - "unit %u: invalid bControlSize == 0\n", - unitid); - return -EINVAL; - } channels = (hdr->bLength - 7) / csize - 1; bmaControls = hdr->bmaControls; - if (hdr->bLength < 7 + csize) { - usb_audio_err(state->chip, - "unit %u: invalid UAC_FEATURE_UNIT descriptor\n", - unitid); - return -EINVAL; - } } else if (state->mixer->protocol == UAC_VERSION_2) { struct uac2_feature_unit_descriptor *ftr = _ftr; - if (hdr->bLength < 6) { - usb_audio_err(state->chip, - "unit %u: invalid UAC_FEATURE_UNIT descriptor\n", - unitid); - return -EINVAL; - } csize = 4; channels = (hdr->bLength - 6) / 4 - 1; bmaControls = ftr->bmaControls; - if (hdr->bLength < 6 + csize) { - usb_audio_err(state->chip, - "unit %u: invalid UAC_FEATURE_UNIT descriptor\n", - unitid); - return -EINVAL; - } } else { /* UAC_VERSION_3 */ struct uac3_feature_unit_descriptor *ftr = _ftr; - if (hdr->bLength < 7) { - usb_audio_err(state->chip, - "unit %u: invalid UAC3_FEATURE_UNIT descriptor\n", - unitid); - return -EINVAL; - } csize = 4; channels = (ftr->bLength - 7) / 4 - 1; bmaControls = ftr->bmaControls; - if (hdr->bLength < 7 + csize) { - usb_audio_err(state->chip, - "unit %u: invalid UAC3_FEATURE_UNIT descriptor\n", - unitid); - return -EINVAL; - } } /* parse the source unit */ @@ -2068,7 +2037,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state, kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); if (!kctl) { usb_audio_err(state->chip, "cannot malloc kcontrol\n"); - kfree(cval); + usb_mixer_elem_info_free(cval); return; } kctl->private_free = snd_usb_mixer_elem_free; @@ -2094,15 +2063,11 @@ static int parse_audio_input_terminal(struct mixer_build *state, int unitid, if (state->mixer->protocol == UAC_VERSION_2) { struct uac2_input_terminal_descriptor *d_v2 = raw_desc; - if (d_v2->bLength < sizeof(*d_v2)) - return -EINVAL; control = UAC2_TE_CONNECTOR; term_id = d_v2->bTerminalID; bmctls = le16_to_cpu(d_v2->bmControls); } else if (state->mixer->protocol == UAC_VERSION_3) { struct uac3_input_terminal_descriptor *d_v3 = raw_desc; - if (d_v3->bLength < sizeof(*d_v3)) - return -EINVAL; control = UAC3_TE_INSERTION; term_id = d_v3->bTerminalID; bmctls = le32_to_cpu(d_v3->bmControls); @@ -2364,18 +2329,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, const char *name = extension_unit ? "Extension Unit" : "Processing Unit"; - if (desc->bLength < 13) { - usb_audio_err(state->chip, "invalid %s descriptor (id %d)\n", name, unitid); - return -EINVAL; - } - num_ins = desc->bNrInPins; - if (desc->bLength < 13 + num_ins || - desc->bLength < num_ins + uac_processing_unit_bControlSize(desc, state->mixer->protocol)) { - usb_audio_err(state->chip, "invalid %s descriptor (id %d)\n", name, unitid); - return -EINVAL; - } - for (i = 0; i < num_ins; i++) { err = parse_audio_unit(state, desc->baSourceID[i]); if (err < 0) @@ -2466,7 +2420,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, kctl = snd_ctl_new1(&mixer_procunit_ctl, cval); if (!kctl) { - kfree(cval); + usb_mixer_elem_info_free(cval); return -ENOMEM; } kctl->private_free = snd_usb_mixer_elem_free; @@ -2604,7 +2558,7 @@ static void usb_mixer_selector_elem_free(struct snd_kcontrol *kctl) if (kctl->private_data) { struct usb_mixer_elem_info *cval = kctl->private_data; num_ins = cval->max; - kfree(cval); + usb_mixer_elem_info_free(cval); kctl->private_data = NULL; } if (kctl->private_value) { @@ -2630,13 +2584,6 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, const struct usbmix_name_map *map; char **namelist; - if (desc->bLength < 5 || !desc->bNrInPins || - desc->bLength < 5 + desc->bNrInPins) { - usb_audio_err(state->chip, - "invalid SELECTOR UNIT descriptor %d\n", unitid); - return -EINVAL; - } - for (i = 0; i < desc->bNrInPins; i++) { err = parse_audio_unit(state, desc->baSourceID[i]); if (err < 0) @@ -2676,10 +2623,10 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, break; } - namelist = kmalloc_array(desc->bNrInPins, sizeof(char *), GFP_KERNEL); + namelist = kcalloc(desc->bNrInPins, sizeof(char *), GFP_KERNEL); if (!namelist) { - kfree(cval); - return -ENOMEM; + err = -ENOMEM; + goto error_cval; } #define MAX_ITEM_NAME_LEN 64 for (i = 0; i < desc->bNrInPins; i++) { @@ -2687,11 +2634,8 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, len = 0; namelist[i] = kmalloc(MAX_ITEM_NAME_LEN, GFP_KERNEL); if (!namelist[i]) { - while (i--) - kfree(namelist[i]); - kfree(namelist); - kfree(cval); - return -ENOMEM; + err = -ENOMEM; + goto error_name; } len = check_mapped_selector_name(state, unitid, i, namelist[i], MAX_ITEM_NAME_LEN); @@ -2705,11 +2649,8 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, kctl = snd_ctl_new1(&mixer_selectunit_ctl, cval); if (! kctl) { usb_audio_err(state->chip, "cannot malloc kcontrol\n"); - for (i = 0; i < desc->bNrInPins; i++) - kfree(namelist[i]); - kfree(namelist); - kfree(cval); - return -ENOMEM; + err = -ENOMEM; + goto error_name; } kctl->private_value = (unsigned long)namelist; kctl->private_free = usb_mixer_selector_elem_free; @@ -2755,6 +2696,14 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, usb_audio_dbg(state->chip, "[%d] SU [%s] items = %d\n", cval->head.id, kctl->id.name, desc->bNrInPins); return snd_usb_mixer_add_control(&cval->head, kctl); + + error_name: + for (i = 0; i < desc->bNrInPins; i++) + kfree(namelist[i]); + kfree(namelist); + error_cval: + usb_mixer_elem_info_free(cval); + return err; } /* @@ -2775,62 +2724,49 @@ static int parse_audio_unit(struct mixer_build *state, int unitid) return -EINVAL; } - if (protocol == UAC_VERSION_1 || protocol == UAC_VERSION_2) { - switch (p1[2]) { - case UAC_INPUT_TERMINAL: - return parse_audio_input_terminal(state, unitid, p1); - case UAC_MIXER_UNIT: - return parse_audio_mixer_unit(state, unitid, p1); - case UAC2_CLOCK_SOURCE: - return parse_clock_source_unit(state, unitid, p1); - case UAC_SELECTOR_UNIT: - case UAC2_CLOCK_SELECTOR: - return parse_audio_selector_unit(state, unitid, p1); - case UAC_FEATURE_UNIT: - return parse_audio_feature_unit(state, unitid, p1); - case UAC1_PROCESSING_UNIT: - /* UAC2_EFFECT_UNIT has the same value */ - if (protocol == UAC_VERSION_1) - return parse_audio_processing_unit(state, unitid, p1); - else - return 0; /* FIXME - effect units not implemented yet */ - case UAC1_EXTENSION_UNIT: - /* UAC2_PROCESSING_UNIT_V2 has the same value */ - if (protocol == UAC_VERSION_1) - return parse_audio_extension_unit(state, unitid, p1); - else /* UAC_VERSION_2 */ - return parse_audio_processing_unit(state, unitid, p1); - case UAC2_EXTENSION_UNIT_V2: - return parse_audio_extension_unit(state, unitid, p1); - default: - usb_audio_err(state->chip, - "unit %u: unexpected type 0x%02x\n", unitid, p1[2]); - return -EINVAL; - } - } else { /* UAC_VERSION_3 */ - switch (p1[2]) { - case UAC_INPUT_TERMINAL: - return parse_audio_input_terminal(state, unitid, p1); - case UAC3_MIXER_UNIT: - return parse_audio_mixer_unit(state, unitid, p1); - case UAC3_CLOCK_SOURCE: - return parse_clock_source_unit(state, unitid, p1); - case UAC3_SELECTOR_UNIT: - case UAC3_CLOCK_SELECTOR: - return parse_audio_selector_unit(state, unitid, p1); - case UAC3_FEATURE_UNIT: - return parse_audio_feature_unit(state, unitid, p1); - case UAC3_EFFECT_UNIT: - return 0; /* FIXME - effect units not implemented yet */ - case UAC3_PROCESSING_UNIT: - return parse_audio_processing_unit(state, unitid, p1); - case UAC3_EXTENSION_UNIT: - return parse_audio_extension_unit(state, unitid, p1); - default: - usb_audio_err(state->chip, - "unit %u: unexpected type 0x%02x\n", unitid, p1[2]); - return -EINVAL; - } + if (!snd_usb_validate_audio_desc(p1, protocol)) { + usb_audio_dbg(state->chip, "invalid unit %d\n", unitid); + return 0; /* skip invalid unit */ + } + + switch (PTYPE(protocol, p1[2])) { + case PTYPE(UAC_VERSION_1, UAC_INPUT_TERMINAL): + case PTYPE(UAC_VERSION_2, UAC_INPUT_TERMINAL): + case PTYPE(UAC_VERSION_3, UAC_INPUT_TERMINAL): + return parse_audio_input_terminal(state, unitid, p1); + case PTYPE(UAC_VERSION_1, UAC_MIXER_UNIT): + case PTYPE(UAC_VERSION_2, UAC_MIXER_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_MIXER_UNIT): + return parse_audio_mixer_unit(state, unitid, p1); + case PTYPE(UAC_VERSION_2, UAC2_CLOCK_SOURCE): + case PTYPE(UAC_VERSION_3, UAC3_CLOCK_SOURCE): + return parse_clock_source_unit(state, unitid, p1); + case PTYPE(UAC_VERSION_1, UAC_SELECTOR_UNIT): + case PTYPE(UAC_VERSION_2, UAC_SELECTOR_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_SELECTOR_UNIT): + case PTYPE(UAC_VERSION_2, UAC2_CLOCK_SELECTOR): + case PTYPE(UAC_VERSION_3, UAC3_CLOCK_SELECTOR): + return parse_audio_selector_unit(state, unitid, p1); + case PTYPE(UAC_VERSION_1, UAC_FEATURE_UNIT): + case PTYPE(UAC_VERSION_2, UAC_FEATURE_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_FEATURE_UNIT): + return parse_audio_feature_unit(state, unitid, p1); + case PTYPE(UAC_VERSION_1, UAC1_PROCESSING_UNIT): + case PTYPE(UAC_VERSION_2, UAC2_PROCESSING_UNIT_V2): + case PTYPE(UAC_VERSION_3, UAC3_PROCESSING_UNIT): + return parse_audio_processing_unit(state, unitid, p1); + case PTYPE(UAC_VERSION_1, UAC1_EXTENSION_UNIT): + case PTYPE(UAC_VERSION_2, UAC2_EXTENSION_UNIT_V2): + case PTYPE(UAC_VERSION_3, UAC3_EXTENSION_UNIT): + return parse_audio_extension_unit(state, unitid, p1); + case PTYPE(UAC_VERSION_2, UAC2_EFFECT_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_EFFECT_UNIT): + return 0; /* FIXME - effect units not implemented yet */ + default: + usb_audio_err(state->chip, + "unit %u: unexpected type 0x%02x\n", + unitid, p1[2]); + return -EINVAL; } } @@ -3145,11 +3081,12 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) while ((p = snd_usb_find_csint_desc(mixer->hostif->extra, mixer->hostif->extralen, p, UAC_OUTPUT_TERMINAL)) != NULL) { + if (!snd_usb_validate_audio_desc(p, mixer->protocol)) + continue; /* skip invalid descriptor */ + if (mixer->protocol == UAC_VERSION_1) { struct uac1_output_terminal_descriptor *desc = p; - if (desc->bLength < sizeof(*desc)) - continue; /* invalid descriptor? */ /* mark terminal ID as visited */ set_bit(desc->bTerminalID, state.unitbitmap); state.oterm.id = desc->bTerminalID; @@ -3161,8 +3098,6 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) } else if (mixer->protocol == UAC_VERSION_2) { struct uac2_output_terminal_descriptor *desc = p; - if (desc->bLength < sizeof(*desc)) - continue; /* invalid descriptor? */ /* mark terminal ID as visited */ set_bit(desc->bTerminalID, state.unitbitmap); state.oterm.id = desc->bTerminalID; @@ -3188,8 +3123,6 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) } else { /* UAC_VERSION_3 */ struct uac3_output_terminal_descriptor *desc = p; - if (desc->bLength < sizeof(*desc)) - continue; /* invalid descriptor? */ /* mark terminal ID as visited */ set_bit(desc->bTerminalID, state.unitbitmap); state.oterm.id = desc->bTerminalID; @@ -3550,6 +3483,8 @@ void snd_usb_mixer_disconnect(struct usb_mixer_interface *mixer) usb_kill_urb(mixer->urb); if (mixer->rc_urb) usb_kill_urb(mixer->rc_urb); + if (mixer->private_free) + mixer->private_free(mixer); mixer->disconnected = true; } @@ -3577,6 +3512,8 @@ static int snd_usb_mixer_activate(struct usb_mixer_interface *mixer) int snd_usb_mixer_suspend(struct usb_mixer_interface *mixer) { snd_usb_mixer_inactivate(mixer); + if (mixer->private_suspend) + mixer->private_suspend(mixer); return 0; } diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 394cd9107507..37e1b234c802 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -28,6 +28,10 @@ struct usb_mixer_interface { struct media_mixer_ctl *media_mixer_ctl; bool disconnected; + + void *private_data; + void (*private_free)(struct usb_mixer_interface *mixer); + void (*private_suspend)(struct usb_mixer_interface *mixer); }; #define MAX_CHANNELS 16 /* max logical channels */ diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 27dcb3743690..39e27ae6c597 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -32,6 +32,7 @@ #include "mixer.h" #include "mixer_quirks.h" #include "mixer_scarlett.h" +#include "mixer_scarlett_gen2.h" #include "mixer_us16x08.h" #include "helper.h" @@ -2258,6 +2259,12 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) err = snd_scarlett_controls_create(mixer); break; + case USB_ID(0x1235, 0x8203): /* Focusrite Scarlett 6i6 2nd Gen */ + case USB_ID(0x1235, 0x8204): /* Focusrite Scarlett 18i8 2nd Gen */ + case USB_ID(0x1235, 0x8201): /* Focusrite Scarlett 18i20 2nd Gen */ + err = snd_scarlett_gen2_controls_create(mixer); + break; + case USB_ID(0x041e, 0x323b): /* Creative Sound Blaster E1 */ err = snd_soundblaster_e1_switch_create(mixer); break; diff --git a/sound/usb/mixer_scarlett_gen2.c b/sound/usb/mixer_scarlett_gen2.c new file mode 100644 index 000000000000..7d460b1f1735 --- /dev/null +++ b/sound/usb/mixer_scarlett_gen2.c @@ -0,0 +1,2075 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Focusrite Scarlett 6i6/18i8/18i20 Gen 2 Driver for ALSA + * + * Copyright (c) 2018-2019 by Geoffrey D. Bennett <g at b4.vu> + * + * Based on the Scarlett (Gen 1) Driver for ALSA: + * + * Copyright (c) 2013 by Tobias Hoffmann + * Copyright (c) 2013 by Robin Gareus <robin at gareus.org> + * Copyright (c) 2002 by Takashi Iwai <tiwai at suse.de> + * Copyright (c) 2014 by Chris J Arges <chris.j.arges at canonical.com> + * + * Many codes borrowed from audio.c by + * Alan Cox (alan at lxorguk.ukuu.org.uk) + * Thomas Sailer (sailer at ife.ee.ethz.ch) + * + * Code cleanup: + * David Henningsson <david.henningsson at canonical.com> + */ + +/* Mixer Interface for the Focusrite Scarlett 6i6/18i8/18i20 Gen 2 audio + * interface. Based on the Gen 1 driver and rewritten. + */ + +/* The protocol was reverse engineered by looking at the communication + * between Focusrite Control 2.3.4 and the Focusrite(R) Scarlett 18i20 + * (firmware 1083) using usbmon in July-August 2018. + * + * Scarlett 18i8 support added in April 2019. + * + * Scarlett 6i6 support added in June 2019 (thanks to Martin Wittmann + * for providing usbmon output and testing). + * + * This ALSA mixer gives access to: + * - input, output, mixer-matrix muxes + * - 18x10 mixer-matrix gain stages + * - gain/volume controls + * - level meters + * - line/inst level and pad controls + * + * <ditaa> + * /--------------\ 18chn 20chn /--------------\ + * | Hardware in +--+------\ /-------------+--+ ALSA PCM out | + * \--------------/ | | | | \--------------/ + * | | | /-----\ | + * | | | | | | + * | v v v | | + * | +---------------+ | | + * | \ Matrix Mux / | | + * | +-----+-----+ | | + * | | | | + * | |18chn | | + * | | | | + * | | 10chn| | + * | v | | + * | +------------+ | | + * | | Mixer | | | + * | | Matrix | | | + * | | | | | + * | | 18x10 Gain | | | + * | | stages | | | + * | +-----+------+ | | + * | | | | + * |18chn |10chn | |20chn + * | | | | + * | +----------/ | + * | | | + * v v v + * =========================== + * +---------------+ +--—------------+ + * \ Output Mux / \ Capture Mux / + * +---+---+---+ +-----+-----+ + * | | | + * 10chn| | |18chn + * | | | + * /--------------\ | | | /--------------\ + * | S/PDIF, ADAT |<--/ |10chn \-->| ALSA PCM in | + * | Hardware out | | \--------------/ + * \--------------/ | + * v + * +-------------+ Software gain per channel. + * | Master Gain |<-- 18i20 only: Switch per channel + * +------+------+ to select HW or SW gain control. + * | + * |10chn + * /--------------\ | + * | Analogue |<------/ + * | Hardware out | + * \--------------/ + * </ditaa> + * + */ + +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/moduleparam.h> + +#include <sound/control.h> +#include <sound/tlv.h> + +#include "usbaudio.h" +#include "mixer.h" +#include "helper.h" + +#include "mixer_scarlett_gen2.h" + +/* device_setup value to enable */ +#define SCARLETT2_ENABLE 0x01 + +/* some gui mixers can't handle negative ctl values */ +#define SCARLETT2_VOLUME_BIAS 127 + +/* mixer range from -80dB to +6dB in 0.5dB steps */ +#define SCARLETT2_MIXER_MIN_DB -80 +#define SCARLETT2_MIXER_BIAS (-SCARLETT2_MIXER_MIN_DB * 2) +#define SCARLETT2_MIXER_MAX_DB 6 +#define SCARLETT2_MIXER_MAX_VALUE \ + ((SCARLETT2_MIXER_MAX_DB - SCARLETT2_MIXER_MIN_DB) * 2) + +/* map from (dB + 80) * 2 to mixer value + * for dB in 0 .. 172: int(8192 * pow(10, ((dB - 160) / 2 / 20))) + */ +static const u16 scarlett2_mixer_values[173] = { + 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, + 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 8, 8, + 9, 9, 10, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 23, 24, 25, 27, 29, 30, 32, 34, 36, 38, 41, 43, 46, 48, 51, + 54, 57, 61, 65, 68, 73, 77, 81, 86, 91, 97, 103, 109, 115, + 122, 129, 137, 145, 154, 163, 173, 183, 194, 205, 217, 230, + 244, 259, 274, 290, 307, 326, 345, 365, 387, 410, 434, 460, + 487, 516, 547, 579, 614, 650, 689, 730, 773, 819, 867, 919, + 973, 1031, 1092, 1157, 1225, 1298, 1375, 1456, 1543, 1634, + 1731, 1833, 1942, 2057, 2179, 2308, 2445, 2590, 2744, 2906, + 3078, 3261, 3454, 3659, 3876, 4105, 4349, 4606, 4879, 5168, + 5475, 5799, 6143, 6507, 6892, 7301, 7733, 8192, 8677, 9191, + 9736, 10313, 10924, 11571, 12257, 12983, 13752, 14567, 15430, + 16345 +}; + +/* Maximum number of analogue outputs */ +#define SCARLETT2_ANALOGUE_MAX 10 + +/* Maximum number of level and pad switches */ +#define SCARLETT2_LEVEL_SWITCH_MAX 2 +#define SCARLETT2_PAD_SWITCH_MAX 4 + +/* Maximum number of inputs to the mixer */ +#define SCARLETT2_INPUT_MIX_MAX 18 + +/* Maximum number of outputs from the mixer */ +#define SCARLETT2_OUTPUT_MIX_MAX 10 + +/* Maximum size of the data in the USB mux assignment message: + * 18 inputs, 20 outputs, 18 matrix inputs, 8 spare + */ +#define SCARLETT2_MUX_MAX 64 + +/* Number of meters: + * 18 inputs, 20 outputs, 18 matrix inputs + */ +#define SCARLETT2_NUM_METERS 56 + +/* Hardware port types: + * - None (no input to mux) + * - Analogue I/O + * - S/PDIF I/O + * - ADAT I/O + * - Mixer I/O + * - PCM I/O + */ +enum { + SCARLETT2_PORT_TYPE_NONE = 0, + SCARLETT2_PORT_TYPE_ANALOGUE = 1, + SCARLETT2_PORT_TYPE_SPDIF = 2, + SCARLETT2_PORT_TYPE_ADAT = 3, + SCARLETT2_PORT_TYPE_MIX = 4, + SCARLETT2_PORT_TYPE_PCM = 5, + SCARLETT2_PORT_TYPE_COUNT = 6, +}; + +/* Count of total I/O and number available at each sample rate */ +enum { + SCARLETT2_PORT_IN = 0, + SCARLETT2_PORT_OUT = 1, + SCARLETT2_PORT_OUT_44 = 2, + SCARLETT2_PORT_OUT_88 = 3, + SCARLETT2_PORT_OUT_176 = 4, + SCARLETT2_PORT_DIRECTIONS = 5, +}; + +/* Hardware buttons on the 18i20 */ +#define SCARLETT2_BUTTON_MAX 2 + +static const char *const scarlett2_button_names[SCARLETT2_BUTTON_MAX] = { + "Mute", "Dim" +}; + +/* Description of each hardware port type: + * - id: hardware ID for this port type + * - num: number of sources/destinations of this port type + * - src_descr: printf format string for mux input selections + * - src_num_offset: added to channel number for the fprintf + * - dst_descr: printf format string for mixer controls + */ +struct scarlett2_ports { + u16 id; + int num[SCARLETT2_PORT_DIRECTIONS]; + const char * const src_descr; + int src_num_offset; + const char * const dst_descr; +}; + +struct scarlett2_device_info { + u8 line_out_hw_vol; /* line out hw volume is sw controlled */ + u8 button_count; /* number of buttons */ + u8 level_input_count; /* inputs with level selectable */ + u8 pad_input_count; /* inputs with pad selectable */ + const char * const line_out_descrs[SCARLETT2_ANALOGUE_MAX]; + struct scarlett2_ports ports[SCARLETT2_PORT_TYPE_COUNT]; +}; + +struct scarlett2_mixer_data { + struct usb_mixer_interface *mixer; + struct mutex usb_mutex; /* prevent sending concurrent USB requests */ + struct mutex data_mutex; /* lock access to this data */ + struct delayed_work work; + const struct scarlett2_device_info *info; + int num_mux_srcs; + u16 scarlett2_seq; + u8 vol_updated; + u8 master_vol; + u8 vol[SCARLETT2_ANALOGUE_MAX]; + u8 vol_sw_hw_switch[SCARLETT2_ANALOGUE_MAX]; + u8 level_switch[SCARLETT2_LEVEL_SWITCH_MAX]; + u8 pad_switch[SCARLETT2_PAD_SWITCH_MAX]; + u8 buttons[SCARLETT2_BUTTON_MAX]; + struct snd_kcontrol *master_vol_ctl; + struct snd_kcontrol *vol_ctls[SCARLETT2_ANALOGUE_MAX]; + struct snd_kcontrol *button_ctls[SCARLETT2_BUTTON_MAX]; + u8 mux[SCARLETT2_MUX_MAX]; + u8 mix[SCARLETT2_INPUT_MIX_MAX * SCARLETT2_OUTPUT_MIX_MAX]; +}; + +/*** Model-specific data ***/ + +static const struct scarlett2_device_info s6i6_gen2_info = { + /* The first two analogue inputs can be switched between line + * and instrument levels. + */ + .level_input_count = 2, + + /* The first two analogue inputs have an optional pad. */ + .pad_input_count = 2, + + .line_out_descrs = { + "Monitor L", + "Monitor R", + "Headphones L", + "Headphones R", + }, + + .ports = { + { + .id = 0x000, + .num = { 1, 0, 8, 8, 8 }, + .src_descr = "Off", + .src_num_offset = 0, + }, + { + .id = 0x080, + .num = { 4, 4, 4, 4, 4 }, + .src_descr = "Analogue %d", + .src_num_offset = 1, + .dst_descr = "Analogue Output %02d Playback" + }, + { + .id = 0x180, + .num = { 2, 2, 2, 2, 2 }, + .src_descr = "S/PDIF %d", + .src_num_offset = 1, + .dst_descr = "S/PDIF Output %d Playback" + }, + { + .id = 0x300, + .num = { 10, 18, 18, 18, 18 }, + .src_descr = "Mix %c", + .src_num_offset = 65, + .dst_descr = "Mixer Input %02d Capture" + }, + { + .id = 0x600, + .num = { 6, 6, 6, 6, 6 }, + .src_descr = "PCM %d", + .src_num_offset = 1, + .dst_descr = "PCM %02d Capture" + }, + }, +}; + +static const struct scarlett2_device_info s18i8_gen2_info = { + /* The first two analogue inputs can be switched between line + * and instrument levels. + */ + .level_input_count = 2, + + /* The first four analogue inputs have an optional pad. */ + .pad_input_count = 4, + + .line_out_descrs = { + "Monitor L", + "Monitor R", + "Headphones 1 L", + "Headphones 1 R", + "Headphones 2 L", + "Headphones 2 R", + }, + + .ports = { + { + .id = 0x000, + .num = { 1, 0, 8, 8, 4 }, + .src_descr = "Off", + .src_num_offset = 0, + }, + { + .id = 0x080, + .num = { 8, 6, 6, 6, 6 }, + .src_descr = "Analogue %d", + .src_num_offset = 1, + .dst_descr = "Analogue Output %02d Playback" + }, + { + /* S/PDIF outputs aren't available at 192KHz + * but are included in the USB mux I/O + * assignment message anyway + */ + .id = 0x180, + .num = { 2, 2, 2, 2, 2 }, + .src_descr = "S/PDIF %d", + .src_num_offset = 1, + .dst_descr = "S/PDIF Output %d Playback" + }, + { + .id = 0x200, + .num = { 8, 0, 0, 0, 0 }, + .src_descr = "ADAT %d", + .src_num_offset = 1, + }, + { + .id = 0x300, + .num = { 10, 18, 18, 18, 18 }, + .src_descr = "Mix %c", + .src_num_offset = 65, + .dst_descr = "Mixer Input %02d Capture" + }, + { + .id = 0x600, + .num = { 20, 18, 18, 14, 10 }, + .src_descr = "PCM %d", + .src_num_offset = 1, + .dst_descr = "PCM %02d Capture" + }, + }, +}; + +static const struct scarlett2_device_info s18i20_gen2_info = { + /* The analogue line outputs on the 18i20 can be switched + * between software and hardware volume control + */ + .line_out_hw_vol = 1, + + /* Mute and dim buttons */ + .button_count = 2, + + .line_out_descrs = { + "Monitor L", + "Monitor R", + NULL, + NULL, + NULL, + NULL, + "Headphones 1 L", + "Headphones 1 R", + "Headphones 2 L", + "Headphones 2 R", + }, + + .ports = { + { + .id = 0x000, + .num = { 1, 0, 8, 8, 6 }, + .src_descr = "Off", + .src_num_offset = 0, + }, + { + .id = 0x080, + .num = { 8, 10, 10, 10, 10 }, + .src_descr = "Analogue %d", + .src_num_offset = 1, + .dst_descr = "Analogue Output %02d Playback" + }, + { + /* S/PDIF outputs aren't available at 192KHz + * but are included in the USB mux I/O + * assignment message anyway + */ + .id = 0x180, + .num = { 2, 2, 2, 2, 2 }, + .src_descr = "S/PDIF %d", + .src_num_offset = 1, + .dst_descr = "S/PDIF Output %d Playback" + }, + { + .id = 0x200, + .num = { 8, 8, 8, 4, 0 }, + .src_descr = "ADAT %d", + .src_num_offset = 1, + .dst_descr = "ADAT Output %d Playback" + }, + { + .id = 0x300, + .num = { 10, 18, 18, 18, 18 }, + .src_descr = "Mix %c", + .src_num_offset = 65, + .dst_descr = "Mixer Input %02d Capture" + }, + { + .id = 0x600, + .num = { 20, 18, 18, 14, 10 }, + .src_descr = "PCM %d", + .src_num_offset = 1, + .dst_descr = "PCM %02d Capture" + }, + }, +}; + +/* get the starting port index number for a given port type/direction */ +static int scarlett2_get_port_start_num(const struct scarlett2_ports *ports, + int direction, int port_type) +{ + int i, num = 0; + + for (i = 0; i < port_type; i++) + num += ports[i].num[direction]; + + return num; +} + +/*** USB Interactions ***/ + +/* Vendor-Specific Interface, Endpoint, MaxPacketSize, Interval */ +#define SCARLETT2_USB_VENDOR_SPECIFIC_INTERFACE 5 +#define SCARLETT2_USB_INTERRUPT_ENDPOINT 4 +#define SCARLETT2_USB_INTERRUPT_MAX_DATA 64 +#define SCARLETT2_USB_INTERRUPT_INTERVAL 3 + +/* Interrupt flags for volume and mute/dim button changes */ +#define SCARLETT2_USB_INTERRUPT_VOL_CHANGE 0x400000 +#define SCARLETT2_USB_INTERRUPT_BUTTON_CHANGE 0x200000 + +/* Commands for sending/receiving requests/responses */ +#define SCARLETT2_USB_VENDOR_SPECIFIC_CMD_REQ 2 +#define SCARLETT2_USB_VENDOR_SPECIFIC_CMD_RESP 3 + +#define SCARLETT2_USB_INIT_SEQ 0x00000000 +#define SCARLETT2_USB_GET_METER_LEVELS 0x00001001 +#define SCARLETT2_USB_SET_MIX 0x00002002 +#define SCARLETT2_USB_SET_MUX 0x00003002 +#define SCARLETT2_USB_GET_DATA 0x00800000 +#define SCARLETT2_USB_SET_DATA 0x00800001 +#define SCARLETT2_USB_DATA_CMD 0x00800002 +#define SCARLETT2_USB_CONFIG_SAVE 6 + +#define SCARLETT2_USB_VOLUME_STATUS_OFFSET 0x31 +#define SCARLETT2_USB_METER_LEVELS_GET_MAGIC 1 + +/* volume status is read together (matches scarlett2_config_items[]) */ +struct scarlett2_usb_volume_status { + /* mute & dim buttons */ + u8 buttons[SCARLETT2_BUTTON_MAX]; + + u8 pad1; + + /* software volume setting */ + s16 sw_vol[SCARLETT2_ANALOGUE_MAX]; + + /* actual volume of output inc. dim (-18dB) */ + s16 hw_vol[SCARLETT2_ANALOGUE_MAX]; + + u8 pad2[SCARLETT2_ANALOGUE_MAX]; + + /* sw (0) or hw (1) controlled */ + u8 sw_hw_switch[SCARLETT2_ANALOGUE_MAX]; + + u8 pad3[6]; + + /* front panel volume knob */ + s16 master_vol; +} __packed; + +/* Configuration parameters that can be read and written */ +enum { + SCARLETT2_CONFIG_BUTTONS = 0, + SCARLETT2_CONFIG_LINE_OUT_VOLUME = 1, + SCARLETT2_CONFIG_SW_HW_SWITCH = 2, + SCARLETT2_CONFIG_LEVEL_SWITCH = 3, + SCARLETT2_CONFIG_PAD_SWITCH = 4, + SCARLETT2_CONFIG_COUNT = 5 +}; + +/* Location, size, and activation command number for the configuration + * parameters + */ +struct scarlett2_config { + u8 offset; + u8 size; + u8 activate; +}; + +static const struct scarlett2_config + scarlett2_config_items[SCARLETT2_CONFIG_COUNT] = { + /* Mute/Dim Buttons */ + { + .offset = 0x31, + .size = 1, + .activate = 2 + }, + + /* Line Out Volume */ + { + .offset = 0x34, + .size = 2, + .activate = 1 + }, + + /* SW/HW Volume Switch */ + { + .offset = 0x66, + .size = 1, + .activate = 3 + }, + + /* Level Switch */ + { + .offset = 0x7c, + .size = 1, + .activate = 7 + }, + + /* Pad Switch */ + { + .offset = 0x84, + .size = 1, + .activate = 8 + } +}; + +/* proprietary request/response format */ +struct scarlett2_usb_packet { + u32 cmd; + u16 size; + u16 seq; + u32 error; + u32 pad; + u8 data[]; +}; + +#define SCARLETT2_USB_PACKET_LEN (sizeof(struct scarlett2_usb_packet)) + +static void scarlett2_fill_request_header(struct scarlett2_mixer_data *private, + struct scarlett2_usb_packet *req, + u32 cmd, u16 req_size) +{ + /* sequence must go up by 1 for each request */ + u16 seq = private->scarlett2_seq++; + + req->cmd = cpu_to_le32(cmd); + req->size = cpu_to_le16(req_size); + req->seq = cpu_to_le16(seq); + req->error = 0; + req->pad = 0; +} + +/* Send a proprietary format request to the Scarlett interface */ +static int scarlett2_usb( + struct usb_mixer_interface *mixer, u32 cmd, + void *req_data, u16 req_size, void *resp_data, u16 resp_size) +{ + struct scarlett2_mixer_data *private = mixer->private_data; + u16 req_buf_size = sizeof(struct scarlett2_usb_packet) + req_size; + u16 resp_buf_size = sizeof(struct scarlett2_usb_packet) + resp_size; + struct scarlett2_usb_packet *req = NULL, *resp = NULL; + int err = 0; + + req = kmalloc(req_buf_size, GFP_KERNEL); + if (!req) { + err = -ENOMEM; + goto error; + } + + resp = kmalloc(resp_buf_size, GFP_KERNEL); + if (!resp) { + err = -ENOMEM; + goto error; + } + + mutex_lock(&private->usb_mutex); + + /* build request message and send it */ + + scarlett2_fill_request_header(private, req, cmd, req_size); + + if (req_size) + memcpy(req->data, req_data, req_size); + + err = snd_usb_ctl_msg(mixer->chip->dev, + usb_sndctrlpipe(mixer->chip->dev, 0), + SCARLETT2_USB_VENDOR_SPECIFIC_CMD_REQ, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + 0, + SCARLETT2_USB_VENDOR_SPECIFIC_INTERFACE, + req, + req_buf_size); + + if (err != req_buf_size) { + usb_audio_err( + mixer->chip, + "Scarlett Gen 2 USB request result cmd %x was %d\n", + cmd, err); + err = -EINVAL; + goto unlock; + } + + /* send a second message to get the response */ + + err = snd_usb_ctl_msg(mixer->chip->dev, + usb_sndctrlpipe(mixer->chip->dev, 0), + SCARLETT2_USB_VENDOR_SPECIFIC_CMD_RESP, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + 0, + SCARLETT2_USB_VENDOR_SPECIFIC_INTERFACE, + resp, + resp_buf_size); + + /* validate the response */ + + if (err != resp_buf_size) { + usb_audio_err( + mixer->chip, + "Scarlett Gen 2 USB response result cmd %x was %d\n", + cmd, err); + err = -EINVAL; + goto unlock; + } + + if (resp->cmd != req->cmd || + resp->seq != req->seq || + resp_size != le16_to_cpu(resp->size) || + resp->error || + resp->pad) { + usb_audio_err( + mixer->chip, + "Scarlett Gen 2 USB invalid response; " + "cmd tx/rx %d/%d seq %d/%d size %d/%d " + "error %d pad %d\n", + le16_to_cpu(req->cmd), le16_to_cpu(resp->cmd), + le16_to_cpu(req->seq), le16_to_cpu(resp->seq), + resp_size, le16_to_cpu(resp->size), + le16_to_cpu(resp->error), + le16_to_cpu(resp->pad)); + err = -EINVAL; + goto unlock; + } + + if (resp_size > 0) + memcpy(resp_data, resp->data, resp_size); + +unlock: + mutex_unlock(&private->usb_mutex); +error: + kfree(req); + kfree(resp); + return err; +} + +/* Send SCARLETT2_USB_DATA_CMD SCARLETT2_USB_CONFIG_SAVE */ +static void scarlett2_config_save(struct usb_mixer_interface *mixer) +{ + u32 req = cpu_to_le32(SCARLETT2_USB_CONFIG_SAVE); + + scarlett2_usb(mixer, SCARLETT2_USB_DATA_CMD, + &req, sizeof(u32), + NULL, 0); +} + +/* Delayed work to save config */ +static void scarlett2_config_save_work(struct work_struct *work) +{ + struct scarlett2_mixer_data *private = + container_of(work, struct scarlett2_mixer_data, work.work); + + scarlett2_config_save(private->mixer); +} + +/* Send a USB message to set a configuration parameter (volume level, + * sw/hw volume switch, line/inst level switch, or pad switch) + */ +static int scarlett2_usb_set_config( + struct usb_mixer_interface *mixer, + int config_item_num, int index, int value) +{ + const struct scarlett2_config config_item = + scarlett2_config_items[config_item_num]; + struct { + u32 offset; + u32 bytes; + s32 value; + } __packed req; + u32 req2; + int err; + struct scarlett2_mixer_data *private = mixer->private_data; + + /* Cancel any pending NVRAM save */ + cancel_delayed_work_sync(&private->work); + + /* Send the configuration parameter data */ + req.offset = cpu_to_le32(config_item.offset + index * config_item.size); + req.bytes = cpu_to_le32(config_item.size); + req.value = cpu_to_le32(value); + err = scarlett2_usb(mixer, SCARLETT2_USB_SET_DATA, + &req, sizeof(u32) * 2 + config_item.size, + NULL, 0); + if (err < 0) + return err; + + /* Activate the change */ + req2 = cpu_to_le32(config_item.activate); + err = scarlett2_usb(mixer, SCARLETT2_USB_DATA_CMD, + &req2, sizeof(req2), NULL, 0); + if (err < 0) + return err; + + /* Schedule the change to be written to NVRAM */ + schedule_delayed_work(&private->work, msecs_to_jiffies(2000)); + + return 0; +} + +/* Send a USB message to get data; result placed in *buf */ +static int scarlett2_usb_get( + struct usb_mixer_interface *mixer, + int offset, void *buf, int size) +{ + struct { + u32 offset; + u32 size; + } __packed req; + + req.offset = cpu_to_le32(offset); + req.size = cpu_to_le32(size); + return scarlett2_usb(mixer, SCARLETT2_USB_GET_DATA, + &req, sizeof(req), buf, size); +} + +/* Send a USB message to get configuration parameters; result placed in *buf */ +static int scarlett2_usb_get_config( + struct usb_mixer_interface *mixer, + int config_item_num, int count, void *buf) +{ + const struct scarlett2_config config_item = + scarlett2_config_items[config_item_num]; + int size = config_item.size * count; + + return scarlett2_usb_get(mixer, config_item.offset, buf, size); +} + +/* Send a USB message to get volume status; result placed in *buf */ +static int scarlett2_usb_get_volume_status( + struct usb_mixer_interface *mixer, + struct scarlett2_usb_volume_status *buf) +{ + return scarlett2_usb_get(mixer, SCARLETT2_USB_VOLUME_STATUS_OFFSET, + buf, sizeof(*buf)); +} + +/* Send a USB message to set the volumes for all inputs of one mix + * (values obtained from private->mix[]) + */ +static int scarlett2_usb_set_mix(struct usb_mixer_interface *mixer, + int mix_num) +{ + struct scarlett2_mixer_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + + struct { + u16 mix_num; + u16 data[SCARLETT2_INPUT_MIX_MAX]; + } __packed req; + + int i, j; + int num_mixer_in = + info->ports[SCARLETT2_PORT_TYPE_MIX].num[SCARLETT2_PORT_OUT]; + + req.mix_num = cpu_to_le16(mix_num); + + for (i = 0, j = mix_num * num_mixer_in; i < num_mixer_in; i++, j++) + req.data[i] = cpu_to_le16( + scarlett2_mixer_values[private->mix[j]] + ); + + return scarlett2_usb(mixer, SCARLETT2_USB_SET_MIX, + &req, (num_mixer_in + 1) * sizeof(u16), + NULL, 0); +} + +/* Convert a port number index (per info->ports) to a hardware ID */ +static u32 scarlett2_mux_src_num_to_id(const struct scarlett2_ports *ports, + int num) +{ + int port_type; + + for (port_type = 0; + port_type < SCARLETT2_PORT_TYPE_COUNT; + port_type++) { + if (num < ports[port_type].num[SCARLETT2_PORT_IN]) + return ports[port_type].id | num; + num -= ports[port_type].num[SCARLETT2_PORT_IN]; + } + + /* Oops */ + return 0; +} + +/* Send USB messages to set mux inputs */ +static int scarlett2_usb_set_mux(struct usb_mixer_interface *mixer) +{ + struct scarlett2_mixer_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + const struct scarlett2_ports *ports = info->ports; + int rate, port_dir_rate; + + static const int assignment_order[SCARLETT2_PORT_TYPE_COUNT] = { + SCARLETT2_PORT_TYPE_PCM, + SCARLETT2_PORT_TYPE_ANALOGUE, + SCARLETT2_PORT_TYPE_SPDIF, + SCARLETT2_PORT_TYPE_ADAT, + SCARLETT2_PORT_TYPE_MIX, + SCARLETT2_PORT_TYPE_NONE, + }; + + struct { + u16 pad; + u16 num; + u32 data[SCARLETT2_MUX_MAX]; + } __packed req; + + req.pad = 0; + + /* mux settings for each rate */ + for (rate = 0, port_dir_rate = SCARLETT2_PORT_OUT_44; + port_dir_rate <= SCARLETT2_PORT_OUT_176; + rate++, port_dir_rate++) { + int order_num, i, err; + + req.num = cpu_to_le16(rate); + + for (order_num = 0, i = 0; + order_num < SCARLETT2_PORT_TYPE_COUNT; + order_num++) { + int port_type = assignment_order[order_num]; + int j = scarlett2_get_port_start_num(ports, + SCARLETT2_PORT_OUT, + port_type); + int port_id = ports[port_type].id; + int channel; + + for (channel = 0; + channel < ports[port_type].num[port_dir_rate]; + channel++, i++, j++) + /* lower 12 bits for the destination and + * next 12 bits for the source + */ + req.data[i] = !port_id + ? 0 + : cpu_to_le32( + port_id | + channel | + scarlett2_mux_src_num_to_id( + ports, private->mux[j] + ) << 12 + ); + + /* skip private->mux[j] entries not output */ + j += ports[port_type].num[SCARLETT2_PORT_OUT] - + ports[port_type].num[port_dir_rate]; + } + + err = scarlett2_usb(mixer, SCARLETT2_USB_SET_MUX, + &req, (i + 1) * sizeof(u32), + NULL, 0); + if (err < 0) + return err; + } + + return 0; +} + +/* Send USB message to get meter levels */ +static int scarlett2_usb_get_meter_levels(struct usb_mixer_interface *mixer, + u16 *levels) +{ + struct { + u16 pad; + u16 num_meters; + u32 magic; + } __packed req; + u32 resp[SCARLETT2_NUM_METERS]; + int i, err; + + req.pad = 0; + req.num_meters = cpu_to_le16(SCARLETT2_NUM_METERS); + req.magic = cpu_to_le32(SCARLETT2_USB_METER_LEVELS_GET_MAGIC); + err = scarlett2_usb(mixer, SCARLETT2_USB_GET_METER_LEVELS, + &req, sizeof(req), resp, sizeof(resp)); + if (err < 0) + return err; + + /* copy, convert to u16 */ + for (i = 0; i < SCARLETT2_NUM_METERS; i++) + levels[i] = resp[i]; + + return 0; +} + +/*** Control Functions ***/ + +/* helper function to create a new control */ +static int scarlett2_add_new_ctl(struct usb_mixer_interface *mixer, + const struct snd_kcontrol_new *ncontrol, + int index, int channels, const char *name, + struct snd_kcontrol **kctl_return) +{ + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *elem; + int err; + + elem = kzalloc(sizeof(*elem), GFP_KERNEL); + if (!elem) + return -ENOMEM; + + elem->head.mixer = mixer; + elem->control = index; + elem->head.id = index; + elem->channels = channels; + + kctl = snd_ctl_new1(ncontrol, elem); + if (!kctl) { + kfree(elem); + return -ENOMEM; + } + kctl->private_free = snd_usb_mixer_elem_free; + + strlcpy(kctl->id.name, name, sizeof(kctl->id.name)); + + err = snd_usb_mixer_add_control(&elem->head, kctl); + if (err < 0) + return err; + + if (kctl_return) + *kctl_return = kctl; + + return 0; +} + +/*** Analogue Line Out Volume Controls ***/ + +/* Update hardware volume controls after receiving notification that + * they have changed + */ +static int scarlett2_update_volumes(struct usb_mixer_interface *mixer) +{ + struct scarlett2_mixer_data *private = mixer->private_data; + const struct scarlett2_ports *ports = private->info->ports; + struct scarlett2_usb_volume_status volume_status; + int num_line_out = + ports[SCARLETT2_PORT_TYPE_ANALOGUE].num[SCARLETT2_PORT_OUT]; + int err, i; + + private->vol_updated = 0; + + err = scarlett2_usb_get_volume_status(mixer, &volume_status); + if (err < 0) + return err; + + private->master_vol = clamp( + volume_status.master_vol + SCARLETT2_VOLUME_BIAS, + 0, SCARLETT2_VOLUME_BIAS); + + for (i = 0; i < num_line_out; i++) { + if (private->vol_sw_hw_switch[i]) + private->vol[i] = private->master_vol; + } + + for (i = 0; i < private->info->button_count; i++) + private->buttons[i] = !!volume_status.buttons[i]; + + return 0; +} + +static int scarlett2_volume_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = elem->channels; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SCARLETT2_VOLUME_BIAS; + uinfo->value.integer.step = 1; + return 0; +} + +static int scarlett2_master_volume_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_mixer_data *private = mixer->private_data; + + if (private->vol_updated) { + mutex_lock(&private->data_mutex); + scarlett2_update_volumes(mixer); + mutex_unlock(&private->data_mutex); + } + + ucontrol->value.integer.value[0] = private->master_vol; + return 0; +} + +static int scarlett2_volume_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_mixer_data *private = mixer->private_data; + int index = elem->control; + + if (private->vol_updated) { + mutex_lock(&private->data_mutex); + scarlett2_update_volumes(mixer); + mutex_unlock(&private->data_mutex); + } + + ucontrol->value.integer.value[0] = private->vol[index]; + return 0; +} + +static int scarlett2_volume_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_mixer_data *private = mixer->private_data; + int index = elem->control; + int oval, val, err = 0; + + mutex_lock(&private->data_mutex); + + oval = private->vol[index]; + val = ucontrol->value.integer.value[0]; + + if (oval == val) + goto unlock; + + private->vol[index] = val; + err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_LINE_OUT_VOLUME, + index, val - SCARLETT2_VOLUME_BIAS); + if (err == 0) + err = 1; + +unlock: + mutex_unlock(&private->data_mutex); + return err; +} + +static const DECLARE_TLV_DB_MINMAX( + db_scale_scarlett2_gain, -SCARLETT2_VOLUME_BIAS * 100, 0 +); + +static const struct snd_kcontrol_new scarlett2_master_volume_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett2_volume_ctl_info, + .get = scarlett2_master_volume_ctl_get, + .private_value = 0, /* max value */ + .tlv = { .p = db_scale_scarlett2_gain } +}; + +static const struct snd_kcontrol_new scarlett2_line_out_volume_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett2_volume_ctl_info, + .get = scarlett2_volume_ctl_get, + .put = scarlett2_volume_ctl_put, + .private_value = 0, /* max value */ + .tlv = { .p = db_scale_scarlett2_gain } +}; + +/*** HW/SW Volume Switch Controls ***/ + +static int scarlett2_sw_hw_enum_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const values[2] = { + "SW", "HW" + }; + + return snd_ctl_enum_info(uinfo, 1, 2, values); +} + +static int scarlett2_sw_hw_enum_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_mixer_data *private = elem->head.mixer->private_data; + + ucontrol->value.enumerated.item[0] = + private->vol_sw_hw_switch[elem->control]; + return 0; +} + +static int scarlett2_sw_hw_enum_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_mixer_data *private = mixer->private_data; + + int index = elem->control; + int oval, val, err = 0; + + mutex_lock(&private->data_mutex); + + oval = private->vol_sw_hw_switch[index]; + val = !!ucontrol->value.integer.value[0]; + + if (oval == val) + goto unlock; + + private->vol_sw_hw_switch[index] = val; + + /* Change access mode to RO (hardware controlled volume) + * or RW (software controlled volume) + */ + if (val) + private->vol_ctls[index]->vd[0].access &= + ~SNDRV_CTL_ELEM_ACCESS_WRITE; + else + private->vol_ctls[index]->vd[0].access |= + SNDRV_CTL_ELEM_ACCESS_WRITE; + + /* Reset volume to master volume */ + private->vol[index] = private->master_vol; + + /* Set SW volume to current HW volume */ + err = scarlett2_usb_set_config( + mixer, SCARLETT2_CONFIG_LINE_OUT_VOLUME, + index, private->master_vol - SCARLETT2_VOLUME_BIAS); + if (err < 0) + goto unlock; + + /* Notify of RO/RW change */ + snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_INFO, + &private->vol_ctls[index]->id); + + /* Send SW/HW switch change to the device */ + err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_SW_HW_SWITCH, + index, val); + +unlock: + mutex_unlock(&private->data_mutex); + return err; +} + +static const struct snd_kcontrol_new scarlett2_sw_hw_enum_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett2_sw_hw_enum_ctl_info, + .get = scarlett2_sw_hw_enum_ctl_get, + .put = scarlett2_sw_hw_enum_ctl_put, +}; + +/*** Line Level/Instrument Level Switch Controls ***/ + +static int scarlett2_level_enum_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const values[2] = { + "Line", "Inst" + }; + + return snd_ctl_enum_info(uinfo, 1, 2, values); +} + +static int scarlett2_level_enum_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_mixer_data *private = elem->head.mixer->private_data; + + ucontrol->value.enumerated.item[0] = + private->level_switch[elem->control]; + return 0; +} + +static int scarlett2_level_enum_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_mixer_data *private = mixer->private_data; + + int index = elem->control; + int oval, val, err = 0; + + mutex_lock(&private->data_mutex); + + oval = private->level_switch[index]; + val = !!ucontrol->value.integer.value[0]; + + if (oval == val) + goto unlock; + + private->level_switch[index] = val; + + /* Send switch change to the device */ + err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_LEVEL_SWITCH, + index, val); + +unlock: + mutex_unlock(&private->data_mutex); + return err; +} + +static const struct snd_kcontrol_new scarlett2_level_enum_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett2_level_enum_ctl_info, + .get = scarlett2_level_enum_ctl_get, + .put = scarlett2_level_enum_ctl_put, +}; + +/*** Pad Switch Controls ***/ + +static int scarlett2_pad_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_mixer_data *private = elem->head.mixer->private_data; + + ucontrol->value.enumerated.item[0] = + private->pad_switch[elem->control]; + return 0; +} + +static int scarlett2_pad_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_mixer_data *private = mixer->private_data; + + int index = elem->control; + int oval, val, err = 0; + + mutex_lock(&private->data_mutex); + + oval = private->pad_switch[index]; + val = !!ucontrol->value.integer.value[0]; + + if (oval == val) + goto unlock; + + private->pad_switch[index] = val; + + /* Send switch change to the device */ + err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_PAD_SWITCH, + index, val); + +unlock: + mutex_unlock(&private->data_mutex); + return err; +} + +static const struct snd_kcontrol_new scarlett2_pad_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = snd_ctl_boolean_mono_info, + .get = scarlett2_pad_ctl_get, + .put = scarlett2_pad_ctl_put, +}; + +/*** Mute/Dim Controls ***/ + +static int scarlett2_button_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_mixer_data *private = mixer->private_data; + + if (private->vol_updated) { + mutex_lock(&private->data_mutex); + scarlett2_update_volumes(mixer); + mutex_unlock(&private->data_mutex); + } + + ucontrol->value.enumerated.item[0] = private->buttons[elem->control]; + return 0; +} + +static int scarlett2_button_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_mixer_data *private = mixer->private_data; + + int index = elem->control; + int oval, val, err = 0; + + mutex_lock(&private->data_mutex); + + oval = private->buttons[index]; + val = !!ucontrol->value.integer.value[0]; + + if (oval == val) + goto unlock; + + private->buttons[index] = val; + + /* Send switch change to the device */ + err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_BUTTONS, + index, val); + +unlock: + mutex_unlock(&private->data_mutex); + return err; +} + +static const struct snd_kcontrol_new scarlett2_button_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = snd_ctl_boolean_mono_info, + .get = scarlett2_button_ctl_get, + .put = scarlett2_button_ctl_put +}; + +/*** Create the analogue output controls ***/ + +static int scarlett2_add_line_out_ctls(struct usb_mixer_interface *mixer) +{ + struct scarlett2_mixer_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + const struct scarlett2_ports *ports = info->ports; + int num_line_out = + ports[SCARLETT2_PORT_TYPE_ANALOGUE].num[SCARLETT2_PORT_OUT]; + int err, i; + char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + + /* Add R/O HW volume control */ + if (info->line_out_hw_vol) { + snprintf(s, sizeof(s), "Master HW Playback Volume"); + err = scarlett2_add_new_ctl(mixer, + &scarlett2_master_volume_ctl, + 0, 1, s, &private->master_vol_ctl); + if (err < 0) + return err; + } + + /* Add volume controls */ + for (i = 0; i < num_line_out; i++) { + + /* Fader */ + if (info->line_out_descrs[i]) + snprintf(s, sizeof(s), + "Line %02d (%s) Playback Volume", + i + 1, info->line_out_descrs[i]); + else + snprintf(s, sizeof(s), + "Line %02d Playback Volume", + i + 1); + err = scarlett2_add_new_ctl(mixer, + &scarlett2_line_out_volume_ctl, + i, 1, s, &private->vol_ctls[i]); + if (err < 0) + return err; + + /* Make the fader read-only if the SW/HW switch is set to HW */ + if (private->vol_sw_hw_switch[i]) + private->vol_ctls[i]->vd[0].access &= + ~SNDRV_CTL_ELEM_ACCESS_WRITE; + + /* SW/HW Switch */ + if (info->line_out_hw_vol) { + snprintf(s, sizeof(s), + "Line Out %02d Volume Control Playback Enum", + i + 1); + err = scarlett2_add_new_ctl(mixer, + &scarlett2_sw_hw_enum_ctl, + i, 1, s, NULL); + if (err < 0) + return err; + } + } + + /* Add HW button controls */ + for (i = 0; i < private->info->button_count; i++) { + err = scarlett2_add_new_ctl(mixer, &scarlett2_button_ctl, + i, 1, scarlett2_button_names[i], + &private->button_ctls[i]); + if (err < 0) + return err; + } + + return 0; +} + +/*** Create the analogue input controls ***/ + +static int scarlett2_add_line_in_ctls(struct usb_mixer_interface *mixer) +{ + struct scarlett2_mixer_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int err, i; + char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + + /* Add input level (line/inst) controls */ + for (i = 0; i < info->level_input_count; i++) { + snprintf(s, sizeof(s), "Line In %d Level Capture Enum", i + 1); + err = scarlett2_add_new_ctl(mixer, &scarlett2_level_enum_ctl, + i, 1, s, NULL); + if (err < 0) + return err; + } + + /* Add input pad controls */ + for (i = 0; i < info->pad_input_count; i++) { + snprintf(s, sizeof(s), "Line In %d Pad Capture Switch", i + 1); + err = scarlett2_add_new_ctl(mixer, &scarlett2_pad_ctl, + i, 1, s, NULL); + if (err < 0) + return err; + } + + return 0; +} + +/*** Mixer Volume Controls ***/ + +static int scarlett2_mixer_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = elem->channels; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SCARLETT2_MIXER_MAX_VALUE; + uinfo->value.integer.step = 1; + return 0; +} + +static int scarlett2_mixer_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_mixer_data *private = elem->head.mixer->private_data; + + ucontrol->value.integer.value[0] = private->mix[elem->control]; + return 0; +} + +static int scarlett2_mixer_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_mixer_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + const struct scarlett2_ports *ports = info->ports; + int oval, val, num_mixer_in, mix_num, err = 0; + + mutex_lock(&private->data_mutex); + + oval = private->mix[elem->control]; + val = ucontrol->value.integer.value[0]; + num_mixer_in = ports[SCARLETT2_PORT_TYPE_MIX].num[SCARLETT2_PORT_OUT]; + mix_num = elem->control / num_mixer_in; + + if (oval == val) + goto unlock; + + private->mix[elem->control] = val; + err = scarlett2_usb_set_mix(mixer, mix_num); + if (err == 0) + err = 1; + +unlock: + mutex_unlock(&private->data_mutex); + return err; +} + +static const DECLARE_TLV_DB_MINMAX( + db_scale_scarlett2_mixer, + SCARLETT2_MIXER_MIN_DB * 100, + SCARLETT2_MIXER_MAX_DB * 100 +); + +static const struct snd_kcontrol_new scarlett2_mixer_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett2_mixer_ctl_info, + .get = scarlett2_mixer_ctl_get, + .put = scarlett2_mixer_ctl_put, + .private_value = SCARLETT2_MIXER_MAX_DB, /* max value */ + .tlv = { .p = db_scale_scarlett2_mixer } +}; + +static int scarlett2_add_mixer_ctls(struct usb_mixer_interface *mixer) +{ + struct scarlett2_mixer_data *private = mixer->private_data; + const struct scarlett2_ports *ports = private->info->ports; + int err, i, j; + int index; + char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + + int num_inputs = ports[SCARLETT2_PORT_TYPE_MIX].num[SCARLETT2_PORT_OUT]; + int num_outputs = ports[SCARLETT2_PORT_TYPE_MIX].num[SCARLETT2_PORT_IN]; + + for (i = 0, index = 0; i < num_outputs; i++) { + for (j = 0; j < num_inputs; j++, index++) { + snprintf(s, sizeof(s), + "Mix %c Input %02d Playback Volume", + 'A' + i, j + 1); + err = scarlett2_add_new_ctl(mixer, &scarlett2_mixer_ctl, + index, 1, s, NULL); + if (err < 0) + return err; + } + } + + return 0; +} + +/*** Mux Source Selection Controls ***/ + +static int scarlett2_mux_src_enum_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_mixer_data *private = elem->head.mixer->private_data; + const struct scarlett2_ports *ports = private->info->ports; + unsigned int item = uinfo->value.enumerated.item; + int items = private->num_mux_srcs; + int port_type; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = elem->channels; + uinfo->value.enumerated.items = items; + + if (item >= items) + item = uinfo->value.enumerated.item = items - 1; + + for (port_type = 0; + port_type < SCARLETT2_PORT_TYPE_COUNT; + port_type++) { + if (item < ports[port_type].num[SCARLETT2_PORT_IN]) { + sprintf(uinfo->value.enumerated.name, + ports[port_type].src_descr, + item + ports[port_type].src_num_offset); + return 0; + } + item -= ports[port_type].num[SCARLETT2_PORT_IN]; + } + + return -EINVAL; +} + +static int scarlett2_mux_src_enum_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_mixer_data *private = elem->head.mixer->private_data; + + ucontrol->value.enumerated.item[0] = private->mux[elem->control]; + return 0; +} + +static int scarlett2_mux_src_enum_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_mixer_data *private = mixer->private_data; + int index = elem->control; + int oval, val, err = 0; + + mutex_lock(&private->data_mutex); + + oval = private->mux[index]; + val = clamp(ucontrol->value.integer.value[0], + 0L, private->num_mux_srcs - 1L); + + if (oval == val) + goto unlock; + + private->mux[index] = val; + err = scarlett2_usb_set_mux(mixer); + if (err == 0) + err = 1; + +unlock: + mutex_unlock(&private->data_mutex); + return err; +} + +static const struct snd_kcontrol_new scarlett2_mux_src_enum_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett2_mux_src_enum_ctl_info, + .get = scarlett2_mux_src_enum_ctl_get, + .put = scarlett2_mux_src_enum_ctl_put, +}; + +static int scarlett2_add_mux_enums(struct usb_mixer_interface *mixer) +{ + struct scarlett2_mixer_data *private = mixer->private_data; + const struct scarlett2_ports *ports = private->info->ports; + int port_type, channel, i; + + for (i = 0, port_type = 0; + port_type < SCARLETT2_PORT_TYPE_COUNT; + port_type++) { + for (channel = 0; + channel < ports[port_type].num[SCARLETT2_PORT_OUT]; + channel++, i++) { + int err; + char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + const char *const descr = ports[port_type].dst_descr; + + snprintf(s, sizeof(s) - 5, descr, channel + 1); + strcat(s, " Enum"); + + err = scarlett2_add_new_ctl(mixer, + &scarlett2_mux_src_enum_ctl, + i, 1, s, NULL); + if (err < 0) + return err; + } + } + + return 0; +} + +/*** Meter Controls ***/ + +static int scarlett2_meter_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = elem->channels; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 4095; + uinfo->value.integer.step = 1; + return 0; +} + +static int scarlett2_meter_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + u16 meter_levels[SCARLETT2_NUM_METERS]; + int i, err; + + err = scarlett2_usb_get_meter_levels(elem->head.mixer, meter_levels); + if (err < 0) + return err; + + for (i = 0; i < elem->channels; i++) + ucontrol->value.integer.value[i] = meter_levels[i]; + + return 0; +} + +static const struct snd_kcontrol_new scarlett2_meter_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .name = "", + .info = scarlett2_meter_ctl_info, + .get = scarlett2_meter_ctl_get +}; + +static int scarlett2_add_meter_ctl(struct usb_mixer_interface *mixer) +{ + return scarlett2_add_new_ctl(mixer, &scarlett2_meter_ctl, + 0, SCARLETT2_NUM_METERS, + "Level Meter", NULL); +} + +/*** Cleanup/Suspend Callbacks ***/ + +static void scarlett2_private_free(struct usb_mixer_interface *mixer) +{ + struct scarlett2_mixer_data *private = mixer->private_data; + + cancel_delayed_work_sync(&private->work); + kfree(private); + mixer->private_data = NULL; +} + +static void scarlett2_private_suspend(struct usb_mixer_interface *mixer) +{ + struct scarlett2_mixer_data *private = mixer->private_data; + + if (cancel_delayed_work_sync(&private->work)) + scarlett2_config_save(private->mixer); +} + +/*** Initialisation ***/ + +static int scarlett2_count_mux_srcs(const struct scarlett2_ports *ports) +{ + int port_type, count = 0; + + for (port_type = 0; + port_type < SCARLETT2_PORT_TYPE_COUNT; + port_type++) + count += ports[port_type].num[SCARLETT2_PORT_IN]; + + return count; +} + +/* Default routing connects PCM outputs and inputs to Analogue, + * S/PDIF, then ADAT + */ +static void scarlett2_init_routing(u8 *mux, + const struct scarlett2_ports *ports) +{ + int i, input_num, input_count, port_type; + int output_num, output_count, port_type_connect_num; + + static const int connect_order[] = { + SCARLETT2_PORT_TYPE_ANALOGUE, + SCARLETT2_PORT_TYPE_SPDIF, + SCARLETT2_PORT_TYPE_ADAT, + -1 + }; + + /* Assign PCM inputs (routing outputs) */ + output_num = scarlett2_get_port_start_num(ports, + SCARLETT2_PORT_OUT, + SCARLETT2_PORT_TYPE_PCM); + output_count = ports[SCARLETT2_PORT_TYPE_PCM].num[SCARLETT2_PORT_OUT]; + + for (port_type = connect_order[port_type_connect_num = 0]; + port_type >= 0; + port_type = connect_order[++port_type_connect_num]) { + input_num = scarlett2_get_port_start_num( + ports, SCARLETT2_PORT_IN, port_type); + input_count = ports[port_type].num[SCARLETT2_PORT_IN]; + for (i = 0; + i < input_count && output_count; + i++, output_count--) + mux[output_num++] = input_num++; + } + + /* Assign PCM outputs (routing inputs) */ + input_num = scarlett2_get_port_start_num(ports, + SCARLETT2_PORT_IN, + SCARLETT2_PORT_TYPE_PCM); + input_count = ports[SCARLETT2_PORT_TYPE_PCM].num[SCARLETT2_PORT_IN]; + + for (port_type = connect_order[port_type_connect_num = 0]; + port_type >= 0; + port_type = connect_order[++port_type_connect_num]) { + output_num = scarlett2_get_port_start_num( + ports, SCARLETT2_PORT_OUT, port_type); + output_count = ports[port_type].num[SCARLETT2_PORT_OUT]; + for (i = 0; + i < output_count && input_count; + i++, input_count--) + mux[output_num++] = input_num++; + } +} + +/* Initialise private data, routing, sequence number */ +static int scarlett2_init_private(struct usb_mixer_interface *mixer, + const struct scarlett2_device_info *info) +{ + struct scarlett2_mixer_data *private = + kzalloc(sizeof(struct scarlett2_mixer_data), GFP_KERNEL); + + if (!private) + return -ENOMEM; + + mutex_init(&private->usb_mutex); + mutex_init(&private->data_mutex); + INIT_DELAYED_WORK(&private->work, scarlett2_config_save_work); + private->info = info; + private->num_mux_srcs = scarlett2_count_mux_srcs(info->ports); + private->scarlett2_seq = 0; + private->mixer = mixer; + mixer->private_data = private; + mixer->private_free = scarlett2_private_free; + mixer->private_suspend = scarlett2_private_suspend; + + /* Setup default routing */ + scarlett2_init_routing(private->mux, info->ports); + + /* Initialise the sequence number used for the proprietary commands */ + return scarlett2_usb(mixer, SCARLETT2_USB_INIT_SEQ, NULL, 0, NULL, 0); +} + +/* Read line-in config and line-out volume settings on start */ +static int scarlett2_read_configs(struct usb_mixer_interface *mixer) +{ + struct scarlett2_mixer_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + const struct scarlett2_ports *ports = info->ports; + int num_line_out = + ports[SCARLETT2_PORT_TYPE_ANALOGUE].num[SCARLETT2_PORT_OUT]; + u8 level_switches[SCARLETT2_LEVEL_SWITCH_MAX]; + u8 pad_switches[SCARLETT2_PAD_SWITCH_MAX]; + struct scarlett2_usb_volume_status volume_status; + int err, i; + + if (info->level_input_count) { + err = scarlett2_usb_get_config( + mixer, + SCARLETT2_CONFIG_LEVEL_SWITCH, + info->level_input_count, + level_switches); + if (err < 0) + return err; + for (i = 0; i < info->level_input_count; i++) + private->level_switch[i] = level_switches[i]; + } + + if (info->pad_input_count) { + err = scarlett2_usb_get_config( + mixer, + SCARLETT2_CONFIG_PAD_SWITCH, + info->pad_input_count, + pad_switches); + if (err < 0) + return err; + for (i = 0; i < info->pad_input_count; i++) + private->pad_switch[i] = pad_switches[i]; + } + + err = scarlett2_usb_get_volume_status(mixer, &volume_status); + if (err < 0) + return err; + + private->master_vol = clamp( + volume_status.master_vol + SCARLETT2_VOLUME_BIAS, + 0, SCARLETT2_VOLUME_BIAS); + + for (i = 0; i < num_line_out; i++) { + int volume; + + private->vol_sw_hw_switch[i] = + info->line_out_hw_vol + && volume_status.sw_hw_switch[i]; + + volume = private->vol_sw_hw_switch[i] + ? volume_status.master_vol + : volume_status.sw_vol[i]; + volume = clamp(volume + SCARLETT2_VOLUME_BIAS, + 0, SCARLETT2_VOLUME_BIAS); + private->vol[i] = volume; + } + + for (i = 0; i < info->button_count; i++) + private->buttons[i] = !!volume_status.buttons[i]; + + return 0; +} + +/* Notify on volume change */ +static void scarlett2_mixer_interrupt_vol_change( + struct usb_mixer_interface *mixer) +{ + struct scarlett2_mixer_data *private = mixer->private_data; + const struct scarlett2_ports *ports = private->info->ports; + int num_line_out = + ports[SCARLETT2_PORT_TYPE_ANALOGUE].num[SCARLETT2_PORT_OUT]; + int i; + + private->vol_updated = 1; + + snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->master_vol_ctl->id); + + for (i = 0; i < num_line_out; i++) { + if (!private->vol_sw_hw_switch[i]) + continue; + snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->vol_ctls[i]->id); + } +} + +/* Notify on button change */ +static void scarlett2_mixer_interrupt_button_change( + struct usb_mixer_interface *mixer) +{ + struct scarlett2_mixer_data *private = mixer->private_data; + int i; + + private->vol_updated = 1; + + for (i = 0; i < private->info->button_count; i++) + snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->button_ctls[i]->id); +} + +/* Interrupt callback */ +static void scarlett2_mixer_interrupt(struct urb *urb) +{ + struct usb_mixer_interface *mixer = urb->context; + int len = urb->actual_length; + int ustatus = urb->status; + u32 data; + + if (ustatus != 0) + goto requeue; + + if (len == 8) { + data = le32_to_cpu(*(u32 *)urb->transfer_buffer); + if (data & SCARLETT2_USB_INTERRUPT_VOL_CHANGE) + scarlett2_mixer_interrupt_vol_change(mixer); + if (data & SCARLETT2_USB_INTERRUPT_BUTTON_CHANGE) + scarlett2_mixer_interrupt_button_change(mixer); + } else { + usb_audio_err(mixer->chip, + "scarlett mixer interrupt length %d\n", len); + } + +requeue: + if (ustatus != -ENOENT && + ustatus != -ECONNRESET && + ustatus != -ESHUTDOWN) { + urb->dev = mixer->chip->dev; + usb_submit_urb(urb, GFP_ATOMIC); + } +} + +static int scarlett2_mixer_status_create(struct usb_mixer_interface *mixer) +{ + struct usb_device *dev = mixer->chip->dev; + unsigned int pipe = usb_rcvintpipe(dev, + SCARLETT2_USB_INTERRUPT_ENDPOINT); + void *transfer_buffer; + + if (mixer->urb) { + usb_audio_err(mixer->chip, + "%s: mixer urb already in use!\n", __func__); + return 0; + } + + if (snd_usb_pipe_sanity_check(dev, pipe)) + return -EINVAL; + + mixer->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!mixer->urb) + return -ENOMEM; + + transfer_buffer = kmalloc(SCARLETT2_USB_INTERRUPT_MAX_DATA, GFP_KERNEL); + if (!transfer_buffer) + return -ENOMEM; + + usb_fill_int_urb(mixer->urb, dev, pipe, + transfer_buffer, SCARLETT2_USB_INTERRUPT_MAX_DATA, + scarlett2_mixer_interrupt, mixer, + SCARLETT2_USB_INTERRUPT_INTERVAL); + + return usb_submit_urb(mixer->urb, GFP_KERNEL); +} + +/* Entry point */ +int snd_scarlett_gen2_controls_create(struct usb_mixer_interface *mixer) +{ + const struct scarlett2_device_info *info; + int err; + + /* only use UAC_VERSION_2 */ + if (!mixer->protocol) + return 0; + + switch (mixer->chip->usb_id) { + case USB_ID(0x1235, 0x8203): + info = &s6i6_gen2_info; + break; + case USB_ID(0x1235, 0x8204): + info = &s18i8_gen2_info; + break; + case USB_ID(0x1235, 0x8201): + info = &s18i20_gen2_info; + break; + default: /* device not (yet) supported */ + return -EINVAL; + } + + if (!(mixer->chip->setup & SCARLETT2_ENABLE)) { + usb_audio_err(mixer->chip, + "Focusrite Scarlett Gen 2 Mixer Driver disabled; " + "use options snd_usb_audio device_setup=1 " + "to enable and report any issues to g@b4.vu"); + return 0; + } + + /* Initialise private data, routing, sequence number */ + err = scarlett2_init_private(mixer, info); + if (err < 0) + return err; + + /* Read volume levels and controls from the interface */ + err = scarlett2_read_configs(mixer); + if (err < 0) + return err; + + /* Create the analogue output controls */ + err = scarlett2_add_line_out_ctls(mixer); + if (err < 0) + return err; + + /* Create the analogue input controls */ + err = scarlett2_add_line_in_ctls(mixer); + if (err < 0) + return err; + + /* Create the input, output, and mixer mux input selections */ + err = scarlett2_add_mux_enums(mixer); + if (err < 0) + return err; + + /* Create the matrix mixer controls */ + err = scarlett2_add_mixer_ctls(mixer); + if (err < 0) + return err; + + /* Create the level meter controls */ + err = scarlett2_add_meter_ctl(mixer); + if (err < 0) + return err; + + /* Set up the interrupt polling if there are hardware buttons */ + if (info->button_count) { + err = scarlett2_mixer_status_create(mixer); + if (err < 0) + return err; + } + + return 0; +} diff --git a/sound/usb/mixer_scarlett_gen2.h b/sound/usb/mixer_scarlett_gen2.h new file mode 100644 index 000000000000..52e1dad77afd --- /dev/null +++ b/sound/usb/mixer_scarlett_gen2.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USB_MIXER_SCARLETT_GEN2_H +#define __USB_MIXER_SCARLETT_GEN2_H + +int snd_scarlett_gen2_controls_create(struct usb_mixer_interface *mixer); + +#endif /* __USB_MIXER_SCARLETT_GEN2_H */ diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index e4bbf79de956..33cd26763c0e 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -457,6 +457,7 @@ static int set_sync_endpoint(struct snd_usb_substream *subs, } ep = get_endpoint(alts, 1)->bEndpointAddress; if (get_endpoint(alts, 0)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && + get_endpoint(alts, 0)->bSynchAddress != 0 && ((is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress | USB_DIR_IN)) || (!is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress & ~USB_DIR_IN)))) { dev_err(&dev->dev, diff --git a/sound/usb/power.c b/sound/usb/power.c index bd303a1ba1b7..606a2cb23eab 100644 --- a/sound/usb/power.c +++ b/sound/usb/power.c @@ -31,6 +31,8 @@ snd_usb_find_power_domain(struct usb_host_interface *ctrl_iface, struct uac3_power_domain_descriptor *pd_desc = p; int i; + if (!snd_usb_validate_audio_desc(p, UAC_VERSION_3)) + continue; for (i = 0; i < pd_desc->bNrEntities; i++) { if (pd_desc->baEntityID[i] == id) { pd->pd_id = pd_desc->bPowerDomainID; diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index e918ce346027..70c338f3ae24 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -3534,5 +3534,62 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), } } }, +{ + /* + * PIONEER DJ DDJ-SX3 + * PCM is 12 channels out, 10 channels in @ 44.1 fixed + * interface 0, vendor class alt setting 1 for endpoints 5 and 0x86 + * The feedback for the output is the input. + */ + USB_DEVICE_VENDOR_SPEC(0x2b73, 0x0023), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 12, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x05, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + { + .ifnum = 0, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 10, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x86, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC| + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + { + .ifnum = -1 + } + } + } +}, #undef USB_DEVICE_VENDOR_SPEC diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 78858918cbc1..25faf2d3c639 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -248,6 +248,9 @@ static int create_yamaha_midi_quirk(struct snd_usb_audio *chip, NULL, USB_MS_MIDI_OUT_JACK); if (!injd && !outjd) return -ENODEV; + if (!(injd && snd_usb_validate_midi_desc(injd)) || + !(outjd && snd_usb_validate_midi_desc(outjd))) + return -ENODEV; if (injd && (injd->bLength < 5 || (injd->bJackType != USB_MS_EMBEDDED && injd->bJackType != USB_MS_EXTERNAL))) @@ -1563,7 +1566,8 @@ u64 snd_usb_interface_dsd_format_quirks(struct snd_usb_audio *chip, struct usb_interface *iface; /* Playback Designs */ - if (USB_ID_VENDOR(chip->usb_id) == 0x23ba) { + if (USB_ID_VENDOR(chip->usb_id) == 0x23ba && + USB_ID_PRODUCT(chip->usb_id) < 0x0110) { switch (fp->altsetting) { case 1: fp->dsd_dop = true; @@ -1580,9 +1584,6 @@ u64 snd_usb_interface_dsd_format_quirks(struct snd_usb_audio *chip, /* XMOS based USB DACs */ switch (chip->usb_id) { case USB_ID(0x1511, 0x0037): /* AURALiC VEGA */ - case USB_ID(0x22d9, 0x0416): /* OPPO HA-1 */ - case USB_ID(0x22d9, 0x0436): /* OPPO Sonica */ - case USB_ID(0x22d9, 0x0461): /* OPPO UDP-205 */ case USB_ID(0x2522, 0x0012): /* LH Labs VI DAC Infinity */ case USB_ID(0x2772, 0x0230): /* Pro-Ject Pre Box S2 Digital */ if (fp->altsetting == 2) @@ -1596,7 +1597,6 @@ u64 snd_usb_interface_dsd_format_quirks(struct snd_usb_audio *chip, case USB_ID(0x16d0, 0x0733): /* Furutech ADL Stratos */ case USB_ID(0x16d0, 0x09db): /* NuPrime Audio DAC-9 */ case USB_ID(0x1db5, 0x0003): /* Bryston BDA3 */ - case USB_ID(0x22d9, 0x0426): /* OPPO HA-2 */ case USB_ID(0x22e1, 0xca01): /* HDTA Serenade DSD */ case USB_ID(0x249c, 0x9326): /* M2Tech Young MkIII */ case USB_ID(0x2616, 0x0106): /* PS Audio NuWave DAC */ @@ -1651,9 +1651,12 @@ u64 snd_usb_interface_dsd_format_quirks(struct snd_usb_audio *chip, * from XMOS/Thesycon */ switch (USB_ID_VENDOR(chip->usb_id)) { - case 0x20b1: /* XMOS based devices */ case 0x152a: /* Thesycon devices */ + case 0x20b1: /* XMOS based devices */ + case 0x22d9: /* Oppo */ + case 0x23ba: /* Playback Designs */ case 0x25ce: /* Mytek devices */ + case 0x278b: /* Rotel? */ case 0x2ab6: /* T+A devices */ if (fp->dsd_raw) return SNDRV_PCM_FMTBIT_DSD_U32_BE; diff --git a/sound/usb/stream.c b/sound/usb/stream.c index e852c7fd6109..11785f9652ad 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -28,6 +28,14 @@ #include "power.h" #include "media.h" +static void audioformat_free(struct audioformat *fp) +{ + list_del(&fp->list); /* unlink for avoiding double-free */ + kfree(fp->rate_table); + kfree(fp->chmap); + kfree(fp); +} + /* * free a substream */ @@ -37,11 +45,8 @@ static void free_substream(struct snd_usb_substream *subs) if (!subs->num_formats) return; /* not initialized */ - list_for_each_entry_safe(fp, n, &subs->fmt_list, list) { - kfree(fp->rate_table); - kfree(fp->chmap); - kfree(fp); - } + list_for_each_entry_safe(fp, n, &subs->fmt_list, list) + audioformat_free(fp); kfree(subs->rate_list.list); kfree(subs->str_pd); snd_media_stream_delete(subs); @@ -627,16 +632,14 @@ static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip, */ static void * snd_usb_find_input_terminal_descriptor(struct usb_host_interface *ctrl_iface, - int terminal_id, bool uac23) + int terminal_id, int protocol) { struct uac2_input_terminal_descriptor *term = NULL; - size_t minlen = uac23 ? sizeof(struct uac2_input_terminal_descriptor) : - sizeof(struct uac_input_terminal_descriptor); while ((term = snd_usb_find_csint_desc(ctrl_iface->extra, ctrl_iface->extralen, term, UAC_INPUT_TERMINAL))) { - if (term->bLength < minlen) + if (!snd_usb_validate_audio_desc(term, protocol)) continue; if (term->bTerminalID == terminal_id) return term; @@ -647,7 +650,7 @@ snd_usb_find_input_terminal_descriptor(struct usb_host_interface *ctrl_iface, static void * snd_usb_find_output_terminal_descriptor(struct usb_host_interface *ctrl_iface, - int terminal_id) + int terminal_id, int protocol) { /* OK to use with both UAC2 and UAC3 */ struct uac2_output_terminal_descriptor *term = NULL; @@ -655,8 +658,9 @@ snd_usb_find_output_terminal_descriptor(struct usb_host_interface *ctrl_iface, while ((term = snd_usb_find_csint_desc(ctrl_iface->extra, ctrl_iface->extralen, term, UAC_OUTPUT_TERMINAL))) { - if (term->bLength >= sizeof(*term) && - term->bTerminalID == terminal_id) + if (!snd_usb_validate_audio_desc(term, protocol)) + continue; + if (term->bTerminalID == terminal_id) return term; } @@ -731,7 +735,7 @@ snd_usb_get_audioformat_uac12(struct snd_usb_audio *chip, iterm = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf, as->bTerminalLink, - false); + protocol); if (iterm) { num_channels = iterm->bNrChannels; chconfig = le16_to_cpu(iterm->wChannelConfig); @@ -767,7 +771,7 @@ snd_usb_get_audioformat_uac12(struct snd_usb_audio *chip, */ input_term = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf, as->bTerminalLink, - true); + protocol); if (input_term) { clock = input_term->bCSourceID; if (!chconfig && (num_channels == input_term->bNrChannels)) @@ -776,7 +780,8 @@ snd_usb_get_audioformat_uac12(struct snd_usb_audio *chip, } output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf, - as->bTerminalLink); + as->bTerminalLink, + protocol); if (output_term) { clock = output_term->bCSourceID; goto found_clock; @@ -832,8 +837,7 @@ found_clock: /* ok, let's parse further... */ if (snd_usb_parse_audio_format(chip, fp, format, fmt, stream) < 0) { - kfree(fp->rate_table); - kfree(fp); + audioformat_free(fp); return NULL; } @@ -1002,14 +1006,15 @@ snd_usb_get_audioformat_uac3(struct snd_usb_audio *chip, */ input_term = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf, as->bTerminalLink, - true); + UAC_VERSION_3); if (input_term) { clock = input_term->bCSourceID; goto found_clock; } output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf, - as->bTerminalLink); + as->bTerminalLink, + UAC_VERSION_3); if (output_term) { clock = output_term->bCSourceID; goto found_clock; @@ -1043,9 +1048,7 @@ found_clock: pd = kzalloc(sizeof(*pd), GFP_KERNEL); if (!pd) { - kfree(fp->chmap); - kfree(fp->rate_table); - kfree(fp); + audioformat_free(fp); return NULL; } pd->pd_id = (stream == SNDRV_PCM_STREAM_PLAYBACK) ? @@ -1064,9 +1067,7 @@ found_clock: /* ok, let's parse further... */ if (snd_usb_parse_audio_format_v3(chip, fp, as, stream) < 0) { kfree(pd); - kfree(fp->chmap); - kfree(fp->rate_table); - kfree(fp); + audioformat_free(fp); return NULL; } } @@ -1077,7 +1078,9 @@ found_clock: return fp; } -int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) +static int __snd_usb_parse_audio_interface(struct snd_usb_audio *chip, + int iface_no, + bool *has_non_pcm, bool non_pcm) { struct usb_device *dev; struct usb_interface *iface; @@ -1178,6 +1181,16 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) else if (IS_ERR(fp)) return PTR_ERR(fp); + if (fp->fmt_type != UAC_FORMAT_TYPE_I) + *has_non_pcm = true; + if ((fp->fmt_type == UAC_FORMAT_TYPE_I) == non_pcm) { + audioformat_free(fp); + kfree(pd); + fp = NULL; + pd = NULL; + continue; + } + dev_dbg(&dev->dev, "%u:%d: add audio endpoint %#x\n", iface_no, altno, fp->endpoint); if (protocol == UAC_VERSION_3) err = snd_usb_add_audio_stream_v3(chip, stream, fp, pd); @@ -1185,11 +1198,8 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) err = snd_usb_add_audio_stream(chip, stream, fp); if (err < 0) { - list_del(&fp->list); /* unlink for avoiding double-free */ + audioformat_free(fp); kfree(pd); - kfree(fp->rate_table); - kfree(fp->chmap); - kfree(fp); return err; } /* try to set the interface... */ @@ -1200,3 +1210,23 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) return 0; } +int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) +{ + int err; + bool has_non_pcm = false; + + /* parse PCM formats */ + err = __snd_usb_parse_audio_interface(chip, iface_no, &has_non_pcm, false); + if (err < 0) + return err; + + if (has_non_pcm) { + /* parse non-PCM formats */ + err = __snd_usb_parse_audio_interface(chip, iface_no, &has_non_pcm, true); + if (err < 0) + return err; + } + + return 0; +} + diff --git a/sound/usb/validate.c b/sound/usb/validate.c new file mode 100644 index 000000000000..3c8f73a0eb12 --- /dev/null +++ b/sound/usb/validate.c @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// Validation of USB-audio class descriptors +// + +#include <linux/init.h> +#include <linux/usb.h> +#include <linux/usb/audio.h> +#include <linux/usb/audio-v2.h> +#include <linux/usb/audio-v3.h> +#include <linux/usb/midi.h> +#include "usbaudio.h" +#include "helper.h" + +struct usb_desc_validator { + unsigned char protocol; + unsigned char type; + bool (*func)(const void *p, const struct usb_desc_validator *v); + size_t size; +}; + +#define UAC_VERSION_ALL (unsigned char)(-1) + +/* UAC1 only */ +static bool validate_uac1_header(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac1_ac_header_descriptor *d = p; + + return d->bLength >= sizeof(*d) && + d->bLength >= sizeof(*d) + d->bInCollection; +} + +/* for mixer unit; covering all UACs */ +static bool validate_mixer_unit(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac_mixer_unit_descriptor *d = p; + size_t len; + + if (d->bLength < sizeof(*d) || !d->bNrInPins) + return false; + len = sizeof(*d) + d->bNrInPins; + /* We can't determine the bitmap size only from this unit descriptor, + * so just check with the remaining length. + * The actual bitmap is checked at mixer unit parser. + */ + switch (v->protocol) { + case UAC_VERSION_1: + default: + len += 2 + 1; /* wChannelConfig, iChannelNames */ + /* bmControls[n*m] */ + len += 1; /* iMixer */ + break; + case UAC_VERSION_2: + len += 4 + 1; /* bmChannelConfig, iChannelNames */ + /* bmMixerControls[n*m] */ + len += 1 + 1; /* bmControls, iMixer */ + break; + case UAC_VERSION_3: + len += 2; /* wClusterDescrID */ + /* bmMixerControls[n*m] */ + break; + } + return d->bLength >= len; +} + +/* both for processing and extension units; covering all UACs */ +static bool validate_processing_unit(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac_processing_unit_descriptor *d = p; + const unsigned char *hdr = p; + size_t len, m; + + if (d->bLength < sizeof(*d)) + return false; + len = d->bLength < sizeof(*d) + d->bNrInPins; + if (d->bLength < len) + return false; + switch (v->protocol) { + case UAC_VERSION_1: + default: + /* bNrChannels, wChannelConfig, iChannelNames, bControlSize */ + len += 1 + 2 + 1 + 1; + if (d->bLength < len) /* bControlSize */ + return false; + m = hdr[len]; + len += 1 + m + 1; /* bControlSize, bmControls, iProcessing */ + break; + case UAC_VERSION_2: + /* bNrChannels, bmChannelConfig, iChannelNames */ + len += 1 + 4 + 1; + if (v->type == UAC2_PROCESSING_UNIT_V2) + len += 2; /* bmControls -- 2 bytes for PU */ + else + len += 1; /* bmControls -- 1 byte for EU */ + len += 1; /* iProcessing */ + break; + case UAC_VERSION_3: + /* wProcessingDescrStr, bmControls */ + len += 2 + 4; + break; + } + if (d->bLength < len) + return false; + + switch (v->protocol) { + case UAC_VERSION_1: + default: + if (v->type == UAC1_EXTENSION_UNIT) + return true; /* OK */ + switch (d->wProcessType) { + case UAC_PROCESS_UP_DOWNMIX: + case UAC_PROCESS_DOLBY_PROLOGIC: + if (d->bLength < len + 1) /* bNrModes */ + return false; + m = hdr[len]; + len += 1 + m * 2; /* bNrModes, waModes(n) */ + break; + default: + break; + } + break; + case UAC_VERSION_2: + if (v->type == UAC2_EXTENSION_UNIT_V2) + return true; /* OK */ + switch (d->wProcessType) { + case UAC2_PROCESS_UP_DOWNMIX: + case UAC2_PROCESS_DOLBY_PROLOCIC: /* SiC! */ + if (d->bLength < len + 1) /* bNrModes */ + return false; + m = hdr[len]; + len += 1 + m * 4; /* bNrModes, daModes(n) */ + break; + default: + break; + } + break; + case UAC_VERSION_3: + if (v->type == UAC3_EXTENSION_UNIT) { + len += 2; /* wClusterDescrID */ + break; + } + switch (d->wProcessType) { + case UAC3_PROCESS_UP_DOWNMIX: + if (d->bLength < len + 1) /* bNrModes */ + return false; + m = hdr[len]; + len += 1 + m * 2; /* bNrModes, waClusterDescrID(n) */ + break; + case UAC3_PROCESS_MULTI_FUNCTION: + len += 2 + 4; /* wClusterDescrID, bmAlgorighms */ + break; + default: + break; + } + break; + } + if (d->bLength < len) + return false; + + return true; +} + +/* both for selector and clock selector units; covering all UACs */ +static bool validate_selector_unit(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac_selector_unit_descriptor *d = p; + size_t len; + + if (d->bLength < sizeof(*d)) + return false; + len = sizeof(*d) + d->bNrInPins; + switch (v->protocol) { + case UAC_VERSION_1: + default: + len += 1; /* iSelector */ + break; + case UAC_VERSION_2: + len += 1 + 1; /* bmControls, iSelector */ + break; + case UAC_VERSION_3: + len += 4 + 2; /* bmControls, wSelectorDescrStr */ + break; + } + return d->bLength >= len; +} + +static bool validate_uac1_feature_unit(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac_feature_unit_descriptor *d = p; + + if (d->bLength < sizeof(*d) || !d->bControlSize) + return false; + /* at least bmaControls(0) for master channel + iFeature */ + return d->bLength >= sizeof(*d) + d->bControlSize + 1; +} + +static bool validate_uac2_feature_unit(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac2_feature_unit_descriptor *d = p; + + if (d->bLength < sizeof(*d)) + return false; + /* at least bmaControls(0) for master channel + iFeature */ + return d->bLength >= sizeof(*d) + 4 + 1; +} + +static bool validate_uac3_feature_unit(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac3_feature_unit_descriptor *d = p; + + if (d->bLength < sizeof(*d)) + return false; + /* at least bmaControls(0) for master channel + wFeatureDescrStr */ + return d->bLength >= sizeof(*d) + 4 + 2; +} + +static bool validate_midi_out_jack(const void *p, + const struct usb_desc_validator *v) +{ + const struct usb_midi_out_jack_descriptor *d = p; + + return d->bLength >= sizeof(*d) && + d->bLength >= sizeof(*d) + d->bNrInputPins * 2; +} + +#define FIXED(p, t, s) { .protocol = (p), .type = (t), .size = sizeof(s) } +#define FUNC(p, t, f) { .protocol = (p), .type = (t), .func = (f) } + +static struct usb_desc_validator audio_validators[] = { + /* UAC1 */ + FUNC(UAC_VERSION_1, UAC_HEADER, validate_uac1_header), + FIXED(UAC_VERSION_1, UAC_INPUT_TERMINAL, + struct uac_input_terminal_descriptor), + FIXED(UAC_VERSION_1, UAC_OUTPUT_TERMINAL, + struct uac1_output_terminal_descriptor), + FUNC(UAC_VERSION_1, UAC_MIXER_UNIT, validate_mixer_unit), + FUNC(UAC_VERSION_1, UAC_SELECTOR_UNIT, validate_selector_unit), + FUNC(UAC_VERSION_1, UAC_FEATURE_UNIT, validate_uac1_feature_unit), + FUNC(UAC_VERSION_1, UAC1_PROCESSING_UNIT, validate_processing_unit), + FUNC(UAC_VERSION_1, UAC1_EXTENSION_UNIT, validate_processing_unit), + + /* UAC2 */ + FIXED(UAC_VERSION_2, UAC_HEADER, struct uac2_ac_header_descriptor), + FIXED(UAC_VERSION_2, UAC_INPUT_TERMINAL, + struct uac2_input_terminal_descriptor), + FIXED(UAC_VERSION_2, UAC_OUTPUT_TERMINAL, + struct uac2_output_terminal_descriptor), + FUNC(UAC_VERSION_2, UAC_MIXER_UNIT, validate_mixer_unit), + FUNC(UAC_VERSION_2, UAC_SELECTOR_UNIT, validate_selector_unit), + FUNC(UAC_VERSION_2, UAC_FEATURE_UNIT, validate_uac2_feature_unit), + /* UAC_VERSION_2, UAC2_EFFECT_UNIT: not implemented yet */ + FUNC(UAC_VERSION_2, UAC2_PROCESSING_UNIT_V2, validate_processing_unit), + FUNC(UAC_VERSION_2, UAC2_EXTENSION_UNIT_V2, validate_processing_unit), + FIXED(UAC_VERSION_2, UAC2_CLOCK_SOURCE, + struct uac_clock_source_descriptor), + FUNC(UAC_VERSION_2, UAC2_CLOCK_SELECTOR, validate_selector_unit), + FIXED(UAC_VERSION_2, UAC2_CLOCK_MULTIPLIER, + struct uac_clock_multiplier_descriptor), + /* UAC_VERSION_2, UAC2_SAMPLE_RATE_CONVERTER: not implemented yet */ + + /* UAC3 */ + FIXED(UAC_VERSION_2, UAC_HEADER, struct uac3_ac_header_descriptor), + FIXED(UAC_VERSION_3, UAC_INPUT_TERMINAL, + struct uac3_input_terminal_descriptor), + FIXED(UAC_VERSION_3, UAC_OUTPUT_TERMINAL, + struct uac3_output_terminal_descriptor), + /* UAC_VERSION_3, UAC3_EXTENDED_TERMINAL: not implemented yet */ + FUNC(UAC_VERSION_3, UAC3_MIXER_UNIT, validate_mixer_unit), + FUNC(UAC_VERSION_3, UAC3_SELECTOR_UNIT, validate_selector_unit), + FUNC(UAC_VERSION_3, UAC_FEATURE_UNIT, validate_uac3_feature_unit), + /* UAC_VERSION_3, UAC3_EFFECT_UNIT: not implemented yet */ + FUNC(UAC_VERSION_3, UAC3_PROCESSING_UNIT, validate_processing_unit), + FUNC(UAC_VERSION_3, UAC3_EXTENSION_UNIT, validate_processing_unit), + FIXED(UAC_VERSION_3, UAC3_CLOCK_SOURCE, + struct uac3_clock_source_descriptor), + FUNC(UAC_VERSION_3, UAC3_CLOCK_SELECTOR, validate_selector_unit), + FIXED(UAC_VERSION_3, UAC3_CLOCK_MULTIPLIER, + struct uac3_clock_multiplier_descriptor), + /* UAC_VERSION_3, UAC3_SAMPLE_RATE_CONVERTER: not implemented yet */ + /* UAC_VERSION_3, UAC3_CONNECTORS: not implemented yet */ + { } /* terminator */ +}; + +static struct usb_desc_validator midi_validators[] = { + FIXED(UAC_VERSION_ALL, USB_MS_HEADER, + struct usb_ms_header_descriptor), + FIXED(UAC_VERSION_ALL, USB_MS_MIDI_IN_JACK, + struct usb_midi_in_jack_descriptor), + FUNC(UAC_VERSION_ALL, USB_MS_MIDI_OUT_JACK, + validate_midi_out_jack), + { } /* terminator */ +}; + + +/* Validate the given unit descriptor, return true if it's OK */ +static bool validate_desc(unsigned char *hdr, int protocol, + const struct usb_desc_validator *v) +{ + if (hdr[1] != USB_DT_CS_INTERFACE) + return true; /* don't care */ + + for (; v->type; v++) { + if (v->type == hdr[2] && + (v->protocol == UAC_VERSION_ALL || + v->protocol == protocol)) { + if (v->func) + return v->func(hdr, v); + /* check for the fixed size */ + return hdr[0] >= v->size; + } + } + + return true; /* not matching, skip validation */ +} + +bool snd_usb_validate_audio_desc(void *p, int protocol) +{ + return validate_desc(p, protocol, audio_validators); +} + +bool snd_usb_validate_midi_desc(void *p) +{ + return validate_desc(p, UAC_VERSION_1, midi_validators); +} + |