diff options
104 files changed, 6475 insertions, 737 deletions
diff --git a/Documentation/sound/hd-audio/notes.rst b/Documentation/sound/hd-audio/notes.rst index 9eeb9b468706..f59c3cdbfaf4 100644 --- a/Documentation/sound/hd-audio/notes.rst +++ b/Documentation/sound/hd-audio/notes.rst @@ -494,6 +494,8 @@ add_hp_mic (bool) hp_mic_detect (bool) enable/disable the hp/mic shared input for a single built-in mic case; default true +vmaster (bool) + enable/disable the virtual Master control; default true mixer_nid (int) specifies the widget NID of the analog-loopback mixer diff --git a/include/sound/hda_register.h b/include/sound/hda_register.h index 0013063db7f2..15fc6daf9096 100644 --- a/include/sound/hda_register.h +++ b/include/sound/hda_register.h @@ -106,8 +106,26 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 }; #define AZX_REG_HSW_EM4 0x100c #define AZX_REG_HSW_EM5 0x1010 -/* Skylake/Broxton display HD-A controller Extended Mode registers */ -#define AZX_REG_SKL_EM4L 0x1040 +/* Skylake/Broxton vendor-specific registers */ +#define AZX_REG_VS_EM1 0x1000 +#define AZX_REG_VS_INRC 0x1004 +#define AZX_REG_VS_OUTRC 0x1008 +#define AZX_REG_VS_FIFOTRK 0x100C +#define AZX_REG_VS_FIFOTRK2 0x1010 +#define AZX_REG_VS_EM2 0x1030 +#define AZX_REG_VS_EM3L 0x1038 +#define AZX_REG_VS_EM3U 0x103C +#define AZX_REG_VS_EM4L 0x1040 +#define AZX_REG_VS_EM4U 0x1044 +#define AZX_REG_VS_LTRC 0x1048 +#define AZX_REG_VS_D0I3C 0x104A +#define AZX_REG_VS_PCE 0x104B +#define AZX_REG_VS_L2MAGC 0x1050 +#define AZX_REG_VS_L2LAHPT 0x1054 +#define AZX_REG_VS_SDXDPIB_XBASE 0x1084 +#define AZX_REG_VS_SDXDPIB_XINTERVAL 0x20 +#define AZX_REG_VS_SDXEFIFOS_XBASE 0x1094 +#define AZX_REG_VS_SDXEFIFOS_XINTERVAL 0x20 /* PCI space */ #define AZX_PCIREG_TCSEL 0x44 @@ -243,9 +261,11 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 }; #define AZX_REG_ML_LOUTPAY 0x20 #define AZX_REG_ML_LINPAY 0x30 -#define AZX_MLCTL_SPA (1<<16) -#define AZX_MLCTL_CPA 23 - +#define ML_LCTL_SCF_MASK 0xF +#define AZX_MLCTL_SPA (0x1 << 16) +#define AZX_MLCTL_CPA (0x1 << 23) +#define AZX_MLCTL_SPA_SHIFT 16 +#define AZX_MLCTL_CPA_SHIFT 23 /* registers for DMA Resume Capability Structure */ #define AZX_DRSM_CAP_ID 0x5 diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 56004ec8d441..96546b30e900 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -368,24 +368,32 @@ void snd_hdac_bus_free_stream_pages(struct hdac_bus *bus); /* * macros for easy use */ -#define _snd_hdac_chip_write(type, chip, reg, value) \ - ((chip)->io_ops->reg_write ## type(value, (chip)->remap_addr + (reg))) -#define _snd_hdac_chip_read(type, chip, reg) \ - ((chip)->io_ops->reg_read ## type((chip)->remap_addr + (reg))) +#define _snd_hdac_chip_writeb(chip, reg, value) \ + ((chip)->io_ops->reg_writeb(value, (chip)->remap_addr + (reg))) +#define _snd_hdac_chip_readb(chip, reg) \ + ((chip)->io_ops->reg_readb((chip)->remap_addr + (reg))) +#define _snd_hdac_chip_writew(chip, reg, value) \ + ((chip)->io_ops->reg_writew(value, (chip)->remap_addr + (reg))) +#define _snd_hdac_chip_readw(chip, reg) \ + ((chip)->io_ops->reg_readw((chip)->remap_addr + (reg))) +#define _snd_hdac_chip_writel(chip, reg, value) \ + ((chip)->io_ops->reg_writel(value, (chip)->remap_addr + (reg))) +#define _snd_hdac_chip_readl(chip, reg) \ + ((chip)->io_ops->reg_readl((chip)->remap_addr + (reg))) /* read/write a register, pass without AZX_REG_ prefix */ #define snd_hdac_chip_writel(chip, reg, value) \ - _snd_hdac_chip_write(l, chip, AZX_REG_ ## reg, value) + _snd_hdac_chip_writel(chip, AZX_REG_ ## reg, value) #define snd_hdac_chip_writew(chip, reg, value) \ - _snd_hdac_chip_write(w, chip, AZX_REG_ ## reg, value) + _snd_hdac_chip_writew(chip, AZX_REG_ ## reg, value) #define snd_hdac_chip_writeb(chip, reg, value) \ - _snd_hdac_chip_write(b, chip, AZX_REG_ ## reg, value) + _snd_hdac_chip_writeb(chip, AZX_REG_ ## reg, value) #define snd_hdac_chip_readl(chip, reg) \ - _snd_hdac_chip_read(l, chip, AZX_REG_ ## reg) + _snd_hdac_chip_readl(chip, AZX_REG_ ## reg) #define snd_hdac_chip_readw(chip, reg) \ - _snd_hdac_chip_read(w, chip, AZX_REG_ ## reg) + _snd_hdac_chip_readw(chip, AZX_REG_ ## reg) #define snd_hdac_chip_readb(chip, reg) \ - _snd_hdac_chip_read(b, chip, AZX_REG_ ## reg) + _snd_hdac_chip_readb(chip, AZX_REG_ ## reg) /* update a register, pass without AZX_REG_ prefix */ #define snd_hdac_chip_updatel(chip, reg, mask, val) \ diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index be353a78c303..fd41697cb4d3 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -107,9 +107,11 @@ enum { SNDRV_HWDEP_IFACE_FW_DIGI00X, /* Digidesign Digi 002/003 family */ SNDRV_HWDEP_IFACE_FW_TASCAM, /* TASCAM FireWire series */ SNDRV_HWDEP_IFACE_LINE6, /* Line6 USB processors */ + SNDRV_HWDEP_IFACE_FW_MOTU, /* MOTU FireWire series */ + SNDRV_HWDEP_IFACE_FW_FIREFACE, /* RME Fireface series */ /* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_LINE6 + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_FIREFACE }; struct snd_hwdep_info { diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index db79a12fcc78..622900488bdc 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -10,6 +10,7 @@ #define SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION 0xd1ce004e #define SNDRV_FIREWIRE_EVENT_EFW_RESPONSE 0x4e617475 #define SNDRV_FIREWIRE_EVENT_DIGI00X_MESSAGE 0x746e736c +#define SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION 0x64776479 struct snd_firewire_event_common { unsigned int type; /* SNDRV_FIREWIRE_EVENT_xxx */ @@ -46,12 +47,18 @@ struct snd_firewire_event_digi00x_message { __u32 message; /* Digi00x-specific message */ }; +struct snd_firewire_event_motu_notification { + unsigned int type; + __u32 message; /* MOTU-specific bits. */ +}; + union snd_firewire_event { struct snd_firewire_event_common common; struct snd_firewire_event_lock_status lock_status; struct snd_firewire_event_dice_notification dice_notification; struct snd_firewire_event_efw_response efw_response; struct snd_firewire_event_digi00x_message digi00x_message; + struct snd_firewire_event_motu_notification motu_notification; }; @@ -65,7 +72,8 @@ union snd_firewire_event { #define SNDRV_FIREWIRE_TYPE_OXFW 4 #define SNDRV_FIREWIRE_TYPE_DIGI00X 5 #define SNDRV_FIREWIRE_TYPE_TASCAM 6 -/* RME, MOTU, ... */ +#define SNDRV_FIREWIRE_TYPE_MOTU 7 +#define SNDRV_FIREWIRE_TYPE_FIREFACE 8 struct snd_firewire_get_info { unsigned int type; /* SNDRV_FIREWIRE_TYPE_xxx */ diff --git a/sound/core/timer.c b/sound/core/timer.c index 6d4fbc439246..2f836ca09860 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -1277,6 +1277,7 @@ static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri, struct timespec tstamp; int prev, append = 0; + memset(&r1, 0, sizeof(r1)); memset(&tstamp, 0, sizeof(tstamp)); spin_lock(&tu->qlock); if ((tu->filter & ((1 << SNDRV_TIMER_EVENT_RESOLUTION) | @@ -1292,7 +1293,6 @@ static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri, } if ((tu->filter & (1 << SNDRV_TIMER_EVENT_RESOLUTION)) && tu->last_resolution != resolution) { - memset(&r1, 0, sizeof(r1)); r1.event = SNDRV_TIMER_EVENT_RESOLUTION; r1.tstamp = tstamp; r1.val = resolution; @@ -1430,18 +1430,13 @@ static int snd_timer_user_next_device(struct snd_timer_id __user *_tid) if (id.card < 0) { id.card = 0; } else { - if (id.card < 0) { - id.card = 0; + if (id.device < 0) { + id.device = 0; } else { - if (id.device < 0) { - id.device = 0; - } else { - if (id.subdevice < 0) { - id.subdevice = 0; - } else { - id.subdevice++; - } - } + if (id.subdevice < 0) + id.subdevice = 0; + else + id.subdevice++; } } list_for_each(p, &snd_timer_list) { diff --git a/sound/drivers/vx/vx_core.c b/sound/drivers/vx/vx_core.c index 289f041706cd..f684fffd1397 100644 --- a/sound/drivers/vx/vx_core.c +++ b/sound/drivers/vx/vx_core.c @@ -795,10 +795,8 @@ struct vx_core *snd_vx_create(struct snd_card *card, struct snd_vx_hardware *hw, return NULL; chip = kzalloc(sizeof(*chip) + extra_size, GFP_KERNEL); - if (! chip) { - snd_printk(KERN_ERR "vx_core: no memory\n"); + if (! chip) return NULL; - } mutex_init(&chip->lock); chip->irq = -1; chip->hw = hw; diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 9f00696c4e4a..529d9f405fa9 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -140,4 +140,24 @@ config SND_FIREWIRE_TASCAM To compile this driver as a module, choose M here: the module will be called snd-firewire-tascam. +config SND_FIREWIRE_MOTU + tristate "Mark of the unicorn FireWire series support" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to enable support for FireWire devices which MOTU produced: + * 828mk2 + * 828mk3 + + To compile this driver as a module, choose M here: the module + will be called snd-firewire-motu. + +config SND_FIREFACE + tristate "RME Fireface series support" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to include support for RME fireface series. + * Fireface 400 + endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index 0ee1fb115d88..1b98fa3fa3d4 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -13,3 +13,5 @@ obj-$(CONFIG_SND_FIREWORKS) += fireworks/ obj-$(CONFIG_SND_BEBOB) += bebob/ obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += digi00x/ obj-$(CONFIG_SND_FIREWIRE_TASCAM) += tascam/ +obj-$(CONFIG_SND_FIREWIRE_MOTU) += motu/ +obj-$(CONFIG_SND_FIREFACE) += fireface/ diff --git a/sound/firewire/amdtp-stream-trace.h b/sound/firewire/amdtp-stream-trace.h index 9c04faf206b2..ea0d486652c8 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(in_packet, - TP_PROTO(const struct amdtp_stream *s, u32 cycles, u32 cip_header[2], unsigned int payload_quadlets, unsigned int index), - TP_ARGS(s, cycles, cip_header, payload_quadlets, index), + TP_PROTO(const struct amdtp_stream *s, u32 cycles, u32 cip_header[2], unsigned int payload_length, unsigned int index), + TP_ARGS(s, cycles, cip_header, payload_length, index), TP_STRUCT__entry( __field(unsigned int, second) __field(unsigned int, cycle) @@ -37,7 +37,7 @@ TRACE_EVENT(in_packet, __entry->dest = fw_parent_device(s->unit)->card->node_id; __entry->cip_header0 = cip_header[0]; __entry->cip_header1 = cip_header[1]; - __entry->payload_quadlets = payload_quadlets; + __entry->payload_quadlets = payload_length / 4; __entry->packet_index = s->packet_index; __entry->irq = !!in_interrupt(); __entry->index = index; @@ -101,6 +101,94 @@ TRACE_EVENT(out_packet, __entry->index) ); +TRACE_EVENT(in_packet_without_header, + TP_PROTO(const struct amdtp_stream *s, u32 cycles, unsigned int payload_quadlets, unsigned int data_blocks, unsigned int index), + TP_ARGS(s, cycles, payload_quadlets, data_blocks, index), + TP_STRUCT__entry( + __field(unsigned int, second) + __field(unsigned int, cycle) + __field(int, channel) + __field(int, src) + __field(int, dest) + __field(unsigned int, payload_quadlets) + __field(unsigned int, data_blocks) + __field(unsigned int, data_block_counter) + __field(unsigned int, packet_index) + __field(unsigned int, irq) + __field(unsigned int, index) + ), + TP_fast_assign( + __entry->second = cycles / CYCLES_PER_SECOND; + __entry->cycle = cycles % CYCLES_PER_SECOND; + __entry->channel = s->context->channel; + __entry->src = fw_parent_device(s->unit)->node_id; + __entry->dest = fw_parent_device(s->unit)->card->node_id; + __entry->payload_quadlets = payload_quadlets; + __entry->data_blocks = data_blocks, + __entry->data_block_counter = s->data_block_counter, + __entry->packet_index = s->packet_index; + __entry->irq = !!in_interrupt(); + __entry->index = index; + ), + TP_printk( + "%02u %04u %04x %04x %02d %03u %3u %3u %02u %01u %02u", + __entry->second, + __entry->cycle, + __entry->src, + __entry->dest, + __entry->channel, + __entry->payload_quadlets, + __entry->data_blocks, + __entry->data_block_counter, + __entry->packet_index, + __entry->irq, + __entry->index) +); + +TRACE_EVENT(out_packet_without_header, + TP_PROTO(const struct amdtp_stream *s, u32 cycles, unsigned int payload_length, unsigned int data_blocks, unsigned int index), + TP_ARGS(s, cycles, payload_length, data_blocks, index), + TP_STRUCT__entry( + __field(unsigned int, second) + __field(unsigned int, cycle) + __field(int, channel) + __field(int, src) + __field(int, dest) + __field(unsigned int, payload_quadlets) + __field(unsigned int, data_blocks) + __field(unsigned int, data_block_counter) + __field(unsigned int, packet_index) + __field(unsigned int, irq) + __field(unsigned int, index) + ), + TP_fast_assign( + __entry->second = cycles / CYCLES_PER_SECOND; + __entry->cycle = cycles % CYCLES_PER_SECOND; + __entry->channel = s->context->channel; + __entry->src = fw_parent_device(s->unit)->card->node_id; + __entry->dest = fw_parent_device(s->unit)->node_id; + __entry->payload_quadlets = payload_length / 4; + __entry->data_blocks = data_blocks, + __entry->data_blocks = s->data_block_counter, + __entry->packet_index = s->packet_index; + __entry->irq = !!in_interrupt(); + __entry->index = index; + ), + TP_printk( + "%02u %04u %04x %04x %02d %03u %02u %03u %02u %01u %02u", + __entry->second, + __entry->cycle, + __entry->src, + __entry->dest, + __entry->channel, + __entry->payload_quadlets, + __entry->data_blocks, + __entry->data_block_counter, + __entry->packet_index, + __entry->irq, + __entry->index) +); + #endif #undef TRACE_INCLUDE_PATH diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c index 00060c4a9deb..9e6f54f8c45d 100644 --- a/sound/firewire/amdtp-stream.c +++ b/sound/firewire/amdtp-stream.c @@ -27,6 +27,7 @@ /* isochronous header parameters */ #define ISO_DATA_LENGTH_SHIFT 16 +#define TAG_NO_CIP_HEADER 0 #define TAG_CIP 1 /* common isochronous packet header parameters */ @@ -37,6 +38,8 @@ #define CIP_SID_MASK 0x3f000000 #define CIP_DBS_MASK 0x00ff0000 #define CIP_DBS_SHIFT 16 +#define CIP_SPH_MASK 0x00000400 +#define CIP_SPH_SHIFT 10 #define CIP_DBC_MASK 0x000000ff #define CIP_FMT_SHIFT 24 #define CIP_FMT_MASK 0x3f000000 @@ -232,11 +235,15 @@ EXPORT_SYMBOL(amdtp_stream_set_parameters); unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s) { unsigned int multiplier = 1; + unsigned int header_size = 0; if (s->flags & CIP_JUMBO_PAYLOAD) multiplier = 5; + if (!(s->flags & CIP_NO_HEADER)) + header_size = 8; - return 8 + s->syt_interval * s->data_block_quadlets * 4 * multiplier; + return header_size + + s->syt_interval * s->data_block_quadlets * 4 * multiplier; } EXPORT_SYMBOL(amdtp_stream_get_max_payload); @@ -378,7 +385,7 @@ static int queue_packet(struct amdtp_stream *s, unsigned int header_length, goto end; p.interrupt = IS_ALIGNED(s->packet_index + 1, INTERRUPT_INTERVAL); - p.tag = TAG_CIP; + p.tag = s->tag; p.header_length = header_length; if (payload_length > 0) p.payload_length = payload_length; @@ -405,17 +412,16 @@ static inline int queue_out_packet(struct amdtp_stream *s, static inline int queue_in_packet(struct amdtp_stream *s) { - return queue_packet(s, IN_PACKET_HEADER_SIZE, - amdtp_stream_get_max_payload(s)); + return queue_packet(s, IN_PACKET_HEADER_SIZE, s->max_payload_length); } -static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle, +static int handle_out_packet(struct amdtp_stream *s, + unsigned int payload_length, unsigned int cycle, unsigned int index) { __be32 *buffer; unsigned int syt; unsigned int data_blocks; - unsigned int payload_length; unsigned int pcm_frames; struct snd_pcm_substream *pcm; @@ -424,15 +430,22 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle, data_blocks = calculate_data_blocks(s, syt); pcm_frames = s->process_data_blocks(s, buffer + 2, data_blocks, &syt); + if (s->flags & CIP_DBC_IS_END_EVENT) + s->data_block_counter = + (s->data_block_counter + data_blocks) & 0xff; + buffer[0] = cpu_to_be32(ACCESS_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); buffer[1] = cpu_to_be32(CIP_EOH | ((s->fmt << CIP_FMT_SHIFT) & CIP_FMT_MASK) | ((s->fdf << CIP_FDF_SHIFT) & CIP_FDF_MASK) | (syt & CIP_SYT_MASK)); - s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; + if (!(s->flags & CIP_DBC_IS_END_EVENT)) + s->data_block_counter = + (s->data_block_counter + data_blocks) & 0xff; payload_length = 8 + data_blocks * 4 * s->data_block_quadlets; trace_out_packet(s, cycle, buffer, payload_length, index); @@ -448,13 +461,45 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle, return 0; } +static int handle_out_packet_without_header(struct amdtp_stream *s, + unsigned int payload_length, unsigned int cycle, + unsigned int index) +{ + __be32 *buffer; + unsigned int syt; + unsigned int data_blocks; + unsigned int pcm_frames; + struct snd_pcm_substream *pcm; + + buffer = s->buffer.packets[s->packet_index].buffer; + syt = calculate_syt(s, cycle); + data_blocks = calculate_data_blocks(s, syt); + pcm_frames = s->process_data_blocks(s, buffer, data_blocks, &syt); + s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; + + payload_length = data_blocks * 4 * s->data_block_quadlets; + + trace_out_packet_without_header(s, cycle, payload_length, data_blocks, + index); + + if (queue_out_packet(s, payload_length) < 0) + return -EIO; + + pcm = ACCESS_ONCE(s->pcm); + if (pcm && pcm_frames > 0) + update_pcm_pointers(s, pcm, pcm_frames); + + /* No need to return the number of handled data blocks. */ + return 0; +} + static int handle_in_packet(struct amdtp_stream *s, - unsigned int payload_quadlets, unsigned int cycle, + unsigned int payload_length, unsigned int cycle, unsigned int index) { __be32 *buffer; u32 cip_header[2]; - unsigned int fmt, fdf, syt; + unsigned int sph, fmt, fdf, syt; unsigned int data_block_quadlets, data_block_counter, dbc_interval; unsigned int data_blocks; struct snd_pcm_substream *pcm; @@ -465,14 +510,15 @@ static int handle_in_packet(struct amdtp_stream *s, cip_header[0] = be32_to_cpu(buffer[0]); cip_header[1] = be32_to_cpu(buffer[1]); - trace_in_packet(s, cycle, cip_header, payload_quadlets, index); + trace_in_packet(s, cycle, cip_header, payload_length, index); /* * This module supports 'Two-quadlet CIP header with SYT field'. * For convenience, also check FMT field is AM824 or not. */ - if (((cip_header[0] & CIP_EOH_MASK) == CIP_EOH) || - ((cip_header[1] & CIP_EOH_MASK) != CIP_EOH)) { + if ((((cip_header[0] & CIP_EOH_MASK) == CIP_EOH) || + ((cip_header[1] & CIP_EOH_MASK) != CIP_EOH)) && + (!(s->flags & CIP_HEADER_WITHOUT_EOH))) { dev_info_ratelimited(&s->unit->device, "Invalid CIP header for AMDTP: %08X:%08X\n", cip_header[0], cip_header[1]); @@ -482,8 +528,9 @@ static int handle_in_packet(struct amdtp_stream *s, } /* Check valid protocol or not. */ + sph = (cip_header[0] & CIP_SPH_MASK) >> CIP_SPH_SHIFT; fmt = (cip_header[1] & CIP_FMT_MASK) >> CIP_FMT_SHIFT; - if (fmt != s->fmt) { + if (sph != s->sph || fmt != s->fmt) { dev_info_ratelimited(&s->unit->device, "Detect unexpected protocol: %08x %08x\n", cip_header[0], cip_header[1]); @@ -494,7 +541,7 @@ static int handle_in_packet(struct amdtp_stream *s, /* Calculate data blocks */ fdf = (cip_header[1] & CIP_FDF_MASK) >> CIP_FDF_SHIFT; - if (payload_quadlets < 3 || + if (payload_length < 12 || (fmt == CIP_FMT_AM && fdf == AMDTP_FDF_NO_DATA)) { data_blocks = 0; } else { @@ -510,7 +557,8 @@ static int handle_in_packet(struct amdtp_stream *s, if (s->flags & CIP_WRONG_DBS) data_block_quadlets = s->data_block_quadlets; - data_blocks = (payload_quadlets - 2) / data_block_quadlets; + data_blocks = (payload_length / 4 - 2) / + data_block_quadlets; } /* Check data block counter continuity */ @@ -561,6 +609,34 @@ end: return 0; } +static int handle_in_packet_without_header(struct amdtp_stream *s, + unsigned int payload_quadlets, unsigned int cycle, + unsigned int index) +{ + __be32 *buffer; + unsigned int data_blocks; + struct snd_pcm_substream *pcm; + unsigned int pcm_frames; + + buffer = s->buffer.packets[s->packet_index].buffer; + data_blocks = payload_quadlets / s->data_block_quadlets; + + trace_in_packet_without_header(s, cycle, payload_quadlets, data_blocks, + index); + + pcm_frames = s->process_data_blocks(s, buffer, data_blocks, NULL); + s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; + + if (queue_in_packet(s) < 0) + return -EIO; + + pcm = ACCESS_ONCE(s->pcm); + if (pcm && pcm_frames > 0) + update_pcm_pointers(s, pcm, pcm_frames); + + return 0; +} + /* * In CYCLE_TIMER register of IEEE 1394, 7 bits are used to represent second. On * the other hand, in DMA descriptors of 1394 OHCI, 3 bits are used to represent @@ -604,7 +680,7 @@ static void out_stream_callback(struct fw_iso_context *context, u32 tstamp, for (i = 0; i < packets; ++i) { cycle = increment_cycle_count(cycle, 1); - if (handle_out_packet(s, cycle, i) < 0) { + if (s->handle_packet(s, 0, cycle, i) < 0) { s->packet_index = -1; amdtp_stream_pcm_abort(s); return; @@ -620,7 +696,7 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp, { struct amdtp_stream *s = private_data; unsigned int i, packets; - unsigned int payload_quadlets, max_payload_quadlets; + unsigned int payload_length, max_payload_length; __be32 *headers = header; u32 cycle; @@ -636,22 +712,22 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp, cycle = decrement_cycle_count(cycle, packets); /* For buffer-over-run prevention. */ - max_payload_quadlets = amdtp_stream_get_max_payload(s) / 4; + max_payload_length = s->max_payload_length; for (i = 0; i < packets; i++) { cycle = increment_cycle_count(cycle, 1); - /* The number of quadlets in this packet */ - payload_quadlets = - (be32_to_cpu(headers[i]) >> ISO_DATA_LENGTH_SHIFT) / 4; - if (payload_quadlets > max_payload_quadlets) { + /* The number of bytes in this packet */ + payload_length = + (be32_to_cpu(headers[i]) >> ISO_DATA_LENGTH_SHIFT); + if (payload_length > max_payload_length) { dev_err(&s->unit->device, - "Detect jumbo payload: %02x %02x\n", - payload_quadlets, max_payload_quadlets); + "Detect jumbo payload: %04x %04x\n", + payload_length, max_payload_length); break; } - if (handle_in_packet(s, payload_quadlets, cycle, i) < 0) + if (s->handle_packet(s, payload_length, cycle, i) < 0) break; } @@ -671,6 +747,10 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context, void *header, void *private_data) { struct amdtp_stream *s = private_data; + u32 cycle; + unsigned int packets; + + s->max_payload_length = amdtp_stream_get_max_payload(s); /* * For in-stream, first packet has come. @@ -679,10 +759,27 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context, s->callbacked = true; wake_up(&s->callback_wait); - if (s->direction == AMDTP_IN_STREAM) + cycle = compute_cycle_count(tstamp); + + if (s->direction == AMDTP_IN_STREAM) { + packets = header_length / IN_PACKET_HEADER_SIZE; + cycle = decrement_cycle_count(cycle, packets); context->callback.sc = in_stream_callback; - else + if (s->flags & CIP_NO_HEADER) + s->handle_packet = handle_in_packet_without_header; + else + s->handle_packet = handle_in_packet; + } else { + packets = header_length / 4; + cycle = increment_cycle_count(cycle, QUEUE_LENGTH - packets); context->callback.sc = out_stream_callback; + if (s->flags & CIP_NO_HEADER) + s->handle_packet = handle_out_packet_without_header; + else + s->handle_packet = handle_out_packet; + } + + s->start_cycle = cycle; context->callback.sc(context, tstamp, header_length, header, s); } @@ -759,6 +856,11 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) amdtp_stream_update(s); + if (s->flags & CIP_NO_HEADER) + s->tag = TAG_NO_CIP_HEADER; + else + s->tag = TAG_CIP; + s->packet_index = 0; do { if (s->direction == AMDTP_IN_STREAM) @@ -771,7 +873,7 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) /* NOTE: TAG1 matches CIP. This just affects in stream. */ tag = FW_ISO_CONTEXT_MATCH_TAG1; - if (s->flags & CIP_EMPTY_WITH_TAG0) + if ((s->flags & CIP_EMPTY_WITH_TAG0) || (s->flags & CIP_NO_HEADER)) tag |= FW_ISO_CONTEXT_MATCH_TAG0; s->callbacked = false; diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h index c1bc7fad056e..7e8831722821 100644 --- a/sound/firewire/amdtp-stream.h +++ b/sound/firewire/amdtp-stream.h @@ -18,8 +18,8 @@ * SYT_INTERVAL samples, with these two types alternating so that * the overall sample rate comes out right. * @CIP_EMPTY_WITH_TAG0: Only for in-stream. Empty in-packets have TAG0. - * @CIP_DBC_IS_END_EVENT: Only for in-stream. The value of dbc in an in-packet - * corresponds to the end of event in the packet. Out of IEC 61883. + * @CIP_DBC_IS_END_EVENT: The value of dbc in an packet corresponds to the end + * of event in the packet. Out of IEC 61883. * @CIP_WRONG_DBS: Only for in-stream. The value of dbs is wrong in in-packets. * The value of data_block_quadlets is used instead of reported value. * @CIP_SKIP_DBC_ZERO_CHECK: Only for in-stream. Packets with zero in dbc is @@ -29,6 +29,9 @@ * @CIP_JUMBO_PAYLOAD: Only for in-stream. The number of data blocks in an * packet is larger than IEC 61883-6 defines. Current implementation * allows 5 times as large as IEC 61883-6 defines. + * @CIP_HEADER_WITHOUT_EOH: Only for in-stream. CIP Header doesn't include + * valid EOH. + * @CIP_NO_HEADERS: a lack of headers in packets */ enum cip_flags { CIP_NONBLOCKING = 0x00, @@ -39,6 +42,8 @@ enum cip_flags { CIP_SKIP_DBC_ZERO_CHECK = 0x10, CIP_EMPTY_HAS_WRONG_DBC = 0x20, CIP_JUMBO_PAYLOAD = 0x40, + CIP_HEADER_WITHOUT_EOH = 0x80, + CIP_NO_HEADER = 0x100, }; /** @@ -101,11 +106,17 @@ struct amdtp_stream { struct fw_iso_context *context; struct iso_packets_buffer buffer; int packet_index; + int tag; + int (*handle_packet)(struct amdtp_stream *s, + unsigned int payload_quadlets, unsigned int cycle, + unsigned int index); + unsigned int max_payload_length; /* For CIP headers. */ unsigned int source_node_id_field; unsigned int data_block_quadlets; unsigned int data_block_counter; + unsigned int sph; unsigned int fmt; unsigned int fdf; /* quirk: fixed interval of dbc between previos/current packets. */ @@ -130,6 +141,7 @@ struct amdtp_stream { /* To wait for first packet. */ bool callbacked; wait_queue_head_t callback_wait; + u32 start_cycle; /* For backends to process data blocks. */ void *protocol; diff --git a/sound/firewire/bebob/bebob_command.c b/sound/firewire/bebob/bebob_command.c index 9402cc15dbc1..f9b4225dd86f 100644 --- a/sound/firewire/bebob/bebob_command.c +++ b/sound/firewire/bebob/bebob_command.c @@ -31,13 +31,15 @@ int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id, err = fcp_avc_transaction(unit, buf, 12, buf, 12, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(8)); - if (err > 0 && err < 9) + if (err < 0) + ; + else if (err < 9) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; else if (buf[0] == 0x0a) /* REJECTED */ err = -EINVAL; - else if (err > 0) + else err = 0; kfree(buf); @@ -67,7 +69,9 @@ int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id, err = fcp_avc_transaction(unit, buf, 12, buf, 12, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(8)); - if (err > 0 && err < 9) + if (err < 0) + ; + else if (err < 9) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -120,7 +124,9 @@ int avc_bridgeco_get_plug_type(struct fw_unit *unit, err = fcp_avc_transaction(unit, buf, 12, buf, 12, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(9)); - if ((err >= 0) && (err < 8)) + if (err < 0) + ; + else if (err < 11) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -150,7 +156,9 @@ int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit, err = fcp_avc_transaction(unit, buf, 12, buf, 256, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(9)); - if ((err >= 0) && (err < 8)) + if (err < 0) + ; + else if (err < 11) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -187,7 +195,9 @@ int avc_bridgeco_get_plug_section_type(struct fw_unit *unit, err = fcp_avc_transaction(unit, buf, 12, buf, 12, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(9) | BIT(10)); - if ((err >= 0) && (err < 8)) + if (err < 0) + ; + else if (err < 12) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -221,7 +231,9 @@ int avc_bridgeco_get_plug_input(struct fw_unit *unit, err = fcp_avc_transaction(unit, buf, 16, buf, 16, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7)); - if ((err >= 0) && (err < 8)) + if (err < 0) + ; + else if (err < 16) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -260,7 +272,9 @@ int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit, err = fcp_avc_transaction(unit, buf, 12, buf, *len, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(10)); - if ((err >= 0) && (err < 12)) + if (err < 0) + ; + else if (err < 12) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; diff --git a/sound/firewire/digi00x/amdtp-dot.c b/sound/firewire/digi00x/amdtp-dot.c index b3cffd01a19f..a4688545339c 100644 --- a/sound/firewire/digi00x/amdtp-dot.c +++ b/sound/firewire/digi00x/amdtp-dot.c @@ -28,6 +28,9 @@ */ #define MAX_MIDI_RX_BLOCKS 8 +/* 3 = MAX(DOT_MIDI_IN_PORTS, DOT_MIDI_OUT_PORTS) + 1. */ +#define MAX_MIDI_PORTS 3 + /* * The double-oh-three algorithm was discovered by Robin Gareus and Damien * Zammit in 2012, with reverse-engineering for Digi 003 Rack. @@ -42,10 +45,8 @@ struct amdtp_dot { unsigned int pcm_channels; struct dot_state state; - unsigned int midi_ports; - /* 2 = MAX(DOT_MIDI_IN_PORTS, DOT_MIDI_OUT_PORTS) */ - struct snd_rawmidi_substream *midi[2]; - int midi_fifo_used[2]; + struct snd_rawmidi_substream *midi[MAX_MIDI_PORTS]; + int midi_fifo_used[MAX_MIDI_PORTS]; int midi_fifo_limit; void (*transfer_samples)(struct amdtp_stream *s, @@ -124,8 +125,8 @@ int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate, return -EBUSY; /* - * A first data channel is for MIDI conformant data channel, the rest is - * Multi Bit Linear Audio data channel. + * A first data channel is for MIDI messages, the rest is Multi Bit + * Linear Audio data channel. */ err = amdtp_stream_set_parameters(s, rate, pcm_channels + 1); if (err < 0) @@ -135,11 +136,6 @@ int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate, p->pcm_channels = pcm_channels; - if (s->direction == AMDTP_IN_STREAM) - p->midi_ports = DOT_MIDI_IN_PORTS; - else - p->midi_ports = DOT_MIDI_OUT_PORTS; - /* * We do not know the actual MIDI FIFO size of most devices. Just * assume two bytes, i.e., one byte can be received over the bus while @@ -281,13 +277,25 @@ static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, b = (u8 *)&buffer[0]; len = 0; - if (port < p->midi_ports && + if (port < MAX_MIDI_PORTS && midi_ratelimit_per_packet(s, port) && p->midi[port] != NULL) len = snd_rawmidi_transmit(p->midi[port], b + 1, 2); if (len > 0) { - b[3] = (0x10 << port) | len; + /* + * Upper 4 bits of LSB represent port number. + * - 0000b: physical MIDI port 1. + * - 0010b: physical MIDI port 2. + * - 1110b: console MIDI port. + */ + if (port == 2) + b[3] = 0xe0; + else if (port == 1) + b[3] = 0x20; + else + b[3] = 0x00; + b[3] |= len; midi_use_bytes(s, port, len); } else { b[1] = 0; @@ -309,11 +317,22 @@ static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer, for (f = 0; f < data_blocks; f++) { b = (u8 *)&buffer[0]; - port = b[3] >> 4; - len = b[3] & 0x0f; - if (port < p->midi_ports && p->midi[port] && len > 0) - snd_rawmidi_receive(p->midi[port], b + 1, len); + len = b[3] & 0x0f; + if (len > 0) { + /* + * Upper 4 bits of LSB represent port number. + * - 0000b: physical MIDI port 1. Use port 0. + * - 1110b: console MIDI port. Use port 2. + */ + if (b[3] >> 4 > 0) + port = 2; + else + port = 0; + + if (port < MAX_MIDI_PORTS && p->midi[port]) + snd_rawmidi_receive(p->midi[port], b + 1, len); + } buffer += s->data_block_quadlets; } @@ -364,7 +383,7 @@ void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port, { struct amdtp_dot *p = s->protocol; - if (port < p->midi_ports) + if (port < MAX_MIDI_PORTS) ACCESS_ONCE(p->midi[port]) = midi; } diff --git a/sound/firewire/digi00x/digi00x-midi.c b/sound/firewire/digi00x/digi00x-midi.c index 915d2a21223e..7ab3d0810f6b 100644 --- a/sound/firewire/digi00x/digi00x-midi.c +++ b/sound/firewire/digi00x/digi00x-midi.c @@ -8,7 +8,7 @@ #include "digi00x.h" -static int midi_phys_open(struct snd_rawmidi_substream *substream) +static int midi_open(struct snd_rawmidi_substream *substream) { struct snd_dg00x *dg00x = substream->rmidi->private_data; int err; @@ -27,7 +27,7 @@ static int midi_phys_open(struct snd_rawmidi_substream *substream) return err; } -static int midi_phys_close(struct snd_rawmidi_substream *substream) +static int midi_close(struct snd_rawmidi_substream *substream) { struct snd_dg00x *dg00x = substream->rmidi->private_data; @@ -40,180 +40,130 @@ static int midi_phys_close(struct snd_rawmidi_substream *substream) return 0; } -static void midi_phys_capture_trigger(struct snd_rawmidi_substream *substream, - int up) +static void midi_capture_trigger(struct snd_rawmidi_substream *substream, + int up) { struct snd_dg00x *dg00x = substream->rmidi->private_data; + unsigned int port; unsigned long flags; - spin_lock_irqsave(&dg00x->lock, flags); - - if (up) - amdtp_dot_midi_trigger(&dg00x->tx_stream, substream->number, - substream); + if (substream->rmidi->device == 0) + port = substream->number; else - amdtp_dot_midi_trigger(&dg00x->tx_stream, substream->number, - NULL); - - spin_unlock_irqrestore(&dg00x->lock, flags); -} - -static void midi_phys_playback_trigger(struct snd_rawmidi_substream *substream, - int up) -{ - struct snd_dg00x *dg00x = substream->rmidi->private_data; - unsigned long flags; + port = 2; spin_lock_irqsave(&dg00x->lock, flags); if (up) - amdtp_dot_midi_trigger(&dg00x->rx_stream, substream->number, - substream); + amdtp_dot_midi_trigger(&dg00x->tx_stream, port, substream); else - amdtp_dot_midi_trigger(&dg00x->rx_stream, substream->number, - NULL); + amdtp_dot_midi_trigger(&dg00x->tx_stream, port, NULL); spin_unlock_irqrestore(&dg00x->lock, flags); } -static int midi_ctl_open(struct snd_rawmidi_substream *substream) -{ - /* Do nothing. */ - return 0; -} - -static int midi_ctl_capture_close(struct snd_rawmidi_substream *substream) -{ - /* Do nothing. */ - return 0; -} - -static int midi_ctl_playback_close(struct snd_rawmidi_substream *substream) -{ - struct snd_dg00x *dg00x = substream->rmidi->private_data; - - snd_fw_async_midi_port_finish(&dg00x->out_control); - - return 0; -} - -static void midi_ctl_capture_trigger(struct snd_rawmidi_substream *substream, - int up) +static void midi_playback_trigger(struct snd_rawmidi_substream *substream, + int up) { struct snd_dg00x *dg00x = substream->rmidi->private_data; + unsigned int port; unsigned long flags; - spin_lock_irqsave(&dg00x->lock, flags); - - if (up) - dg00x->in_control = substream; + if (substream->rmidi->device == 0) + port = substream->number; else - dg00x->in_control = NULL; - - spin_unlock_irqrestore(&dg00x->lock, flags); -} - -static void midi_ctl_playback_trigger(struct snd_rawmidi_substream *substream, - int up) -{ - struct snd_dg00x *dg00x = substream->rmidi->private_data; - unsigned long flags; + port = 2; spin_lock_irqsave(&dg00x->lock, flags); if (up) - snd_fw_async_midi_port_run(&dg00x->out_control, substream); + amdtp_dot_midi_trigger(&dg00x->rx_stream, port, substream); + else + amdtp_dot_midi_trigger(&dg00x->rx_stream, port, NULL); spin_unlock_irqrestore(&dg00x->lock, flags); } -static void set_midi_substream_names(struct snd_dg00x *dg00x, - struct snd_rawmidi_str *str, - bool is_ctl) +static void set_substream_names(struct snd_dg00x *dg00x, + struct snd_rawmidi *rmidi, bool is_console) { struct snd_rawmidi_substream *subs; - - list_for_each_entry(subs, &str->substreams, list) { - if (!is_ctl) - snprintf(subs->name, sizeof(subs->name), - "%s MIDI %d", - dg00x->card->shortname, subs->number + 1); - else - /* This port is for asynchronous transaction. */ - snprintf(subs->name, sizeof(subs->name), - "%s control", - dg00x->card->shortname); + struct snd_rawmidi_str *str; + int i; + + for (i = 0; i < 2; ++i) { + str = &rmidi->streams[i]; + + list_for_each_entry(subs, &str->substreams, list) { + if (!is_console) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", + dg00x->card->shortname, + subs->number + 1); + } else { + snprintf(subs->name, sizeof(subs->name), + "%s control", + dg00x->card->shortname); + } + } } } -int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x) +static int add_substream_pair(struct snd_dg00x *dg00x, unsigned int out_ports, + unsigned int in_ports, bool is_console) { - static const struct snd_rawmidi_ops phys_capture_ops = { - .open = midi_phys_open, - .close = midi_phys_close, - .trigger = midi_phys_capture_trigger, - }; - static const struct snd_rawmidi_ops phys_playback_ops = { - .open = midi_phys_open, - .close = midi_phys_close, - .trigger = midi_phys_playback_trigger, + static const struct snd_rawmidi_ops capture_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_capture_trigger, }; - static const struct snd_rawmidi_ops ctl_capture_ops = { - .open = midi_ctl_open, - .close = midi_ctl_capture_close, - .trigger = midi_ctl_capture_trigger, + static const struct snd_rawmidi_ops playback_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_playback_trigger, }; - static const struct snd_rawmidi_ops ctl_playback_ops = { - .open = midi_ctl_open, - .close = midi_ctl_playback_close, - .trigger = midi_ctl_playback_trigger, - }; - struct snd_rawmidi *rmidi[2]; - struct snd_rawmidi_str *str; - unsigned int i; + const char *label; + struct snd_rawmidi *rmidi; int err; /* Add physical midi ports. */ - err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 0, - DOT_MIDI_OUT_PORTS, DOT_MIDI_IN_PORTS, &rmidi[0]); + err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, is_console, + out_ports, in_ports, &rmidi); if (err < 0) return err; + rmidi->private_data = dg00x; - snprintf(rmidi[0]->name, sizeof(rmidi[0]->name), - "%s MIDI", dg00x->card->shortname); - - snd_rawmidi_set_ops(rmidi[0], SNDRV_RAWMIDI_STREAM_INPUT, - &phys_capture_ops); - snd_rawmidi_set_ops(rmidi[0], SNDRV_RAWMIDI_STREAM_OUTPUT, - &phys_playback_ops); + if (!is_console) + label = "%s control"; + else + label = "%s MIDI"; + snprintf(rmidi->name, sizeof(rmidi->name), label, + dg00x->card->shortname); - /* Add a pair of control midi ports. */ - err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 1, - 1, 1, &rmidi[1]); - if (err < 0) - return err; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &playback_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &capture_ops); - snprintf(rmidi[1]->name, sizeof(rmidi[1]->name), - "%s control", dg00x->card->shortname); + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; - snd_rawmidi_set_ops(rmidi[1], SNDRV_RAWMIDI_STREAM_INPUT, - &ctl_capture_ops); - snd_rawmidi_set_ops(rmidi[1], SNDRV_RAWMIDI_STREAM_OUTPUT, - &ctl_playback_ops); + set_substream_names(dg00x, rmidi, is_console); - for (i = 0; i < ARRAY_SIZE(rmidi); i++) { - rmidi[i]->private_data = dg00x; + return 0; +} - rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; - str = &rmidi[i]->streams[SNDRV_RAWMIDI_STREAM_INPUT]; - set_midi_substream_names(dg00x, str, i); +int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x) +{ + int err; - rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; - str = &rmidi[i]->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; - set_midi_substream_names(dg00x, str, i); + /* Add physical midi ports. */ + err = add_substream_pair(dg00x, DOT_MIDI_OUT_PORTS, DOT_MIDI_IN_PORTS, + false); + if (err < 0) + return err; - rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; - } + if (dg00x->is_console) + err = add_substream_pair(dg00x, 1, 1, true); - return 0; + return err; } diff --git a/sound/firewire/digi00x/digi00x-transaction.c b/sound/firewire/digi00x/digi00x-transaction.c index 735d35640807..af9bc8504781 100644 --- a/sound/firewire/digi00x/digi00x-transaction.c +++ b/sound/firewire/digi00x/digi00x-transaction.c @@ -9,40 +9,6 @@ #include <sound/asound.h> #include "digi00x.h" -static int fill_midi_message(struct snd_rawmidi_substream *substream, u8 *buf) -{ - int bytes; - - buf[0] = 0x80; - bytes = snd_rawmidi_transmit_peek(substream, buf + 1, 2); - if (bytes >= 0) - buf[3] = 0xc0 | bytes; - - return bytes; -} - -static void handle_midi_control(struct snd_dg00x *dg00x, __be32 *buf, - unsigned int length) -{ - struct snd_rawmidi_substream *substream; - unsigned int i; - unsigned int len; - u8 *b; - - substream = ACCESS_ONCE(dg00x->in_control); - if (substream == NULL) - return; - - length /= 4; - - for (i = 0; i < length; i++) { - b = (u8 *)&buf[i]; - len = b[3] & 0xf; - if (len > 0) - snd_rawmidi_receive(dg00x->in_control, b + 1, len); - } -} - static void handle_unknown_message(struct snd_dg00x *dg00x, unsigned long long offset, __be32 *buf) { @@ -63,39 +29,36 @@ static void handle_message(struct fw_card *card, struct fw_request *request, struct snd_dg00x *dg00x = callback_data; __be32 *buf = (__be32 *)data; + fw_send_response(card, request, RCODE_COMPLETE); + if (offset == dg00x->async_handler.offset) handle_unknown_message(dg00x, offset, buf); - else if (offset == dg00x->async_handler.offset + 4) - handle_midi_control(dg00x, buf, length); - - fw_send_response(card, request, RCODE_COMPLETE); } int snd_dg00x_transaction_reregister(struct snd_dg00x *dg00x) { struct fw_device *device = fw_parent_device(dg00x->unit); __be32 data[2]; - int err; /* Unknown. 4bytes. */ data[0] = cpu_to_be32((device->card->node_id << 16) | (dg00x->async_handler.offset >> 32)); data[1] = cpu_to_be32(dg00x->async_handler.offset); - err = snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST, - DG00X_ADDR_BASE + DG00X_OFFSET_MESSAGE_ADDR, - &data, sizeof(data), 0); - if (err < 0) - return err; - - /* Asynchronous transactions for MIDI control message. */ - data[0] = cpu_to_be32((device->card->node_id << 16) | - (dg00x->async_handler.offset >> 32)); - data[1] = cpu_to_be32(dg00x->async_handler.offset + 4); return snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST, - DG00X_ADDR_BASE + DG00X_OFFSET_MIDI_CTL_ADDR, + DG00X_ADDR_BASE + DG00X_OFFSET_MESSAGE_ADDR, &data, sizeof(data), 0); } +void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x) +{ + if (dg00x->async_handler.callback_data == NULL) + return; + + fw_core_remove_address_handler(&dg00x->async_handler); + + dg00x->async_handler.callback_data = NULL; +} + int snd_dg00x_transaction_register(struct snd_dg00x *dg00x) { static const struct fw_address_region resp_register_region = { @@ -104,7 +67,7 @@ int snd_dg00x_transaction_register(struct snd_dg00x *dg00x) }; int err; - dg00x->async_handler.length = 12; + dg00x->async_handler.length = 4; dg00x->async_handler.address_callback = handle_message; dg00x->async_handler.callback_data = dg00x; @@ -115,28 +78,7 @@ int snd_dg00x_transaction_register(struct snd_dg00x *dg00x) err = snd_dg00x_transaction_reregister(dg00x); if (err < 0) - goto error; - - err = snd_fw_async_midi_port_init(&dg00x->out_control, dg00x->unit, - DG00X_ADDR_BASE + DG00X_OFFSET_MMC, - 4, fill_midi_message); - if (err < 0) - goto error; + snd_dg00x_transaction_unregister(dg00x); return err; -error: - fw_core_remove_address_handler(&dg00x->async_handler); - dg00x->async_handler.callback_data = NULL; - return err; -} - -void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x) -{ - if (dg00x->async_handler.callback_data == NULL) - return; - - snd_fw_async_midi_port_destroy(&dg00x->out_control); - fw_core_remove_address_handler(&dg00x->async_handler); - - dg00x->async_handler.callback_data = NULL; } diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c index cc4776c6ded3..1f5e1d23f31a 100644 --- a/sound/firewire/digi00x/digi00x.c +++ b/sound/firewire/digi00x/digi00x.c @@ -13,7 +13,8 @@ MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>"); MODULE_LICENSE("GPL v2"); #define VENDOR_DIGIDESIGN 0x00a07e -#define MODEL_DIGI00X 0x000002 +#define MODEL_CONSOLE 0x000001 +#define MODEL_RACK 0x000002 static int name_card(struct snd_dg00x *dg00x) { @@ -129,6 +130,8 @@ static int snd_dg00x_probe(struct fw_unit *unit, spin_lock_init(&dg00x->lock); init_waitqueue_head(&dg00x->hwdep_wait); + dg00x->is_console = entry->model_id == MODEL_CONSOLE; + /* Allocate and register this sound card later. */ INIT_DEFERRABLE_WORK(&dg00x->dwork, do_registration); snd_fw_schedule_registration(unit, &dg00x->dwork); @@ -183,7 +186,13 @@ static const struct ieee1394_device_id snd_dg00x_id_table[] = { .match_flags = IEEE1394_MATCH_VENDOR_ID | IEEE1394_MATCH_MODEL_ID, .vendor_id = VENDOR_DIGIDESIGN, - .model_id = MODEL_DIGI00X, + .model_id = MODEL_CONSOLE, + }, + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = VENDOR_DIGIDESIGN, + .model_id = MODEL_RACK, }, {} }; diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h index 9dc761bdacca..1275a50956c0 100644 --- a/sound/firewire/digi00x/digi00x.h +++ b/sound/firewire/digi00x/digi00x.h @@ -58,16 +58,15 @@ struct snd_dg00x { struct fw_address_handler async_handler; u32 msg; - /* For asynchronous MIDI controls. */ - struct snd_rawmidi_substream *in_control; - struct snd_fw_async_midi_port out_control; + /* Console models have additional MIDI ports for control surface. */ + bool is_console; }; #define DG00X_ADDR_BASE 0xffffe0000000ull #define DG00X_OFFSET_STREAMING_STATE 0x0000 #define DG00X_OFFSET_STREAMING_SET 0x0004 -#define DG00X_OFFSET_MIDI_CTL_ADDR 0x0008 +/* unknown but address in host space 0x0008 */ /* For LSB of the address 0x000c */ /* unknown 0x0010 */ #define DG00X_OFFSET_MESSAGE_ADDR 0x0014 diff --git a/sound/firewire/fcp.c b/sound/firewire/fcp.c index cce19768f43d..61dda828f767 100644 --- a/sound/firewire/fcp.c +++ b/sound/firewire/fcp.c @@ -63,7 +63,9 @@ int avc_general_set_sig_fmt(struct fw_unit *unit, unsigned int rate, /* do transaction and check buf[1-5] are the same against command */ err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); - if (err >= 0 && err < 8) + if (err < 0) + ; + else if (err < 8) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -106,7 +108,9 @@ int avc_general_get_sig_fmt(struct fw_unit *unit, unsigned int *rate, /* do transaction and check buf[1-4] are the same against command */ err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2) | BIT(3) | BIT(4)); - if (err >= 0 && err < 8) + if (err < 0) + ; + else if (err < 8) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -154,7 +158,9 @@ int avc_general_get_plug_info(struct fw_unit *unit, unsigned int subunit_type, buf[3] = 0xff & subfunction; err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2)); - if (err >= 0 && err < 8) + if (err < 0) + ; + else if (err < 8) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile new file mode 100644 index 000000000000..8f807284ba54 --- /dev/null +++ b/sound/firewire/fireface/Makefile @@ -0,0 +1,3 @@ +snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \ + ff-stream.o ff-pcm.o ff-hwdep.o ff-protocol-ff400.o +obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/amdtp-ff.c b/sound/firewire/fireface/amdtp-ff.c new file mode 100644 index 000000000000..780da9deb2f0 --- /dev/null +++ b/sound/firewire/fireface/amdtp-ff.c @@ -0,0 +1,155 @@ +/* + * amdtp-ff.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <sound/pcm.h> +#include "ff.h" + +struct amdtp_ff { + unsigned int pcm_channels; +}; + +int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels) +{ + struct amdtp_ff *p = s->protocol; + unsigned int data_channels; + + if (amdtp_stream_running(s)) + return -EBUSY; + + p->pcm_channels = pcm_channels; + data_channels = pcm_channels; + + 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) +{ + struct amdtp_ff *p = s->protocol; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, i, c; + const u32 *src; + + 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; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + buffer[c] = cpu_to_le32(*src); + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void read_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __le32 *buffer, unsigned int frames) +{ + struct amdtp_ff *p = s->protocol; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, i, c; + u32 *dst; + + 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; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + *dst = le32_to_cpu(buffer[c]) & 0xffffff00; + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, + __le32 *buffer, unsigned int frames) +{ + struct amdtp_ff *p = s->protocol; + unsigned int i, c, channels = p->pcm_channels; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) + buffer[c] = cpu_to_le32(0x00000000); + buffer += s->data_block_quadlets; + } +} + +int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + + 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) +{ + struct snd_pcm_substream *pcm = ACCESS_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; + } + + return pcm_frames; +} + +static unsigned int process_tx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct snd_pcm_substream *pcm = ACCESS_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; + } + + return pcm_frames; +} + +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; + + if (dir == AMDTP_IN_STREAM) + process_data_blocks = process_tx_data_blocks; + else + process_data_blocks = process_rx_data_blocks; + + return amdtp_stream_init(s, unit, dir, CIP_NO_HEADER, 0, + process_data_blocks, sizeof(struct amdtp_ff)); +} diff --git a/sound/firewire/fireface/ff-hwdep.c b/sound/firewire/fireface/ff-hwdep.c new file mode 100644 index 000000000000..3ee04b054585 --- /dev/null +++ b/sound/firewire/fireface/ff-hwdep.c @@ -0,0 +1,191 @@ +/* + * ff-hwdep.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node information + * 2.get notification about starting/stopping stream + * 3.lock/unlock stream + */ + +#include "ff.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_ff *ff = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&ff->lock); + + while (!ff->dev_lock_changed) { + prepare_to_wait(&ff->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&ff->lock); + schedule(); + finish_wait(&ff->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&ff->lock); + } + + memset(&event, 0, sizeof(event)); + if (ff->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (ff->dev_lock_count > 0); + ff->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } + + spin_unlock_irq(&ff->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_ff *ff = hwdep->private_data; + unsigned int events; + + poll_wait(file, &ff->hwdep_wait, wait); + + spin_lock_irq(&ff->lock); + if (ff->dev_lock_changed) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&ff->lock); + + return events; +} + +static int hwdep_get_info(struct snd_ff *ff, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(ff->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_FIREFACE; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int hwdep_lock(struct snd_ff *ff) +{ + int err; + + spin_lock_irq(&ff->lock); + + if (ff->dev_lock_count == 0) { + ff->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&ff->lock); + + return err; +} + +static int hwdep_unlock(struct snd_ff *ff) +{ + int err; + + spin_lock_irq(&ff->lock); + + if (ff->dev_lock_count == -1) { + ff->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&ff->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_ff *ff = hwdep->private_data; + + spin_lock_irq(&ff->lock); + if (ff->dev_lock_count == -1) + ff->dev_lock_count = 0; + spin_unlock_irq(&ff->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_ff *ff = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(ff, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(ff); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(ff); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +int snd_ff_create_hwdep_devices(struct snd_ff *ff) +{ + static const struct snd_hwdep_ops hwdep_ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(ff->card, ff->card->driver, 0, &hwdep); + if (err < 0) + return err; + + strcpy(hwdep->name, ff->card->driver); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREFACE; + hwdep->ops = hwdep_ops; + hwdep->private_data = ff; + hwdep->exclusive = true; + + return 0; +} diff --git a/sound/firewire/fireface/ff-midi.c b/sound/firewire/fireface/ff-midi.c new file mode 100644 index 000000000000..29ee0a7365c3 --- /dev/null +++ b/sound/firewire/fireface/ff-midi.c @@ -0,0 +1,131 @@ +/* + * ff-midi.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + /* Do nothing. */ + return 0; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_ff *ff = substream->rmidi->private_data; + + /* Initialize internal status. */ + ff->running_status[substream->number] = 0; + ff->rx_midi_error[substream->number] = false; + + ACCESS_ONCE(ff->rx_midi_substreams[substream->number]) = substream; + + return 0; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + /* Do nothing. */ + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_ff *ff = substream->rmidi->private_data; + + cancel_work_sync(&ff->rx_midi_work[substream->number]); + ACCESS_ONCE(ff->rx_midi_substreams[substream->number]) = NULL; + + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_ff *ff = substream->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&ff->lock, flags); + + if (up) + ACCESS_ONCE(ff->tx_midi_substreams[substream->number]) = + substream; + else + ACCESS_ONCE(ff->tx_midi_substreams[substream->number]) = NULL; + + spin_unlock_irqrestore(&ff->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_ff *ff = substream->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&ff->lock, flags); + + if (up || !ff->rx_midi_error[substream->number]) + schedule_work(&ff->rx_midi_work[substream->number]); + + spin_unlock_irqrestore(&ff->lock, flags); +} + +static struct snd_rawmidi_ops midi_capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, +}; + +static struct snd_rawmidi_ops midi_playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, +}; + +static void set_midi_substream_names(struct snd_rawmidi_str *stream, + const char *const name) +{ + struct snd_rawmidi_substream *substream; + + list_for_each_entry(substream, &stream->substreams, list) { + snprintf(substream->name, sizeof(substream->name), + "%s MIDI %d", name, substream->number + 1); + } +} + +int snd_ff_create_midi_devices(struct snd_ff *ff) +{ + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *stream; + int err; + + err = snd_rawmidi_new(ff->card, ff->card->driver, 0, + ff->spec->midi_out_ports, ff->spec->midi_in_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", ff->card->shortname); + rmidi->private_data = ff; + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &midi_capture_ops); + stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + set_midi_substream_names(stream, ff->card->shortname); + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &midi_playback_ops); + stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + set_midi_substream_names(stream, ff->card->shortname); + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/fireface/ff-pcm.c b/sound/firewire/fireface/ff-pcm.c new file mode 100644 index 000000000000..93cee1978e8e --- /dev/null +++ b/sound/firewire/fireface/ff-pcm.c @@ -0,0 +1,409 @@ +/* + * ff-pcm.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +static inline unsigned int get_multiplier_mode_with_index(unsigned int index) +{ + return ((int)index - 1) / 2; +} + +static int hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + const unsigned int *pcm_channels = rule->private; + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, mode; + + for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (!snd_interval_test(c, pcm_channels[mode])) + continue; + + t.min = min(t.min, amdtp_rate_table[i]); + t.max = max(t.max, amdtp_rate_table[i]); + } + + return snd_interval_refine(r, &t); +} + +static int hw_rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + const unsigned int *pcm_channels = rule->private; + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, mode; + + for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (!snd_interval_test(r, amdtp_rate_table[i])) + continue; + + t.min = min(t.min, pcm_channels[mode]); + t.max = max(t.max, pcm_channels[mode]); + } + + return snd_interval_refine(c, &t); +} + +static void limit_channels_and_rates(struct snd_pcm_hardware *hw, + const unsigned int *pcm_channels) +{ + unsigned int mode; + unsigned int rate, channels; + int i; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + hw->rate_min = UINT_MAX; + hw->rate_max = 0; + + for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) { + mode = get_multiplier_mode_with_index(i); + + channels = pcm_channels[mode]; + if (pcm_channels[mode] == 0) + continue; + hw->channels_min = min(hw->channels_min, channels); + hw->channels_max = max(hw->channels_max, channels); + + rate = amdtp_rate_table[i]; + hw->rates |= snd_pcm_rate_to_rate_bit(rate); + hw->rate_min = min(hw->rate_min, rate); + hw->rate_max = max(hw->rate_max, rate); + } +} + +static void limit_period_and_buffer(struct snd_pcm_hardware *hw) +{ + hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */ + hw->periods_max = UINT_MAX; + + hw->period_bytes_min = 4 * hw->channels_max; /* bytes for a frame */ + + /* Just to prevent from allocating much pages. */ + hw->period_bytes_max = hw->period_bytes_min * 2048; + hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min; +} + +static int pcm_init_hw_params(struct snd_ff *ff, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct amdtp_stream *s; + const unsigned int *pcm_channels; + int err; + + runtime->hw.info = SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + s = &ff->tx_stream; + pcm_channels = ff->spec->pcm_capture_channels; + } else { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + s = &ff->rx_stream; + pcm_channels = ff->spec->pcm_playback_channels; + } + + /* limit rates */ + limit_channels_and_rates(&runtime->hw, pcm_channels); + limit_period_and_buffer(&runtime->hw); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, (void *)pcm_channels, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, (void *)pcm_channels, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + + return amdtp_ff_add_pcm_hw_constraints(s, runtime); +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + unsigned int rate; + enum snd_ff_clock_src src; + int i, err; + + err = snd_ff_stream_lock_try(ff); + if (err < 0) + return err; + + err = pcm_init_hw_params(ff, substream); + if (err < 0) { + snd_ff_stream_lock_release(ff); + return err; + } + + err = ff->spec->protocol->get_clock(ff, &rate, &src); + if (err < 0) { + snd_ff_stream_lock_release(ff); + return err; + } + + if (src != SND_FF_CLOCK_SRC_INTERNAL) { + for (i = 0; i < CIP_SFC_COUNT; ++i) { + if (amdtp_rate_table[i] == rate) + break; + } + /* + * The unit is configured at sampling frequency which packet + * streaming engine can't support. + */ + if (i >= CIP_SFC_COUNT) { + snd_ff_stream_lock_release(ff); + return -EIO; + } + + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } else { + if (amdtp_stream_pcm_running(&ff->rx_stream) || + amdtp_stream_pcm_running(&ff->tx_stream)) { + rate = amdtp_rate_table[ff->rx_stream.sfc]; + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } + } + + snd_pcm_set_sync(substream); + + return 0; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + + snd_ff_stream_lock_release(ff); + + return 0; +} + +static int pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_ff *ff = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&ff->mutex); + ff->substreams_counter++; + mutex_unlock(&ff->mutex); + } + + return 0; +} + +static int pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_ff *ff = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&ff->mutex); + ff->substreams_counter++; + mutex_unlock(&ff->mutex); + } + + return 0; +} + +static int pcm_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + + mutex_lock(&ff->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + ff->substreams_counter--; + + snd_ff_stream_stop_duplex(ff); + + mutex_unlock(&ff->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + + mutex_lock(&ff->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + ff->substreams_counter--; + + snd_ff_stream_stop_duplex(ff); + + mutex_unlock(&ff->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&ff->mutex); + + err = snd_ff_stream_start_duplex(ff, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&ff->tx_stream); + + mutex_unlock(&ff->mutex); + + return err; +} + +static int pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&ff->mutex); + + err = snd_ff_stream_start_duplex(ff, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&ff->rx_stream); + + mutex_unlock(&ff->mutex); + + return err; +} + +static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_ff *ff = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&ff->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&ff->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_ff *ff = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&ff->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&ff->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_ff *ff = sbstrm->private_data; + + return amdtp_stream_pcm_pointer(&ff->tx_stream); +} + +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_ff *ff = sbstrm->private_data; + + return amdtp_stream_pcm_pointer(&ff->rx_stream); +} + +static struct snd_pcm_ops pcm_capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_capture_hw_params, + .hw_free = pcm_capture_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static struct snd_pcm_ops pcm_playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_playback_hw_params, + .hw_free = pcm_playback_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, +}; + +int snd_ff_create_pcm_devices(struct snd_ff *ff) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(ff->card, ff->card->driver, 0, 1, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = ff; + snprintf(pcm->name, sizeof(pcm->name), + "%s PCM", ff->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops); + + return 0; +} diff --git a/sound/firewire/fireface/ff-proc.c b/sound/firewire/fireface/ff-proc.c new file mode 100644 index 000000000000..69441d121f71 --- /dev/null +++ b/sound/firewire/fireface/ff-proc.c @@ -0,0 +1,63 @@ +/* + * ff-proc.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./ff.h" + +static void proc_dump_clock_config(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ff *ff = entry->private_data; + + ff->spec->protocol->dump_clock_config(ff, buffer); +} + +static void proc_dump_sync_status(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ff *ff = entry->private_data; + + ff->spec->protocol->dump_sync_status(ff, buffer); +} + +static void add_node(struct snd_ff *ff, struct snd_info_entry *root, + const char *name, + void (*op)(struct snd_info_entry *e, + struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(ff->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, ff, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_ff_proc_init(struct snd_ff *ff) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(ff->card, "firewire", + ff->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(ff, root, "clock-config", proc_dump_clock_config); + add_node(ff, root, "sync-status", proc_dump_sync_status); +} diff --git a/sound/firewire/fireface/ff-protocol-ff400.c b/sound/firewire/fireface/ff-protocol-ff400.c new file mode 100644 index 000000000000..fcec6de80eeb --- /dev/null +++ b/sound/firewire/fireface/ff-protocol-ff400.c @@ -0,0 +1,371 @@ +/* + * ff-protocol-ff400.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/delay.h> +#include "ff.h" + +#define FF400_STF 0x000080100500ull +#define FF400_RX_PACKET_FORMAT 0x000080100504ull +#define FF400_ISOC_COMM_START 0x000080100508ull +#define FF400_TX_PACKET_FORMAT 0x00008010050cull +#define FF400_ISOC_COMM_STOP 0x000080100510ull +#define FF400_SYNC_STATUS 0x0000801c0000ull +#define FF400_FETCH_PCM_FRAMES 0x0000801c0000ull /* For block request. */ +#define FF400_CLOCK_CONFIG 0x0000801c0004ull + +#define FF400_MIDI_HIGH_ADDR 0x0000801003f4ull +#define FF400_MIDI_RX_PORT_0 0x000080180000ull +#define FF400_MIDI_RX_PORT_1 0x000080190000ull + +static int ff400_get_clock(struct snd_ff *ff, unsigned int *rate, + enum snd_ff_clock_src *src) +{ + __le32 reg; + u32 data; + int err; + + err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST, + FF400_SYNC_STATUS, ®, sizeof(reg), 0); + if (err < 0) + return err; + data = le32_to_cpu(reg); + + /* Calculate sampling rate. */ + switch ((data >> 1) & 0x03) { + case 0x01: + *rate = 32000; + break; + case 0x00: + *rate = 44100; + break; + case 0x03: + *rate = 48000; + break; + case 0x02: + default: + return -EIO; + } + + if (data & 0x08) + *rate *= 2; + else if (data & 0x10) + *rate *= 4; + + /* Calculate source of clock. */ + if (data & 0x01) { + *src = SND_FF_CLOCK_SRC_INTERNAL; + } else { + /* TODO: 0x00, 0x01, 0x02, 0x06, 0x07? */ + switch ((data >> 10) & 0x07) { + case 0x03: + *src = SND_FF_CLOCK_SRC_SPDIF; + break; + case 0x04: + *src = SND_FF_CLOCK_SRC_WORD; + break; + case 0x05: + *src = SND_FF_CLOCK_SRC_LTC; + break; + case 0x00: + default: + *src = SND_FF_CLOCK_SRC_ADAT; + break; + } + } + + return 0; +} + +static int ff400_begin_session(struct snd_ff *ff, unsigned int rate) +{ + __le32 reg; + int i, err; + + /* Check whether the given value is supported or not. */ + for (i = 0; i < CIP_SFC_COUNT; i++) { + if (amdtp_rate_table[i] == rate) + break; + } + if (i == CIP_SFC_COUNT) + return -EINVAL; + + /* Set the number of data blocks transferred in a second. */ + reg = cpu_to_le32(rate); + err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_STF, ®, sizeof(reg), 0); + if (err < 0) + return err; + + msleep(100); + + /* + * Set isochronous channel and the number of quadlets of received + * packets. + */ + reg = cpu_to_le32(((ff->rx_stream.data_block_quadlets << 3) << 8) | + ff->rx_resources.channel); + err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_RX_PACKET_FORMAT, ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* + * Set isochronous channel and the number of quadlets of transmitted + * packet. + */ + /* TODO: investigate the purpose of this 0x80. */ + reg = cpu_to_le32((0x80 << 24) | + (ff->tx_resources.channel << 5) | + (ff->tx_stream.data_block_quadlets)); + err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_TX_PACKET_FORMAT, ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* Allow to transmit packets. */ + reg = cpu_to_le32(0x00000001); + return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_ISOC_COMM_START, ®, sizeof(reg), 0); +} + +static void ff400_finish_session(struct snd_ff *ff) +{ + __le32 reg; + + reg = cpu_to_le32(0x80000000); + snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_ISOC_COMM_STOP, ®, sizeof(reg), 0); +} + +static int ff400_switch_fetching_mode(struct snd_ff *ff, bool enable) +{ + __le32 *reg; + int i; + + reg = kzalloc(sizeof(__le32) * 18, GFP_KERNEL); + if (reg == NULL) + return -ENOMEM; + + if (enable) { + /* + * Each quadlet is corresponding to data channels in a data + * blocks in reverse order. Precisely, quadlets for available + * data channels should be enabled. Here, I take second best + * to fetch PCM frames from all of data channels regardless of + * stf. + */ + for (i = 0; i < 18; ++i) + reg[i] = cpu_to_le32(0x00000001); + } + + return snd_fw_transaction(ff->unit, TCODE_WRITE_BLOCK_REQUEST, + FF400_FETCH_PCM_FRAMES, reg, + sizeof(__le32) * 18, 0); +} + +static void ff400_dump_sync_status(struct snd_ff *ff, + struct snd_info_buffer *buffer) +{ + __le32 reg; + u32 data; + int err; + + err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST, + FF400_SYNC_STATUS, ®, sizeof(reg), 0); + if (err < 0) + return; + + data = le32_to_cpu(reg); + + snd_iprintf(buffer, "External source detection:\n"); + + snd_iprintf(buffer, "Word Clock:"); + if ((data >> 24) & 0x20) { + if ((data >> 24) & 0x40) + snd_iprintf(buffer, "sync\n"); + else + snd_iprintf(buffer, "lock\n"); + } else { + snd_iprintf(buffer, "none\n"); + } + + snd_iprintf(buffer, "S/PDIF:"); + if ((data >> 16) & 0x10) { + if ((data >> 16) & 0x04) + snd_iprintf(buffer, "sync\n"); + else + snd_iprintf(buffer, "lock\n"); + } else { + snd_iprintf(buffer, "none\n"); + } + + snd_iprintf(buffer, "ADAT:"); + if ((data >> 8) & 0x04) { + if ((data >> 8) & 0x10) + snd_iprintf(buffer, "sync\n"); + else + snd_iprintf(buffer, "lock\n"); + } else { + snd_iprintf(buffer, "none\n"); + } + + snd_iprintf(buffer, "\nUsed external source:\n"); + + if (((data >> 22) & 0x07) == 0x07) { + snd_iprintf(buffer, "None\n"); + } else { + switch ((data >> 22) & 0x07) { + case 0x00: + snd_iprintf(buffer, "ADAT:"); + break; + case 0x03: + snd_iprintf(buffer, "S/PDIF:"); + break; + case 0x04: + snd_iprintf(buffer, "Word:"); + break; + case 0x07: + snd_iprintf(buffer, "Nothing:"); + break; + case 0x01: + case 0x02: + case 0x05: + case 0x06: + default: + snd_iprintf(buffer, "unknown:"); + break; + } + + if ((data >> 25) & 0x07) { + switch ((data >> 25) & 0x07) { + case 0x01: + snd_iprintf(buffer, "32000\n"); + break; + case 0x02: + snd_iprintf(buffer, "44100\n"); + break; + case 0x03: + snd_iprintf(buffer, "48000\n"); + break; + case 0x04: + snd_iprintf(buffer, "64000\n"); + break; + case 0x05: + snd_iprintf(buffer, "88200\n"); + break; + case 0x06: + snd_iprintf(buffer, "96000\n"); + break; + case 0x07: + snd_iprintf(buffer, "128000\n"); + break; + case 0x08: + snd_iprintf(buffer, "176400\n"); + break; + case 0x09: + snd_iprintf(buffer, "192000\n"); + break; + case 0x00: + snd_iprintf(buffer, "unknown\n"); + break; + } + } + } + + snd_iprintf(buffer, "Multiplied:"); + snd_iprintf(buffer, "%d\n", (data & 0x3ff) * 250); +} + +static void ff400_dump_clock_config(struct snd_ff *ff, + struct snd_info_buffer *buffer) +{ + __le32 reg; + u32 data; + unsigned int rate; + const char *src; + int err; + + err = snd_fw_transaction(ff->unit, TCODE_READ_BLOCK_REQUEST, + FF400_CLOCK_CONFIG, ®, sizeof(reg), 0); + if (err < 0) + return; + + data = le32_to_cpu(reg); + + snd_iprintf(buffer, "Output S/PDIF format: %s (Emphasis: %s)\n", + (data & 0x20) ? "Professional" : "Consumer", + (data & 0x40) ? "on" : "off"); + + snd_iprintf(buffer, "Optical output interface format: %s\n", + ((data >> 8) & 0x01) ? "S/PDIF" : "ADAT"); + + snd_iprintf(buffer, "Word output single speed: %s\n", + ((data >> 8) & 0x20) ? "on" : "off"); + + snd_iprintf(buffer, "S/PDIF input interface: %s\n", + ((data >> 8) & 0x02) ? "Optical" : "Coaxial"); + + switch ((data >> 1) & 0x03) { + case 0x01: + rate = 32000; + break; + case 0x00: + rate = 44100; + break; + case 0x03: + rate = 48000; + break; + case 0x02: + default: + return; + } + + if (data & 0x08) + rate *= 2; + else if (data & 0x10) + rate *= 4; + + snd_iprintf(buffer, "Sampling rate: %d\n", rate); + + if (data & 0x01) { + src = "Internal"; + } else { + switch ((data >> 10) & 0x07) { + case 0x00: + src = "ADAT"; + break; + case 0x03: + src = "S/PDIF"; + break; + case 0x04: + src = "Word"; + break; + case 0x05: + src = "LTC"; + break; + default: + return; + } + } + + snd_iprintf(buffer, "Sync to clock source: %s\n", src); +} + +struct snd_ff_protocol snd_ff_protocol_ff400 = { + .get_clock = ff400_get_clock, + .begin_session = ff400_begin_session, + .finish_session = ff400_finish_session, + .switch_fetching_mode = ff400_switch_fetching_mode, + + .dump_sync_status = ff400_dump_sync_status, + .dump_clock_config = ff400_dump_clock_config, + + .midi_high_addr_reg = FF400_MIDI_HIGH_ADDR, + .midi_rx_port_0_reg = FF400_MIDI_RX_PORT_0, + .midi_rx_port_1_reg = FF400_MIDI_RX_PORT_1, +}; diff --git a/sound/firewire/fireface/ff-stream.c b/sound/firewire/fireface/ff-stream.c new file mode 100644 index 000000000000..78880922120e --- /dev/null +++ b/sound/firewire/fireface/ff-stream.c @@ -0,0 +1,282 @@ +/* + * ff-stream.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +#define CALLBACK_TIMEOUT_MS 200 + +static int get_rate_mode(unsigned int rate, unsigned int *mode) +{ + int i; + + for (i = 0; i < CIP_SFC_COUNT; i++) { + if (amdtp_rate_table[i] == rate) + break; + } + + if (i == CIP_SFC_COUNT) + return -EINVAL; + + *mode = ((int)i - 1) / 2; + + return 0; +} + +/* + * Fireface 400 manages isochronous channel number in 3 bit field. Therefore, + * we can allocate between 0 and 7 channel. + */ +static int keep_resources(struct snd_ff *ff, unsigned int rate) +{ + int mode; + int err; + + err = get_rate_mode(rate, &mode); + if (err < 0) + return err; + + /* Keep resources for in-stream. */ + err = amdtp_ff_set_parameters(&ff->tx_stream, rate, + ff->spec->pcm_capture_channels[mode]); + if (err < 0) + return err; + ff->tx_resources.channels_mask = 0x00000000000000ffuLL; + err = fw_iso_resources_allocate(&ff->tx_resources, + amdtp_stream_get_max_payload(&ff->tx_stream), + fw_parent_device(ff->unit)->max_speed); + if (err < 0) + return err; + + /* Keep resources for out-stream. */ + err = amdtp_ff_set_parameters(&ff->rx_stream, rate, + ff->spec->pcm_playback_channels[mode]); + if (err < 0) + return err; + ff->rx_resources.channels_mask = 0x00000000000000ffuLL; + err = fw_iso_resources_allocate(&ff->rx_resources, + amdtp_stream_get_max_payload(&ff->rx_stream), + fw_parent_device(ff->unit)->max_speed); + if (err < 0) + fw_iso_resources_free(&ff->tx_resources); + + return err; +} + +static void release_resources(struct snd_ff *ff) +{ + fw_iso_resources_free(&ff->tx_resources); + fw_iso_resources_free(&ff->rx_resources); +} + +static inline void finish_session(struct snd_ff *ff) +{ + 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) +{ + int err; + struct fw_iso_resources *resources; + struct amdtp_stream *stream; + + if (dir == AMDTP_IN_STREAM) { + resources = &ff->tx_resources; + stream = &ff->tx_stream; + } else { + resources = &ff->rx_resources; + stream = &ff->rx_stream; + } + + err = fw_iso_resources_init(resources, ff->unit); + if (err < 0) + return err; + + err = amdtp_ff_init(stream, 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) +{ + if (dir == AMDTP_IN_STREAM) { + amdtp_stream_destroy(&ff->tx_stream); + fw_iso_resources_destroy(&ff->tx_resources); + } else { + amdtp_stream_destroy(&ff->rx_stream); + 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); + if (err < 0) + goto end; + + err = init_stream(ff, AMDTP_IN_STREAM); + if (err < 0) + destroy_stream(ff, AMDTP_OUT_STREAM); +end: + return err; +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +void snd_ff_stream_destroy_duplex(struct snd_ff *ff) +{ + destroy_stream(ff, AMDTP_IN_STREAM); + destroy_stream(ff, AMDTP_OUT_STREAM); +} + +int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate) +{ + unsigned int curr_rate; + enum snd_ff_clock_src src; + int err; + + if (ff->substreams_counter == 0) + return 0; + + err = ff->spec->protocol->get_clock(ff, &curr_rate, &src); + if (err < 0) + return err; + if (curr_rate != rate || + amdtp_streaming_error(&ff->tx_stream) || + amdtp_streaming_error(&ff->rx_stream)) { + finish_session(ff); + + amdtp_stream_stop(&ff->tx_stream); + amdtp_stream_stop(&ff->rx_stream); + + release_resources(ff); + } + + /* + * Regardless of current source of clock signal, drivers transfer some + * packets. Then, the device transfers packets. + */ + if (!amdtp_stream_running(&ff->rx_stream)) { + err = keep_resources(ff, rate); + if (err < 0) + goto error; + + 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); + 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); + 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); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&ff->tx_stream, + CALLBACK_TIMEOUT_MS)) { + err = -ETIMEDOUT; + goto error; + } + } + + return 0; +error: + amdtp_stream_stop(&ff->tx_stream); + amdtp_stream_stop(&ff->rx_stream); + + finish_session(ff); + release_resources(ff); + + return err; +} + +void snd_ff_stream_stop_duplex(struct snd_ff *ff) +{ + if (ff->substreams_counter > 0) + return; + + amdtp_stream_stop(&ff->tx_stream); + amdtp_stream_stop(&ff->rx_stream); + finish_session(ff); + release_resources(ff); +} + +void snd_ff_stream_update_duplex(struct snd_ff *ff) +{ + /* 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); + + fw_iso_resources_update(&ff->tx_resources); + fw_iso_resources_update(&ff->rx_resources); +} + +void snd_ff_stream_lock_changed(struct snd_ff *ff) +{ + ff->dev_lock_changed = true; + wake_up(&ff->hwdep_wait); +} + +int snd_ff_stream_lock_try(struct snd_ff *ff) +{ + int err; + + spin_lock_irq(&ff->lock); + + /* user land lock this */ + if (ff->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (ff->dev_lock_count++ == 0) + snd_ff_stream_lock_changed(ff); + err = 0; +end: + spin_unlock_irq(&ff->lock); + return err; +} + +void snd_ff_stream_lock_release(struct snd_ff *ff) +{ + spin_lock_irq(&ff->lock); + + if (WARN_ON(ff->dev_lock_count <= 0)) + goto end; + if (--ff->dev_lock_count == 0) + snd_ff_stream_lock_changed(ff); +end: + spin_unlock_irq(&ff->lock); +} diff --git a/sound/firewire/fireface/ff-transaction.c b/sound/firewire/fireface/ff-transaction.c new file mode 100644 index 000000000000..dd6c8e839647 --- /dev/null +++ b/sound/firewire/fireface/ff-transaction.c @@ -0,0 +1,295 @@ +/* + * ff-transaction.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +static void finish_transmit_midi_msg(struct snd_ff *ff, unsigned int port, + int rcode) +{ + struct snd_rawmidi_substream *substream = + ACCESS_ONCE(ff->rx_midi_substreams[port]); + + if (rcode_is_permanent_error(rcode)) { + ff->rx_midi_error[port] = true; + return; + } + + if (rcode != RCODE_COMPLETE) { + /* Transfer the message again, immediately. */ + ff->next_ktime[port] = 0; + schedule_work(&ff->rx_midi_work[port]); + return; + } + + snd_rawmidi_transmit_ack(substream, ff->rx_bytes[port]); + ff->rx_bytes[port] = 0; + + if (!snd_rawmidi_transmit_empty(substream)) + schedule_work(&ff->rx_midi_work[port]); +} + +static void finish_transmit_midi0_msg(struct fw_card *card, int rcode, + void *data, size_t length, + void *callback_data) +{ + struct snd_ff *ff = + container_of(callback_data, struct snd_ff, transactions[0]); + finish_transmit_midi_msg(ff, 0, rcode); +} + +static void finish_transmit_midi1_msg(struct fw_card *card, int rcode, + void *data, size_t length, + void *callback_data) +{ + struct snd_ff *ff = + container_of(callback_data, struct snd_ff, transactions[1]); + finish_transmit_midi_msg(ff, 1, rcode); +} + +static inline void fill_midi_buf(struct snd_ff *ff, unsigned int port, + unsigned int index, u8 byte) +{ + ff->msg_buf[port][index] = cpu_to_le32(byte); +} + +static void transmit_midi_msg(struct snd_ff *ff, unsigned int port) +{ + struct snd_rawmidi_substream *substream = + ACCESS_ONCE(ff->rx_midi_substreams[port]); + u8 *buf = (u8 *)ff->msg_buf[port]; + int i, len; + + struct fw_device *fw_dev = fw_parent_device(ff->unit); + unsigned long long addr; + int generation; + fw_transaction_callback_t callback; + + if (substream == NULL || snd_rawmidi_transmit_empty(substream)) + return; + + if (ff->rx_bytes[port] > 0 || ff->rx_midi_error[port]) + return; + + /* Do it in next chance. */ + if (ktime_after(ff->next_ktime[port], ktime_get())) { + schedule_work(&ff->rx_midi_work[port]); + return; + } + + len = snd_rawmidi_transmit_peek(substream, buf, + SND_FF_MAXIMIM_MIDI_QUADS); + if (len <= 0) + return; + + for (i = len - 1; i >= 0; i--) + fill_midi_buf(ff, port, i, buf[i]); + + if (port == 0) { + addr = ff->spec->protocol->midi_rx_port_0_reg; + callback = finish_transmit_midi0_msg; + } else { + addr = ff->spec->protocol->midi_rx_port_1_reg; + callback = finish_transmit_midi1_msg; + } + + /* Set interval to next transaction. */ + ff->next_ktime[port] = ktime_add_ns(ktime_get(), + len * 8 * NSEC_PER_SEC / 31250); + ff->rx_bytes[port] = len; + + /* + * In Linux FireWire core, when generation is updated with memory + * barrier, node id has already been updated. In this module, After + * this smp_rmb(), load/store instructions to memory are completed. + * Thus, both of generation and node id are available with recent + * values. This is a light-serialization solution to handle bus reset + * events on IEEE 1394 bus. + */ + generation = fw_dev->generation; + smp_rmb(); + fw_send_request(fw_dev->card, &ff->transactions[port], + TCODE_WRITE_BLOCK_REQUEST, + fw_dev->node_id, generation, fw_dev->max_speed, + addr, &ff->msg_buf[port], len * 4, + callback, &ff->transactions[port]); +} + +static void transmit_midi0_msg(struct work_struct *work) +{ + struct snd_ff *ff = container_of(work, struct snd_ff, rx_midi_work[0]); + + transmit_midi_msg(ff, 0); +} + +static void transmit_midi1_msg(struct work_struct *work) +{ + struct snd_ff *ff = container_of(work, struct snd_ff, rx_midi_work[1]); + + transmit_midi_msg(ff, 1); +} + +static void handle_midi_msg(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + struct snd_ff *ff = callback_data; + __le32 *buf = data; + u32 quad; + u8 byte; + unsigned int index; + struct snd_rawmidi_substream *substream; + int i; + + fw_send_response(card, request, RCODE_COMPLETE); + + for (i = 0; i < length / 4; i++) { + quad = le32_to_cpu(buf[i]); + + /* Message in first port. */ + /* + * This value may represent the index of this unit when the same + * units are on the same IEEE 1394 bus. This driver doesn't use + * it. + */ + index = (quad >> 8) & 0xff; + if (index > 0) { + substream = ACCESS_ONCE(ff->tx_midi_substreams[0]); + if (substream != NULL) { + byte = quad & 0xff; + snd_rawmidi_receive(substream, &byte, 1); + } + } + + /* Message in second port. */ + index = (quad >> 24) & 0xff; + if (index > 0) { + substream = ACCESS_ONCE(ff->tx_midi_substreams[1]); + if (substream != NULL) { + byte = (quad >> 16) & 0xff; + snd_rawmidi_receive(substream, &byte, 1); + } + } + } +} + +static int allocate_own_address(struct snd_ff *ff, int i) +{ + struct fw_address_region midi_msg_region; + int err; + + ff->async_handler.length = SND_FF_MAXIMIM_MIDI_QUADS * 4; + ff->async_handler.address_callback = handle_midi_msg; + ff->async_handler.callback_data = ff; + + midi_msg_region.start = 0x000100000000ull * i; + midi_msg_region.end = midi_msg_region.start + ff->async_handler.length; + + err = fw_core_add_address_handler(&ff->async_handler, &midi_msg_region); + if (err >= 0) { + /* Controllers are allowed to register this region. */ + if (ff->async_handler.offset & 0x0000ffffffff) { + fw_core_remove_address_handler(&ff->async_handler); + err = -EAGAIN; + } + } + + return err; +} + +/* + * The configuration to start asynchronous transactions for MIDI messages is in + * 0x'0000'8010'051c. This register includes the other options, thus this driver + * doesn't touch it and leaves the decision to userspace. The userspace MUST add + * 0x04000000 to write transactions to the register to receive any MIDI + * messages. + * + * Here, I just describe MIDI-related offsets of the register, in little-endian + * order. + * + * Controllers are allowed to register higher 4 bytes of address to receive + * the transactions. The register is 0x'0000'8010'03f4. On the other hand, the + * controllers are not allowed to register lower 4 bytes of the address. They + * are forced to select from 4 options by writing corresponding bits to + * 0x'0000'8010'051c. + * + * The 3rd-6th bits in MSB of this register are used to indicate lower 4 bytes + * of address to which the device transferrs the transactions. + * - 6th: 0x'....'....'0000'0180 + * - 5th: 0x'....'....'0000'0100 + * - 4th: 0x'....'....'0000'0080 + * - 3rd: 0x'....'....'0000'0000 + * + * This driver configure 0x'....'....'0000'0000 for units to receive MIDI + * messages. 3rd bit of the register should be configured, however this driver + * deligates this task to user space applications due to a restriction that + * this register is write-only and the other bits have own effects. + * + * The 1st and 2nd bits in LSB of this register are used to cancel transferring + * asynchronous transactions. These two bits have the same effect. + * - 1st/2nd: cancel transferring + */ +int snd_ff_transaction_reregister(struct snd_ff *ff) +{ + struct fw_card *fw_card = fw_parent_device(ff->unit)->card; + u32 addr; + __le32 reg; + + /* + * Controllers are allowed to register its node ID and upper 2 byte of + * local address to listen asynchronous transactions. + */ + addr = (fw_card->node_id << 16) | (ff->async_handler.offset >> 32); + reg = cpu_to_le32(addr); + return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + ff->spec->protocol->midi_high_addr_reg, + ®, sizeof(reg), 0); +} + +int snd_ff_transaction_register(struct snd_ff *ff) +{ + int i, err; + + /* + * Allocate in Memory Space of IEC 13213, but lower 4 byte in LSB should + * be zero due to device specification. + */ + for (i = 0; i < 0xffff; i++) { + err = allocate_own_address(ff, i); + if (err != -EBUSY && err != -EAGAIN) + break; + } + if (err < 0) + return err; + + err = snd_ff_transaction_reregister(ff); + if (err < 0) + return err; + + INIT_WORK(&ff->rx_midi_work[0], transmit_midi0_msg); + INIT_WORK(&ff->rx_midi_work[1], transmit_midi1_msg); + + return 0; +} + +void snd_ff_transaction_unregister(struct snd_ff *ff) +{ + __le32 reg; + + if (ff->async_handler.callback_data == NULL) + return; + ff->async_handler.callback_data = NULL; + + /* Release higher 4 bytes of address. */ + reg = cpu_to_le32(0x00000000); + snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + ff->spec->protocol->midi_high_addr_reg, + ®, sizeof(reg), 0); + + fw_core_remove_address_handler(&ff->async_handler); +} diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c new file mode 100644 index 000000000000..eee7c8eac7a6 --- /dev/null +++ b/sound/firewire/fireface/ff.c @@ -0,0 +1,209 @@ +/* + * ff.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +#define OUI_RME 0x000a35 + +MODULE_DESCRIPTION("RME Fireface series Driver"); +MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>"); +MODULE_LICENSE("GPL v2"); + +static void name_card(struct snd_ff *ff) +{ + struct fw_device *fw_dev = fw_parent_device(ff->unit); + + strcpy(ff->card->driver, "Fireface"); + strcpy(ff->card->shortname, ff->spec->name); + strcpy(ff->card->mixername, ff->spec->name); + snprintf(ff->card->longname, sizeof(ff->card->longname), + "RME %s, GUID %08x%08x at %s, S%d", ff->spec->name, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&ff->unit->device), 100 << fw_dev->max_speed); +} + +static void ff_free(struct snd_ff *ff) +{ + snd_ff_stream_destroy_duplex(ff); + snd_ff_transaction_unregister(ff); + + fw_unit_put(ff->unit); + + mutex_destroy(&ff->mutex); + kfree(ff); +} + +static void ff_card_free(struct snd_card *card) +{ + ff_free(card->private_data); +} + +static void do_registration(struct work_struct *work) +{ + struct snd_ff *ff = container_of(work, struct snd_ff, dwork.work); + int err; + + if (ff->registered) + return; + + err = snd_card_new(&ff->unit->device, -1, NULL, THIS_MODULE, 0, + &ff->card); + if (err < 0) + return; + + err = snd_ff_transaction_register(ff); + if (err < 0) + goto error; + + name_card(ff); + + err = snd_ff_stream_init_duplex(ff); + if (err < 0) + goto error; + + snd_ff_proc_init(ff); + + err = snd_ff_create_midi_devices(ff); + if (err < 0) + goto error; + + err = snd_ff_create_pcm_devices(ff); + if (err < 0) + goto error; + + err = snd_ff_create_hwdep_devices(ff); + if (err < 0) + goto error; + + err = snd_card_register(ff->card); + if (err < 0) + goto error; + + ff->card->private_free = ff_card_free; + ff->card->private_data = ff; + ff->registered = true; + + return; +error: + snd_ff_transaction_unregister(ff); + snd_ff_stream_destroy_duplex(ff); + snd_card_free(ff->card); + dev_info(&ff->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int snd_ff_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_ff *ff; + + ff = kzalloc(sizeof(struct snd_ff), GFP_KERNEL); + if (ff == NULL) + return -ENOMEM; + + /* initialize myself */ + ff->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, ff); + + mutex_init(&ff->mutex); + spin_lock_init(&ff->lock); + init_waitqueue_head(&ff->hwdep_wait); + + ff->spec = (const struct snd_ff_spec *)entry->driver_data; + + /* Register this sound card later. */ + INIT_DEFERRABLE_WORK(&ff->dwork, do_registration); + snd_fw_schedule_registration(unit, &ff->dwork); + + return 0; +} + +static void snd_ff_update(struct fw_unit *unit) +{ + struct snd_ff *ff = dev_get_drvdata(&unit->device); + + /* Postpone a workqueue for deferred registration. */ + if (!ff->registered) + snd_fw_schedule_registration(unit, &ff->dwork); + + snd_ff_transaction_reregister(ff); + + if (ff->registered) + snd_ff_stream_update_duplex(ff); +} + +static void snd_ff_remove(struct fw_unit *unit) +{ + struct snd_ff *ff = dev_get_drvdata(&unit->device); + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_work_sync(&ff->dwork.work); + + if (ff->registered) { + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(ff->card); + } else { + /* Don't forget this case. */ + ff_free(ff); + } +} + +static struct snd_ff_spec spec_ff400 = { + .name = "Fireface400", + .pcm_capture_channels = {18, 14, 10}, + .pcm_playback_channels = {18, 14, 10}, + .midi_in_ports = 2, + .midi_out_ports = 2, + .protocol = &snd_ff_protocol_ff400, +}; + +static const struct ieee1394_device_id snd_ff_id_table[] = { + /* Fireface 400 */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_RME, + .specifier_id = 0x000a35, + .version = 0x000002, + .model_id = 0x101800, + .driver_data = (kernel_ulong_t)&spec_ff400, + }, + {} +}; +MODULE_DEVICE_TABLE(ieee1394, snd_ff_id_table); + +static struct fw_driver ff_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "snd-fireface", + .bus = &fw_bus_type, + }, + .probe = snd_ff_probe, + .update = snd_ff_update, + .remove = snd_ff_remove, + .id_table = snd_ff_id_table, +}; + +static int __init snd_ff_init(void) +{ + return driver_register(&ff_driver.driver); +} + +static void __exit snd_ff_exit(void) +{ + driver_unregister(&ff_driver.driver); +} + +module_init(snd_ff_init); +module_exit(snd_ff_exit); diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h new file mode 100644 index 000000000000..3cb812a50030 --- /dev/null +++ b/sound/firewire/fireface/ff.h @@ -0,0 +1,146 @@ +/* + * ff.h - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef SOUND_FIREFACE_H_INCLUDED +#define SOUND_FIREFACE_H_INCLUDED + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/compat.h> +#include <linux/sched/signal.h> + +#include <sound/core.h> +#include <sound/info.h> +#include <sound/rawmidi.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/hwdep.h> +#include <sound/firewire.h> + +#include "../lib.h" +#include "../amdtp-stream.h" +#include "../iso-resources.h" + +#define SND_FF_STREAM_MODES 3 + +#define SND_FF_MAXIMIM_MIDI_QUADS 9 +#define SND_FF_IN_MIDI_PORTS 2 +#define SND_FF_OUT_MIDI_PORTS 2 + +struct snd_ff_protocol; +struct snd_ff_spec { + const char *const name; + + const unsigned int pcm_capture_channels[SND_FF_STREAM_MODES]; + const unsigned int pcm_playback_channels[SND_FF_STREAM_MODES]; + + unsigned int midi_in_ports; + unsigned int midi_out_ports; + + struct snd_ff_protocol *protocol; +}; + +struct snd_ff { + struct snd_card *card; + struct fw_unit *unit; + struct mutex mutex; + spinlock_t lock; + + bool registered; + struct delayed_work dwork; + + const struct snd_ff_spec *spec; + + /* To handle MIDI tx. */ + struct snd_rawmidi_substream *tx_midi_substreams[SND_FF_IN_MIDI_PORTS]; + struct fw_address_handler async_handler; + + /* TO handle MIDI rx. */ + struct snd_rawmidi_substream *rx_midi_substreams[SND_FF_OUT_MIDI_PORTS]; + u8 running_status[SND_FF_OUT_MIDI_PORTS]; + __le32 msg_buf[SND_FF_OUT_MIDI_PORTS][SND_FF_MAXIMIM_MIDI_QUADS]; + struct work_struct rx_midi_work[SND_FF_OUT_MIDI_PORTS]; + struct fw_transaction transactions[SND_FF_OUT_MIDI_PORTS]; + ktime_t next_ktime[SND_FF_OUT_MIDI_PORTS]; + bool rx_midi_error[SND_FF_OUT_MIDI_PORTS]; + unsigned int rx_bytes[SND_FF_OUT_MIDI_PORTS]; + + unsigned int substreams_counter; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + struct fw_iso_resources tx_resources; + struct fw_iso_resources rx_resources; + + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; +}; + +enum snd_ff_clock_src { + SND_FF_CLOCK_SRC_INTERNAL, + SND_FF_CLOCK_SRC_SPDIF, + SND_FF_CLOCK_SRC_ADAT, + SND_FF_CLOCK_SRC_WORD, + SND_FF_CLOCK_SRC_LTC, + /* TODO: perhaps ADAT2 and TCO exists. */ +}; + +struct snd_ff_protocol { + int (*get_clock)(struct snd_ff *ff, unsigned int *rate, + enum snd_ff_clock_src *src); + int (*begin_session)(struct snd_ff *ff, unsigned int rate); + void (*finish_session)(struct snd_ff *ff); + int (*switch_fetching_mode)(struct snd_ff *ff, bool enable); + + void (*dump_sync_status)(struct snd_ff *ff, + struct snd_info_buffer *buffer); + void (*dump_clock_config)(struct snd_ff *ff, + struct snd_info_buffer *buffer); + + u64 midi_high_addr_reg; + u64 midi_rx_port_0_reg; + u64 midi_rx_port_1_reg; +}; + +extern struct snd_ff_protocol snd_ff_protocol_ff400; + +int snd_ff_transaction_register(struct snd_ff *ff); +int snd_ff_transaction_reregister(struct snd_ff *ff); +void snd_ff_transaction_unregister(struct snd_ff *ff); + +int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels); +int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); +int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir); + +int snd_ff_stream_init_duplex(struct snd_ff *ff); +void snd_ff_stream_destroy_duplex(struct snd_ff *ff); +int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate); +void snd_ff_stream_stop_duplex(struct snd_ff *ff); +void snd_ff_stream_update_duplex(struct snd_ff *ff); + +void snd_ff_stream_lock_changed(struct snd_ff *ff); +int snd_ff_stream_lock_try(struct snd_ff *ff); +void snd_ff_stream_lock_release(struct snd_ff *ff); + +void snd_ff_proc_init(struct snd_ff *ff); + +int snd_ff_create_midi_devices(struct snd_ff *ff); + +int snd_ff_create_pcm_devices(struct snd_ff *ff); + +int snd_ff_create_hwdep_devices(struct snd_ff *ff); + +#endif diff --git a/sound/firewire/lib.c b/sound/firewire/lib.c index 7683238283b6..39dfa74906ef 100644 --- a/sound/firewire/lib.c +++ b/sound/firewire/lib.c @@ -99,147 +99,6 @@ void snd_fw_schedule_registration(struct fw_unit *unit, } EXPORT_SYMBOL(snd_fw_schedule_registration); -static void async_midi_port_callback(struct fw_card *card, int rcode, - void *data, size_t length, - void *callback_data) -{ - struct snd_fw_async_midi_port *port = callback_data; - struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream); - - /* This port is closed. */ - if (substream == NULL) - return; - - if (rcode == RCODE_COMPLETE) - snd_rawmidi_transmit_ack(substream, port->consume_bytes); - else if (!rcode_is_permanent_error(rcode)) - /* To start next transaction immediately for recovery. */ - port->next_ktime = 0; - else - /* Don't continue processing. */ - port->error = true; - - port->idling = true; - - if (!snd_rawmidi_transmit_empty(substream)) - schedule_work(&port->work); -} - -static void midi_port_work(struct work_struct *work) -{ - struct snd_fw_async_midi_port *port = - container_of(work, struct snd_fw_async_midi_port, work); - struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream); - int generation; - int type; - - /* Under transacting or error state. */ - if (!port->idling || port->error) - return; - - /* Nothing to do. */ - if (substream == NULL || snd_rawmidi_transmit_empty(substream)) - return; - - /* Do it in next chance. */ - if (ktime_after(port->next_ktime, ktime_get())) { - schedule_work(&port->work); - return; - } - - /* - * Fill the buffer. The callee must use snd_rawmidi_transmit_peek(). - * Later, snd_rawmidi_transmit_ack() is called. - */ - memset(port->buf, 0, port->len); - port->consume_bytes = port->fill(substream, port->buf); - if (port->consume_bytes <= 0) { - /* Do it in next chance, immediately. */ - if (port->consume_bytes == 0) { - port->next_ktime = 0; - schedule_work(&port->work); - } else { - /* Fatal error. */ - port->error = true; - } - return; - } - - /* Calculate type of transaction. */ - if (port->len == 4) - type = TCODE_WRITE_QUADLET_REQUEST; - else - type = TCODE_WRITE_BLOCK_REQUEST; - - /* Set interval to next transaction. */ - port->next_ktime = ktime_add_ns(ktime_get(), - port->consume_bytes * 8 * NSEC_PER_SEC / 31250); - - /* Start this transaction. */ - port->idling = false; - - /* - * In Linux FireWire core, when generation is updated with memory - * barrier, node id has already been updated. In this module, After - * this smp_rmb(), load/store instructions to memory are completed. - * Thus, both of generation and node id are available with recent - * values. This is a light-serialization solution to handle bus reset - * events on IEEE 1394 bus. - */ - generation = port->parent->generation; - smp_rmb(); - - fw_send_request(port->parent->card, &port->transaction, type, - port->parent->node_id, generation, - port->parent->max_speed, port->addr, - port->buf, port->len, async_midi_port_callback, - port); -} - -/** - * snd_fw_async_midi_port_init - initialize asynchronous MIDI port structure - * @port: the asynchronous MIDI port to initialize - * @unit: the target of the asynchronous transaction - * @addr: the address to which transactions are transferred - * @len: the length of transaction - * @fill: the callback function to fill given buffer, and returns the - * number of consumed bytes for MIDI message. - * - */ -int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, - struct fw_unit *unit, u64 addr, unsigned int len, - snd_fw_async_midi_port_fill fill) -{ - port->len = DIV_ROUND_UP(len, 4) * 4; - port->buf = kzalloc(port->len, GFP_KERNEL); - if (port->buf == NULL) - return -ENOMEM; - - port->parent = fw_parent_device(unit); - port->addr = addr; - port->fill = fill; - port->idling = true; - port->next_ktime = 0; - port->error = false; - - INIT_WORK(&port->work, midi_port_work); - - return 0; -} -EXPORT_SYMBOL(snd_fw_async_midi_port_init); - -/** - * snd_fw_async_midi_port_destroy - free asynchronous MIDI port structure - * @port: the asynchronous MIDI port structure - */ -void snd_fw_async_midi_port_destroy(struct snd_fw_async_midi_port *port) -{ - snd_fw_async_midi_port_finish(port); - cancel_work_sync(&port->work); - kfree(port->buf); -} -EXPORT_SYMBOL(snd_fw_async_midi_port_destroy); - MODULE_DESCRIPTION("FireWire audio helper functions"); MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); MODULE_LICENSE("GPL v2"); diff --git a/sound/firewire/lib.h b/sound/firewire/lib.h index c3768cd494a5..eef70922ed89 100644 --- a/sound/firewire/lib.h +++ b/sound/firewire/lib.h @@ -25,58 +25,4 @@ static inline bool rcode_is_permanent_error(int rcode) void snd_fw_schedule_registration(struct fw_unit *unit, struct delayed_work *dwork); -struct snd_fw_async_midi_port; -typedef int (*snd_fw_async_midi_port_fill)( - struct snd_rawmidi_substream *substream, - u8 *buf); - -struct snd_fw_async_midi_port { - struct fw_device *parent; - struct work_struct work; - bool idling; - ktime_t next_ktime; - bool error; - - u64 addr; - struct fw_transaction transaction; - - u8 *buf; - unsigned int len; - - struct snd_rawmidi_substream *substream; - snd_fw_async_midi_port_fill fill; - int consume_bytes; -}; - -int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, - struct fw_unit *unit, u64 addr, unsigned int len, - snd_fw_async_midi_port_fill fill); -void snd_fw_async_midi_port_destroy(struct snd_fw_async_midi_port *port); - -/** - * snd_fw_async_midi_port_run - run transactions for the async MIDI port - * @port: the asynchronous MIDI port - * @substream: the MIDI substream - */ -static inline void -snd_fw_async_midi_port_run(struct snd_fw_async_midi_port *port, - struct snd_rawmidi_substream *substream) -{ - if (!port->error) { - port->substream = substream; - schedule_work(&port->work); - } -} - -/** - * snd_fw_async_midi_port_finish - finish the asynchronous MIDI port - * @port: the asynchronous MIDI port - */ -static inline void -snd_fw_async_midi_port_finish(struct snd_fw_async_midi_port *port) -{ - port->substream = NULL; - port->error = false; -} - #endif diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile new file mode 100644 index 000000000000..728f586e754b --- /dev/null +++ b/sound/firewire/motu/Makefile @@ -0,0 +1,6 @@ +CFLAGS_amdtp-motu.o := -I$(src) + +snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \ + motu-proc.o motu-pcm.o motu-midi.o motu-hwdep.o \ + motu-protocol-v2.o motu-protocol-v3.o +obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/amdtp-motu-trace.h b/sound/firewire/motu/amdtp-motu-trace.h new file mode 100644 index 000000000000..cd0cbfa9f96f --- /dev/null +++ b/sound/firewire/motu/amdtp-motu-trace.h @@ -0,0 +1,123 @@ +/* + * amdtp-motu-trace.h - tracepoint definitions to dump a part of packet data + * + * Copyright (c) 2017 Takashi Sakamoto + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM snd_firewire_motu + +#if !defined(_SND_FIREWIRE_MOTU_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _SND_FIREWIRE_MOTU_TRACE_H + +#include <linux/tracepoint.h> + +static void copy_sph(u32 *frame, __be32 *buffer, unsigned int data_blocks, + unsigned int data_block_quadlets); +static void copy_message(u64 *frames, __be32 *buffer, unsigned int data_blocks, + unsigned int data_block_quadlets); + +TRACE_EVENT(in_data_block_sph, + TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), + TP_ARGS(s, data_blocks, buffer), + TP_STRUCT__entry( + __field(int, src) + __field(int, dst) + __field(unsigned int, data_blocks) + __dynamic_array(u32, tstamps, data_blocks) + ), + TP_fast_assign( + __entry->src = fw_parent_device(s->unit)->node_id; + __entry->dst = fw_parent_device(s->unit)->card->node_id; + __entry->data_blocks = data_blocks; + copy_sph(__get_dynamic_array(tstamps), buffer, data_blocks, s->data_block_quadlets); + ), + TP_printk( + "%04x %04x %u %s", + __entry->src, + __entry->dst, + __entry->data_blocks, + __print_array(__get_dynamic_array(tstamps), __entry->data_blocks, 4) + ) +); + +TRACE_EVENT(out_data_block_sph, + TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), + TP_ARGS(s, data_blocks, buffer), + TP_STRUCT__entry( + __field(int, src) + __field(int, dst) + __field(unsigned int, data_blocks) + __dynamic_array(u32, tstamps, data_blocks) + ), + TP_fast_assign( + __entry->src = fw_parent_device(s->unit)->card->node_id; + __entry->dst = fw_parent_device(s->unit)->node_id; + __entry->data_blocks = data_blocks; + copy_sph(__get_dynamic_array(tstamps), buffer, data_blocks, s->data_block_quadlets); + ), + TP_printk( + "%04x %04x %u %s", + __entry->src, + __entry->dst, + __entry->data_blocks, + __print_array(__get_dynamic_array(tstamps), __entry->data_blocks, 4) + ) +); + +TRACE_EVENT(in_data_block_message, + TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), + TP_ARGS(s, data_blocks, buffer), + TP_STRUCT__entry( + __field(int, src) + __field(int, dst) + __field(unsigned int, data_blocks) + __dynamic_array(u64, messages, data_blocks) + ), + TP_fast_assign( + __entry->src = fw_parent_device(s->unit)->node_id; + __entry->dst = fw_parent_device(s->unit)->card->node_id; + __entry->data_blocks = data_blocks; + copy_message(__get_dynamic_array(messages), buffer, data_blocks, s->data_block_quadlets); + ), + TP_printk( + "%04x %04x %u %s", + __entry->src, + __entry->dst, + __entry->data_blocks, + __print_array(__get_dynamic_array(messages), __entry->data_blocks, 8) + ) +); + +TRACE_EVENT(out_data_block_message, + TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), + TP_ARGS(s, data_blocks, buffer), + TP_STRUCT__entry( + __field(int, src) + __field(int, dst) + __field(unsigned int, data_blocks) + __dynamic_array(u64, messages, data_blocks) + ), + TP_fast_assign( + __entry->src = fw_parent_device(s->unit)->card->node_id; + __entry->dst = fw_parent_device(s->unit)->node_id; + __entry->data_blocks = data_blocks; + copy_message(__get_dynamic_array(messages), buffer, data_blocks, s->data_block_quadlets); + ), + TP_printk( + "%04x %04x %u %s", + __entry->src, + __entry->dst, + __entry->data_blocks, + __print_array(__get_dynamic_array(messages), __entry->data_blocks, 8) + ) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE amdtp-motu-trace +#include <trace/define_trace.h> diff --git a/sound/firewire/motu/amdtp-motu.c b/sound/firewire/motu/amdtp-motu.c new file mode 100644 index 000000000000..96f0091144bb --- /dev/null +++ b/sound/firewire/motu/amdtp-motu.c @@ -0,0 +1,427 @@ +/* + * amdtp-motu.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/slab.h> +#include <sound/pcm.h> +#include "motu.h" + +#define CREATE_TRACE_POINTS +#include "amdtp-motu-trace.h" + +#define CIP_FMT_MOTU 0x02 +#define CIP_FMT_MOTU_TX_V3 0x22 +#define MOTU_FDF_AM824 0x22 + +/* + * Nominally 3125 bytes/second, but the MIDI port's clock might be + * 1% too slow, and the bus clock 100 ppm too fast. + */ +#define MIDI_BYTES_PER_SECOND 3093 + +struct amdtp_motu { + /* For timestamp processing. */ + unsigned int quotient_ticks_per_event; + unsigned int remainder_ticks_per_event; + unsigned int next_ticks; + unsigned int next_accumulated; + unsigned int next_cycles; + unsigned int next_seconds; + + unsigned int pcm_chunks; + unsigned int pcm_byte_offset; + + struct snd_rawmidi_substream *midi; + unsigned int midi_ports; + unsigned int midi_flag_offset; + unsigned int midi_byte_offset; + + int midi_db_count; + unsigned int midi_db_interval; +}; + +int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int midi_ports, + struct snd_motu_packet_format *formats) +{ + static const struct { + unsigned int quotient_ticks_per_event; + unsigned int remainder_ticks_per_event; + } params[] = { + [CIP_SFC_44100] = { 557, 123 }, + [CIP_SFC_48000] = { 512, 0 }, + [CIP_SFC_88200] = { 278, 282 }, + [CIP_SFC_96000] = { 256, 0 }, + [CIP_SFC_176400] = { 139, 141 }, + [CIP_SFC_192000] = { 128, 0 }, + }; + struct amdtp_motu *p = s->protocol; + unsigned int pcm_chunks, data_chunks, data_block_quadlets; + unsigned int delay; + unsigned int mode; + int i, err; + + if (amdtp_stream_running(s)) + return -EBUSY; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) { + mode = i >> 1; + break; + } + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + pcm_chunks = formats->fixed_part_pcm_chunks[mode] + + formats->differed_part_pcm_chunks[mode]; + data_chunks = formats->msg_chunks + pcm_chunks; + + /* + * Each data block includes SPH in its head. Data chunks follow with + * 3 byte alignment. Padding follows with zero to conform to quadlet + * alignment. + */ + data_block_quadlets = 1 + DIV_ROUND_UP(data_chunks * 3, 4); + + err = amdtp_stream_set_parameters(s, rate, data_block_quadlets); + if (err < 0) + return err; + + p->pcm_chunks = pcm_chunks; + p->pcm_byte_offset = formats->pcm_byte_offset; + + p->midi_ports = midi_ports; + p->midi_flag_offset = formats->midi_flag_offset; + p->midi_byte_offset = formats->midi_byte_offset; + + p->midi_db_count = 0; + p->midi_db_interval = rate / MIDI_BYTES_PER_SECOND; + + /* IEEE 1394 bus requires. */ + delay = 0x2e00; + + /* For no-data or empty packets to adjust PCM sampling frequency. */ + delay += 8000 * 3072 * s->syt_interval / rate; + + p->next_seconds = 0; + p->next_cycles = delay / 3072; + p->quotient_ticks_per_event = params[s->sfc].quotient_ticks_per_event; + p->remainder_ticks_per_event = params[s->sfc].remainder_ticks_per_event; + p->next_ticks = delay % 3072; + p->next_accumulated = 0; + + return 0; +} + +static void read_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + u8 *byte; + u32 *dst; + + 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; + + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + *dst = (byte[0] << 24) | (byte[1] << 16) | byte[2]; + byte += 3; + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + u8 *byte; + const u32 *src; + + 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; + + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + byte[0] = (*src >> 24) & 0xff; + byte[1] = (*src >> 16) & 0xff; + byte[2] = (*src >> 8) & 0xff; + byte += 3; + src++; + } + + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, i, c; + u8 *byte; + + channels = p->pcm_chunks; + + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + byte[0] = 0; + byte[1] = 0; + byte[2] = 0; + byte += 3; + } + + buffer += s->data_block_quadlets; + } +} + +int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + /* TODO: how to set an constraint for exactly 24bit PCM sample? */ + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + + return amdtp_stream_add_pcm_hw_constraints(s, runtime); +} + +void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi) +{ + struct amdtp_motu *p = s->protocol; + + if (port < p->midi_ports) + WRITE_ONCE(p->midi, midi); +} + +static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + struct snd_rawmidi_substream *midi = READ_ONCE(p->midi); + u8 *b; + int i; + + for (i = 0; i < data_blocks; i++) { + b = (u8 *)buffer; + + if (midi && p->midi_db_count == 0 && + snd_rawmidi_transmit(midi, b + p->midi_byte_offset, 1) == 1) { + b[p->midi_flag_offset] = 0x01; + } else { + b[p->midi_byte_offset] = 0x00; + b[p->midi_flag_offset] = 0x00; + } + + buffer += s->data_block_quadlets; + + if (--p->midi_db_count < 0) + p->midi_db_count = p->midi_db_interval; + } +} + +static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + struct snd_rawmidi_substream *midi; + u8 *b; + int i; + + for (i = 0; i < data_blocks; i++) { + b = (u8 *)buffer; + midi = READ_ONCE(p->midi); + + if (midi && (b[p->midi_flag_offset] & 0x01)) + snd_rawmidi_receive(midi, b + p->midi_byte_offset, 1); + + buffer += s->data_block_quadlets; + } +} + +/* For tracepoints. */ +static void __maybe_unused copy_sph(u32 *frames, __be32 *buffer, + unsigned int data_blocks, + unsigned int data_block_quadlets) +{ + unsigned int i; + + for (i = 0; i < data_blocks; ++i) { + *frames = be32_to_cpu(*buffer); + buffer += data_block_quadlets; + frames++; + } +} + +/* For tracepoints. */ +static void __maybe_unused copy_message(u64 *frames, __be32 *buffer, + unsigned int data_blocks, + unsigned int data_block_quadlets) +{ + unsigned int i; + + /* This is just for v2/v3 protocol. */ + for (i = 0; i < data_blocks; ++i) { + *frames = (be32_to_cpu(buffer[1]) << 16) | + (be32_to_cpu(buffer[2]) >> 16); + buffer += data_block_quadlets; + frames++; + } +} + +static unsigned int process_tx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, unsigned int data_blocks, + unsigned int *syt) +{ + struct amdtp_motu *p = s->protocol; + struct snd_pcm_substream *pcm; + + trace_in_data_block_sph(s, data_blocks, buffer); + trace_in_data_block_message(s, data_blocks, buffer); + + if (p->midi_ports) + read_midi_messages(s, buffer, data_blocks); + + pcm = ACCESS_ONCE(s->pcm); + if (data_blocks > 0 && pcm) + read_pcm_s32(s, pcm->runtime, buffer, data_blocks); + + return data_blocks; +} + +static inline void compute_next_elapse_from_start(struct amdtp_motu *p) +{ + p->next_accumulated += p->remainder_ticks_per_event; + if (p->next_accumulated >= 441) { + p->next_accumulated -= 441; + p->next_ticks++; + } + + p->next_ticks += p->quotient_ticks_per_event; + if (p->next_ticks >= 3072) { + p->next_ticks -= 3072; + p->next_cycles++; + } + + if (p->next_cycles >= 8000) { + p->next_cycles -= 8000; + p->next_seconds++; + } + + if (p->next_seconds >= 128) + p->next_seconds -= 128; +} + +static void write_sph(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int next_cycles; + unsigned int i; + u32 sph; + + for (i = 0; i < data_blocks; i++) { + next_cycles = (s->start_cycle + p->next_cycles) % 8000; + sph = ((next_cycles << 12) | p->next_ticks) & 0x01ffffff; + *buffer = cpu_to_be32(sph); + + compute_next_elapse_from_start(p); + + buffer += s->data_block_quadlets; + } +} + +static unsigned int process_rx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, unsigned int data_blocks, + unsigned int *syt) +{ + struct amdtp_motu *p = (struct amdtp_motu *)s->protocol; + struct snd_pcm_substream *pcm; + + /* Not used. */ + *syt = 0xffff; + + /* TODO: how to interact control messages between userspace? */ + + if (p->midi_ports) + write_midi_messages(s, buffer, data_blocks); + + pcm = ACCESS_ONCE(s->pcm); + if (pcm) + write_pcm_s32(s, pcm->runtime, buffer, data_blocks); + else + write_pcm_silence(s, buffer, data_blocks); + + write_sph(s, buffer, data_blocks); + + trace_out_data_block_sph(s, data_blocks, buffer); + trace_out_data_block_message(s, data_blocks, buffer); + + return data_blocks; +} + +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; + int fmt = CIP_FMT_MOTU; + int flags = CIP_BLOCKING; + int err; + + if (dir == AMDTP_IN_STREAM) { + process_data_blocks = process_tx_data_blocks; + + /* + * Units of version 3 transmits packets with invalid CIP header + * against IEC 61883-1. + */ + if (protocol == &snd_motu_protocol_v3) { + flags |= CIP_WRONG_DBS | + CIP_SKIP_DBC_ZERO_CHECK | + CIP_HEADER_WITHOUT_EOH; + fmt = CIP_FMT_MOTU_TX_V3; + } + } else { + process_data_blocks = process_rx_data_blocks; + flags |= CIP_DBC_IS_END_EVENT; + } + + err = amdtp_stream_init(s, unit, dir, flags, fmt, process_data_blocks, + sizeof(struct amdtp_motu)); + if (err < 0) + return err; + + s->sph = 1; + s->fdf = MOTU_FDF_AM824; + + return 0; +} diff --git a/sound/firewire/motu/motu-hwdep.c b/sound/firewire/motu/motu-hwdep.c new file mode 100644 index 000000000000..b87ccb69d597 --- /dev/null +++ b/sound/firewire/motu/motu-hwdep.c @@ -0,0 +1,198 @@ +/* + * motu-hwdep.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes have five functionalities. + * + * 1.get information about firewire node + * 2.get notification about starting/stopping stream + * 3.lock/unlock streaming + * + */ + +#include "motu.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_motu *motu = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&motu->lock); + + while (!motu->dev_lock_changed && motu->msg == 0) { + prepare_to_wait(&motu->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&motu->lock); + schedule(); + finish_wait(&motu->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&motu->lock); + } + + memset(&event, 0, sizeof(event)); + if (motu->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (motu->dev_lock_count > 0); + motu->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } else { + event.motu_notification.type = SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION; + event.motu_notification.message = motu->msg; + motu->msg = 0; + + count = min_t(long, count, sizeof(event.motu_notification)); + } + + spin_unlock_irq(&motu->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_motu *motu = hwdep->private_data; + unsigned int events; + + poll_wait(file, &motu->hwdep_wait, wait); + + spin_lock_irq(&motu->lock); + if (motu->dev_lock_changed || motu->msg) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&motu->lock); + + return events | POLLOUT; +} + +static int hwdep_get_info(struct snd_motu *motu, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(motu->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_MOTU; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int hwdep_lock(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count == 0) { + motu->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&motu->lock); + + return err; +} + +static int hwdep_unlock(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count == -1) { + motu->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&motu->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_motu *motu = hwdep->private_data; + + spin_lock_irq(&motu->lock); + if (motu->dev_lock_count == -1) + motu->dev_lock_count = 0; + spin_unlock_irq(&motu->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_motu *motu = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(motu, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(motu); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(motu); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +int snd_motu_create_hwdep_device(struct snd_motu *motu) +{ + static const struct snd_hwdep_ops ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(motu->card, motu->card->driver, 0, &hwdep); + if (err < 0) + return err; + + strcpy(hwdep->name, "MOTU"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_MOTU; + hwdep->ops = ops; + hwdep->private_data = motu; + hwdep->exclusive = true; + + return 0; +} diff --git a/sound/firewire/motu/motu-midi.c b/sound/firewire/motu/motu-midi.c new file mode 100644 index 000000000000..e3acfcc53f4e --- /dev/null +++ b/sound/firewire/motu/motu-midi.c @@ -0,0 +1,169 @@ +/* + * motu-midi.h - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ +#include "motu.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + int err; + + err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + + mutex_lock(&motu->mutex); + + motu->capture_substreams++; + err = snd_motu_stream_start_duplex(motu, 0); + + mutex_unlock(&motu->mutex); + + if (err < 0) + snd_motu_stream_lock_release(motu); + + return err; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + int err; + + err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + + mutex_lock(&motu->mutex); + + motu->playback_substreams++; + err = snd_motu_stream_start_duplex(motu, 0); + + mutex_unlock(&motu->mutex); + + if (err < 0) + snd_motu_stream_lock_release(motu); + + return err; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + + mutex_lock(&motu->mutex); + + motu->capture_substreams--; + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + snd_motu_stream_lock_release(motu); + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + + mutex_lock(&motu->mutex); + + motu->playback_substreams--; + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + snd_motu_stream_lock_release(motu); + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_motu *motu = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&motu->lock, flags); + + if (up) + amdtp_motu_midi_trigger(&motu->tx_stream, substrm->number, + substrm); + else + amdtp_motu_midi_trigger(&motu->tx_stream, substrm->number, + NULL); + + spin_unlock_irqrestore(&motu->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_motu *motu = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&motu->lock, flags); + + if (up) + amdtp_motu_midi_trigger(&motu->rx_stream, substrm->number, + substrm); + else + amdtp_motu_midi_trigger(&motu->rx_stream, substrm->number, + NULL); + + spin_unlock_irqrestore(&motu->lock, flags); +} + +static void set_midi_substream_names(struct snd_motu *motu, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", motu->card->shortname, subs->number + 1); + } +} + +int snd_motu_create_midi_devices(struct snd_motu *motu) +{ + static struct snd_rawmidi_ops capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, + }; + static struct snd_rawmidi_ops playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, + }; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + /* create midi ports */ + err = snd_rawmidi_new(motu->card, motu->card->driver, 0, 1, 1, &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", motu->card->shortname); + rmidi->private_data = motu; + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &capture_ops); + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + set_midi_substream_names(motu, str); + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &playback_ops); + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + set_midi_substream_names(motu, str); + + return 0; +} diff --git a/sound/firewire/motu/motu-pcm.c b/sound/firewire/motu/motu-pcm.c new file mode 100644 index 000000000000..94558f3d218b --- /dev/null +++ b/sound/firewire/motu/motu-pcm.c @@ -0,0 +1,398 @@ +/* + * motu-pcm.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <sound/pcm_params.h> +#include "motu.h" + +static int motu_rate_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_motu_packet_format *formats = rule->private; + + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval rates = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, pcm_channels, rate, mode; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + pcm_channels = formats->fixed_part_pcm_chunks[mode] + + formats->differed_part_pcm_chunks[mode]; + if (!snd_interval_test(c, pcm_channels)) + continue; + + rates.min = min(rates.min, rate); + rates.max = max(rates.max, rate); + } + + return snd_interval_refine(r, &rates); +} + +static int motu_channels_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_motu_packet_format *formats = rule->private; + + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval channels = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, pcm_channels, rate, mode; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + if (!snd_interval_test(r, rate)) + continue; + + pcm_channels = formats->fixed_part_pcm_chunks[mode] + + formats->differed_part_pcm_chunks[mode]; + channels.min = min(channels.min, pcm_channels); + channels.max = max(channels.max, pcm_channels); + } + + return snd_interval_refine(c, &channels); +} + +static void limit_channels_and_rates(struct snd_motu *motu, + struct snd_pcm_runtime *runtime, + struct snd_motu_packet_format *formats) +{ + struct snd_pcm_hardware *hw = &runtime->hw; + unsigned int i, pcm_channels, rate, mode; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + pcm_channels = formats->fixed_part_pcm_chunks[mode] + + formats->differed_part_pcm_chunks[mode]; + if (pcm_channels == 0) + continue; + + hw->rates |= snd_pcm_rate_to_rate_bit(rate); + hw->channels_min = min(hw->channels_min, pcm_channels); + hw->channels_max = max(hw->channels_max, pcm_channels); + } + + snd_pcm_limit_hw_rates(runtime); +} + +static void limit_period_and_buffer(struct snd_pcm_hardware *hw) +{ + hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */ + hw->periods_max = UINT_MAX; + + hw->period_bytes_min = 4 * hw->channels_max; /* byte for a frame */ + + /* Just to prevent from allocating much pages. */ + hw->period_bytes_max = hw->period_bytes_min * 2048; + hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min; +} + +static int init_hw_info(struct snd_motu *motu, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; + struct amdtp_stream *stream; + struct snd_motu_packet_format *formats; + int err; + + hw->info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | + SNDRV_PCM_INFO_BLOCK_TRANSFER; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + hw->formats = SNDRV_PCM_FMTBIT_S32; + stream = &motu->tx_stream; + formats = &motu->tx_packet_formats; + } else { + hw->formats = SNDRV_PCM_FMTBIT_S32; + stream = &motu->rx_stream; + formats = &motu->rx_packet_formats; + } + + limit_channels_and_rates(motu, runtime, formats); + limit_period_and_buffer(hw); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + motu_rate_constraint, formats, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + motu_channels_constraint, formats, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + return amdtp_motu_add_pcm_hw_constraints(stream, runtime); +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + const struct snd_motu_protocol *const protocol = motu->spec->protocol; + enum snd_motu_clock_source src; + unsigned int rate; + int err; + + err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + + mutex_lock(&motu->mutex); + + err = protocol->cache_packet_formats(motu); + if (err < 0) + goto err_locked; + + err = init_hw_info(motu, substream); + if (err < 0) + goto err_locked; + + /* + * When source of clock is not internal or any PCM streams are running, + * available sampling rate is limited at current sampling rate. + */ + err = protocol->get_clock_source(motu, &src); + if (err < 0) + goto err_locked; + if (src != SND_MOTU_CLOCK_SOURCE_INTERNAL || + amdtp_stream_pcm_running(&motu->tx_stream) || + amdtp_stream_pcm_running(&motu->rx_stream)) { + err = protocol->get_clock_rate(motu, &rate); + if (err < 0) + goto err_locked; + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } + + snd_pcm_set_sync(substream); + + mutex_unlock(&motu->mutex); + + return err; +err_locked: + mutex_unlock(&motu->mutex); + snd_motu_stream_lock_release(motu); + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + snd_motu_stream_lock_release(motu); + + return 0; +} + +static int capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_motu *motu = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&motu->mutex); + motu->capture_substreams++; + mutex_unlock(&motu->mutex); + } + + return 0; +} +static int playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_motu *motu = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&motu->mutex); + motu->playback_substreams++; + mutex_unlock(&motu->mutex); + } + + return 0; +} + +static int capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + mutex_lock(&motu->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + motu->capture_substreams--; + + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + mutex_lock(&motu->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + motu->playback_substreams--; + + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + int err; + + mutex_lock(&motu->mutex); + err = snd_motu_stream_start_duplex(motu, substream->runtime->rate); + mutex_unlock(&motu->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(&motu->tx_stream); + + return 0; +} +static int playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + int err; + + mutex_lock(&motu->mutex); + err = snd_motu_stream_start_duplex(motu, substream->runtime->rate); + mutex_unlock(&motu->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(&motu->rx_stream); + + return err; +} + +static int capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_motu *motu = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&motu->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&motu->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_motu *motu = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&motu->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&motu->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_stream_pcm_pointer(&motu->tx_stream); +} +static snd_pcm_uframes_t playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_stream_pcm_pointer(&motu->rx_stream); +} + +int snd_motu_create_pcm_devices(struct snd_motu *motu) +{ + static struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = capture_hw_params, + .hw_free = capture_hw_free, + .prepare = capture_prepare, + .trigger = capture_trigger, + .pointer = capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, + }; + static struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = playback_hw_params, + .hw_free = playback_hw_free, + .prepare = playback_prepare, + .trigger = playback_trigger, + .pointer = playback_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(motu->card, motu->card->driver, 0, 1, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = motu; + strcpy(pcm->name, motu->card->shortname); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + + return 0; +} diff --git a/sound/firewire/motu/motu-proc.c b/sound/firewire/motu/motu-proc.c new file mode 100644 index 000000000000..4edc064999ed --- /dev/null +++ b/sound/firewire/motu/motu-proc.c @@ -0,0 +1,118 @@ +/* + * motu-proc.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./motu.h" + +static const char *const clock_names[] = { + [SND_MOTU_CLOCK_SOURCE_INTERNAL] = "Internal", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB] = "ADAT on Dsub-9pin interface", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT] = "ADAT on optical interface", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A] = "ADAT on optical interface A", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B] = "ADAT on optical interface B", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT] = "S/PDIF on optical interface", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A] = "S/PDIF on optical interface A", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B] = "S/PDIF on optical interface B", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX] = "S/PCIF on coaxial interface", + [SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR] = "AESEBU on XLR interface", + [SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC] = "Word clock on BNC interface", +}; + +static void proc_read_clock(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + + struct snd_motu *motu = entry->private_data; + const struct snd_motu_protocol *const protocol = motu->spec->protocol; + unsigned int rate; + enum snd_motu_clock_source source; + + if (protocol->get_clock_rate(motu, &rate) < 0) + return; + if (protocol->get_clock_source(motu, &source) < 0) + return; + + snd_iprintf(buffer, "Rate:\t%d\n", rate); + snd_iprintf(buffer, "Source:\t%s\n", clock_names[source]); +} + +static void proc_read_format(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_motu *motu = entry->private_data; + const struct snd_motu_protocol *const protocol = motu->spec->protocol; + unsigned int mode; + struct snd_motu_packet_format *formats; + int i; + + if (protocol->cache_packet_formats(motu) < 0) + return; + + snd_iprintf(buffer, "tx:\tmsg\tfixed\tdiffered\n"); + for (i = 0; i < SND_MOTU_CLOCK_RATE_COUNT; ++i) { + mode = i >> 1; + + formats = &motu->tx_packet_formats; + snd_iprintf(buffer, + "%u:\t%u\t%u\t%u\n", + snd_motu_clock_rates[i], + formats->msg_chunks, + formats->fixed_part_pcm_chunks[mode], + formats->differed_part_pcm_chunks[mode]); + } + + snd_iprintf(buffer, "rx:\tmsg\tfixed\tdiffered\n"); + for (i = 0; i < SND_MOTU_CLOCK_RATE_COUNT; ++i) { + mode = i >> 1; + + formats = &motu->rx_packet_formats; + snd_iprintf(buffer, + "%u:\t%u\t%u\t%u\n", + snd_motu_clock_rates[i], + formats->msg_chunks, + formats->fixed_part_pcm_chunks[mode], + formats->differed_part_pcm_chunks[mode]); + } +} + +static void add_node(struct snd_motu *motu, struct snd_info_entry *root, + const char *name, + void (*op)(struct snd_info_entry *e, + struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(motu->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, motu, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_motu_proc_init(struct snd_motu *motu) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(motu->card, "firewire", + motu->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(motu, root, "clock", proc_read_clock); + add_node(motu, root, "format", proc_read_format); +} diff --git a/sound/firewire/motu/motu-protocol-v2.c b/sound/firewire/motu/motu-protocol-v2.c new file mode 100644 index 000000000000..05b5d287c2f3 --- /dev/null +++ b/sound/firewire/motu/motu-protocol-v2.c @@ -0,0 +1,237 @@ +/* + * motu-protocol-v2.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "motu.h" + +#define V2_CLOCK_STATUS_OFFSET 0x0b14 +#define V2_CLOCK_RATE_MASK 0x00000038 +#define V2_CLOCK_RATE_SHIFT 3 +#define V2_CLOCK_SRC_MASK 0x00000007 +#define V2_CLOCK_SRC_SHIFT 0 + +#define V2_IN_OUT_CONF_OFFSET 0x0c04 +#define V2_OPT_OUT_IFACE_MASK 0x00000c00 +#define V2_OPT_OUT_IFACE_SHIFT 10 +#define V2_OPT_IN_IFACE_MASK 0x00000300 +#define V2_OPT_IN_IFACE_SHIFT 8 +#define V2_OPT_IFACE_MODE_NONE 0 +#define V2_OPT_IFACE_MODE_ADAT 1 +#define V2_OPT_IFACE_MODE_SPDIF 2 + +static int v2_get_clock_rate(struct snd_motu *motu, unsigned int *rate) +{ + __be32 reg; + unsigned int index; + int err; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + index = (be32_to_cpu(reg) & V2_CLOCK_RATE_MASK) >> V2_CLOCK_RATE_SHIFT; + if (index >= ARRAY_SIZE(snd_motu_clock_rates)) + return -EIO; + + *rate = snd_motu_clock_rates[index]; + + return 0; +} + +static int v2_set_clock_rate(struct snd_motu *motu, unsigned int rate) +{ + __be32 reg; + u32 data; + int i; + int err; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) + break; + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~V2_CLOCK_RATE_MASK; + data |= i << V2_CLOCK_RATE_SHIFT; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); +} + +static int v2_get_clock_source(struct snd_motu *motu, + enum snd_motu_clock_source *src) +{ + __be32 reg; + unsigned int index; + int err; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + index = be32_to_cpu(reg) & V2_CLOCK_SRC_MASK; + if (index > 5) + return -EIO; + + /* To check the configuration of optical interface. */ + err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + switch (index) { + case 0: + *src = SND_MOTU_CLOCK_SOURCE_INTERNAL; + break; + case 1: + if (be32_to_cpu(reg) & 0x00000200) + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT; + else + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT; + break; + case 2: + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX; + break; + case 4: + *src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC; + break; + case 5: + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB; + break; + default: + return -EIO; + } + + return 0; +} + +static int v2_switch_fetching_mode(struct snd_motu *motu, bool enable) +{ + /* V2 protocol doesn't have this feature. */ + return 0; +} + +static void calculate_fixed_part(struct snd_motu_packet_format *formats, + enum amdtp_stream_direction dir, + enum snd_motu_spec_flags flags, + unsigned char analog_ports) +{ + unsigned char pcm_chunks[3] = {0, 0, 0}; + + formats->msg_chunks = 2; + + pcm_chunks[0] = analog_ports; + pcm_chunks[1] = analog_ports; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + pcm_chunks[2] = analog_ports; + + if (dir == AMDTP_IN_STREAM) { + if (flags & SND_MOTU_SPEC_TX_MICINST_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + if (flags & SND_MOTU_SPEC_TX_RETURN_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + } else { + /* + * Packets to v2 units transfer main-out-1/2 and phone-out-1/2. + */ + pcm_chunks[0] += 4; + pcm_chunks[1] += 4; + } + + /* + * All of v2 models have a pair of coaxial interfaces for digital in/out + * port. At 44.1/48.0/88.2/96.0 kHz, packets includes PCM from these + * ports. + */ + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + + /* This part should be multiples of 4. */ + formats->fixed_part_pcm_chunks[0] = round_up(2 + pcm_chunks[0], 4) - 2; + formats->fixed_part_pcm_chunks[1] = round_up(2 + pcm_chunks[1], 4) - 2; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + formats->fixed_part_pcm_chunks[2] = + round_up(2 + pcm_chunks[2], 4) - 2; +} + +static void calculate_differed_part(struct snd_motu_packet_format *formats, + enum snd_motu_spec_flags flags, + u32 data, u32 mask, u32 shift) +{ + unsigned char pcm_chunks[3] = {0, 0}; + + /* + * When optical interfaces are configured for S/PDIF (TOSLINK), + * the above PCM frames come from them, instead of coaxial + * interfaces. + */ + data = (data & mask) >> shift; + if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_A) && + data == V2_OPT_IFACE_MODE_ADAT) { + pcm_chunks[0] += 8; + pcm_chunks[1] += 4; + } + + /* At mode x4, no data chunks are supported in this part. */ + formats->differed_part_pcm_chunks[0] = pcm_chunks[0]; + formats->differed_part_pcm_chunks[1] = pcm_chunks[1]; +} + +static int v2_cache_packet_formats(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + calculate_fixed_part(&motu->tx_packet_formats, AMDTP_IN_STREAM, + motu->spec->flags, motu->spec->analog_in_ports); + calculate_differed_part(&motu->tx_packet_formats, motu->spec->flags, + data, V2_OPT_IN_IFACE_MASK, V2_OPT_IN_IFACE_SHIFT); + + calculate_fixed_part(&motu->rx_packet_formats, AMDTP_OUT_STREAM, + motu->spec->flags, motu->spec->analog_out_ports); + calculate_differed_part(&motu->rx_packet_formats, motu->spec->flags, + data, V2_OPT_OUT_IFACE_MASK, V2_OPT_OUT_IFACE_SHIFT); + + motu->tx_packet_formats.midi_flag_offset = 4; + motu->tx_packet_formats.midi_byte_offset = 6; + motu->tx_packet_formats.pcm_byte_offset = 10; + + motu->rx_packet_formats.midi_flag_offset = 4; + motu->rx_packet_formats.midi_byte_offset = 6; + motu->rx_packet_formats.pcm_byte_offset = 10; + + return 0; +} + +const struct snd_motu_protocol snd_motu_protocol_v2 = { + .get_clock_rate = v2_get_clock_rate, + .set_clock_rate = v2_set_clock_rate, + .get_clock_source = v2_get_clock_source, + .switch_fetching_mode = v2_switch_fetching_mode, + .cache_packet_formats = v2_cache_packet_formats, +}; diff --git a/sound/firewire/motu/motu-protocol-v3.c b/sound/firewire/motu/motu-protocol-v3.c new file mode 100644 index 000000000000..ddb647254ed2 --- /dev/null +++ b/sound/firewire/motu/motu-protocol-v3.c @@ -0,0 +1,311 @@ +/* + * motu-protocol-v3.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/delay.h> +#include "motu.h" + +#define V3_CLOCK_STATUS_OFFSET 0x0b14 +#define V3_FETCH_PCM_FRAMES 0x02000000 +#define V3_CLOCK_RATE_MASK 0x0000ff00 +#define V3_CLOCK_RATE_SHIFT 8 +#define V3_CLOCK_SOURCE_MASK 0x000000ff + +#define V3_OPT_IFACE_MODE_OFFSET 0x0c94 +#define V3_ENABLE_OPT_IN_IFACE_A 0x00000001 +#define V3_ENABLE_OPT_IN_IFACE_B 0x00000002 +#define V3_ENABLE_OPT_OUT_IFACE_A 0x00000100 +#define V3_ENABLE_OPT_OUT_IFACE_B 0x00000200 +#define V3_NO_ADAT_OPT_IN_IFACE_A 0x00010000 +#define V3_NO_ADAT_OPT_IN_IFACE_B 0x00100000 +#define V3_NO_ADAT_OPT_OUT_IFACE_A 0x00040000 +#define V3_NO_ADAT_OPT_OUT_IFACE_B 0x00400000 + +static int v3_get_clock_rate(struct snd_motu *motu, unsigned int *rate) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data = (data & V3_CLOCK_RATE_MASK) >> V3_CLOCK_RATE_SHIFT; + if (data >= ARRAY_SIZE(snd_motu_clock_rates)) + return -EIO; + + *rate = snd_motu_clock_rates[data]; + + return 0; +} + +static int v3_set_clock_rate(struct snd_motu *motu, unsigned int rate) +{ + __be32 reg; + u32 data; + bool need_to_wait; + int i, err; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) + break; + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~(V3_CLOCK_RATE_MASK | V3_FETCH_PCM_FRAMES); + data |= i << V3_CLOCK_RATE_SHIFT; + + need_to_wait = data != be32_to_cpu(reg); + + reg = cpu_to_be32(data); + err = snd_motu_transaction_write(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + if (need_to_wait) { + /* Cost expensive. */ + if (msleep_interruptible(4000) > 0) + return -EINTR; + } + + return 0; +} + +static int v3_get_clock_source(struct snd_motu *motu, + enum snd_motu_clock_source *src) +{ + __be32 reg; + u32 data; + unsigned int val; + int err; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + val = data & V3_CLOCK_SOURCE_MASK; + if (val == 0x00) { + *src = SND_MOTU_CLOCK_SOURCE_INTERNAL; + } else if (val == 0x01) { + *src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC; + } else if (val == 0x10) { + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX; + } else if (val == 0x18 || val == 0x19) { + err = snd_motu_transaction_read(motu, V3_OPT_IFACE_MODE_OFFSET, + ®, sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + if (val == 0x18) { + if (data & V3_NO_ADAT_OPT_IN_IFACE_A) + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A; + else + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A; + } else { + if (data & V3_NO_ADAT_OPT_IN_IFACE_B) + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B; + else + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B; + } + } else { + *src = SND_MOTU_CLOCK_SOURCE_UNKNOWN; + } + + return 0; +} + +static int v3_switch_fetching_mode(struct snd_motu *motu, bool enable) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return 0; + data = be32_to_cpu(reg); + + if (enable) + data |= V3_FETCH_PCM_FRAMES; + else + data &= ~V3_FETCH_PCM_FRAMES; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); +} + +static void calculate_fixed_part(struct snd_motu_packet_format *formats, + enum amdtp_stream_direction dir, + enum snd_motu_spec_flags flags, + unsigned char analog_ports) +{ + unsigned char pcm_chunks[3] = {0, 0, 0}; + + formats->msg_chunks = 2; + + pcm_chunks[0] = analog_ports; + pcm_chunks[1] = analog_ports; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + pcm_chunks[2] = analog_ports; + + if (dir == AMDTP_IN_STREAM) { + if (flags & SND_MOTU_SPEC_TX_MICINST_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + pcm_chunks[2] += 2; + } + + if (flags & SND_MOTU_SPEC_TX_RETURN_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + pcm_chunks[2] += 2; + } + + if (flags & SND_MOTU_SPEC_TX_REVERB_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + } else { + /* + * Packets to v2 units transfer main-out-1/2 and phone-out-1/2. + */ + pcm_chunks[0] += 4; + pcm_chunks[1] += 4; + } + + /* + * At least, packets have two data chunks for S/PDIF on coaxial + * interface. + */ + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + + /* + * Fixed part consists of PCM chunks multiple of 4, with msg chunks. As + * a result, this part can includes empty data chunks. + */ + formats->fixed_part_pcm_chunks[0] = round_up(2 + pcm_chunks[0], 4) - 2; + formats->fixed_part_pcm_chunks[1] = round_up(2 + pcm_chunks[1], 4) - 2; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + formats->fixed_part_pcm_chunks[2] = + round_up(2 + pcm_chunks[2], 4) - 2; +} + +static void calculate_differed_part(struct snd_motu_packet_format *formats, + enum snd_motu_spec_flags flags, u32 data, + u32 a_enable_mask, u32 a_no_adat_mask, + u32 b_enable_mask, u32 b_no_adat_mask) +{ + unsigned char pcm_chunks[3] = {0, 0, 0}; + int i; + + if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_A) && (data & a_enable_mask)) { + if (data & a_no_adat_mask) { + /* + * Additional two data chunks for S/PDIF on optical + * interface A. This includes empty data chunks. + */ + pcm_chunks[0] += 4; + pcm_chunks[1] += 4; + } else { + /* + * Additional data chunks for ADAT on optical interface + * A. + */ + pcm_chunks[0] += 8; + pcm_chunks[1] += 4; + } + } + + if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_B) && (data & b_enable_mask)) { + if (data & b_no_adat_mask) { + /* + * Additional two data chunks for S/PDIF on optical + * interface B. This includes empty data chunks. + */ + pcm_chunks[0] += 4; + pcm_chunks[1] += 4; + } else { + /* + * Additional data chunks for ADAT on optical interface + * B. + */ + pcm_chunks[0] += 8; + pcm_chunks[1] += 4; + } + } + + for (i = 0; i < 3; ++i) { + if (pcm_chunks[i] > 0) + pcm_chunks[i] = round_up(pcm_chunks[i], 4); + + formats->differed_part_pcm_chunks[i] = pcm_chunks[i]; + } +} + +static int v3_cache_packet_formats(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V3_OPT_IFACE_MODE_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + calculate_fixed_part(&motu->tx_packet_formats, AMDTP_IN_STREAM, + motu->spec->flags, motu->spec->analog_in_ports); + calculate_differed_part(&motu->tx_packet_formats, + motu->spec->flags, data, + V3_ENABLE_OPT_IN_IFACE_A, V3_NO_ADAT_OPT_IN_IFACE_A, + V3_ENABLE_OPT_IN_IFACE_B, V3_NO_ADAT_OPT_IN_IFACE_B); + + calculate_fixed_part(&motu->rx_packet_formats, AMDTP_OUT_STREAM, + motu->spec->flags, motu->spec->analog_out_ports); + calculate_differed_part(&motu->rx_packet_formats, + motu->spec->flags, data, + V3_ENABLE_OPT_OUT_IFACE_A, V3_NO_ADAT_OPT_OUT_IFACE_A, + V3_ENABLE_OPT_OUT_IFACE_B, V3_NO_ADAT_OPT_OUT_IFACE_B); + + motu->tx_packet_formats.midi_flag_offset = 8; + motu->tx_packet_formats.midi_byte_offset = 7; + motu->tx_packet_formats.pcm_byte_offset = 10; + + motu->rx_packet_formats.midi_flag_offset = 8; + motu->rx_packet_formats.midi_byte_offset = 7; + motu->rx_packet_formats.pcm_byte_offset = 10; + + return 0; +} + +const struct snd_motu_protocol snd_motu_protocol_v3 = { + .get_clock_rate = v3_get_clock_rate, + .set_clock_rate = v3_set_clock_rate, + .get_clock_source = v3_get_clock_source, + .switch_fetching_mode = v3_switch_fetching_mode, + .cache_packet_formats = v3_cache_packet_formats, +}; diff --git a/sound/firewire/motu/motu-stream.c b/sound/firewire/motu/motu-stream.c new file mode 100644 index 000000000000..bd458029099e --- /dev/null +++ b/sound/firewire/motu/motu-stream.c @@ -0,0 +1,381 @@ +/* + * motu-stream.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "motu.h" + +#define CALLBACK_TIMEOUT 200 + +#define ISOC_COMM_CONTROL_OFFSET 0x0b00 +#define ISOC_COMM_CONTROL_MASK 0xffff0000 +#define CHANGE_RX_ISOC_COMM_STATE 0x80000000 +#define RX_ISOC_COMM_IS_ACTIVATED 0x40000000 +#define RX_ISOC_COMM_CHANNEL_MASK 0x3f000000 +#define RX_ISOC_COMM_CHANNEL_SHIFT 24 +#define CHANGE_TX_ISOC_COMM_STATE 0x00800000 +#define TX_ISOC_COMM_IS_ACTIVATED 0x00400000 +#define TX_ISOC_COMM_CHANNEL_MASK 0x003f0000 +#define TX_ISOC_COMM_CHANNEL_SHIFT 16 + +#define PACKET_FORMAT_OFFSET 0x0b10 +#define TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS 0x00000080 +#define RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS 0x00000040 +#define TX_PACKET_TRANSMISSION_SPEED_MASK 0x0000000f + +static int start_both_streams(struct snd_motu *motu, unsigned int rate) +{ + unsigned int midi_ports = 0; + __be32 reg; + u32 data; + int err; + + if (motu->spec->flags & SND_MOTU_SPEC_HAS_MIDI) + midi_ports = 1; + + /* Set packet formation to our packet streaming engine. */ + err = amdtp_motu_set_parameters(&motu->rx_stream, rate, midi_ports, + &motu->rx_packet_formats); + if (err < 0) + return err; + + err = amdtp_motu_set_parameters(&motu->tx_stream, rate, midi_ports, + &motu->tx_packet_formats); + if (err < 0) + return err; + + /* Get isochronous resources on the bus. */ + err = fw_iso_resources_allocate(&motu->rx_resources, + amdtp_stream_get_max_payload(&motu->rx_stream), + fw_parent_device(motu->unit)->max_speed); + if (err < 0) + return err; + + err = fw_iso_resources_allocate(&motu->tx_resources, + amdtp_stream_get_max_payload(&motu->tx_stream), + fw_parent_device(motu->unit)->max_speed); + if (err < 0) + return err; + + /* Configure the unit to start isochronous communication. */ + err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg) & ~ISOC_COMM_CONTROL_MASK; + + data |= CHANGE_RX_ISOC_COMM_STATE | RX_ISOC_COMM_IS_ACTIVATED | + (motu->rx_resources.channel << RX_ISOC_COMM_CHANNEL_SHIFT) | + CHANGE_TX_ISOC_COMM_STATE | TX_ISOC_COMM_IS_ACTIVATED | + (motu->tx_resources.channel << TX_ISOC_COMM_CHANNEL_SHIFT); + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); +} + +static void stop_both_streams(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = motu->spec->protocol->switch_fetching_mode(motu, false); + if (err < 0) + return; + + err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return; + data = be32_to_cpu(reg); + + data &= ~(RX_ISOC_COMM_IS_ACTIVATED | TX_ISOC_COMM_IS_ACTIVATED); + data |= CHANGE_RX_ISOC_COMM_STATE | CHANGE_TX_ISOC_COMM_STATE; + + reg = cpu_to_be32(data); + snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); + + fw_iso_resources_free(&motu->tx_resources); + fw_iso_resources_free(&motu->rx_resources); +} + +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)) { + amdtp_stream_stop(stream); + fw_iso_resources_free(resources); + return -ETIMEDOUT; + } + + return 0; +} + +static void stop_isoc_ctx(struct snd_motu *motu, struct amdtp_stream *stream) +{ + struct fw_iso_resources *resources; + + if (stream == &motu->rx_stream) + resources = &motu->rx_resources; + else + resources = &motu->tx_resources; + + amdtp_stream_stop(stream); + fw_iso_resources_free(resources); +} + +static int ensure_packet_formats(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, PACKET_FORMAT_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~(TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS | + RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS| + TX_PACKET_TRANSMISSION_SPEED_MASK); + if (motu->tx_packet_formats.differed_part_pcm_chunks[0] == 0) + data |= TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS; + if (motu->rx_packet_formats.differed_part_pcm_chunks[0] == 0) + data |= RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS; + data |= fw_parent_device(motu->unit)->max_speed; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, PACKET_FORMAT_OFFSET, ®, + sizeof(reg)); +} + +int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate) +{ + const struct snd_motu_protocol *protocol = motu->spec->protocol; + unsigned int curr_rate; + int err = 0; + + if (motu->capture_substreams == 0 && motu->playback_substreams == 0) + return 0; + + /* Some packet queueing errors. */ + if (amdtp_streaming_error(&motu->rx_stream) || + amdtp_streaming_error(&motu->tx_stream)) { + amdtp_stream_stop(&motu->rx_stream); + amdtp_stream_stop(&motu->tx_stream); + stop_both_streams(motu); + } + + err = protocol->cache_packet_formats(motu); + if (err < 0) + return err; + + /* Stop stream if rate is different. */ + err = protocol->get_clock_rate(motu, &curr_rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to get sampling rate: %d\n", err); + return err; + } + if (rate == 0) + rate = curr_rate; + if (rate != curr_rate) { + amdtp_stream_stop(&motu->rx_stream); + amdtp_stream_stop(&motu->tx_stream); + stop_both_streams(motu); + } + + if (!amdtp_stream_running(&motu->rx_stream)) { + err = protocol->set_clock_rate(motu, rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to set sampling rate: %d\n", err); + return err; + } + + err = ensure_packet_formats(motu); + if (err < 0) + return err; + + err = start_both_streams(motu, rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to start isochronous comm: %d\n", err); + stop_both_streams(motu); + return err; + } + + err = start_isoc_ctx(motu, &motu->rx_stream); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to start IT context: %d\n", err); + stop_both_streams(motu); + return err; + } + + err = protocol->switch_fetching_mode(motu, true); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to enable frame fetching: %d\n", err); + stop_both_streams(motu); + return err; + } + } + + if (!amdtp_stream_running(&motu->tx_stream) && + motu->capture_substreams > 0) { + err = start_isoc_ctx(motu, &motu->tx_stream); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to start IR context: %d", err); + amdtp_stream_stop(&motu->rx_stream); + stop_both_streams(motu); + return err; + } + } + + return 0; +} + +void snd_motu_stream_stop_duplex(struct snd_motu *motu) +{ + if (motu->capture_substreams == 0) { + if (amdtp_stream_running(&motu->tx_stream)) + stop_isoc_ctx(motu, &motu->tx_stream); + + if (motu->playback_substreams == 0) { + if (amdtp_stream_running(&motu->rx_stream)) + stop_isoc_ctx(motu, &motu->rx_stream); + stop_both_streams(motu); + } + } +} + +static int init_stream(struct snd_motu *motu, enum amdtp_stream_direction dir) +{ + int err; + 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; + } + + 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); + fw_iso_resources_destroy(resources); + } + + return err; +} + +static void destroy_stream(struct snd_motu *motu, + enum amdtp_stream_direction dir) +{ + 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(stream); + fw_iso_resources_free(resources); +} + +int snd_motu_stream_init_duplex(struct snd_motu *motu) +{ + int err; + + err = init_stream(motu, AMDTP_IN_STREAM); + if (err < 0) + return err; + + err = init_stream(motu, AMDTP_OUT_STREAM); + if (err < 0) + destroy_stream(motu, AMDTP_IN_STREAM); + + return err; +} + +/* + * 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); + + motu->playback_substreams = 0; + motu->capture_substreams = 0; +} + +static void motu_lock_changed(struct snd_motu *motu) +{ + motu->dev_lock_changed = true; + wake_up(&motu->hwdep_wait); +} + +int snd_motu_stream_lock_try(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count < 0) { + err = -EBUSY; + goto out; + } + + if (motu->dev_lock_count++ == 0) + motu_lock_changed(motu); + err = 0; +out: + spin_unlock_irq(&motu->lock); + return err; +} + +void snd_motu_stream_lock_release(struct snd_motu *motu) +{ + spin_lock_irq(&motu->lock); + + if (WARN_ON(motu->dev_lock_count <= 0)) + goto out; + + if (--motu->dev_lock_count == 0) + motu_lock_changed(motu); +out: + spin_unlock_irq(&motu->lock); +} diff --git a/sound/firewire/motu/motu-transaction.c b/sound/firewire/motu/motu-transaction.c new file mode 100644 index 000000000000..7fc30091e0de --- /dev/null +++ b/sound/firewire/motu/motu-transaction.c @@ -0,0 +1,137 @@ +/* + * motu-transaction.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + + +#include "motu.h" + +#define SND_MOTU_ADDR_BASE 0xfffff0000000ULL +#define ASYNC_ADDR_HI 0x0b04 +#define ASYNC_ADDR_LO 0x0b08 + +int snd_motu_transaction_read(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size) +{ + int tcode; + + if (size % sizeof(__be32) > 0 || size <= 0) + return -EINVAL; + if (size == sizeof(__be32)) + tcode = TCODE_READ_QUADLET_REQUEST; + else + tcode = TCODE_READ_BLOCK_REQUEST; + + return snd_fw_transaction(motu->unit, tcode, + SND_MOTU_ADDR_BASE + offset, reg, size, 0); +} + +int snd_motu_transaction_write(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size) +{ + int tcode; + + if (size % sizeof(__be32) > 0 || size <= 0) + return -EINVAL; + if (size == sizeof(__be32)) + tcode = TCODE_WRITE_QUADLET_REQUEST; + else + tcode = TCODE_WRITE_BLOCK_REQUEST; + + return snd_fw_transaction(motu->unit, tcode, + SND_MOTU_ADDR_BASE + offset, reg, size, 0); +} + +static void handle_message(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + struct snd_motu *motu = callback_data; + __be32 *buf = (__be32 *)data; + unsigned long flags; + + if (tcode != TCODE_WRITE_QUADLET_REQUEST) { + fw_send_response(card, request, RCODE_COMPLETE); + return; + } + + if (offset != motu->async_handler.offset || length != 4) { + fw_send_response(card, request, RCODE_ADDRESS_ERROR); + return; + } + + spin_lock_irqsave(&motu->lock, flags); + motu->msg = be32_to_cpu(*buf); + spin_unlock_irqrestore(&motu->lock, flags); + + fw_send_response(card, request, RCODE_COMPLETE); + + wake_up(&motu->hwdep_wait); +} + +int snd_motu_transaction_reregister(struct snd_motu *motu) +{ + struct fw_device *device = fw_parent_device(motu->unit); + __be32 data; + int err; + + if (motu->async_handler.callback_data == NULL) + return -EINVAL; + + /* Register messaging address. Block transaction is not allowed. */ + data = cpu_to_be32((device->card->node_id << 16) | + (motu->async_handler.offset >> 32)); + err = snd_motu_transaction_write(motu, ASYNC_ADDR_HI, &data, + sizeof(data)); + if (err < 0) + return err; + + data = cpu_to_be32(motu->async_handler.offset); + return snd_motu_transaction_write(motu, ASYNC_ADDR_LO, &data, + sizeof(data)); +} + +int snd_motu_transaction_register(struct snd_motu *motu) +{ + static const struct fw_address_region resp_register_region = { + .start = 0xffffe0000000ull, + .end = 0xffffe000ffffull, + }; + int err; + + /* Perhaps, 4 byte messages are transferred. */ + motu->async_handler.length = 4; + motu->async_handler.address_callback = handle_message; + motu->async_handler.callback_data = motu; + + err = fw_core_add_address_handler(&motu->async_handler, + &resp_register_region); + if (err < 0) + return err; + + err = snd_motu_transaction_reregister(motu); + if (err < 0) { + fw_core_remove_address_handler(&motu->async_handler); + motu->async_handler.address_callback = NULL; + } + + return err; +} + +void snd_motu_transaction_unregister(struct snd_motu *motu) +{ + __be32 data; + + if (motu->async_handler.address_callback != NULL) + fw_core_remove_address_handler(&motu->async_handler); + motu->async_handler.address_callback = NULL; + + /* Unregister the address. */ + data = cpu_to_be32(0x00000000); + snd_motu_transaction_write(motu, ASYNC_ADDR_HI, &data, sizeof(data)); + snd_motu_transaction_write(motu, ASYNC_ADDR_LO, &data, sizeof(data)); +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c new file mode 100644 index 000000000000..bf779cfeef0d --- /dev/null +++ b/sound/firewire/motu/motu.c @@ -0,0 +1,264 @@ +/* + * motu.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "motu.h" + +#define OUI_MOTU 0x0001f2 + +MODULE_DESCRIPTION("MOTU FireWire driver"); +MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>"); +MODULE_LICENSE("GPL v2"); + +const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT] = { + /* mode 0 */ + [0] = 44100, + [1] = 48000, + /* mode 1 */ + [2] = 88200, + [3] = 96000, + /* mode 2 */ + [4] = 176400, + [5] = 192000, +}; + +static void name_card(struct snd_motu *motu) +{ + struct fw_device *fw_dev = fw_parent_device(motu->unit); + struct fw_csr_iterator it; + int key, val; + u32 version = 0; + + fw_csr_iterator_init(&it, motu->unit->directory); + while (fw_csr_iterator_next(&it, &key, &val)) { + switch (key) { + case CSR_VERSION: + version = val; + break; + } + } + + strcpy(motu->card->driver, "FW-MOTU"); + strcpy(motu->card->shortname, motu->spec->name); + strcpy(motu->card->mixername, motu->spec->name); + snprintf(motu->card->longname, sizeof(motu->card->longname), + "MOTU %s (version:%d), GUID %08x%08x at %s, S%d", + motu->spec->name, version, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&motu->unit->device), 100 << fw_dev->max_speed); +} + +static void motu_free(struct snd_motu *motu) +{ + snd_motu_transaction_unregister(motu); + + snd_motu_stream_destroy_duplex(motu); + fw_unit_put(motu->unit); + + mutex_destroy(&motu->mutex); + kfree(motu); +} + +/* + * This module releases the FireWire unit data after all ALSA character devices + * are released by applications. This is for releasing stream data or finishing + * transactions safely. Thus at returning from .remove(), this module still keep + * references for the unit. + */ +static void motu_card_free(struct snd_card *card) +{ + motu_free(card->private_data); +} + +static void do_registration(struct work_struct *work) +{ + struct snd_motu *motu = container_of(work, struct snd_motu, dwork.work); + int err; + + if (motu->registered) + return; + + err = snd_card_new(&motu->unit->device, -1, NULL, THIS_MODULE, 0, + &motu->card); + if (err < 0) + return; + + name_card(motu); + + err = snd_motu_transaction_register(motu); + if (err < 0) + goto error; + + err = snd_motu_stream_init_duplex(motu); + if (err < 0) + goto error; + + snd_motu_proc_init(motu); + + err = snd_motu_create_pcm_devices(motu); + if (err < 0) + goto error; + + if (motu->spec->flags & SND_MOTU_SPEC_HAS_MIDI) { + err = snd_motu_create_midi_devices(motu); + if (err < 0) + goto error; + } + + err = snd_motu_create_hwdep_device(motu); + if (err < 0) + goto error; + + err = snd_card_register(motu->card); + if (err < 0) + goto error; + + /* + * After registered, motu instance can be released corresponding to + * releasing the sound card instance. + */ + motu->card->private_free = motu_card_free; + motu->card->private_data = motu; + motu->registered = true; + + return; +error: + snd_motu_transaction_unregister(motu); + snd_card_free(motu->card); + dev_info(&motu->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int motu_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_motu *motu; + + /* Allocate this independently of sound card instance. */ + motu = kzalloc(sizeof(struct snd_motu), GFP_KERNEL); + if (motu == NULL) + return -ENOMEM; + + motu->spec = (const struct snd_motu_spec *)entry->driver_data; + motu->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, motu); + + mutex_init(&motu->mutex); + spin_lock_init(&motu->lock); + init_waitqueue_head(&motu->hwdep_wait); + + /* Allocate and register this sound card later. */ + INIT_DEFERRABLE_WORK(&motu->dwork, do_registration); + snd_fw_schedule_registration(unit, &motu->dwork); + + return 0; +} + +static void motu_remove(struct fw_unit *unit) +{ + struct snd_motu *motu = dev_get_drvdata(&unit->device); + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_delayed_work_sync(&motu->dwork); + + if (motu->registered) { + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(motu->card); + } else { + /* Don't forget this case. */ + motu_free(motu); + } +} + +static void motu_bus_update(struct fw_unit *unit) +{ + struct snd_motu *motu = dev_get_drvdata(&unit->device); + + /* Postpone a workqueue for deferred registration. */ + if (!motu->registered) + snd_fw_schedule_registration(unit, &motu->dwork); + + /* The handler address register becomes initialized. */ + snd_motu_transaction_reregister(motu); +} + +static struct snd_motu_spec motu_828mk2 = { + .name = "828mk2", + .protocol = &snd_motu_protocol_v2, + .flags = SND_MOTU_SPEC_SUPPORT_CLOCK_X2 | + SND_MOTU_SPEC_TX_MICINST_CHUNK | + SND_MOTU_SPEC_TX_RETURN_CHUNK | + SND_MOTU_SPEC_HAS_OPT_IFACE_A | + SND_MOTU_SPEC_HAS_MIDI, + + .analog_in_ports = 8, + .analog_out_ports = 8, +}; + +static struct snd_motu_spec motu_828mk3 = { + .name = "828mk3", + .protocol = &snd_motu_protocol_v3, + .flags = SND_MOTU_SPEC_SUPPORT_CLOCK_X2 | + SND_MOTU_SPEC_SUPPORT_CLOCK_X4 | + SND_MOTU_SPEC_TX_MICINST_CHUNK | + SND_MOTU_SPEC_TX_RETURN_CHUNK | + SND_MOTU_SPEC_TX_REVERB_CHUNK | + SND_MOTU_SPEC_HAS_OPT_IFACE_A | + SND_MOTU_SPEC_HAS_OPT_IFACE_B | + SND_MOTU_SPEC_HAS_MIDI, + + .analog_in_ports = 8, + .analog_out_ports = 8, +}; + +#define SND_MOTU_DEV_ENTRY(model, data) \ +{ \ + .match_flags = IEEE1394_MATCH_VENDOR_ID | \ + IEEE1394_MATCH_MODEL_ID | \ + IEEE1394_MATCH_SPECIFIER_ID, \ + .vendor_id = OUI_MOTU, \ + .model_id = model, \ + .specifier_id = OUI_MOTU, \ + .driver_data = (kernel_ulong_t)data, \ +} + +static const struct ieee1394_device_id motu_id_table[] = { + SND_MOTU_DEV_ENTRY(0x101800, &motu_828mk2), + SND_MOTU_DEV_ENTRY(0x106800, &motu_828mk3), /* FireWire only. */ + SND_MOTU_DEV_ENTRY(0x100800, &motu_828mk3), /* Hybrid. */ + { } +}; +MODULE_DEVICE_TABLE(ieee1394, motu_id_table); + +static struct fw_driver motu_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = motu_probe, + .update = motu_bus_update, + .remove = motu_remove, + .id_table = motu_id_table, +}; + +static int __init alsa_motu_init(void) +{ + return driver_register(&motu_driver.driver); +} + +static void __exit alsa_motu_exit(void) +{ + driver_unregister(&motu_driver.driver); +} + +module_init(alsa_motu_init); +module_exit(alsa_motu_exit); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h new file mode 100644 index 000000000000..8d6a4a3af9cc --- /dev/null +++ b/sound/firewire/motu/motu.h @@ -0,0 +1,161 @@ +/* + * motu.h - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef SOUND_FIREWIRE_MOTU_H_INCLUDED +#define SOUND_FIREWIRE_MOTU_H_INCLUDED + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/compat.h> +#include <linux/sched/signal.h> + +#include <sound/control.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/info.h> +#include <sound/rawmidi.h> +#include <sound/firewire.h> +#include <sound/hwdep.h> + +#include "../lib.h" +#include "../amdtp-stream.h" +#include "../iso-resources.h" + +struct snd_motu_packet_format { + unsigned char midi_flag_offset; + unsigned char midi_byte_offset; + unsigned char pcm_byte_offset; + + unsigned char msg_chunks; + unsigned char fixed_part_pcm_chunks[3]; + unsigned char differed_part_pcm_chunks[3]; +}; + +struct snd_motu { + struct snd_card *card; + struct fw_unit *unit; + struct mutex mutex; + spinlock_t lock; + + bool registered; + struct delayed_work dwork; + + /* Model dependent information. */ + const struct snd_motu_spec *spec; + + /* For packet streaming */ + struct snd_motu_packet_format tx_packet_formats; + struct snd_motu_packet_format rx_packet_formats; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + struct fw_iso_resources tx_resources; + struct fw_iso_resources rx_resources; + unsigned int capture_substreams; + unsigned int playback_substreams; + + /* For notification. */ + struct fw_address_handler async_handler; + u32 msg; + + /* For uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; +}; + +enum snd_motu_spec_flags { + SND_MOTU_SPEC_SUPPORT_CLOCK_X2 = 0x0001, + SND_MOTU_SPEC_SUPPORT_CLOCK_X4 = 0x0002, + SND_MOTU_SPEC_TX_MICINST_CHUNK = 0x0004, + SND_MOTU_SPEC_TX_RETURN_CHUNK = 0x0008, + SND_MOTU_SPEC_TX_REVERB_CHUNK = 0x0010, + SND_MOTU_SPEC_TX_AESEBU_CHUNK = 0x0020, + SND_MOTU_SPEC_HAS_OPT_IFACE_A = 0x0040, + SND_MOTU_SPEC_HAS_OPT_IFACE_B = 0x0080, + SND_MOTU_SPEC_HAS_MIDI = 0x0100, +}; + +#define SND_MOTU_CLOCK_RATE_COUNT 6 +extern const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT]; + +enum snd_motu_clock_source { + SND_MOTU_CLOCK_SOURCE_INTERNAL, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX, + SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR, + SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC, + SND_MOTU_CLOCK_SOURCE_UNKNOWN, +}; + +struct snd_motu_protocol { + int (*get_clock_rate)(struct snd_motu *motu, unsigned int *rate); + int (*set_clock_rate)(struct snd_motu *motu, unsigned int rate); + int (*get_clock_source)(struct snd_motu *motu, + enum snd_motu_clock_source *source); + int (*switch_fetching_mode)(struct snd_motu *motu, bool enable); + int (*cache_packet_formats)(struct snd_motu *motu); +}; + +struct snd_motu_spec { + const char *const name; + enum snd_motu_spec_flags flags; + + unsigned char analog_in_ports; + unsigned char analog_out_ports; + + const struct snd_motu_protocol *const protocol; +}; + +extern const struct snd_motu_protocol snd_motu_protocol_v2; +extern const struct snd_motu_protocol snd_motu_protocol_v3; + +int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, + const struct snd_motu_protocol *const protocol); +int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int midi_ports, + struct snd_motu_packet_format *formats); +int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); +void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi); + +int snd_motu_transaction_read(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size); +int snd_motu_transaction_write(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size); +int snd_motu_transaction_register(struct snd_motu *motu); +int snd_motu_transaction_reregister(struct snd_motu *motu); +void snd_motu_transaction_unregister(struct snd_motu *motu); + +int snd_motu_stream_init_duplex(struct snd_motu *motu); +void snd_motu_stream_destroy_duplex(struct snd_motu *motu); +int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate); +void snd_motu_stream_stop_duplex(struct snd_motu *motu); +int snd_motu_stream_lock_try(struct snd_motu *motu); +void snd_motu_stream_lock_release(struct snd_motu *motu); + +void snd_motu_proc_init(struct snd_motu *motu); + +int snd_motu_create_pcm_devices(struct snd_motu *motu); + +int snd_motu_create_midi_devices(struct snd_motu *motu); + +int snd_motu_create_hwdep_device(struct snd_motu *motu); +#endif diff --git a/sound/firewire/oxfw/oxfw-command.c b/sound/firewire/oxfw/oxfw-command.c index 12ef3253bc89..ac3e2e301666 100644 --- a/sound/firewire/oxfw/oxfw-command.c +++ b/sound/firewire/oxfw/oxfw-command.c @@ -34,7 +34,9 @@ int avc_stream_set_format(struct fw_unit *unit, enum avc_general_plug_dir dir, err = fcp_avc_transaction(unit, buf, len + 10, buf, len + 10, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(8)); - if ((err > 0) && (err < len + 10)) + if (err < 0) + ; + else if (err < len + 10) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -77,7 +79,9 @@ int avc_stream_get_format(struct fw_unit *unit, err = fcp_avc_transaction(unit, buf, 12, buf, *len, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7)); - if ((err > 0) && (err < 10)) + if (err < 0) + ; + else if (err < 12) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -139,7 +143,9 @@ int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate, /* do transaction and check buf[1-5] are the same against command */ err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); - if ((err > 0) && (err < 8)) + if (err < 0) + ; + else if (err < 8) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; diff --git a/sound/firewire/tascam/tascam-midi.c b/sound/firewire/tascam/tascam-midi.c index df4f95d65925..4a741570d536 100644 --- a/sound/firewire/tascam/tascam-midi.c +++ b/sound/firewire/tascam/tascam-midi.c @@ -18,9 +18,8 @@ static int midi_playback_open(struct snd_rawmidi_substream *substream) { struct snd_tscm *tscm = substream->rmidi->private_data; - /* Initialize internal status. */ - tscm->running_status[substream->number] = 0; - tscm->on_sysex[substream->number] = 0; + snd_fw_async_midi_port_init(&tscm->out_ports[substream->number]); + return 0; } @@ -32,11 +31,14 @@ static int midi_capture_close(struct snd_rawmidi_substream *substream) static int midi_playback_close(struct snd_rawmidi_substream *substream) { + return 0; +} + +static void midi_playback_drain(struct snd_rawmidi_substream *substream) +{ struct snd_tscm *tscm = substream->rmidi->private_data; snd_fw_async_midi_port_finish(&tscm->out_ports[substream->number]); - - return 0; } static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) @@ -78,6 +80,7 @@ int snd_tscm_create_midi_devices(struct snd_tscm *tscm) static const struct snd_rawmidi_ops playback_ops = { .open = midi_playback_open, .close = midi_playback_close, + .drain = midi_playback_drain, .trigger = midi_playback_trigger, }; struct snd_rawmidi *rmidi; diff --git a/sound/firewire/tascam/tascam-transaction.c b/sound/firewire/tascam/tascam-transaction.c index 040a96d1ba8e..8967c52f5032 100644 --- a/sound/firewire/tascam/tascam-transaction.c +++ b/sound/firewire/tascam/tascam-transaction.c @@ -58,39 +58,38 @@ static inline int calculate_message_bytes(u8 status) return -EINVAL; } -static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf) +static int fill_message(struct snd_fw_async_midi_port *port, + struct snd_rawmidi_substream *substream) { - struct snd_tscm *tscm = substream->rmidi->private_data; - unsigned int port = substream->number; int i, len, consume; u8 *label, *msg; u8 status; /* The first byte is used for label, the rest for MIDI bytes. */ - label = buf; - msg = buf + 1; + label = port->buf; + msg = port->buf + 1; consume = snd_rawmidi_transmit_peek(substream, msg, 3); if (consume == 0) return 0; /* On exclusive message. */ - if (tscm->on_sysex[port]) { + if (port->on_sysex) { /* Seek the end of exclusives. */ for (i = 0; i < consume; ++i) { if (msg[i] == 0xf7) { - tscm->on_sysex[port] = false; + port->on_sysex = false; break; } } /* At the end of exclusive message, use label 0x07. */ - if (!tscm->on_sysex[port]) { + if (!port->on_sysex) { consume = i + 1; - *label = (port << 4) | 0x07; + *label = (substream->number << 4) | 0x07; /* During exclusive message, use label 0x04. */ } else if (consume == 3) { - *label = (port << 4) | 0x04; + *label = (substream->number << 4) | 0x04; /* We need to fill whole 3 bytes. Go to next change. */ } else { return 0; @@ -101,12 +100,12 @@ static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf) /* The beginning of exclusives. */ if (msg[0] == 0xf0) { /* Transfer it in next chance in another condition. */ - tscm->on_sysex[port] = true; + port->on_sysex = true; return 0; } else { /* On running-status. */ if ((msg[0] & 0x80) != 0x80) - status = tscm->running_status[port]; + status = port->running_status; else status = msg[0]; @@ -124,18 +123,18 @@ static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf) msg[2] = msg[1]; msg[1] = msg[0]; - msg[0] = tscm->running_status[port]; + msg[0] = port->running_status; } else { /* Enough MIDI bytes were not retrieved. */ if (consume < len) return 0; consume = len; - tscm->running_status[port] = msg[0]; + port->running_status = msg[0]; } } - *label = (port << 4) | (msg[0] >> 4); + *label = (substream->number << 4) | (msg[0] >> 4); } if (len > 0 && len < 3) @@ -144,6 +143,106 @@ static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf) return consume; } +static void async_midi_port_callback(struct fw_card *card, int rcode, + void *data, size_t length, + void *callback_data) +{ + struct snd_fw_async_midi_port *port = callback_data; + struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream); + + /* This port is closed. */ + if (substream == NULL) + return; + + if (rcode == RCODE_COMPLETE) + snd_rawmidi_transmit_ack(substream, port->consume_bytes); + else if (!rcode_is_permanent_error(rcode)) + /* To start next transaction immediately for recovery. */ + port->next_ktime = 0; + else + /* Don't continue processing. */ + port->error = true; + + port->idling = true; + + if (!snd_rawmidi_transmit_empty(substream)) + schedule_work(&port->work); +} + +static void midi_port_work(struct work_struct *work) +{ + struct snd_fw_async_midi_port *port = + container_of(work, struct snd_fw_async_midi_port, work); + struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream); + int generation; + + /* Under transacting or error state. */ + if (!port->idling || port->error) + return; + + /* Nothing to do. */ + if (substream == NULL || snd_rawmidi_transmit_empty(substream)) + return; + + /* Do it in next chance. */ + if (ktime_after(port->next_ktime, ktime_get())) { + schedule_work(&port->work); + return; + } + + /* + * Fill the buffer. The callee must use snd_rawmidi_transmit_peek(). + * Later, snd_rawmidi_transmit_ack() is called. + */ + memset(port->buf, 0, 4); + port->consume_bytes = fill_message(port, substream); + if (port->consume_bytes <= 0) { + /* Do it in next chance, immediately. */ + if (port->consume_bytes == 0) { + port->next_ktime = 0; + schedule_work(&port->work); + } else { + /* Fatal error. */ + port->error = true; + } + return; + } + + /* Set interval to next transaction. */ + port->next_ktime = ktime_add_ns(ktime_get(), + port->consume_bytes * 8 * NSEC_PER_SEC / 31250); + + /* Start this transaction. */ + port->idling = false; + + /* + * In Linux FireWire core, when generation is updated with memory + * barrier, node id has already been updated. In this module, After + * this smp_rmb(), load/store instructions to memory are completed. + * Thus, both of generation and node id are available with recent + * values. This is a light-serialization solution to handle bus reset + * events on IEEE 1394 bus. + */ + generation = port->parent->generation; + smp_rmb(); + + fw_send_request(port->parent->card, &port->transaction, + TCODE_WRITE_QUADLET_REQUEST, + port->parent->node_id, generation, + port->parent->max_speed, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_RX_QUAD, + port->buf, 4, async_midi_port_callback, + port); +} + +void snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port) +{ + port->idling = true; + port->error = false; + port->running_status = 0; + port->on_sysex = false; +} + static void handle_midi_tx(struct fw_card *card, struct fw_request *request, int tcode, int destination, int source, int generation, unsigned long long offset, @@ -219,12 +318,9 @@ int snd_tscm_transaction_register(struct snd_tscm *tscm) goto error; for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++) { - err = snd_fw_async_midi_port_init( - &tscm->out_ports[i], tscm->unit, - TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_RX_QUAD, - 4, fill_message); - if (err < 0) - goto error; + tscm->out_ports[i].parent = fw_parent_device(tscm->unit); + tscm->out_ports[i].next_ktime = 0; + INIT_WORK(&tscm->out_ports[i].work, midi_port_work); } return err; @@ -275,7 +371,6 @@ int snd_tscm_transaction_reregister(struct snd_tscm *tscm) void snd_tscm_transaction_unregister(struct snd_tscm *tscm) { __be32 reg; - unsigned int i; if (tscm->async_handler.callback_data == NULL) return; @@ -302,7 +397,4 @@ void snd_tscm_transaction_unregister(struct snd_tscm *tscm) fw_core_remove_address_handler(&tscm->async_handler); tscm->async_handler.callback_data = NULL; - - for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++) - snd_fw_async_midi_port_destroy(&tscm->out_ports[i]); } diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index d3cd4065722b..08ecfae5c584 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -45,6 +45,23 @@ struct snd_tscm_spec { #define TSCM_MIDI_IN_PORT_MAX 4 #define TSCM_MIDI_OUT_PORT_MAX 4 +struct snd_fw_async_midi_port { + struct fw_device *parent; + struct work_struct work; + bool idling; + ktime_t next_ktime; + bool error; + + struct fw_transaction transaction; + + u8 buf[4]; + u8 running_status; + bool on_sysex; + + struct snd_rawmidi_substream *substream; + int consume_bytes; +}; + struct snd_tscm { struct snd_card *card; struct fw_unit *unit; @@ -72,8 +89,6 @@ struct snd_tscm { /* For MIDI message outgoing transactions. */ struct snd_fw_async_midi_port out_ports[TSCM_MIDI_OUT_PORT_MAX]; - u8 running_status[TSCM_MIDI_OUT_PORT_MAX]; - bool on_sysex[TSCM_MIDI_OUT_PORT_MAX]; }; #define TSCM_ADDR_BASE 0xffff00000000ull @@ -131,6 +146,26 @@ void snd_tscm_stream_lock_changed(struct snd_tscm *tscm); int snd_tscm_stream_lock_try(struct snd_tscm *tscm); void snd_tscm_stream_lock_release(struct snd_tscm *tscm); +void snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port); + +static inline void +snd_fw_async_midi_port_run(struct snd_fw_async_midi_port *port, + struct snd_rawmidi_substream *substream) +{ + if (!port->error) { + port->substream = substream; + schedule_work(&port->work); + } +} + +static inline void +snd_fw_async_midi_port_finish(struct snd_fw_async_midi_port *port) +{ + port->substream = NULL; + cancel_work_sync(&port->work); + port->error = false; +} + int snd_tscm_transaction_register(struct snd_tscm *tscm); int snd_tscm_transaction_reregister(struct snd_tscm *tscm); void snd_tscm_transaction_unregister(struct snd_tscm *tscm); diff --git a/sound/hda/ext/hdac_ext_controller.c b/sound/hda/ext/hdac_ext_controller.c index 261469188566..84f3b8168716 100644 --- a/sound/hda/ext/hdac_ext_controller.c +++ b/sound/hda/ext/hdac_ext_controller.c @@ -171,7 +171,7 @@ static int check_hdac_link_power_active(struct hdac_ext_link *link, bool enable) { int timeout; u32 val; - int mask = (1 << AZX_MLCTL_CPA); + int mask = (1 << AZX_MLCTL_CPA_SHIFT); udelay(3); timeout = 150; @@ -179,10 +179,10 @@ static int check_hdac_link_power_active(struct hdac_ext_link *link, bool enable) do { val = readl(link->ml_addr + AZX_REG_ML_LCTL); if (enable) { - if (((val & mask) >> AZX_MLCTL_CPA)) + if (((val & mask) >> AZX_MLCTL_CPA_SHIFT)) return 0; } else { - if (!((val & mask) >> AZX_MLCTL_CPA)) + if (!((val & mask) >> AZX_MLCTL_CPA_SHIFT)) return 0; } udelay(3); diff --git a/sound/hda/hdac_controller.c b/sound/hda/hdac_controller.c index 043065867656..d15b653de0bf 100644 --- a/sound/hda/hdac_controller.c +++ b/sound/hda/hdac_controller.c @@ -272,7 +272,7 @@ int snd_hdac_bus_parse_capabilities(struct hdac_bus *bus) /* Lets walk the linked capabilities list */ do { - cur_cap = _snd_hdac_chip_read(l, bus, offset); + cur_cap = _snd_hdac_chip_readl(bus, offset); dev_dbg(bus->dev, "Capability version: 0x%x\n", (cur_cap & AZX_CAP_HDR_VER_MASK) >> AZX_CAP_HDR_VER_OFF); diff --git a/sound/hda/hdac_stream.c b/sound/hda/hdac_stream.c index c6994ebb4567..e1472c7ab6c1 100644 --- a/sound/hda/hdac_stream.c +++ b/sound/hda/hdac_stream.c @@ -555,12 +555,12 @@ void snd_hdac_stream_sync_trigger(struct hdac_stream *azx_dev, bool set, if (!reg) reg = AZX_REG_SSYNC; - val = _snd_hdac_chip_read(l, bus, reg); + val = _snd_hdac_chip_readl(bus, reg); if (set) val |= streams; else val &= ~streams; - _snd_hdac_chip_write(l, bus, reg, val); + _snd_hdac_chip_writel(bus, reg, val); } EXPORT_SYMBOL_GPL(snd_hdac_stream_sync_trigger); diff --git a/sound/isa/es1688/es1688_lib.c b/sound/isa/es1688/es1688_lib.c index e2cf508841b1..81cf26fa28d6 100644 --- a/sound/isa/es1688/es1688_lib.c +++ b/sound/isa/es1688/es1688_lib.c @@ -742,7 +742,7 @@ int snd_es1688_pcm(struct snd_card *card, struct snd_es1688 *chip, int device) pcm->private_data = chip; pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX; - sprintf(pcm->name, snd_es1688_chip_id(chip)); + strcpy(pcm->name, snd_es1688_chip_id(chip)); chip->pcm = pcm; snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, diff --git a/sound/pci/ali5451/ali5451.c b/sound/pci/ali5451/ali5451.c index 92b819e4f729..34bbc2e730a6 100644 --- a/sound/pci/ali5451/ali5451.c +++ b/sound/pci/ali5451/ali5451.c @@ -1339,7 +1339,7 @@ static int snd_ali_prepare(struct snd_pcm_substream *substream) rate = snd_ali_get_spdif_in_rate(codec); if (rate == 0) { dev_warn(codec->card->dev, - "ali_capture_preapre: spdif rate detect err!\n"); + "ali_capture_prepare: spdif rate detect err!\n"); rate = 48000; } spin_lock_irq(&codec->reg_lock); diff --git a/sound/pci/au88x0/au88x0_a3d.c b/sound/pci/au88x0/au88x0_a3d.c index ab0f87312911..7a4558a70fb9 100644 --- a/sound/pci/au88x0/au88x0_a3d.c +++ b/sound/pci/au88x0/au88x0_a3d.c @@ -846,7 +846,7 @@ snd_vortex_a3d_filter_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new vortex_a3d_kcontrol = { +static const struct snd_kcontrol_new vortex_a3d_kcontrol = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "Playback PCM advanced processing", .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, diff --git a/sound/pci/au88x0/au88x0_core.c b/sound/pci/au88x0/au88x0_core.c index e1af24f87566..c308a4f70550 100644 --- a/sound/pci/au88x0/au88x0_core.c +++ b/sound/pci/au88x0/au88x0_core.c @@ -2279,6 +2279,9 @@ vortex_adb_allocroute(vortex_t *vortex, int dma, int nr_ch, int dir, } else { int src[2], mix[2]; + if (nr_ch < 1) + return -EINVAL; + /* Get SRC and MIXER hardware resources. */ for (i = 0; i < nr_ch; i++) { if ((mix[i] = diff --git a/sound/pci/au88x0/au88x0_eq.c b/sound/pci/au88x0/au88x0_eq.c index 9585c5c63b96..b566b44e4da7 100644 --- a/sound/pci/au88x0/au88x0_eq.c +++ b/sound/pci/au88x0/au88x0_eq.c @@ -757,7 +757,7 @@ snd_vortex_eqtoggle_put(struct snd_kcontrol *kcontrol, return 1; /* Allways changes */ } -static struct snd_kcontrol_new vortex_eqtoggle_kcontrol = { +static const struct snd_kcontrol_new vortex_eqtoggle_kcontrol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "EQ Enable", .index = 0, @@ -815,7 +815,7 @@ snd_vortex_eq_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucon return changed; } -static struct snd_kcontrol_new vortex_eq_kcontrol = { +static const struct snd_kcontrol_new vortex_eq_kcontrol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = " .", .index = 0, @@ -855,7 +855,7 @@ snd_vortex_peaks_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u return 0; } -static struct snd_kcontrol_new vortex_levels_kcontrol = { +static const struct snd_kcontrol_new vortex_levels_kcontrol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "EQ Peaks", .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, diff --git a/sound/pci/au88x0/au88x0_pcm.c b/sound/pci/au88x0/au88x0_pcm.c index df5741a78fd2..335979a753fe 100644 --- a/sound/pci/au88x0/au88x0_pcm.c +++ b/sound/pci/au88x0/au88x0_pcm.c @@ -601,7 +601,7 @@ static int snd_vortex_pcm_vol_put(struct snd_kcontrol *kcontrol, static const DECLARE_TLV_DB_MINMAX(vortex_pcm_vol_db_scale, -9600, 2400); -static struct snd_kcontrol_new snd_vortex_pcm_vol = { +static const struct snd_kcontrol_new snd_vortex_pcm_vol = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "PCM Playback Volume", .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | diff --git a/sound/pci/aw2/aw2-alsa.c b/sound/pci/aw2/aw2-alsa.c index 57bbb87d0c62..8356180bfe0e 100644 --- a/sound/pci/aw2/aw2-alsa.c +++ b/sound/pci/aw2/aw2-alsa.c @@ -202,7 +202,7 @@ static const struct snd_pcm_ops snd_aw2_capture_ops = { .pointer = snd_aw2_pcm_pointer_capture, }; -static struct snd_kcontrol_new aw2_control = { +static const struct snd_kcontrol_new aw2_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Capture Route", .index = 0, diff --git a/sound/pci/bt87x.c b/sound/pci/bt87x.c index f2c0709d7441..099efb046b1c 100644 --- a/sound/pci/bt87x.c +++ b/sound/pci/bt87x.c @@ -598,7 +598,7 @@ static int snd_bt87x_capture_volume_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_bt87x_capture_volume = { +static const struct snd_kcontrol_new snd_bt87x_capture_volume = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture Volume", .info = snd_bt87x_capture_volume_info, @@ -634,7 +634,7 @@ static int snd_bt87x_capture_boost_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_bt87x_capture_boost = { +static const struct snd_kcontrol_new snd_bt87x_capture_boost = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture Boost", .info = snd_bt87x_capture_boost_info, @@ -676,7 +676,7 @@ static int snd_bt87x_capture_source_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_bt87x_capture_source = { +static const struct snd_kcontrol_new snd_bt87x_capture_source = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture Source", .info = snd_bt87x_capture_source_info, diff --git a/sound/pci/ca0106/ca0106_mixer.c b/sound/pci/ca0106/ca0106_mixer.c index 025805cba779..b4d3415331f6 100644 --- a/sound/pci/ca0106/ca0106_mixer.c +++ b/sound/pci/ca0106/ca0106_mixer.c @@ -301,7 +301,7 @@ static int snd_ca0106_capture_mic_line_in_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ca0106_capture_mic_line_in = +static const struct snd_kcontrol_new snd_ca0106_capture_mic_line_in = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Shared Mic/Line in Capture Switch", @@ -310,7 +310,7 @@ static struct snd_kcontrol_new snd_ca0106_capture_mic_line_in = .put = snd_ca0106_capture_mic_line_in_put }; -static struct snd_kcontrol_new snd_ca0106_capture_line_in_side_out = +static const struct snd_kcontrol_new snd_ca0106_capture_line_in_side_out = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Shared Line in/Side out Capture Switch", diff --git a/sound/pci/cmipci.c b/sound/pci/cmipci.c index aeedc270ed9b..227c9d3802b8 100644 --- a/sound/pci/cmipci.c +++ b/sound/pci/cmipci.c @@ -1045,7 +1045,7 @@ static int snd_cmipci_spdif_default_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_cmipci_spdif_default = +static const struct snd_kcontrol_new snd_cmipci_spdif_default = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), @@ -1072,7 +1072,7 @@ static int snd_cmipci_spdif_mask_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_cmipci_spdif_mask = +static const struct snd_kcontrol_new snd_cmipci_spdif_mask = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1119,7 +1119,7 @@ static int snd_cmipci_spdif_stream_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_cmipci_spdif_stream = +static const struct snd_kcontrol_new snd_cmipci_spdif_stream = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, diff --git a/sound/pci/cs4281.c b/sound/pci/cs4281.c index fa7c51684dd2..f870697aca67 100644 --- a/sound/pci/cs4281.c +++ b/sound/pci/cs4281.c @@ -1055,7 +1055,7 @@ static int snd_cs4281_put_volume(struct snd_kcontrol *kcontrol, static const DECLARE_TLV_DB_SCALE(db_scale_dsp, -4650, 150, 0); -static struct snd_kcontrol_new snd_cs4281_fm_vol = +static const struct snd_kcontrol_new snd_cs4281_fm_vol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Synth Playback Volume", @@ -1066,7 +1066,7 @@ static struct snd_kcontrol_new snd_cs4281_fm_vol = .tlv = { .p = db_scale_dsp }, }; -static struct snd_kcontrol_new snd_cs4281_pcm_vol = +static const struct snd_kcontrol_new snd_cs4281_pcm_vol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Stream Playback Volume", diff --git a/sound/pci/echoaudio/echoaudio.c b/sound/pci/echoaudio/echoaudio.c index 937071760bc4..d15ecf9febbf 100644 --- a/sound/pci/echoaudio/echoaudio.c +++ b/sound/pci/echoaudio/echoaudio.c @@ -1039,7 +1039,7 @@ static int snd_echo_output_gain_put(struct snd_kcontrol *kcontrol, #ifdef ECHOCARD_HAS_LINE_OUT_GAIN /* On the Mia this one controls the line-out volume */ -static struct snd_kcontrol_new snd_echo_line_output_gain = { +static const struct snd_kcontrol_new snd_echo_line_output_gain = { .name = "Line Playback Volume", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | @@ -1050,7 +1050,7 @@ static struct snd_kcontrol_new snd_echo_line_output_gain = { .tlv = {.p = db_scale_output_gain}, }; #else -static struct snd_kcontrol_new snd_echo_pcm_output_gain = { +static const struct snd_kcontrol_new snd_echo_pcm_output_gain = { .name = "PCM Playback Volume", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, @@ -1120,7 +1120,7 @@ static int snd_echo_input_gain_put(struct snd_kcontrol *kcontrol, static const DECLARE_TLV_DB_SCALE(db_scale_input_gain, -2500, 50, 0); -static struct snd_kcontrol_new snd_echo_line_input_gain = { +static const struct snd_kcontrol_new snd_echo_line_input_gain = { .name = "Line Capture Volume", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, @@ -1184,7 +1184,7 @@ static int snd_echo_output_nominal_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_echo_output_nominal_level = { +static const struct snd_kcontrol_new snd_echo_output_nominal_level = { .name = "Line Playback Switch (-10dBV)", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .info = snd_echo_output_nominal_info, @@ -1250,7 +1250,7 @@ static int snd_echo_input_nominal_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_echo_intput_nominal_level = { +static const struct snd_kcontrol_new snd_echo_intput_nominal_level = { .name = "Line Capture Switch (-10dBV)", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .info = snd_echo_input_nominal_info, @@ -1477,7 +1477,7 @@ static int snd_echo_digital_mode_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_echo_digital_mode_switch = { +static const struct snd_kcontrol_new snd_echo_digital_mode_switch = { .name = "Digital mode Switch", .iface = SNDRV_CTL_ELEM_IFACE_CARD, .info = snd_echo_digital_mode_info, @@ -1527,7 +1527,7 @@ static int snd_echo_spdif_mode_put(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_echo_spdif_mode_switch = { +static const struct snd_kcontrol_new snd_echo_spdif_mode_switch = { .name = "S/PDIF mode Switch", .iface = SNDRV_CTL_ELEM_IFACE_CARD, .info = snd_echo_spdif_mode_info, @@ -1600,7 +1600,7 @@ static int snd_echo_clock_source_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_echo_clock_source_switch = { +static const struct snd_kcontrol_new snd_echo_clock_source_switch = { .name = "Sample Clock Source", .iface = SNDRV_CTL_ELEM_IFACE_PCM, .info = snd_echo_clock_source_info, @@ -1643,7 +1643,7 @@ static int snd_echo_phantom_power_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_echo_phantom_power_switch = { +static const struct snd_kcontrol_new snd_echo_phantom_power_switch = { .name = "Phantom power Switch", .iface = SNDRV_CTL_ELEM_IFACE_CARD, .info = snd_echo_phantom_power_info, @@ -1686,7 +1686,7 @@ static int snd_echo_automute_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_echo_automute_switch = { +static const struct snd_kcontrol_new snd_echo_automute_switch = { .name = "Digital Capture Switch (automute)", .iface = SNDRV_CTL_ELEM_IFACE_CARD, .info = snd_echo_automute_info, @@ -1713,7 +1713,7 @@ static int snd_echo_vumeters_switch_put(struct snd_kcontrol *kcontrol, return 1; } -static struct snd_kcontrol_new snd_echo_vumeters_switch = { +static const struct snd_kcontrol_new snd_echo_vumeters_switch = { .name = "VU-meters Switch", .iface = SNDRV_CTL_ELEM_IFACE_CARD, .access = SNDRV_CTL_ELEM_ACCESS_WRITE, @@ -1751,7 +1751,7 @@ static int snd_echo_vumeters_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_echo_vumeters = { +static const struct snd_kcontrol_new snd_echo_vumeters = { .name = "VU-meters", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READ | @@ -1804,7 +1804,7 @@ static int snd_echo_channels_info_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_echo_channels_info = { +static const struct snd_kcontrol_new snd_echo_channels_info = { .name = "Channels info", .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, diff --git a/sound/pci/emu10k1/emu10k1x.c b/sound/pci/emu10k1/emu10k1x.c index 32842734ada6..77a4413f4564 100644 --- a/sound/pci/emu10k1/emu10k1x.c +++ b/sound/pci/emu10k1/emu10k1x.c @@ -1112,7 +1112,7 @@ static int snd_emu10k1x_shared_spdif_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1x_shared_spdif = +static const struct snd_kcontrol_new snd_emu10k1x_shared_spdif = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Analog/Digital Output Jack", @@ -1171,7 +1171,7 @@ static int snd_emu10k1x_spdif_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1x_spdif_mask_control = +static const struct snd_kcontrol_new snd_emu10k1x_spdif_mask_control = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1181,7 +1181,7 @@ static struct snd_kcontrol_new snd_emu10k1x_spdif_mask_control = .get = snd_emu10k1x_spdif_get_mask }; -static struct snd_kcontrol_new snd_emu10k1x_spdif_control = +static const struct snd_kcontrol_new snd_emu10k1x_spdif_control = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 076b117009c5..b2219a73c17c 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -795,7 +795,7 @@ static int snd_emu1010_internal_clock_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu1010_internal_clock = +static const struct snd_kcontrol_new snd_emu1010_internal_clock = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .iface = SNDRV_CTL_ELEM_IFACE_MIXER, @@ -847,7 +847,7 @@ static int snd_emu1010_optical_out_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu1010_optical_out = { +static const struct snd_kcontrol_new snd_emu1010_optical_out = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Optical Output Mode", @@ -898,7 +898,7 @@ static int snd_emu1010_optical_in_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu1010_optical_in = { +static const struct snd_kcontrol_new snd_emu1010_optical_in = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Optical Input Mode", @@ -978,7 +978,7 @@ static int snd_audigy_i2c_capture_source_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_audigy_i2c_capture_source = +static const struct snd_kcontrol_new snd_audigy_i2c_capture_source = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture Source", @@ -1177,7 +1177,7 @@ static int snd_emu10k1_spdif_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_spdif_mask_control = +static const struct snd_kcontrol_new snd_emu10k1_spdif_mask_control = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1187,7 +1187,7 @@ static struct snd_kcontrol_new snd_emu10k1_spdif_mask_control = .get = snd_emu10k1_spdif_get_mask }; -static struct snd_kcontrol_new snd_emu10k1_spdif_control = +static const struct snd_kcontrol_new snd_emu10k1_spdif_control = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), @@ -1293,7 +1293,7 @@ static int snd_emu10k1_send_routing_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_send_routing_control = +static const struct snd_kcontrol_new snd_emu10k1_send_routing_control = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1364,7 +1364,7 @@ static int snd_emu10k1_send_volume_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_send_volume_control = +static const struct snd_kcontrol_new snd_emu10k1_send_volume_control = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1429,7 +1429,7 @@ static int snd_emu10k1_attn_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_attn_control = +static const struct snd_kcontrol_new snd_emu10k1_attn_control = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1501,7 +1501,7 @@ static int snd_emu10k1_efx_send_routing_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_efx_send_routing_control = +static const struct snd_kcontrol_new snd_emu10k1_efx_send_routing_control = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1568,7 +1568,7 @@ static int snd_emu10k1_efx_send_volume_put(struct snd_kcontrol *kcontrol, } -static struct snd_kcontrol_new snd_emu10k1_efx_send_volume_control = +static const struct snd_kcontrol_new snd_emu10k1_efx_send_volume_control = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1626,7 +1626,7 @@ static int snd_emu10k1_efx_attn_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_efx_attn_control = +static const struct snd_kcontrol_new snd_emu10k1_efx_attn_control = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1691,7 +1691,7 @@ static int snd_emu10k1_shared_spdif_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_shared_spdif = +static const struct snd_kcontrol_new snd_emu10k1_shared_spdif = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "SB Live Analog/Digital Output Jack", @@ -1700,7 +1700,7 @@ static struct snd_kcontrol_new snd_emu10k1_shared_spdif = .put = snd_emu10k1_shared_spdif_put }; -static struct snd_kcontrol_new snd_audigy_shared_spdif = +static const struct snd_kcontrol_new snd_audigy_shared_spdif = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Audigy Analog/Digital Output Jack", @@ -1738,7 +1738,7 @@ static int snd_audigy_capture_boost_put(struct snd_kcontrol *kcontrol, return snd_ac97_update(emu->ac97, AC97_REC_GAIN, val); } -static struct snd_kcontrol_new snd_audigy_capture_boost = +static const struct snd_kcontrol_new snd_audigy_capture_boost = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Mic Extra Boost", diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 37be1e14d756..ef1cf530c929 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -1542,7 +1542,7 @@ static int snd_emu10k1_pcm_efx_voices_mask_put(struct snd_kcontrol *kcontrol, st return change; } -static struct snd_kcontrol_new snd_emu10k1_pcm_efx_voices_mask = { +static const struct snd_kcontrol_new snd_emu10k1_pcm_efx_voices_mask = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "Captured FX8010 Outputs", .info = snd_emu10k1_pcm_efx_voices_mask_info, diff --git a/sound/pci/ens1370.c b/sound/pci/ens1370.c index 164adad91650..5d10349d11ce 100644 --- a/sound/pci/ens1370.c +++ b/sound/pci/ens1370.c @@ -1530,7 +1530,7 @@ static int snd_es1373_rear_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ens1373_rear = +static const struct snd_kcontrol_new snd_ens1373_rear = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "AC97 2ch->4ch Copy Switch", @@ -1575,7 +1575,7 @@ static int snd_es1373_line_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_ens1373_line = +static const struct snd_kcontrol_new snd_ens1373_line = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Line In->Rear Out Switch", diff --git a/sound/pci/hda/hda_auto_parser.c b/sound/pci/hda/hda_auto_parser.c index a03cf68d0bcd..d3ea73171a3d 100644 --- a/sound/pci/hda/hda_auto_parser.c +++ b/sound/pci/hda/hda_auto_parser.c @@ -580,6 +580,7 @@ const char *hda_get_autocfg_input_label(struct hda_codec *codec, has_multiple_pins = 1; if (has_multiple_pins && type == AUTO_PIN_MIC) has_multiple_pins &= check_mic_location_need(codec, cfg, input); + has_multiple_pins |= codec->force_pin_prefix; return hda_get_input_pin_label(codec, &cfg->inputs[input], cfg->inputs[input].pin, has_multiple_pins); diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 8fd745cb3f36..70bb365a08d2 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -1965,7 +1965,7 @@ static int vmaster_mute_mode_put(struct snd_kcontrol *kcontrol, return 1; } -static struct snd_kcontrol_new vmaster_mute_mode = { +static const struct snd_kcontrol_new vmaster_mute_mode = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Mute-LED Mode", .info = vmaster_mute_mode_info, @@ -2705,7 +2705,7 @@ static int spdif_share_sw_put(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new spdif_share_sw = { +static const struct snd_kcontrol_new spdif_share_sw = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "IEC958 Default PCM Playback Switch", .info = snd_ctl_boolean_mono_info, diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h index f17f25245e52..d6fb2d5d01a7 100644 --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -256,6 +256,7 @@ struct hda_codec { unsigned int dump_coef:1; /* dump processing coefs in codec proc file */ unsigned int power_save_node:1; /* advanced PM for each widget */ unsigned int auto_runtime_pm:1; /* enable automatic codec runtime pm */ + unsigned int force_pin_prefix:1; /* Add location prefix */ #ifdef CONFIG_PM unsigned long power_on_acct; unsigned long power_off_acct; diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c index e7c8f4f076d5..2842c82363c0 100644 --- a/sound/pci/hda/hda_generic.c +++ b/sound/pci/hda/hda_generic.c @@ -196,6 +196,9 @@ static void parse_user_hints(struct hda_codec *codec) val = snd_hda_get_bool_hint(codec, "hp_mic_detect"); if (val >= 0) spec->suppress_hp_mic_detect = !val; + val = snd_hda_get_bool_hint(codec, "vmaster"); + if (val >= 0) + spec->suppress_vmaster = !val; if (!snd_hda_get_int_hint(codec, "mixer_nid", &val)) spec->mixer_nid = val; @@ -1125,6 +1128,7 @@ static const char *get_line_out_pfx(struct hda_codec *codec, int ch, *index = 0; if (cfg->line_outs == 1 && !spec->multi_ios && + !codec->force_pin_prefix && !cfg->hp_outs && !cfg->speaker_outs) return spec->vmaster_mute.hook ? "PCM" : "Master"; @@ -1132,6 +1136,7 @@ static const char *get_line_out_pfx(struct hda_codec *codec, int ch, * use it master (or "PCM" if a vmaster hook is present) */ if (spec->multiout.num_dacs == 1 && !spec->mixer_nid && + !codec->force_pin_prefix && !spec->multiout.hp_out_nid[0] && !spec->multiout.extra_out_nid[0]) return spec->vmaster_mute.hook ? "PCM" : "Master"; @@ -5031,7 +5036,7 @@ int snd_hda_gen_build_controls(struct hda_codec *codec) } /* if we have no master control, let's create it */ - if (!spec->no_analog && + if (!spec->no_analog && !spec->suppress_vmaster && !snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) { err = snd_hda_add_vmaster(codec, "Master Playback Volume", spec->vmaster_tlv, slave_pfxs, @@ -5039,7 +5044,7 @@ int snd_hda_gen_build_controls(struct hda_codec *codec) if (err < 0) return err; } - if (!spec->no_analog && + if (!spec->no_analog && !spec->suppress_vmaster && !snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) { err = __snd_hda_add_vmaster(codec, "Master Playback Switch", NULL, slave_pfxs, diff --git a/sound/pci/hda/hda_generic.h b/sound/pci/hda/hda_generic.h index f66fc7e25e07..61772317de46 100644 --- a/sound/pci/hda/hda_generic.h +++ b/sound/pci/hda/hda_generic.h @@ -229,6 +229,7 @@ struct hda_gen_spec { unsigned int add_jack_modes:1; /* add i/o jack mode enum ctls */ unsigned int power_down_unused:1; /* power down unused widgets */ unsigned int dac_min_mute:1; /* minimal = mute for DACs */ + unsigned int suppress_vmaster:1; /* don't create vmaster kctls */ /* other internal flags */ unsigned int no_analog:1; /* digital I/O only */ diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index c8256a89375a..b786fbab029f 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -77,6 +77,7 @@ enum { POS_FIX_POSBUF, POS_FIX_VIACOMBO, POS_FIX_COMBO, + POS_FIX_SKL, }; /* Defines for ATI HD Audio support in SB450 south bridge */ @@ -148,7 +149,7 @@ module_param_array(model, charp, NULL, 0444); MODULE_PARM_DESC(model, "Use the given board model."); module_param_array(position_fix, int, NULL, 0444); MODULE_PARM_DESC(position_fix, "DMA pointer read method." - "(-1 = system default, 0 = auto, 1 = LPIB, 2 = POSBUF, 3 = VIACOMBO, 4 = COMBO)."); + "(-1 = system default, 0 = auto, 1 = LPIB, 2 = POSBUF, 3 = VIACOMBO, 4 = COMBO, 5 = SKL+)."); module_param_array(bdl_pos_adj, int, NULL, 0644); MODULE_PARM_DESC(bdl_pos_adj, "BDL position adjustment offset."); module_param_array(probe_mask, int, NULL, 0444); @@ -369,8 +370,10 @@ enum { #define IS_KBL_LP(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x9d71) #define IS_KBL_H(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0xa2f0) #define IS_BXT(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x5a98) +#define IS_GLK(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x3198) #define IS_SKL_PLUS(pci) (IS_SKL(pci) || IS_SKL_LP(pci) || IS_BXT(pci)) || \ - IS_KBL(pci) || IS_KBL_LP(pci) || IS_KBL_H(pci) + IS_KBL(pci) || IS_KBL_LP(pci) || IS_KBL_H(pci) || \ + IS_GLK(pci) static char *driver_short_names[] = { [AZX_DRIVER_ICH] = "HDA Intel", @@ -534,9 +537,101 @@ static void bxt_reduce_dma_latency(struct azx *chip) { u32 val; - val = azx_readl(chip, SKL_EM4L); + val = azx_readl(chip, VS_EM4L); val &= (0x3 << 20); - azx_writel(chip, SKL_EM4L, val); + azx_writel(chip, VS_EM4L, val); +} + +/* + * ML_LCAP bits: + * bit 0: 6 MHz Supported + * bit 1: 12 MHz Supported + * bit 2: 24 MHz Supported + * bit 3: 48 MHz Supported + * bit 4: 96 MHz Supported + * bit 5: 192 MHz Supported + */ +static int intel_get_lctl_scf(struct azx *chip) +{ + struct hdac_bus *bus = azx_bus(chip); + static int preferred_bits[] = { 2, 3, 1, 4, 5 }; + u32 val, t; + int i; + + val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCAP); + + for (i = 0; i < ARRAY_SIZE(preferred_bits); i++) { + t = preferred_bits[i]; + if (val & (1 << t)) + return t; + } + + dev_warn(chip->card->dev, "set audio clock frequency to 6MHz"); + return 0; +} + +static int intel_ml_lctl_set_power(struct azx *chip, int state) +{ + struct hdac_bus *bus = azx_bus(chip); + u32 val; + int timeout; + + /* + * the codecs are sharing the first link setting by default + * If other links are enabled for stream, they need similar fix + */ + val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL); + val &= ~AZX_MLCTL_SPA; + val |= state << AZX_MLCTL_SPA_SHIFT; + writel(val, bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL); + /* wait for CPA */ + timeout = 50; + while (timeout) { + if (((readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL)) & + AZX_MLCTL_CPA) == (state << AZX_MLCTL_CPA_SHIFT)) + return 0; + timeout--; + udelay(10); + } + + return -1; +} + +static void intel_init_lctl(struct azx *chip) +{ + struct hdac_bus *bus = azx_bus(chip); + u32 val; + int ret; + + /* 0. check lctl register value is correct or not */ + val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL); + /* if SCF is already set, let's use it */ + if ((val & ML_LCTL_SCF_MASK) != 0) + return; + + /* + * Before operating on SPA, CPA must match SPA. + * Any deviation may result in undefined behavior. + */ + if (((val & AZX_MLCTL_SPA) >> AZX_MLCTL_SPA_SHIFT) != + ((val & AZX_MLCTL_CPA) >> AZX_MLCTL_CPA_SHIFT)) + return; + + /* 1. turn link down: set SPA to 0 and wait CPA to 0 */ + ret = intel_ml_lctl_set_power(chip, 0); + udelay(100); + if (ret) + goto set_spa; + + /* 2. update SCF to select a properly audio clock*/ + val &= ~ML_LCTL_SCF_MASK; + val |= intel_get_lctl_scf(chip); + writel(val, bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL); + +set_spa: + /* 4. turn link up: set SPA to 1 and wait CPA to 1 */ + intel_ml_lctl_set_power(chip, 1); + udelay(100); } static void hda_intel_init_chip(struct azx *chip, bool full_reset) @@ -564,6 +659,9 @@ static void hda_intel_init_chip(struct azx *chip, bool full_reset) /* reduce dma latency to avoid noise */ if (IS_BXT(pci)) bxt_reduce_dma_latency(chip); + + if (bus->mlcap != NULL) + intel_init_lctl(chip); } /* calculate runtime delay from LPIB */ @@ -815,6 +913,31 @@ static unsigned int azx_via_get_position(struct azx *chip, return bound_pos + mod_dma_pos; } +static unsigned int azx_skl_get_dpib_pos(struct azx *chip, + struct azx_dev *azx_dev) +{ + return _snd_hdac_chip_readl(azx_bus(chip), + AZX_REG_VS_SDXDPIB_XBASE + + (AZX_REG_VS_SDXDPIB_XINTERVAL * + azx_dev->core.index)); +} + +/* get the current DMA position with correction on SKL+ chips */ +static unsigned int azx_get_pos_skl(struct azx *chip, struct azx_dev *azx_dev) +{ + /* DPIB register gives a more accurate position for playback */ + if (azx_dev->core.substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return azx_skl_get_dpib_pos(chip, azx_dev); + + /* For capture, we need to read posbuf, but it requires a delay + * for the possible boundary overlap; the read of DPIB fetches the + * actual posbuf + */ + udelay(20); + azx_skl_get_dpib_pos(chip, azx_dev); + return azx_get_pos_posbuf(chip, azx_dev); +} + #ifdef CONFIG_PM static DEFINE_MUTEX(card_list_lock); static LIST_HEAD(card_list); @@ -1351,6 +1474,7 @@ static int check_position_fix(struct azx *chip, int fix) case POS_FIX_POSBUF: case POS_FIX_VIACOMBO: case POS_FIX_COMBO: + case POS_FIX_SKL: return fix; } @@ -1371,6 +1495,10 @@ static int check_position_fix(struct azx *chip, int fix) dev_dbg(chip->card->dev, "Using LPIB position fix\n"); return POS_FIX_LPIB; } + if (IS_SKL_PLUS(chip->pci)) { + dev_dbg(chip->card->dev, "Using SKL position fix\n"); + return POS_FIX_SKL; + } return POS_FIX_AUTO; } @@ -1382,6 +1510,7 @@ static void assign_position_fix(struct azx *chip, int fix) [POS_FIX_POSBUF] = azx_get_pos_posbuf, [POS_FIX_VIACOMBO] = azx_via_get_position, [POS_FIX_COMBO] = azx_get_pos_lpib, + [POS_FIX_SKL] = azx_get_pos_skl, }; chip->get_position[0] = chip->get_position[1] = callbacks[fix]; @@ -1390,7 +1519,7 @@ static void assign_position_fix(struct azx *chip, int fix) if (fix == POS_FIX_COMBO) chip->get_position[1] = NULL; - if (fix == POS_FIX_POSBUF && + if ((fix == POS_FIX_POSBUF || fix == POS_FIX_SKL) && (chip->driver_caps & AZX_DCAPS_COUNT_LPIB_DELAY)) { chip->get_delay[0] = chip->get_delay[1] = azx_get_delay_from_lpib; diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 07a9deb17477..a148176c16a9 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -857,7 +857,7 @@ static int chipio_write_address(struct hda_codec *codec, chip_addx >> 16); } - spec->curr_chip_addx = (res < 0) ? ~0UL : chip_addx; + spec->curr_chip_addx = (res < 0) ? ~0U : chip_addx; return res; } @@ -882,7 +882,7 @@ static int chipio_write_data(struct hda_codec *codec, unsigned int data) /*If no error encountered, automatically increment the address as per chip behaviour*/ spec->curr_chip_addx = (res != -EIO) ? - (spec->curr_chip_addx + 4) : ~0UL; + (spec->curr_chip_addx + 4) : ~0U; return res; } @@ -933,7 +933,7 @@ static int chipio_read_data(struct hda_codec *codec, unsigned int *data) /*If no error encountered, automatically increment the address as per chip behaviour*/ spec->curr_chip_addx = (res != -EIO) ? - (spec->curr_chip_addx + 4) : ~0UL; + (spec->curr_chip_addx + 4) : ~0U; return res; } @@ -1168,7 +1168,7 @@ static int dspio_write_multiple(struct hda_codec *codec, int status = 0; unsigned int count; - if ((buffer == NULL)) + if (buffer == NULL) return -EINVAL; count = 0; @@ -1210,7 +1210,7 @@ static int dspio_read_multiple(struct hda_codec *codec, unsigned int *buffer, unsigned int skip_count; unsigned int dummy; - if ((buffer == NULL)) + if (buffer == NULL) return -1; count = 0; diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index 69266b8ea2ad..e8253737c47a 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -52,6 +52,12 @@ struct conexant_spec { bool dc_enable; unsigned int dc_input_bias; /* offset into olpc_xo_dc_bias */ struct nid_path *dc_mode_path; + + int mute_led_polarity; + unsigned int gpio_led; + unsigned int gpio_mute_led_mask; + unsigned int gpio_mic_led_mask; + }; @@ -264,6 +270,7 @@ enum { CXT_FIXUP_HP_DOCK, CXT_FIXUP_HP_SPECTRE, CXT_FIXUP_HP_GATE_MIC, + CXT_FIXUP_MUTE_LED_GPIO, }; /* for hda_fixup_thinkpad_acpi() */ @@ -646,6 +653,74 @@ static void cxt_fixup_hp_gate_mic_jack(struct hda_codec *codec, snd_hda_jack_set_gating_jack(codec, 0x19, 0x16); } +/* update LED status via GPIO */ +static void cxt_update_gpio_led(struct hda_codec *codec, unsigned int mask, + bool enabled) +{ + struct conexant_spec *spec = codec->spec; + unsigned int oldval = spec->gpio_led; + + if (spec->mute_led_polarity) + enabled = !enabled; + + if (enabled) + spec->gpio_led &= ~mask; + else + spec->gpio_led |= mask; + if (spec->gpio_led != oldval) + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, + spec->gpio_led); +} + +/* turn on/off mute LED via GPIO per vmaster hook */ +static void cxt_fixup_gpio_mute_hook(void *private_data, int enabled) +{ + struct hda_codec *codec = private_data; + struct conexant_spec *spec = codec->spec; + + cxt_update_gpio_led(codec, spec->gpio_mute_led_mask, enabled); +} + +/* turn on/off mic-mute LED via GPIO per capture hook */ +static void cxt_fixup_gpio_mic_mute_hook(struct hda_codec *codec, + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct conexant_spec *spec = codec->spec; + + if (ucontrol) + cxt_update_gpio_led(codec, spec->gpio_mic_led_mask, + ucontrol->value.integer.value[0] || + ucontrol->value.integer.value[1]); +} + + +static void cxt_fixup_mute_led_gpio(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct conexant_spec *spec = codec->spec; + static const struct hda_verb gpio_init[] = { + { 0x01, AC_VERB_SET_GPIO_MASK, 0x03 }, + { 0x01, AC_VERB_SET_GPIO_DIRECTION, 0x03 }, + {} + }; + codec_info(codec, "action: %d gpio_led: %d\n", action, spec->gpio_led); + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->gen.vmaster_mute.hook = cxt_fixup_gpio_mute_hook; + spec->gen.cap_sync_hook = cxt_fixup_gpio_mic_mute_hook; + spec->gpio_led = 0; + spec->mute_led_polarity = 0; + spec->gpio_mute_led_mask = 0x01; + spec->gpio_mic_led_mask = 0x02; + } + snd_hda_add_verbs(codec, gpio_init); + if (spec->gpio_led) + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, + spec->gpio_led); +} + + /* ThinkPad X200 & co with cxt5051 */ static const struct hda_pintbl cxt_pincfg_lenovo_x200[] = { { 0x16, 0x042140ff }, /* HP (seq# overridden) */ @@ -799,6 +874,10 @@ static const struct hda_fixup cxt_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = cxt_fixup_hp_gate_mic_jack, }, + [CXT_FIXUP_MUTE_LED_GPIO] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_mute_led_gpio, + }, }; static const struct snd_pci_quirk cxt5045_fixups[] = { @@ -851,6 +930,7 @@ static const struct snd_pci_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x103c, 0x8079, "HP EliteBook 840 G3", CXT_FIXUP_HP_DOCK), SND_PCI_QUIRK(0x103c, 0x8174, "HP Spectre x360", CXT_FIXUP_HP_SPECTRE), SND_PCI_QUIRK(0x103c, 0x8115, "HP Z1 Gen3", CXT_FIXUP_HP_GATE_MIC), + SND_PCI_QUIRK(0x103c, 0x814f, "HP ZBook 15u G3", CXT_FIXUP_MUTE_LED_GPIO), SND_PCI_QUIRK(0x1043, 0x138d, "Asus", CXT_FIXUP_HEADPHONE_MIC_PIN), SND_PCI_QUIRK(0x152d, 0x0833, "OLPC XO-1.5", CXT_FIXUP_OLPC_XO), SND_PCI_QUIRK(0x17aa, 0x20f2, "Lenovo T400", CXT_PINCFG_LENOVO_TP410), @@ -882,6 +962,7 @@ static const struct hda_model_fixup cxt5066_fixup_models[] = { { .id = CXT_FIXUP_OLPC_XO, .name = "olpc-xo" }, { .id = CXT_FIXUP_MUTE_LED_EAPD, .name = "mute-led-eapd" }, { .id = CXT_FIXUP_HP_DOCK, .name = "hp-dock" }, + { .id = CXT_FIXUP_MUTE_LED_GPIO, .name = "mute-led-gpio" }, {} }; diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index 1461ef8eb749..90e4ff87445e 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -177,6 +177,7 @@ struct hdmi_spec { bool i915_bound; /* was i915 bound in this driver? */ struct hdac_chmap chmap; + hda_nid_t vendor_nid; }; #ifdef CONFIG_SND_HDA_I915 @@ -383,7 +384,7 @@ static int hdmi_eld_ctl_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new eld_bytes_ctl = { +static const struct snd_kcontrol_new eld_bytes_ctl = { .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "ELD", @@ -2372,6 +2373,7 @@ static void intel_haswell_fixup_connect_list(struct hda_codec *codec, } #define INTEL_VENDOR_NID 0x08 +#define INTEL_GLK_VENDOR_NID 0x0B #define INTEL_GET_VENDOR_VERB 0xf81 #define INTEL_SET_VENDOR_VERB 0x781 #define INTEL_EN_DP12 0x02 /* enable DP 1.2 features */ @@ -2381,14 +2383,15 @@ static void intel_haswell_enable_all_pins(struct hda_codec *codec, bool update_tree) { unsigned int vendor_param; + struct hdmi_spec *spec = codec->spec; - vendor_param = snd_hda_codec_read(codec, INTEL_VENDOR_NID, 0, + vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0, INTEL_GET_VENDOR_VERB, 0); if (vendor_param == -1 || vendor_param & INTEL_EN_ALL_PIN_CVTS) return; vendor_param |= INTEL_EN_ALL_PIN_CVTS; - vendor_param = snd_hda_codec_read(codec, INTEL_VENDOR_NID, 0, + vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0, INTEL_SET_VENDOR_VERB, vendor_param); if (vendor_param == -1) return; @@ -2400,8 +2403,9 @@ static void intel_haswell_enable_all_pins(struct hda_codec *codec, static void intel_haswell_fixup_enable_dp12(struct hda_codec *codec) { unsigned int vendor_param; + struct hdmi_spec *spec = codec->spec; - vendor_param = snd_hda_codec_read(codec, INTEL_VENDOR_NID, 0, + vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0, INTEL_GET_VENDOR_VERB, 0); if (vendor_param == -1 || vendor_param & INTEL_EN_DP12) return; @@ -2409,7 +2413,7 @@ static void intel_haswell_fixup_enable_dp12(struct hda_codec *codec) /* enable DP1.2 mode */ vendor_param |= INTEL_EN_DP12; snd_hdac_regmap_add_vendor_verb(&codec->core, INTEL_SET_VENDOR_VERB); - snd_hda_codec_write_cache(codec, INTEL_VENDOR_NID, 0, + snd_hda_codec_write_cache(codec, spec->vendor_nid, 0, INTEL_SET_VENDOR_VERB, vendor_param); } @@ -2503,7 +2507,7 @@ static void i915_pin_cvt_fixup(struct hda_codec *codec, } /* Intel Haswell and onwards; audio component with eld notifier */ -static int patch_i915_hsw_hdmi(struct hda_codec *codec) +static int intel_hsw_common_init(struct hda_codec *codec, hda_nid_t vendor_nid) { struct hdmi_spec *spec; int err; @@ -2520,6 +2524,7 @@ static int patch_i915_hsw_hdmi(struct hda_codec *codec) spec = codec->spec; codec->dp_mst = true; spec->dyn_pcm_assign = true; + spec->vendor_nid = vendor_nid; intel_haswell_enable_all_pins(codec, true); intel_haswell_fixup_enable_dp12(codec); @@ -2548,6 +2553,16 @@ static int patch_i915_hsw_hdmi(struct hda_codec *codec) return 0; } +static int patch_i915_hsw_hdmi(struct hda_codec *codec) +{ + return intel_hsw_common_init(codec, INTEL_VENDOR_NID); +} + +static int patch_i915_glk_hdmi(struct hda_codec *codec) +{ + return intel_hsw_common_init(codec, INTEL_GLK_VENDOR_NID); +} + /* Intel Baytrail and Braswell; with eld notifier */ static int patch_i915_byt_hdmi(struct hda_codec *codec) { @@ -3800,7 +3815,7 @@ HDA_CODEC_ENTRY(0x80862808, "Broadwell HDMI", patch_i915_hsw_hdmi), HDA_CODEC_ENTRY(0x80862809, "Skylake HDMI", patch_i915_hsw_hdmi), HDA_CODEC_ENTRY(0x8086280a, "Broxton HDMI", patch_i915_hsw_hdmi), HDA_CODEC_ENTRY(0x8086280b, "Kabylake HDMI", patch_i915_hsw_hdmi), -HDA_CODEC_ENTRY(0x8086280d, "Geminilake HDMI", patch_i915_hsw_hdmi), +HDA_CODEC_ENTRY(0x8086280d, "Geminilake HDMI", patch_i915_glk_hdmi), HDA_CODEC_ENTRY(0x80862880, "CedarTrail HDMI", patch_generic_hdmi), HDA_CODEC_ENTRY(0x80862882, "Valleyview2 HDMI", patch_i915_byt_hdmi), HDA_CODEC_ENTRY(0x80862883, "Braswell HDMI", patch_i915_byt_hdmi), diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 299835d1fbaa..58df440013c5 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -1800,6 +1800,7 @@ enum { ALC882_FIXUP_NO_PRIMARY_HP, ALC887_FIXUP_ASUS_BASS, ALC887_FIXUP_BASS_CHMAP, + ALC1220_FIXUP_GB_DUAL_CODECS, }; static void alc889_fixup_coef(struct hda_codec *codec, @@ -1962,6 +1963,61 @@ static void alc882_fixup_no_primary_hp(struct hda_codec *codec, static void alc_fixup_bass_chmap(struct hda_codec *codec, const struct hda_fixup *fix, int action); +/* For dual-codec configuration, we need to disable some features to avoid + * conflicts of kctls and PCM streams + */ +static void alc_fixup_dual_codecs(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + /* disable vmaster */ + spec->gen.suppress_vmaster = 1; + /* auto-mute and auto-mic switch don't work with multiple codecs */ + spec->gen.suppress_auto_mute = 1; + spec->gen.suppress_auto_mic = 1; + /* disable aamix as well */ + spec->gen.mixer_nid = 0; + /* add location prefix to avoid conflicts */ + codec->force_pin_prefix = 1; +} + +static void rename_ctl(struct hda_codec *codec, const char *oldname, + const char *newname) +{ + struct snd_kcontrol *kctl; + + kctl = snd_hda_find_mixer_ctl(codec, oldname); + if (kctl) + strcpy(kctl->id.name, newname); +} + +static void alc1220_fixup_gb_dual_codecs(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + alc_fixup_dual_codecs(codec, fix, action); + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + /* override card longname to provide a unique UCM profile */ + strcpy(codec->card->longname, "HDAudio-Gigabyte-ALC1220DualCodecs"); + break; + case HDA_FIXUP_ACT_BUILD: + /* rename Capture controls depending on the codec */ + rename_ctl(codec, "Capture Volume", + codec->addr == 0 ? + "Rear-Panel Capture Volume" : + "Front-Panel Capture Volume"); + rename_ctl(codec, "Capture Switch", + codec->addr == 0 ? + "Rear-Panel Capture Switch" : + "Front-Panel Capture Switch"); + break; + } +} + static const struct hda_fixup alc882_fixups[] = { [ALC882_FIXUP_ABIT_AW9D_MAX] = { .type = HDA_FIXUP_PINS, @@ -2198,6 +2254,10 @@ static const struct hda_fixup alc882_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_bass_chmap, }, + [ALC1220_FIXUP_GB_DUAL_CODECS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc1220_fixup_gb_dual_codecs, + }, }; static const struct snd_pci_quirk alc882_fixup_tbl[] = { @@ -2267,6 +2327,7 @@ static const struct snd_pci_quirk alc882_fixup_tbl[] = { SND_PCI_QUIRK(0x1462, 0x7350, "MSI-7350", ALC889_FIXUP_CD), SND_PCI_QUIRK_VENDOR(0x1462, "MSI", ALC882_FIXUP_GPIO3), SND_PCI_QUIRK(0x1458, 0xa002, "Gigabyte EP45-DS3/Z87X-UD3H", ALC889_FIXUP_FRONT_HP_NO_PRESENCE), + SND_PCI_QUIRK(0x1458, 0xa0b8, "Gigabyte AZ370-Gaming", ALC1220_FIXUP_GB_DUAL_CODECS), SND_PCI_QUIRK(0x147b, 0x107a, "Abit AW9D-MAX", ALC882_FIXUP_ABIT_AW9D_MAX), SND_PCI_QUIRK_VENDOR(0x1558, "Clevo laptop", ALC882_FIXUP_EAPD), SND_PCI_QUIRK(0x161f, 0x2054, "Medion laptop", ALC883_FIXUP_EAPD), @@ -4663,7 +4724,6 @@ static void alc282_fixup_asus_tx300(struct hda_codec *codec, { 0x1b, 0x21114000 }, /* dock speaker pin */ {} }; - struct snd_kcontrol *kctl; switch (action) { case HDA_FIXUP_ACT_PRE_PROBE: @@ -4678,12 +4738,10 @@ static void alc282_fixup_asus_tx300(struct hda_codec *codec, /* this is a bit tricky; give more sane names for the main * (tablet) speaker and the dock speaker, respectively */ - kctl = snd_hda_find_mixer_ctl(codec, "Speaker Playback Switch"); - if (kctl) - strcpy(kctl->id.name, "Dock Speaker Playback Switch"); - kctl = snd_hda_find_mixer_ctl(codec, "Bass Speaker Playback Switch"); - if (kctl) - strcpy(kctl->id.name, "Speaker Playback Switch"); + rename_ctl(codec, "Speaker Playback Switch", + "Dock Speaker Playback Switch"); + rename_ctl(codec, "Bass Speaker Playback Switch", + "Speaker Playback Switch"); break; } } @@ -4766,6 +4824,30 @@ static void alc280_fixup_hp_9480m(struct hda_codec *codec, } } +static void alc233_alc662_fixup_lenovo_dual_codecs(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + alc_fixup_dual_codecs(codec, fix, action); + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + /* override card longname to provide a unique UCM profile */ + strcpy(codec->card->longname, "HDAudio-Lenovo-DualCodecs"); + break; + case HDA_FIXUP_ACT_BUILD: + /* rename Capture controls depending on the codec */ + rename_ctl(codec, "Capture Volume", + codec->addr == 0 ? + "Rear-Panel Capture Volume" : + "Front-Panel Capture Volume"); + rename_ctl(codec, "Capture Switch", + codec->addr == 0 ? + "Rear-Panel Capture Switch" : + "Front-Panel Capture Switch"); + break; + } +} + /* for hda_fixup_thinkpad_acpi() */ #include "thinkpad_helper.c" @@ -4833,6 +4915,8 @@ enum { ALC290_FIXUP_SUBWOOFER_HSJACK, ALC269_FIXUP_THINKPAD_ACPI, ALC269_FIXUP_DMIC_THINKPAD_ACPI, + ALC255_FIXUP_ACER_MIC_NO_PRESENCE, + ALC255_FIXUP_ASUS_MIC_NO_PRESENCE, ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, ALC255_FIXUP_DELL2_MIC_NO_PRESENCE, ALC255_FIXUP_HEADSET_MODE, @@ -4872,6 +4956,12 @@ enum { ALC256_FIXUP_DELL_INSPIRON_7559_SUBWOOFER, ALC269_FIXUP_ATIV_BOOK_8, ALC221_FIXUP_HP_MIC_NO_PRESENCE, + ALC256_FIXUP_ASUS_HEADSET_MODE, + ALC256_FIXUP_ASUS_MIC, + ALC256_FIXUP_ASUS_AIO_GPIO2, + ALC233_FIXUP_ASUS_MIC_NO_PRESENCE, + ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE, + ALC233_FIXUP_LENOVO_MULTI_CODECS, }; static const struct hda_fixup alc269_fixups[] = { @@ -5289,6 +5379,24 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_THINKPAD_ACPI, }, + [ALC255_FIXUP_ACER_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC255_FIXUP_HEADSET_MODE + }, + [ALC255_FIXUP_ASUS_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC255_FIXUP_HEADSET_MODE + }, [ALC255_FIXUP_DELL1_MIC_NO_PRESENCE] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { @@ -5579,6 +5687,54 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_HEADSET_MODE }, + [ALC256_FIXUP_ASUS_HEADSET_MODE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode, + }, + [ALC256_FIXUP_ASUS_MIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x13, 0x90a60160 }, /* use as internal mic */ + { 0x19, 0x04a11120 }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC256_FIXUP_ASUS_HEADSET_MODE + }, + [ALC256_FIXUP_ASUS_AIO_GPIO2] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Set up GPIO2 for the speaker amp */ + { 0x01, AC_VERB_SET_GPIO_MASK, 0x04 }, + { 0x01, AC_VERB_SET_GPIO_DIRECTION, 0x04 }, + { 0x01, AC_VERB_SET_GPIO_DATA, 0x04 }, + {} + }, + }, + [ALC233_FIXUP_ASUS_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MIC + }, + [ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Enables internal speaker */ + {0x20, AC_VERB_SET_COEF_INDEX, 0x40}, + {0x20, AC_VERB_SET_PROC_COEF, 0x8800}, + {} + }, + .chained = true, + .chain_id = ALC233_FIXUP_ASUS_MIC_NO_PRESENCE + }, + [ALC233_FIXUP_LENOVO_MULTI_CODECS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc233_alc662_fixup_lenovo_dual_codecs, + }, }; static const struct snd_pci_quirk alc269_fixup_tbl[] = { @@ -5692,15 +5848,27 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8256, "HP", ALC221_FIXUP_HP_FRONT_MIC), SND_PCI_QUIRK(0x103c, 0x82bf, "HP", ALC221_FIXUP_HP_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x82c0, "HP", ALC221_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x103e, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), SND_PCI_QUIRK(0x1043, 0x103f, "ASUS TX300", ALC282_FIXUP_ASUS_TX300), SND_PCI_QUIRK(0x1043, 0x106d, "Asus K53BE", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x10c0, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), SND_PCI_QUIRK(0x1043, 0x115d, "Asus 1015E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x12f0, "ASUS X541UV", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x12e0, "ASUS X541SA", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x13b0, "ASUS Z550SA", ALC256_FIXUP_ASUS_MIC), SND_PCI_QUIRK(0x1043, 0x1427, "Asus Zenbook UX31E", ALC269VB_FIXUP_ASUS_ZENBOOK), SND_PCI_QUIRK(0x1043, 0x1517, "Asus Zenbook UX31A", ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A), SND_PCI_QUIRK(0x1043, 0x16e3, "ASUS UX50", ALC269_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x1043, 0x1a13, "Asus G73Jw", ALC269_FIXUP_ASUS_G73JW), SND_PCI_QUIRK(0x1043, 0x1b13, "Asus U41SV", ALC269_FIXUP_INV_DMIC), SND_PCI_QUIRK(0x1043, 0x1c23, "Asus X55U", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x1bbd, "ASUS Z550MA", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x10d0, "ASUS X540LA/X540LJ", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x11c0, "ASUS X556UR", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1290, "ASUS X441SA", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x12a0, "ASUS X441UV", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1ccd, "ASUS X555UB", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x3030, "ASUS ZN270IE", ALC256_FIXUP_ASUS_AIO_GPIO2), SND_PCI_QUIRK(0x1043, 0x831a, "ASUS P901", ALC269_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x1043, 0x834a, "ASUS S101", ALC269_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x1043, 0x8398, "ASUS P1005", ALC269_FIXUP_STEREO_DMIC), @@ -5721,6 +5889,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x144d, 0xc740, "Samsung Ativ book 8 (NP870Z5G)", ALC269_FIXUP_ATIV_BOOK_8), SND_PCI_QUIRK(0x1458, 0xfa53, "Gigabyte BXBT-2807", ALC283_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1462, 0xb120, "MSI Cubi MS-B120", ALC283_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x17aa, 0x1036, "Lenovo P520", ALC233_FIXUP_LENOVO_MULTI_CODECS), SND_PCI_QUIRK(0x17aa, 0x20f2, "Thinkpad SL410/510", ALC269_FIXUP_SKU_IGNORE), SND_PCI_QUIRK(0x17aa, 0x215e, "Thinkpad L512", ALC269_FIXUP_SKU_IGNORE), SND_PCI_QUIRK(0x17aa, 0x21b8, "Thinkpad Edge 14", ALC269_FIXUP_SKU_IGNORE), @@ -5875,6 +6044,18 @@ static const struct hda_model_fixup alc269_fixup_models[] = { {0x21, 0x03211020} static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = { + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1025, "Acer", ALC255_FIXUP_ACER_MIC_NO_PRESENCE, + {0x12, 0x90a601c0}, + {0x14, 0x90171120}, + {0x21, 0x02211030}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1043, "ASUS", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE, + {0x14, 0x90170110}, + {0x1b, 0x90a70130}, + {0x21, 0x03211020}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1043, "ASUS", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE, + {0x1a, 0x90a70130}, + {0x1b, 0x90170110}, + {0x21, 0x03211020}), SND_HDA_PIN_QUIRK(0x10ec0225, 0x1028, "Dell", ALC225_FIXUP_DELL1_MIC_NO_PRESENCE, ALC225_STANDARD_PINS, {0x12, 0xb7a60130}, @@ -5995,6 +6176,14 @@ static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = { {0x21, 0x02211020}), SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, ALC256_STANDARD_PINS), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC, + {0x14, 0x90170110}, + {0x1b, 0x90a70130}, + {0x21, 0x04211020}), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC, + {0x14, 0x90170110}, + {0x1b, 0x90a70130}, + {0x21, 0x03211020}), SND_HDA_PIN_QUIRK(0x10ec0280, 0x103c, "HP", ALC280_FIXUP_HP_GPIO4, {0x12, 0x90a60130}, {0x14, 0x90170110}, @@ -6714,6 +6903,7 @@ enum { ALC668_FIXUP_DELL_DISABLE_AAMIX, ALC668_FIXUP_DELL_XPS13, ALC662_FIXUP_ASUS_Nx50, + ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE, ALC668_FIXUP_ASUS_Nx51, ALC891_FIXUP_HEADSET_MODE, ALC891_FIXUP_DELL_MIC_NO_PRESENCE, @@ -6721,6 +6911,7 @@ enum { ALC892_FIXUP_ASROCK_MOBO, ALC662_FIXUP_USI_FUNC, ALC662_FIXUP_USI_HEADSET_MODE, + ALC662_FIXUP_LENOVO_MULTI_CODECS, }; static const struct hda_fixup alc662_fixups[] = { @@ -6967,14 +7158,21 @@ static const struct hda_fixup alc662_fixups[] = { .chained = true, .chain_id = ALC662_FIXUP_BASS_1A }, + [ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode_alc668, + .chain_id = ALC662_FIXUP_BASS_CHMAP + }, [ALC668_FIXUP_ASUS_Nx51] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { - {0x1a, 0x90170151}, /* bass speaker */ + { 0x19, 0x03a1913d }, /* use as headphone mic, without its own jack detect */ + { 0x1a, 0x90170151 }, /* bass speaker */ + { 0x1b, 0x03a1113c }, /* use as headset mic, without its own jack detect */ {} }, .chained = true, - .chain_id = ALC662_FIXUP_BASS_CHMAP, + .chain_id = ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE, }, [ALC891_FIXUP_HEADSET_MODE] = { .type = HDA_FIXUP_FUNC, @@ -7019,6 +7217,10 @@ static const struct hda_fixup alc662_fixups[] = { .chained = true, .chain_id = ALC662_FIXUP_USI_FUNC }, + [ALC662_FIXUP_LENOVO_MULTI_CODECS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc233_alc662_fixup_lenovo_dual_codecs, + }, }; static const struct snd_pci_quirk alc662_fixup_tbl[] = { @@ -7056,6 +7258,7 @@ static const struct snd_pci_quirk alc662_fixup_tbl[] = { SND_PCI_QUIRK(0x105b, 0x0cd6, "Foxconn", ALC662_FIXUP_ASUS_MODE2), SND_PCI_QUIRK(0x144d, 0xc051, "Samsung R720", ALC662_FIXUP_IDEAPAD), SND_PCI_QUIRK(0x14cd, 0x5003, "USI", ALC662_FIXUP_USI_HEADSET_MODE), + SND_PCI_QUIRK(0x17aa, 0x1036, "Lenovo P520", ALC662_FIXUP_LENOVO_MULTI_CODECS), SND_PCI_QUIRK(0x17aa, 0x38af, "Lenovo Ideapad Y550P", ALC662_FIXUP_IDEAPAD), SND_PCI_QUIRK(0x17aa, 0x3a0d, "Lenovo Ideapad Y550", ALC662_FIXUP_IDEAPAD), SND_PCI_QUIRK(0x1849, 0x5892, "ASRock B150M", ALC892_FIXUP_ASROCK_MOBO), diff --git a/sound/pci/ice1712/delta.c b/sound/pci/ice1712/delta.c index 3bfdc78cbc5f..da5f37b7fdd0 100644 --- a/sound/pci/ice1712/delta.c +++ b/sound/pci/ice1712/delta.c @@ -432,7 +432,7 @@ static int snd_ice1712_delta1010lt_wordclock_status_get(struct snd_kcontrol *kco return 0; } -static struct snd_kcontrol_new snd_ice1712_delta1010lt_wordclock_status = +static const struct snd_kcontrol_new snd_ice1712_delta1010lt_wordclock_status = { .access = (SNDRV_CTL_ELEM_ACCESS_READ), .iface = SNDRV_CTL_ELEM_IFACE_MIXER, diff --git a/sound/pci/ice1712/ews.c b/sound/pci/ice1712/ews.c index 5cb587cf360e..ec07136fc288 100644 --- a/sound/pci/ice1712/ews.c +++ b/sound/pci/ice1712/ews.c @@ -719,7 +719,7 @@ static int snd_ice1712_ews88mt_input_sense_put(struct snd_kcontrol *kcontrol, st return ndata != data; } -static struct snd_kcontrol_new snd_ice1712_ews88mt_input_sense = { +static const struct snd_kcontrol_new snd_ice1712_ews88mt_input_sense = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Input Sensitivity Switch", .info = snd_ice1712_ewx_io_sense_info, @@ -728,7 +728,7 @@ static struct snd_kcontrol_new snd_ice1712_ews88mt_input_sense = { .count = 8, }; -static struct snd_kcontrol_new snd_ice1712_ews88mt_output_sense = { +static const struct snd_kcontrol_new snd_ice1712_ews88mt_output_sense = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Output Sensitivity Switch", .info = snd_ice1712_ewx_io_sense_info, diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c index b4aa4c1370a8..1d8612cabb9e 100644 --- a/sound/pci/ice1712/ice1712.c +++ b/sound/pci/ice1712/ice1712.c @@ -279,7 +279,7 @@ static int snd_ice1712_digmix_route_ac97_put(struct snd_kcontrol *kcontrol, stru return val != nval; } -static struct snd_kcontrol_new snd_ice1712_mixer_digmix_route_ac97 = { +static const struct snd_kcontrol_new snd_ice1712_mixer_digmix_route_ac97 = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Digital Mixer To AC97", .info = snd_ice1712_digmix_route_ac97_info, @@ -1410,7 +1410,7 @@ static struct snd_kcontrol_new snd_ice1712_multi_capture_analog_switch = { .private_value = 10, }; -static struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_switch = { +static const struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = SNDRV_CTL_NAME_IEC958("Multi ", CAPTURE, SWITCH), .info = snd_ice1712_pro_mixer_switch_info, @@ -1432,7 +1432,7 @@ static struct snd_kcontrol_new snd_ice1712_multi_capture_analog_volume = { .tlv = { .p = db_scale_playback } }; -static struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_volume = { +static const struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_volume = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = SNDRV_CTL_NAME_IEC958("Multi ", CAPTURE, VOLUME), .info = snd_ice1712_pro_mixer_volume_info, @@ -1630,7 +1630,7 @@ static int snd_ice1712_eeprom_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ice1712_eeprom = { +static const struct snd_kcontrol_new snd_ice1712_eeprom = { .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = "ICE1712 EEPROM", .access = SNDRV_CTL_ELEM_ACCESS_READ, @@ -1666,7 +1666,7 @@ static int snd_ice1712_spdif_default_put(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ice1712_spdif_default = +static const struct snd_kcontrol_new snd_ice1712_spdif_default = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), @@ -1717,7 +1717,7 @@ static int snd_ice1712_spdif_maskp_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ice1712_spdif_maskc = +static const struct snd_kcontrol_new snd_ice1712_spdif_maskc = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1726,7 +1726,7 @@ static struct snd_kcontrol_new snd_ice1712_spdif_maskc = .get = snd_ice1712_spdif_maskc_get, }; -static struct snd_kcontrol_new snd_ice1712_spdif_maskp = +static const struct snd_kcontrol_new snd_ice1712_spdif_maskp = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1753,7 +1753,7 @@ static int snd_ice1712_spdif_stream_put(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ice1712_spdif_stream = +static const struct snd_kcontrol_new snd_ice1712_spdif_stream = { .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE), @@ -1878,7 +1878,7 @@ static int snd_ice1712_pro_internal_clock_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ice1712_pro_internal_clock = { +static const struct snd_kcontrol_new snd_ice1712_pro_internal_clock = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Internal Clock", .info = snd_ice1712_pro_internal_clock_info, @@ -1943,7 +1943,7 @@ static int snd_ice1712_pro_internal_clock_default_put(struct snd_kcontrol *kcont return change; } -static struct snd_kcontrol_new snd_ice1712_pro_internal_clock_default = { +static const struct snd_kcontrol_new snd_ice1712_pro_internal_clock_default = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Internal Clock Default", .info = snd_ice1712_pro_internal_clock_default_info, @@ -1974,7 +1974,7 @@ static int snd_ice1712_pro_rate_locking_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ice1712_pro_rate_locking = { +static const struct snd_kcontrol_new snd_ice1712_pro_rate_locking = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Rate Locking", .info = snd_ice1712_pro_rate_locking_info, @@ -2005,7 +2005,7 @@ static int snd_ice1712_pro_rate_reset_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ice1712_pro_rate_reset = { +static const struct snd_kcontrol_new snd_ice1712_pro_rate_reset = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Rate Reset", .info = snd_ice1712_pro_rate_reset_info, @@ -2173,7 +2173,7 @@ static struct snd_kcontrol_new snd_ice1712_mixer_pro_analog_route = { .put = snd_ice1712_pro_route_analog_put, }; -static struct snd_kcontrol_new snd_ice1712_mixer_pro_spdif_route = { +static const struct snd_kcontrol_new snd_ice1712_mixer_pro_spdif_route = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, NONE) "Route", .info = snd_ice1712_pro_route_info, @@ -2215,7 +2215,7 @@ static int snd_ice1712_pro_volume_rate_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ice1712_mixer_pro_volume_rate = { +static const struct snd_kcontrol_new snd_ice1712_mixer_pro_volume_rate = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Volume Rate", .info = snd_ice1712_pro_volume_rate_info, @@ -2248,7 +2248,7 @@ static int snd_ice1712_pro_peak_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ice1712_mixer_pro_peak = { +static const struct snd_kcontrol_new snd_ice1712_mixer_pro_peak = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "Multi Track Peak", .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, diff --git a/sound/pci/ice1712/ice1724.c b/sound/pci/ice1712/ice1724.c index 842744e7a139..9cd6e55c0642 100644 --- a/sound/pci/ice1712/ice1724.c +++ b/sound/pci/ice1712/ice1724.c @@ -1598,7 +1598,7 @@ static int snd_vt1724_eeprom_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_vt1724_eeprom = { +static const struct snd_kcontrol_new snd_vt1724_eeprom = { .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = "ICE1724 EEPROM", .access = SNDRV_CTL_ELEM_ACCESS_READ, @@ -1711,7 +1711,7 @@ static int snd_vt1724_spdif_default_put(struct snd_kcontrol *kcontrol, return val != old; } -static struct snd_kcontrol_new snd_vt1724_spdif_default = +static const struct snd_kcontrol_new snd_vt1724_spdif_default = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), @@ -1743,7 +1743,7 @@ static int snd_vt1724_spdif_maskp_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_vt1724_spdif_maskc = +static const struct snd_kcontrol_new snd_vt1724_spdif_maskc = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1752,7 +1752,7 @@ static struct snd_kcontrol_new snd_vt1724_spdif_maskc = .get = snd_vt1724_spdif_maskc_get, }; -static struct snd_kcontrol_new snd_vt1724_spdif_maskp = +static const struct snd_kcontrol_new snd_vt1724_spdif_maskp = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1789,7 +1789,7 @@ static int snd_vt1724_spdif_sw_put(struct snd_kcontrol *kcontrol, return old != val; } -static struct snd_kcontrol_new snd_vt1724_spdif_switch = +static const struct snd_kcontrol_new snd_vt1724_spdif_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, /* FIXME: the following conflict with IEC958 Playback Route */ @@ -1964,7 +1964,7 @@ static int snd_vt1724_pro_internal_clock_put(struct snd_kcontrol *kcontrol, return old_rate != new_rate; } -static struct snd_kcontrol_new snd_vt1724_pro_internal_clock = { +static const struct snd_kcontrol_new snd_vt1724_pro_internal_clock = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Internal Clock", .info = snd_vt1724_pro_internal_clock_info, @@ -1995,7 +1995,7 @@ static int snd_vt1724_pro_rate_locking_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_vt1724_pro_rate_locking = { +static const struct snd_kcontrol_new snd_vt1724_pro_rate_locking = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Rate Locking", .info = snd_vt1724_pro_rate_locking_info, @@ -2026,7 +2026,7 @@ static int snd_vt1724_pro_rate_reset_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_vt1724_pro_rate_reset = { +static const struct snd_kcontrol_new snd_vt1724_pro_rate_reset = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Rate Reset", .info = snd_vt1724_pro_rate_reset_info, @@ -2151,7 +2151,7 @@ static struct snd_kcontrol_new snd_vt1724_mixer_pro_analog_route = .put = snd_vt1724_pro_route_analog_put, }; -static struct snd_kcontrol_new snd_vt1724_mixer_pro_spdif_route = { +static const struct snd_kcontrol_new snd_vt1724_mixer_pro_spdif_route = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, NONE) "Route", .info = snd_vt1724_pro_route_info, @@ -2187,7 +2187,7 @@ static int snd_vt1724_pro_peak_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_vt1724_mixer_pro_peak = { +static const struct snd_kcontrol_new snd_vt1724_mixer_pro_peak = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "Multi Track Peak", .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, diff --git a/sound/pci/lola/lola_mixer.c b/sound/pci/lola/lola_mixer.c index e7fe15dd5a90..cb25acf7bc49 100644 --- a/sound/pci/lola/lola_mixer.c +++ b/sound/pci/lola/lola_mixer.c @@ -645,7 +645,7 @@ static int lola_input_src_put(struct snd_kcontrol *kcontrol, return lola_set_src_config(chip, mask, true); } -static struct snd_kcontrol_new lola_input_src_mixer = { +static const struct snd_kcontrol_new lola_input_src_mixer = { .name = "Digital SRC Capture Switch", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .info = lola_input_src_info, diff --git a/sound/pci/lx6464es/lx6464es.c b/sound/pci/lx6464es/lx6464es.c index c0f0c349c3ec..f9c3e86d55d5 100644 --- a/sound/pci/lx6464es/lx6464es.c +++ b/sound/pci/lx6464es/lx6464es.c @@ -899,7 +899,7 @@ static int lx_control_playback_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new lx_control_playback_switch = { +static const struct snd_kcontrol_new lx_control_playback_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Playback Switch", .index = 0, diff --git a/sound/pci/mixart/mixart_mixer.c b/sound/pci/mixart/mixart_mixer.c index 51e53497f0ad..4a4616aac787 100644 --- a/sound/pci/mixart/mixart_mixer.c +++ b/sound/pci/mixart/mixart_mixer.c @@ -448,7 +448,7 @@ static int mixart_audio_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_ele return changed; } -static struct snd_kcontrol_new mixart_control_output_switch = { +static const struct snd_kcontrol_new mixart_control_output_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Master Playback Switch", .info = mixart_sw_info, /* shared */ @@ -1024,7 +1024,7 @@ static int mixart_monitor_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_ return changed; } -static struct snd_kcontrol_new mixart_control_monitor_vol = { +static const struct snd_kcontrol_new mixart_control_monitor_vol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), @@ -1091,7 +1091,7 @@ static int mixart_monitor_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_e return (changed != 0); } -static struct snd_kcontrol_new mixart_control_monitor_sw = { +static const struct snd_kcontrol_new mixart_control_monitor_sw = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Monitoring Switch", .info = mixart_sw_info, /* shared */ diff --git a/sound/pci/oxygen/oxygen.c b/sound/pci/oxygen/oxygen.c index 74afb6b75976..e36ed8af55ad 100644 --- a/sound/pci/oxygen/oxygen.c +++ b/sound/pci/oxygen/oxygen.c @@ -767,6 +767,8 @@ static int get_oxygen_model(struct oxygen *chip, [MODEL_FANTASIA] = "TempoTec HiFier Fantasia", [MODEL_SERENADE] = "TempoTec HiFier Serenade", [MODEL_HG2PCI] = "CMI8787-HG2PCI", + [MODEL_XONAR_DG] = "Xonar DG", + [MODEL_XONAR_DGX] = "Xonar DGX", }; chip->model = model_generic; @@ -829,12 +831,8 @@ static int get_oxygen_model(struct oxygen *chip, chip->model.dac_channels_mixer = 2; break; case MODEL_XONAR_DG: - chip->model = model_xonar_dg; - chip->model.shortname = "Xonar DG"; - break; case MODEL_XONAR_DGX: chip->model = model_xonar_dg; - chip->model.shortname = "Xonar DGX"; break; } if (id->driver_data == MODEL_MERIDIAN || diff --git a/sound/pci/pcxhr/pcxhr_mix22.c b/sound/pci/pcxhr/pcxhr_mix22.c index 6a56e5306a65..8b4d0282efb8 100644 --- a/sound/pci/pcxhr/pcxhr_mix22.c +++ b/sound/pci/pcxhr/pcxhr_mix22.c @@ -744,7 +744,7 @@ static int hr222_mic_vol_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new hr222_control_mic_level = { +static const struct snd_kcontrol_new hr222_control_mic_level = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), @@ -794,7 +794,7 @@ static int hr222_mic_boost_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new hr222_control_mic_boost = { +static const struct snd_kcontrol_new hr222_control_mic_boost = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), @@ -836,7 +836,7 @@ static int hr222_phantom_power_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new hr222_phantom_power_switch = { +static const struct snd_kcontrol_new hr222_phantom_power_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Phantom Power Switch", .info = hr222_phantom_power_info, diff --git a/sound/pci/pcxhr/pcxhr_mixer.c b/sound/pci/pcxhr/pcxhr_mixer.c index 63136c4f3f3d..36875df30dbf 100644 --- a/sound/pci/pcxhr/pcxhr_mixer.c +++ b/sound/pci/pcxhr/pcxhr_mixer.c @@ -235,7 +235,7 @@ static int pcxhr_audio_sw_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new pcxhr_control_output_switch = { +static const struct snd_kcontrol_new pcxhr_control_output_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Master Playback Switch", .info = pcxhr_sw_info, /* shared */ @@ -460,7 +460,7 @@ static int pcxhr_pcm_sw_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new pcxhr_control_pcm_switch = { +static const struct snd_kcontrol_new pcxhr_control_pcm_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Playback Switch", .count = PCXHR_PLAYBACK_STREAMS, @@ -509,7 +509,7 @@ static int pcxhr_monitor_vol_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new pcxhr_control_monitor_vol = { +static const struct snd_kcontrol_new pcxhr_control_monitor_vol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), @@ -562,7 +562,7 @@ static int pcxhr_monitor_sw_put(struct snd_kcontrol *kcontrol, return (changed != 0); } -static struct snd_kcontrol_new pcxhr_control_monitor_sw = { +static const struct snd_kcontrol_new pcxhr_control_monitor_sw = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Monitoring Playback Switch", .info = pcxhr_sw_info, /* shared */ @@ -697,7 +697,7 @@ static int pcxhr_audio_src_put(struct snd_kcontrol *kcontrol, return ret; } -static struct snd_kcontrol_new pcxhr_control_audio_src = { +static const struct snd_kcontrol_new pcxhr_control_audio_src = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture Source", .info = pcxhr_audio_src_info, @@ -798,7 +798,7 @@ static int pcxhr_clock_type_put(struct snd_kcontrol *kcontrol, return ret; } -static struct snd_kcontrol_new pcxhr_control_clock_type = { +static const struct snd_kcontrol_new pcxhr_control_clock_type = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Clock Mode", .info = pcxhr_clock_type_info, @@ -842,7 +842,7 @@ static int pcxhr_clock_rate_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new pcxhr_control_clock_rate = { +static const struct snd_kcontrol_new pcxhr_control_clock_rate = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = "Clock Rates", @@ -1017,14 +1017,14 @@ static int pcxhr_iec958_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new pcxhr_control_playback_iec958_mask = { +static const struct snd_kcontrol_new pcxhr_control_playback_iec958_mask = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK), .info = pcxhr_iec958_info, .get = pcxhr_iec958_mask_get }; -static struct snd_kcontrol_new pcxhr_control_playback_iec958 = { +static const struct snd_kcontrol_new pcxhr_control_playback_iec958 = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), .info = pcxhr_iec958_info, @@ -1033,14 +1033,14 @@ static struct snd_kcontrol_new pcxhr_control_playback_iec958 = { .private_value = 0 /* playback */ }; -static struct snd_kcontrol_new pcxhr_control_capture_iec958_mask = { +static const struct snd_kcontrol_new pcxhr_control_capture_iec958_mask = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",CAPTURE,MASK), .info = pcxhr_iec958_info, .get = pcxhr_iec958_mask_get }; -static struct snd_kcontrol_new pcxhr_control_capture_iec958 = { +static const struct snd_kcontrol_new pcxhr_control_capture_iec958 = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",CAPTURE,DEFAULT), diff --git a/sound/pci/trident/trident_main.c b/sound/pci/trident/trident_main.c index 92ad2d7a6bf8..64d3b8eba4bb 100644 --- a/sound/pci/trident/trident_main.c +++ b/sound/pci/trident/trident_main.c @@ -2356,7 +2356,7 @@ static int snd_trident_spdif_control_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_spdif_control = +static const struct snd_kcontrol_new snd_trident_spdif_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH), @@ -2419,7 +2419,7 @@ static int snd_trident_spdif_default_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_spdif_default = +static const struct snd_kcontrol_new snd_trident_spdif_default = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), @@ -2452,7 +2452,7 @@ static int snd_trident_spdif_mask_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_trident_spdif_mask = +static const struct snd_kcontrol_new snd_trident_spdif_mask = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -2514,7 +2514,7 @@ static int snd_trident_spdif_stream_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_spdif_stream = +static const struct snd_kcontrol_new snd_trident_spdif_stream = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -2564,7 +2564,7 @@ static int snd_trident_ac97_control_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_ac97_rear_control = +static const struct snd_kcontrol_new snd_trident_ac97_rear_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Rear Path", @@ -2622,7 +2622,7 @@ static int snd_trident_vol_control_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_vol_music_control = +static const struct snd_kcontrol_new snd_trident_vol_music_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Music Playback Volume", @@ -2633,7 +2633,7 @@ static struct snd_kcontrol_new snd_trident_vol_music_control = .tlv = { .p = db_scale_gvol }, }; -static struct snd_kcontrol_new snd_trident_vol_wave_control = +static const struct snd_kcontrol_new snd_trident_vol_wave_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Wave Playback Volume", @@ -2700,7 +2700,7 @@ static int snd_trident_pcm_vol_control_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_pcm_vol_control = +static const struct snd_kcontrol_new snd_trident_pcm_vol_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Front Playback Volume", @@ -2764,7 +2764,7 @@ static int snd_trident_pcm_pan_control_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_pcm_pan_control = +static const struct snd_kcontrol_new snd_trident_pcm_pan_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Pan Playback Control", @@ -2821,7 +2821,7 @@ static int snd_trident_pcm_rvol_control_put(struct snd_kcontrol *kcontrol, static const DECLARE_TLV_DB_SCALE(db_scale_crvol, -3175, 25, 1); -static struct snd_kcontrol_new snd_trident_pcm_rvol_control = +static const struct snd_kcontrol_new snd_trident_pcm_rvol_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Reverb Playback Volume", @@ -2877,7 +2877,7 @@ static int snd_trident_pcm_cvol_control_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_pcm_cvol_control = +static const struct snd_kcontrol_new snd_trident_pcm_cvol_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Chorus Playback Volume", diff --git a/sound/pci/via82xx.c b/sound/pci/via82xx.c index 2d8c14e3f8d2..d078e86414c2 100644 --- a/sound/pci/via82xx.c +++ b/sound/pci/via82xx.c @@ -1683,7 +1683,7 @@ static int snd_via8233_dxs3_spdif_put(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_via8233_dxs3_spdif_control = { +static const struct snd_kcontrol_new snd_via8233_dxs3_spdif_control = { .name = SNDRV_CTL_NAME_IEC958("Output ",NONE,SWITCH), .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .info = snd_via8233_dxs3_spdif_info, @@ -1772,7 +1772,7 @@ static int snd_via8233_pcmdxs_volume_put(struct snd_kcontrol *kcontrol, static const DECLARE_TLV_DB_SCALE(db_scale_dxs, -4650, 150, 1); -static struct snd_kcontrol_new snd_via8233_pcmdxs_volume_control = { +static const struct snd_kcontrol_new snd_via8233_pcmdxs_volume_control = { .name = "PCM Playback Volume", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | @@ -1783,7 +1783,7 @@ static struct snd_kcontrol_new snd_via8233_pcmdxs_volume_control = { .tlv = { .p = db_scale_dxs } }; -static struct snd_kcontrol_new snd_via8233_dxs_volume_control = { +static const struct snd_kcontrol_new snd_via8233_dxs_volume_control = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .device = 0, /* .subdevice set later */ diff --git a/sound/pci/vx222/vx222_ops.c b/sound/pci/vx222/vx222_ops.c index 8e457ea27f89..7df1663ea510 100644 --- a/sound/pci/vx222/vx222_ops.c +++ b/sound/pci/vx222/vx222_ops.c @@ -945,7 +945,7 @@ static int vx_mic_level_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v return 0; } -static struct snd_kcontrol_new vx_control_input_level = { +static const struct snd_kcontrol_new vx_control_input_level = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), @@ -956,7 +956,7 @@ static struct snd_kcontrol_new vx_control_input_level = { .tlv = { .p = db_scale_mic }, }; -static struct snd_kcontrol_new vx_control_mic_level = { +static const struct snd_kcontrol_new vx_control_mic_level = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), diff --git a/sound/pci/ymfpci/ymfpci_main.c b/sound/pci/ymfpci/ymfpci_main.c index ffee284898b3..fe4ba463b57c 100644 --- a/sound/pci/ymfpci/ymfpci_main.c +++ b/sound/pci/ymfpci/ymfpci_main.c @@ -1316,7 +1316,7 @@ static int snd_ymfpci_spdif_default_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ymfpci_spdif_default = +static const struct snd_kcontrol_new snd_ymfpci_spdif_default = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), @@ -1344,7 +1344,7 @@ static int snd_ymfpci_spdif_mask_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ymfpci_spdif_mask = +static const struct snd_kcontrol_new snd_ymfpci_spdif_mask = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1391,7 +1391,7 @@ static int snd_ymfpci_spdif_stream_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ymfpci_spdif_stream = +static const struct snd_kcontrol_new snd_ymfpci_spdif_stream = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1439,7 +1439,7 @@ static int snd_ymfpci_drec_source_put(struct snd_kcontrol *kcontrol, struct snd_ return reg != old_reg; } -static struct snd_kcontrol_new snd_ymfpci_drec_source = { +static const struct snd_kcontrol_new snd_ymfpci_drec_source = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Direct Recording Source", @@ -1609,7 +1609,7 @@ static int snd_ymfpci_put_dup4ch(struct snd_kcontrol *kcontrol, struct snd_ctl_e return change; } -static struct snd_kcontrol_new snd_ymfpci_dup4ch = { +static const struct snd_kcontrol_new snd_ymfpci_dup4ch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "4ch Duplication", .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, @@ -1712,7 +1712,7 @@ static int snd_ymfpci_gpio_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_ return 0; } -static struct snd_kcontrol_new snd_ymfpci_rear_shared = { +static const struct snd_kcontrol_new snd_ymfpci_rear_shared = { .name = "Shared Rear/Line-In Switch", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .info = snd_ymfpci_gpio_sw_info, @@ -1776,7 +1776,7 @@ static int snd_ymfpci_pcm_vol_put(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ymfpci_pcm_volume = { +static const struct snd_kcontrol_new snd_ymfpci_pcm_volume = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "PCM Playback Volume", .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | diff --git a/sound/soc/intel/skylake/skl.h b/sound/soc/intel/skylake/skl.h index bbef77d2b917..8e2878012d53 100644 --- a/sound/soc/intel/skylake/skl.h +++ b/sound/soc/intel/skylake/skl.h @@ -27,27 +27,6 @@ #define SKL_SUSPEND_DELAY 2000 -/* Vendor Specific Registers */ -#define AZX_REG_VS_EM1 0x1000 -#define AZX_REG_VS_INRC 0x1004 -#define AZX_REG_VS_OUTRC 0x1008 -#define AZX_REG_VS_FIFOTRK 0x100C -#define AZX_REG_VS_FIFOTRK2 0x1010 -#define AZX_REG_VS_EM2 0x1030 -#define AZX_REG_VS_EM3L 0x1038 -#define AZX_REG_VS_EM3U 0x103C -#define AZX_REG_VS_EM4L 0x1040 -#define AZX_REG_VS_EM4U 0x1044 -#define AZX_REG_VS_LTRC 0x1048 -#define AZX_REG_VS_D0I3C 0x104A -#define AZX_REG_VS_PCE 0x104B -#define AZX_REG_VS_L2MAGC 0x1050 -#define AZX_REG_VS_L2LAHPT 0x1054 -#define AZX_REG_VS_SDXDPIB_XBASE 0x1084 -#define AZX_REG_VS_SDXDPIB_XINTERVAL 0x20 -#define AZX_REG_VS_SDXEFIFOS_XBASE 0x1094 -#define AZX_REG_VS_SDXEFIFOS_XINTERVAL 0x20 - #define AZX_PCIREG_PGCTL 0x44 #define AZX_PGCTL_LSRMD_MASK (1 << 4) #define AZX_PCIREG_CGCTL 0x48 diff --git a/sound/synth/emux/emux_oss.c b/sound/synth/emux/emux_oss.c index ac75816ada7c..850fab4a8f3b 100644 --- a/sound/synth/emux/emux_oss.c +++ b/sound/synth/emux/emux_oss.c @@ -225,9 +225,9 @@ snd_emux_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format, else if (format == SNDRV_OSS_SOUNDFONT_PATCH) { struct soundfont_patch_info patch; if (count < (int)sizeof(patch)) - rc = -EINVAL; + return -EINVAL; if (copy_from_user(&patch, buf, sizeof(patch))) - rc = -EFAULT; + return -EFAULT; if (patch.type >= SNDRV_SFNT_LOAD_INFO && patch.type <= SNDRV_SFNT_PROBE_DATA) rc = snd_soundfont_load(emu->sflist, buf, count, SF_CLIENT_NO(p->chset.port)); diff --git a/sound/usb/card.c b/sound/usb/card.c index f36cb068dad3..6640277a725b 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -332,6 +332,7 @@ static int snd_usb_audio_dev_free(struct snd_device *device) static int snd_usb_audio_create(struct usb_interface *intf, struct usb_device *dev, int idx, const struct snd_usb_audio_quirk *quirk, + unsigned int usb_id, struct snd_usb_audio **rchip) { struct snd_card *card; @@ -381,8 +382,7 @@ static int snd_usb_audio_create(struct usb_interface *intf, atomic_set(&chip->usage_count, 0); atomic_set(&chip->shutdown, 0); - chip->usb_id = USB_ID(le16_to_cpu(dev->descriptor.idVendor), - le16_to_cpu(dev->descriptor.idProduct)); + chip->usb_id = usb_id; INIT_LIST_HEAD(&chip->pcm_list); INIT_LIST_HEAD(&chip->ep_list); INIT_LIST_HEAD(&chip->midi_list); @@ -569,7 +569,7 @@ static int usb_audio_probe(struct usb_interface *intf, (vid[i] == -1 || vid[i] == USB_ID_VENDOR(id)) && (pid[i] == -1 || pid[i] == USB_ID_PRODUCT(id))) { err = snd_usb_audio_create(intf, dev, i, quirk, - &chip); + id, &chip); if (err < 0) goto __error; chip->pm_intf = intf; diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index fab53f58d447..b3854f8c0c67 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -430,7 +430,7 @@ static int snd_line6_control_playback_put(struct snd_kcontrol *kcontrol, } /* control definition */ -static struct snd_kcontrol_new line6_controls[] = { +static const struct snd_kcontrol_new line6_controls[] = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Playback Volume", diff --git a/sound/usb/line6/pod.c b/sound/usb/line6/pod.c index 17aa616e61f5..358224cc5638 100644 --- a/sound/usb/line6/pod.c +++ b/sound/usb/line6/pod.c @@ -380,7 +380,7 @@ static int snd_pod_control_monitor_put(struct snd_kcontrol *kcontrol, } /* control definition */ -static struct snd_kcontrol_new pod_control_monitor = { +static const struct snd_kcontrol_new pod_control_monitor = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Monitor Playback Volume", .index = 0, diff --git a/sound/usb/line6/toneport.c b/sound/usb/line6/toneport.c index 8e22f430d700..ba7975c0d03d 100644 --- a/sound/usb/line6/toneport.c +++ b/sound/usb/line6/toneport.c @@ -250,7 +250,7 @@ static void toneport_start_pcm(unsigned long arg) } /* control definition */ -static struct snd_kcontrol_new toneport_control_monitor = { +static const struct snd_kcontrol_new toneport_control_monitor = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Monitor Playback Volume", .index = 0, @@ -261,7 +261,7 @@ static struct snd_kcontrol_new toneport_control_monitor = { }; /* source selector definition */ -static struct snd_kcontrol_new toneport_control_source = { +static const struct snd_kcontrol_new toneport_control_source = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Capture Source", .index = 0, diff --git a/sound/usb/midi.c b/sound/usb/midi.c index 6e763bc8d7db..a35f41467237 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -1922,7 +1922,7 @@ static int roland_load_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new roland_load_ctl = { +static const struct snd_kcontrol_new roland_load_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "MIDI Input Mode", .info = roland_load_info, diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 4703caea56b2..082736c539bc 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1172,7 +1172,7 @@ static struct snd_kcontrol_new usb_feature_unit_ctl = { }; /* the read-only variant */ -static struct snd_kcontrol_new usb_feature_unit_ctl_ro = { +static const struct snd_kcontrol_new usb_feature_unit_ctl_ro = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", /* will be filled later manually */ .info = mixer_ctl_feature_info, @@ -1745,7 +1745,7 @@ static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol, } /* alsa control interface for processing/extension unit */ -static struct snd_kcontrol_new mixer_procunit_ctl = { +static const struct snd_kcontrol_new mixer_procunit_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", /* will be filled later */ .info = mixer_ctl_feature_info, @@ -2033,7 +2033,7 @@ static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol, } /* alsa control interface for selector unit */ -static struct snd_kcontrol_new mixer_selectunit_ctl = { +static const struct snd_kcontrol_new mixer_selectunit_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", /* will be filled later */ .info = mixer_ctl_selector_info, diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c index 7438e7c4a842..c33e2378089d 100644 --- a/sound/usb/mixer_scarlett.c +++ b/sound/usb/mixer_scarlett.c @@ -477,7 +477,7 @@ static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl, return 0; } -static struct snd_kcontrol_new usb_scarlett_ctl_switch = { +static const struct snd_kcontrol_new usb_scarlett_ctl_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", .info = scarlett_ctl_switch_info, @@ -487,7 +487,7 @@ static struct snd_kcontrol_new usb_scarlett_ctl_switch = { static const DECLARE_TLV_DB_SCALE(db_scale_scarlett_gain, -12800, 100, 0); -static struct snd_kcontrol_new usb_scarlett_ctl = { +static const struct snd_kcontrol_new usb_scarlett_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, @@ -499,7 +499,7 @@ static struct snd_kcontrol_new usb_scarlett_ctl = { .tlv = { .p = db_scale_scarlett_gain } }; -static struct snd_kcontrol_new usb_scarlett_ctl_master = { +static const struct snd_kcontrol_new usb_scarlett_ctl_master = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, @@ -511,7 +511,7 @@ static struct snd_kcontrol_new usb_scarlett_ctl_master = { .tlv = { .p = db_scale_scarlett_gain } }; -static struct snd_kcontrol_new usb_scarlett_ctl_enum = { +static const struct snd_kcontrol_new usb_scarlett_ctl_enum = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", .info = scarlett_ctl_enum_info, @@ -519,7 +519,7 @@ static struct snd_kcontrol_new usb_scarlett_ctl_enum = { .put = scarlett_ctl_enum_put, }; -static struct snd_kcontrol_new usb_scarlett_ctl_dynamic_enum = { +static const struct snd_kcontrol_new usb_scarlett_ctl_dynamic_enum = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", .info = scarlett_ctl_enum_dynamic_info, @@ -527,7 +527,7 @@ static struct snd_kcontrol_new usb_scarlett_ctl_dynamic_enum = { .put = scarlett_ctl_enum_put, }; -static struct snd_kcontrol_new usb_scarlett_ctl_sync = { +static const struct snd_kcontrol_new usb_scarlett_ctl_sync = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, .name = "", diff --git a/sound/usb/usx2y/us122l.c b/sound/usb/usx2y/us122l.c index cf45bf1f7ee0..e118bdca983d 100644 --- a/sound/usb/usx2y/us122l.c +++ b/sound/usb/usx2y/us122l.c @@ -472,7 +472,7 @@ static int usb_stream_hwdep_new(struct snd_card *card) hw->ops.mmap = usb_stream_hwdep_mmap; hw->ops.poll = usb_stream_hwdep_poll; - sprintf(hw->name, "/proc/bus/usb/%03d/%03d/hwdeppcm", + sprintf(hw->name, "/dev/bus/usb/%03d/%03d/hwdeppcm", dev->bus->busnum, dev->devnum); return 0; } diff --git a/sound/usb/usx2y/usX2Yhwdep.c b/sound/usb/usx2y/usX2Yhwdep.c index 605e1047c01d..f4b3cda412fc 100644 --- a/sound/usb/usx2y/usX2Yhwdep.c +++ b/sound/usb/usx2y/usX2Yhwdep.c @@ -258,7 +258,7 @@ int usX2Y_hwdep_new(struct snd_card *card, struct usb_device* device) hw->ops.mmap = snd_us428ctls_mmap; hw->ops.poll = snd_us428ctls_poll; hw->exclusive = 1; - sprintf(hw->name, "/proc/bus/usb/%03d/%03d", device->bus->busnum, device->devnum); + sprintf(hw->name, "/dev/bus/usb/%03d/%03d", device->bus->busnum, device->devnum); return 0; } diff --git a/sound/usb/usx2y/usx2yhwdeppcm.c b/sound/usb/usx2y/usx2yhwdeppcm.c index f95164b91152..d51c7fd7835b 100644 --- a/sound/usb/usx2y/usx2yhwdeppcm.c +++ b/sound/usb/usx2y/usx2yhwdeppcm.c @@ -723,7 +723,7 @@ int usX2Y_hwdep_pcm_new(struct snd_card *card) hw->ops.release = snd_usX2Y_hwdep_pcm_release; hw->ops.mmap = snd_usX2Y_hwdep_pcm_mmap; hw->exclusive = 1; - sprintf(hw->name, "/proc/bus/usb/%03d/%03d/hwdeppcm", dev->bus->busnum, dev->devnum); + sprintf(hw->name, "/dev/bus/usb/%03d/%03d/hwdeppcm", dev->bus->busnum, dev->devnum); err = snd_pcm_new(card, NAME_ALLCAPS" hwdep Audio", 2, 1, 1, &pcm); if (err < 0) { |