Merge branch 'for-next' into for-linus
Merge 5.16-devel branch for upstreaming Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
commit
8beea31350
@ -88,6 +88,8 @@ struct hdac_ext_stream *snd_hdac_ext_stream_assign(struct hdac_bus *bus,
|
||||
struct snd_pcm_substream *substream,
|
||||
int type);
|
||||
void snd_hdac_ext_stream_release(struct hdac_ext_stream *azx_dev, int type);
|
||||
void snd_hdac_ext_stream_decouple_locked(struct hdac_bus *bus,
|
||||
struct hdac_ext_stream *azx_dev, bool decouple);
|
||||
void snd_hdac_ext_stream_decouple(struct hdac_bus *bus,
|
||||
struct hdac_ext_stream *azx_dev, bool decouple);
|
||||
void snd_hdac_ext_stop_streams(struct hdac_bus *bus);
|
||||
|
@ -9,16 +9,20 @@
|
||||
#ifndef __SOUND_MEMALLOC_H
|
||||
#define __SOUND_MEMALLOC_H
|
||||
|
||||
#include <linux/dma-direction.h>
|
||||
#include <asm/page.h>
|
||||
|
||||
struct device;
|
||||
struct vm_area_struct;
|
||||
struct sg_table;
|
||||
|
||||
/*
|
||||
* buffer device info
|
||||
*/
|
||||
struct snd_dma_device {
|
||||
int type; /* SNDRV_DMA_TYPE_XXX */
|
||||
enum dma_data_direction dir; /* DMA direction */
|
||||
bool need_sync; /* explicit sync needed? */
|
||||
struct device *dev; /* generic device */
|
||||
};
|
||||
|
||||
@ -32,19 +36,21 @@ struct snd_dma_device {
|
||||
#define SNDRV_DMA_TYPE_CONTINUOUS 1 /* continuous no-DMA memory */
|
||||
#define SNDRV_DMA_TYPE_DEV 2 /* generic device continuous */
|
||||
#define SNDRV_DMA_TYPE_DEV_WC 5 /* continuous write-combined */
|
||||
#ifdef CONFIG_SND_DMA_SGBUF
|
||||
#define SNDRV_DMA_TYPE_DEV_SG 3 /* generic device SG-buffer */
|
||||
#define SNDRV_DMA_TYPE_DEV_WC_SG 6 /* SG write-combined */
|
||||
#else
|
||||
#define SNDRV_DMA_TYPE_DEV_SG SNDRV_DMA_TYPE_DEV /* no SG-buf support */
|
||||
#define SNDRV_DMA_TYPE_DEV_WC_SG SNDRV_DMA_TYPE_DEV_WC
|
||||
#endif
|
||||
#ifdef CONFIG_GENERIC_ALLOCATOR
|
||||
#define SNDRV_DMA_TYPE_DEV_IRAM 4 /* generic device iram-buffer */
|
||||
#else
|
||||
#define SNDRV_DMA_TYPE_DEV_IRAM SNDRV_DMA_TYPE_DEV
|
||||
#endif
|
||||
#define SNDRV_DMA_TYPE_VMALLOC 7 /* vmalloc'ed buffer */
|
||||
#define SNDRV_DMA_TYPE_NONCONTIG 8 /* non-coherent SG buffer */
|
||||
#define SNDRV_DMA_TYPE_NONCOHERENT 9 /* non-coherent buffer */
|
||||
#ifdef CONFIG_SND_DMA_SGBUF
|
||||
#define SNDRV_DMA_TYPE_DEV_SG SNDRV_DMA_TYPE_NONCONTIG
|
||||
#define SNDRV_DMA_TYPE_DEV_WC_SG 6 /* SG write-combined */
|
||||
#else
|
||||
#define SNDRV_DMA_TYPE_DEV_SG SNDRV_DMA_TYPE_DEV /* no SG-buf support */
|
||||
#define SNDRV_DMA_TYPE_DEV_WC_SG SNDRV_DMA_TYPE_DEV_WC
|
||||
#endif
|
||||
|
||||
/*
|
||||
* info for buffer allocation
|
||||
@ -66,22 +72,52 @@ static inline unsigned int snd_sgbuf_aligned_pages(size_t size)
|
||||
}
|
||||
|
||||
/* allocate/release a buffer */
|
||||
int snd_dma_alloc_pages(int type, struct device *dev, size_t size,
|
||||
struct snd_dma_buffer *dmab);
|
||||
int snd_dma_alloc_dir_pages(int type, struct device *dev,
|
||||
enum dma_data_direction dir, size_t size,
|
||||
struct snd_dma_buffer *dmab);
|
||||
|
||||
static inline int snd_dma_alloc_pages(int type, struct device *dev,
|
||||
size_t size, struct snd_dma_buffer *dmab)
|
||||
{
|
||||
return snd_dma_alloc_dir_pages(type, dev, DMA_BIDIRECTIONAL, size, dmab);
|
||||
}
|
||||
|
||||
int snd_dma_alloc_pages_fallback(int type, struct device *dev, size_t size,
|
||||
struct snd_dma_buffer *dmab);
|
||||
void snd_dma_free_pages(struct snd_dma_buffer *dmab);
|
||||
int snd_dma_buffer_mmap(struct snd_dma_buffer *dmab,
|
||||
struct vm_area_struct *area);
|
||||
|
||||
enum snd_dma_sync_mode { SNDRV_DMA_SYNC_CPU, SNDRV_DMA_SYNC_DEVICE };
|
||||
#ifdef CONFIG_HAS_DMA
|
||||
void snd_dma_buffer_sync(struct snd_dma_buffer *dmab,
|
||||
enum snd_dma_sync_mode mode);
|
||||
#else
|
||||
static inline void snd_dma_buffer_sync(struct snd_dma_buffer *dmab,
|
||||
enum snd_dma_sync_mode mode) {}
|
||||
#endif
|
||||
|
||||
dma_addr_t snd_sgbuf_get_addr(struct snd_dma_buffer *dmab, size_t offset);
|
||||
struct page *snd_sgbuf_get_page(struct snd_dma_buffer *dmab, size_t offset);
|
||||
unsigned int snd_sgbuf_get_chunk_size(struct snd_dma_buffer *dmab,
|
||||
unsigned int ofs, unsigned int size);
|
||||
|
||||
/* device-managed memory allocator */
|
||||
struct snd_dma_buffer *snd_devm_alloc_pages(struct device *dev, int type,
|
||||
size_t size);
|
||||
struct snd_dma_buffer *snd_devm_alloc_dir_pages(struct device *dev, int type,
|
||||
enum dma_data_direction dir,
|
||||
size_t size);
|
||||
|
||||
static inline struct snd_dma_buffer *
|
||||
snd_devm_alloc_pages(struct device *dev, int type, size_t size)
|
||||
{
|
||||
return snd_devm_alloc_dir_pages(dev, type, DMA_BIDIRECTIONAL, size);
|
||||
}
|
||||
|
||||
static inline struct sg_table *
|
||||
snd_dma_noncontig_sg_table(struct snd_dma_buffer *dmab)
|
||||
{
|
||||
return dmab->private_data;
|
||||
}
|
||||
|
||||
#endif /* __SOUND_MEMALLOC_H */
|
||||
|
||||
|
@ -1002,7 +1002,7 @@ typedef int __bitwise snd_ctl_elem_iface_t;
|
||||
#define SNDRV_CTL_ELEM_ACCESS_WRITE (1<<1)
|
||||
#define SNDRV_CTL_ELEM_ACCESS_READWRITE (SNDRV_CTL_ELEM_ACCESS_READ|SNDRV_CTL_ELEM_ACCESS_WRITE)
|
||||
#define SNDRV_CTL_ELEM_ACCESS_VOLATILE (1<<2) /* control value may be changed without a notification */
|
||||
// (1 << 3) is unused.
|
||||
/* (1 << 3) is unused. */
|
||||
#define SNDRV_CTL_ELEM_ACCESS_TLV_READ (1<<4) /* TLV read is possible */
|
||||
#define SNDRV_CTL_ELEM_ACCESS_TLV_WRITE (1<<5) /* TLV write is possible */
|
||||
#define SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE (SNDRV_CTL_ELEM_ACCESS_TLV_READ|SNDRV_CTL_ELEM_ACCESS_TLV_WRITE)
|
||||
|
@ -13,6 +13,7 @@
|
||||
#define SNDRV_FIREWIRE_EVENT_DIGI00X_MESSAGE 0x746e736c
|
||||
#define SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION 0x64776479
|
||||
#define SNDRV_FIREWIRE_EVENT_TASCAM_CONTROL 0x7473636d
|
||||
#define SNDRV_FIREWIRE_EVENT_MOTU_REGISTER_DSP_CHANGE 0x4d545244
|
||||
|
||||
struct snd_firewire_event_common {
|
||||
unsigned int type; /* SNDRV_FIREWIRE_EVENT_xxx */
|
||||
@ -65,6 +66,12 @@ struct snd_firewire_event_tascam_control {
|
||||
struct snd_firewire_tascam_change changes[0];
|
||||
};
|
||||
|
||||
struct snd_firewire_event_motu_register_dsp_change {
|
||||
unsigned int type;
|
||||
__u32 count; /* The number of changes. */
|
||||
__u32 changes[]; /* Encoded event for change of register DSP. */
|
||||
};
|
||||
|
||||
union snd_firewire_event {
|
||||
struct snd_firewire_event_common common;
|
||||
struct snd_firewire_event_lock_status lock_status;
|
||||
@ -73,6 +80,7 @@ union snd_firewire_event {
|
||||
struct snd_firewire_event_digi00x_message digi00x_message;
|
||||
struct snd_firewire_event_tascam_control tascam_control;
|
||||
struct snd_firewire_event_motu_notification motu_notification;
|
||||
struct snd_firewire_event_motu_register_dsp_change motu_register_dsp_change;
|
||||
};
|
||||
|
||||
|
||||
@ -80,6 +88,9 @@ union snd_firewire_event {
|
||||
#define SNDRV_FIREWIRE_IOCTL_LOCK _IO('H', 0xf9)
|
||||
#define SNDRV_FIREWIRE_IOCTL_UNLOCK _IO('H', 0xfa)
|
||||
#define SNDRV_FIREWIRE_IOCTL_TASCAM_STATE _IOR('H', 0xfb, struct snd_firewire_tascam_state)
|
||||
#define SNDRV_FIREWIRE_IOCTL_MOTU_REGISTER_DSP_METER _IOR('H', 0xfc, struct snd_firewire_motu_register_dsp_meter)
|
||||
#define SNDRV_FIREWIRE_IOCTL_MOTU_COMMAND_DSP_METER _IOR('H', 0xfd, struct snd_firewire_motu_command_dsp_meter)
|
||||
#define SNDRV_FIREWIRE_IOCTL_MOTU_REGISTER_DSP_PARAMETER _IOR('H', 0xfe, struct snd_firewire_motu_register_dsp_parameter)
|
||||
|
||||
#define SNDRV_FIREWIRE_TYPE_DICE 1
|
||||
#define SNDRV_FIREWIRE_TYPE_FIREWORKS 2
|
||||
@ -108,4 +119,143 @@ struct snd_firewire_tascam_state {
|
||||
__be32 data[SNDRV_FIREWIRE_TASCAM_STATE_COUNT];
|
||||
};
|
||||
|
||||
/*
|
||||
* In below MOTU models, software is allowed to control their DSP by accessing to registers.
|
||||
* - 828mk2
|
||||
* - 896hd
|
||||
* - Traveler
|
||||
* - 8 pre
|
||||
* - Ultralite
|
||||
* - 4 pre
|
||||
* - Audio Express
|
||||
*
|
||||
* On the other hand, the status of DSP is split into specific messages included in the sequence of
|
||||
* isochronous packet. ALSA firewire-motu driver gathers the messages and allow userspace applications
|
||||
* to read it via ioctl. In 828mk2, 896hd, and Traveler, hardware meter for all of physical inputs
|
||||
* are put into the message, while one pair of physical outputs is selected. The selection is done by
|
||||
* LSB one byte in asynchronous write quadlet transaction to 0x'ffff'f000'0b2c.
|
||||
*
|
||||
* I note that V3HD/V4HD uses asynchronous transaction for the purpose. The destination address is
|
||||
* registered to 0x'ffff'f000'0b38 and '0b3c by asynchronous write quadlet request. The size of
|
||||
* message differs between 23 and 51 quadlets. For the case, the number of mixer bus can be extended
|
||||
* up to 12.
|
||||
*/
|
||||
|
||||
#define SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_INPUT_COUNT 24
|
||||
#define SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_OUTPUT_COUNT 24
|
||||
#define SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_COUNT \
|
||||
(SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_INPUT_COUNT + SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_OUTPUT_COUNT)
|
||||
|
||||
/**
|
||||
* struct snd_firewire_motu_register_dsp_meter - the container for meter information in DSP
|
||||
* controlled by register access
|
||||
* @data: Signal level meters. The mapping between position and input/output channel is
|
||||
* model-dependent.
|
||||
*
|
||||
* The structure expresses the part of DSP status for hardware meter. The u8 storage includes linear
|
||||
* value for audio signal level between 0x00 and 0x7f.
|
||||
*/
|
||||
struct snd_firewire_motu_register_dsp_meter {
|
||||
__u8 data[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_COUNT];
|
||||
};
|
||||
|
||||
#define SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_COUNT 4
|
||||
#define SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_SRC_COUNT 20
|
||||
#define SNDRV_FIREWIRE_MOTU_REGISTER_DSP_INPUT_COUNT 10
|
||||
#define SNDRV_FIREWIRE_MOTU_REGISTER_DSP_ALIGNED_INPUT_COUNT (SNDRV_FIREWIRE_MOTU_REGISTER_DSP_INPUT_COUNT + 2)
|
||||
|
||||
/**
|
||||
* snd_firewire_motu_register_dsp_parameter - the container for parameters of DSP controlled
|
||||
* by register access.
|
||||
* @mixer.source.gain: The gain of source to mixer.
|
||||
* @mixer.source.pan: The L/R balance of source to mixer.
|
||||
* @mixer.source.flag: The flag of source to mixer, including mute, solo.
|
||||
* @mixer.source.paired_balance: The L/R balance of paired source to mixer, only for 4 pre and
|
||||
* Audio Express.
|
||||
* @mixer.source.paired_width: The width of paired source to mixer, only for 4 pre and
|
||||
* Audio Express.
|
||||
* @mixer.output.paired_volume: The volume of paired output from mixer.
|
||||
* @mixer.output.paired_flag: The flag of paired output from mixer.
|
||||
* @output.main_paired_volume: The volume of paired main output.
|
||||
* @output.hp_paired_volume: The volume of paired hp output.
|
||||
* @output.hp_paired_assignment: The source assigned to paired hp output.
|
||||
* @output.reserved: Padding for 32 bit alignment for future extension.
|
||||
* @line_input.boost_flag: The flags of boost for line inputs, only for 828mk2 and Traveler.
|
||||
* @line_input.nominal_level_flag: The flags of nominal level for line inputs, only for 828mk2 and
|
||||
* Traveler.
|
||||
* @line_input.reserved: Padding for 32 bit alignment for future extension.
|
||||
* @input.gain_and_invert: The value including gain and invert for input, only for Ultralite, 4 pre
|
||||
* and Audio Express.
|
||||
* @input.flag: The flag of input; e.g. jack detection, phantom power, and pad, only for Ultralite,
|
||||
* 4 pre and Audio express.
|
||||
* @reserved: Padding so that the size of structure is kept to 512 byte, but for future extension.
|
||||
*
|
||||
* The structure expresses the set of parameters for DSP controlled by register access.
|
||||
*/
|
||||
struct snd_firewire_motu_register_dsp_parameter {
|
||||
struct {
|
||||
struct {
|
||||
__u8 gain[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_SRC_COUNT];
|
||||
__u8 pan[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_SRC_COUNT];
|
||||
__u8 flag[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_SRC_COUNT];
|
||||
__u8 paired_balance[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_SRC_COUNT];
|
||||
__u8 paired_width[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_SRC_COUNT];
|
||||
} source[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_COUNT];
|
||||
struct {
|
||||
__u8 paired_volume[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_COUNT];
|
||||
__u8 paired_flag[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_COUNT];
|
||||
} output;
|
||||
} mixer;
|
||||
struct {
|
||||
__u8 main_paired_volume;
|
||||
__u8 hp_paired_volume;
|
||||
__u8 hp_paired_assignment;
|
||||
__u8 reserved[5];
|
||||
} output;
|
||||
struct {
|
||||
__u8 boost_flag;
|
||||
__u8 nominal_level_flag;
|
||||
__u8 reserved[6];
|
||||
} line_input;
|
||||
struct {
|
||||
__u8 gain_and_invert[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_ALIGNED_INPUT_COUNT];
|
||||
__u8 flag[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_ALIGNED_INPUT_COUNT];
|
||||
} input;
|
||||
__u8 reserved[64];
|
||||
};
|
||||
|
||||
/*
|
||||
* In below MOTU models, software is allowed to control their DSP by command in frame of
|
||||
* asynchronous transaction to 0x'ffff'0001'0000:
|
||||
*
|
||||
* - 828 mk3 (FireWire only and Hybrid)
|
||||
* - 896 mk3 (FireWire only and Hybrid)
|
||||
* - Ultralite mk3 (FireWire only and Hybrid)
|
||||
* - Traveler mk3
|
||||
* - Track 16
|
||||
*
|
||||
* On the other hand, the states of hardware meter is split into specific messages included in the
|
||||
* sequence of isochronous packet. ALSA firewire-motu driver gathers the message and allow userspace
|
||||
* application to read it via ioctl.
|
||||
*/
|
||||
|
||||
#define SNDRV_FIREWIRE_MOTU_COMMAND_DSP_METER_COUNT 400
|
||||
|
||||
/**
|
||||
* struct snd_firewire_motu_command_dsp_meter - the container for meter information in DSP
|
||||
* controlled by command
|
||||
* @data: Signal level meters. The mapping between position and signal channel is model-dependent.
|
||||
*
|
||||
* The structure expresses the part of DSP status for hardware meter. The 32 bit storage is
|
||||
* estimated to include IEEE 764 32 bit single precision floating point (binary32) value. It is
|
||||
* expected to be linear value (not logarithm) for audio signal level between 0.0 and +1.0.
|
||||
*/
|
||||
struct snd_firewire_motu_command_dsp_meter {
|
||||
#ifdef __KERNEL__
|
||||
__u32 data[SNDRV_FIREWIRE_MOTU_COMMAND_DSP_METER_COUNT];
|
||||
#else
|
||||
float data[SNDRV_FIREWIRE_MOTU_COMMAND_DSP_METER_COUNT];
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif /* _UAPI_SOUND_FIREWIRE_H_INCLUDED */
|
||||
|
@ -9,7 +9,9 @@ ifneq ($(CONFIG_SND_PROC_FS),)
|
||||
snd-y += info.o
|
||||
snd-$(CONFIG_SND_OSSEMUL) += info_oss.o
|
||||
endif
|
||||
ifneq ($(CONFIG_M68K),y)
|
||||
snd-$(CONFIG_ISA_DMA_API) += isadma.o
|
||||
endif
|
||||
snd-$(CONFIG_SND_OSSEMUL) += sound_oss.o
|
||||
snd-$(CONFIG_SND_VMASTER) += vmaster.o
|
||||
snd-$(CONFIG_SND_JACK) += ctljack.o jack.o
|
||||
@ -17,7 +19,6 @@ snd-$(CONFIG_SND_JACK) += ctljack.o jack.o
|
||||
snd-pcm-y := pcm.o pcm_native.o pcm_lib.o pcm_misc.o \
|
||||
pcm_memory.o memalloc.o
|
||||
snd-pcm-$(CONFIG_SND_PCM_TIMER) += pcm_timer.o
|
||||
snd-pcm-$(CONFIG_SND_DMA_SGBUF) += sgbuf.o
|
||||
snd-pcm-$(CONFIG_SND_PCM_ELD) += pcm_drm_eld.o
|
||||
snd-pcm-$(CONFIG_SND_PCM_IEC958) += pcm_iec958.o
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <linux/mm.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/genalloc.h>
|
||||
#include <linux/highmem.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#ifdef CONFIG_X86
|
||||
#include <asm/set_memory.h>
|
||||
@ -39,9 +40,11 @@ static void *__snd_dma_alloc_pages(struct snd_dma_buffer *dmab, size_t size)
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_dma_alloc_pages - allocate the buffer area according to the given type
|
||||
* snd_dma_alloc_dir_pages - allocate the buffer area according to the given
|
||||
* type and direction
|
||||
* @type: the DMA buffer type
|
||||
* @device: the device pointer
|
||||
* @dir: DMA direction
|
||||
* @size: the buffer size to allocate
|
||||
* @dmab: buffer allocation record to store the allocated data
|
||||
*
|
||||
@ -51,8 +54,9 @@ static void *__snd_dma_alloc_pages(struct snd_dma_buffer *dmab, size_t size)
|
||||
* Return: Zero if the buffer with the given size is allocated successfully,
|
||||
* otherwise a negative value on error.
|
||||
*/
|
||||
int snd_dma_alloc_pages(int type, struct device *device, size_t size,
|
||||
struct snd_dma_buffer *dmab)
|
||||
int snd_dma_alloc_dir_pages(int type, struct device *device,
|
||||
enum dma_data_direction dir, size_t size,
|
||||
struct snd_dma_buffer *dmab)
|
||||
{
|
||||
if (WARN_ON(!size))
|
||||
return -ENXIO;
|
||||
@ -62,6 +66,7 @@ int snd_dma_alloc_pages(int type, struct device *device, size_t size,
|
||||
size = PAGE_ALIGN(size);
|
||||
dmab->dev.type = type;
|
||||
dmab->dev.dev = device;
|
||||
dmab->dev.dir = dir;
|
||||
dmab->bytes = 0;
|
||||
dmab->addr = 0;
|
||||
dmab->private_data = NULL;
|
||||
@ -71,7 +76,7 @@ int snd_dma_alloc_pages(int type, struct device *device, size_t size,
|
||||
dmab->bytes = size;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_dma_alloc_pages);
|
||||
EXPORT_SYMBOL(snd_dma_alloc_dir_pages);
|
||||
|
||||
/**
|
||||
* snd_dma_alloc_pages_fallback - allocate the buffer area according to the given type with fallback
|
||||
@ -129,9 +134,10 @@ static void __snd_release_pages(struct device *dev, void *res)
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_devm_alloc_pages - allocate the buffer and manage with devres
|
||||
* snd_devm_alloc_dir_pages - allocate the buffer and manage with devres
|
||||
* @dev: the device pointer
|
||||
* @type: the DMA buffer type
|
||||
* @dir: DMA direction
|
||||
* @size: the buffer size to allocate
|
||||
*
|
||||
* Allocate buffer pages depending on the given type and manage using devres.
|
||||
@ -144,7 +150,8 @@ static void __snd_release_pages(struct device *dev, void *res)
|
||||
* The function returns the snd_dma_buffer object at success, or NULL if failed.
|
||||
*/
|
||||
struct snd_dma_buffer *
|
||||
snd_devm_alloc_pages(struct device *dev, int type, size_t size)
|
||||
snd_devm_alloc_dir_pages(struct device *dev, int type,
|
||||
enum dma_data_direction dir, size_t size)
|
||||
{
|
||||
struct snd_dma_buffer *dmab;
|
||||
int err;
|
||||
@ -157,7 +164,7 @@ snd_devm_alloc_pages(struct device *dev, int type, size_t size)
|
||||
if (!dmab)
|
||||
return NULL;
|
||||
|
||||
err = snd_dma_alloc_pages(type, dev, size, dmab);
|
||||
err = snd_dma_alloc_dir_pages(type, dev, dir, size, dmab);
|
||||
if (err < 0) {
|
||||
devres_free(dmab);
|
||||
return NULL;
|
||||
@ -166,7 +173,7 @@ snd_devm_alloc_pages(struct device *dev, int type, size_t size)
|
||||
devres_add(dev, dmab);
|
||||
return dmab;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_devm_alloc_pages);
|
||||
EXPORT_SYMBOL_GPL(snd_devm_alloc_dir_pages);
|
||||
|
||||
/**
|
||||
* snd_dma_buffer_mmap - perform mmap of the given DMA buffer
|
||||
@ -185,6 +192,26 @@ int snd_dma_buffer_mmap(struct snd_dma_buffer *dmab,
|
||||
}
|
||||
EXPORT_SYMBOL(snd_dma_buffer_mmap);
|
||||
|
||||
#ifdef CONFIG_HAS_DMA
|
||||
/**
|
||||
* snd_dma_buffer_sync - sync DMA buffer between CPU and device
|
||||
* @dmab: buffer allocation information
|
||||
* @mode: sync mode
|
||||
*/
|
||||
void snd_dma_buffer_sync(struct snd_dma_buffer *dmab,
|
||||
enum snd_dma_sync_mode mode)
|
||||
{
|
||||
const struct snd_malloc_ops *ops;
|
||||
|
||||
if (!dmab || !dmab->dev.need_sync)
|
||||
return;
|
||||
ops = snd_dma_get_ops(dmab);
|
||||
if (ops && ops->sync)
|
||||
ops->sync(dmab, mode);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_dma_buffer_sync);
|
||||
#endif /* CONFIG_HAS_DMA */
|
||||
|
||||
/**
|
||||
* snd_sgbuf_get_addr - return the physical address at the corresponding offset
|
||||
* @dmab: buffer allocation information
|
||||
@ -468,6 +495,161 @@ static const struct snd_malloc_ops snd_dma_wc_ops = {
|
||||
.mmap = snd_dma_wc_mmap,
|
||||
};
|
||||
#endif /* CONFIG_X86 */
|
||||
|
||||
/*
|
||||
* Non-contiguous pages allocator
|
||||
*/
|
||||
static void *snd_dma_noncontig_alloc(struct snd_dma_buffer *dmab, size_t size)
|
||||
{
|
||||
struct sg_table *sgt;
|
||||
void *p;
|
||||
|
||||
sgt = dma_alloc_noncontiguous(dmab->dev.dev, size, dmab->dev.dir,
|
||||
DEFAULT_GFP, 0);
|
||||
if (!sgt)
|
||||
return NULL;
|
||||
dmab->dev.need_sync = dma_need_sync(dmab->dev.dev, dmab->dev.dir);
|
||||
p = dma_vmap_noncontiguous(dmab->dev.dev, size, sgt);
|
||||
if (p)
|
||||
dmab->private_data = sgt;
|
||||
else
|
||||
dma_free_noncontiguous(dmab->dev.dev, size, sgt, dmab->dev.dir);
|
||||
return p;
|
||||
}
|
||||
|
||||
static void snd_dma_noncontig_free(struct snd_dma_buffer *dmab)
|
||||
{
|
||||
dma_vunmap_noncontiguous(dmab->dev.dev, dmab->area);
|
||||
dma_free_noncontiguous(dmab->dev.dev, dmab->bytes, dmab->private_data,
|
||||
dmab->dev.dir);
|
||||
}
|
||||
|
||||
static int snd_dma_noncontig_mmap(struct snd_dma_buffer *dmab,
|
||||
struct vm_area_struct *area)
|
||||
{
|
||||
return dma_mmap_noncontiguous(dmab->dev.dev, area,
|
||||
dmab->bytes, dmab->private_data);
|
||||
}
|
||||
|
||||
static void snd_dma_noncontig_sync(struct snd_dma_buffer *dmab,
|
||||
enum snd_dma_sync_mode mode)
|
||||
{
|
||||
if (mode == SNDRV_DMA_SYNC_CPU) {
|
||||
if (dmab->dev.dir == DMA_TO_DEVICE)
|
||||
return;
|
||||
dma_sync_sgtable_for_cpu(dmab->dev.dev, dmab->private_data,
|
||||
dmab->dev.dir);
|
||||
invalidate_kernel_vmap_range(dmab->area, dmab->bytes);
|
||||
} else {
|
||||
if (dmab->dev.dir == DMA_FROM_DEVICE)
|
||||
return;
|
||||
flush_kernel_vmap_range(dmab->area, dmab->bytes);
|
||||
dma_sync_sgtable_for_device(dmab->dev.dev, dmab->private_data,
|
||||
dmab->dev.dir);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct snd_malloc_ops snd_dma_noncontig_ops = {
|
||||
.alloc = snd_dma_noncontig_alloc,
|
||||
.free = snd_dma_noncontig_free,
|
||||
.mmap = snd_dma_noncontig_mmap,
|
||||
.sync = snd_dma_noncontig_sync,
|
||||
/* re-use vmalloc helpers for get_* ops */
|
||||
.get_addr = snd_dma_vmalloc_get_addr,
|
||||
.get_page = snd_dma_vmalloc_get_page,
|
||||
.get_chunk_size = snd_dma_vmalloc_get_chunk_size,
|
||||
};
|
||||
|
||||
/* x86-specific SG-buffer with WC pages */
|
||||
#ifdef CONFIG_SND_DMA_SGBUF
|
||||
#define vmalloc_to_virt(v) (unsigned long)page_to_virt(vmalloc_to_page(v))
|
||||
|
||||
static void *snd_dma_sg_wc_alloc(struct snd_dma_buffer *dmab, size_t size)
|
||||
{
|
||||
void *p = snd_dma_noncontig_alloc(dmab, size);
|
||||
size_t ofs;
|
||||
|
||||
if (!p)
|
||||
return NULL;
|
||||
for (ofs = 0; ofs < size; ofs += PAGE_SIZE)
|
||||
set_memory_uc(vmalloc_to_virt(p + ofs), 1);
|
||||
return p;
|
||||
}
|
||||
|
||||
static void snd_dma_sg_wc_free(struct snd_dma_buffer *dmab)
|
||||
{
|
||||
size_t ofs;
|
||||
|
||||
for (ofs = 0; ofs < dmab->bytes; ofs += PAGE_SIZE)
|
||||
set_memory_wb(vmalloc_to_virt(dmab->area + ofs), 1);
|
||||
snd_dma_noncontig_free(dmab);
|
||||
}
|
||||
|
||||
static int snd_dma_sg_wc_mmap(struct snd_dma_buffer *dmab,
|
||||
struct vm_area_struct *area)
|
||||
{
|
||||
area->vm_page_prot = pgprot_writecombine(area->vm_page_prot);
|
||||
/* FIXME: dma_mmap_noncontiguous() works? */
|
||||
return -ENOENT; /* continue with the default mmap handler */
|
||||
}
|
||||
|
||||
const struct snd_malloc_ops snd_dma_sg_wc_ops = {
|
||||
.alloc = snd_dma_sg_wc_alloc,
|
||||
.free = snd_dma_sg_wc_free,
|
||||
.mmap = snd_dma_sg_wc_mmap,
|
||||
.sync = snd_dma_noncontig_sync,
|
||||
.get_addr = snd_dma_vmalloc_get_addr,
|
||||
.get_page = snd_dma_vmalloc_get_page,
|
||||
.get_chunk_size = snd_dma_vmalloc_get_chunk_size,
|
||||
};
|
||||
#endif /* CONFIG_SND_DMA_SGBUF */
|
||||
|
||||
/*
|
||||
* Non-coherent pages allocator
|
||||
*/
|
||||
static void *snd_dma_noncoherent_alloc(struct snd_dma_buffer *dmab, size_t size)
|
||||
{
|
||||
dmab->dev.need_sync = dma_need_sync(dmab->dev.dev, dmab->dev.dir);
|
||||
return dma_alloc_noncoherent(dmab->dev.dev, size, &dmab->addr,
|
||||
dmab->dev.dir, DEFAULT_GFP);
|
||||
}
|
||||
|
||||
static void snd_dma_noncoherent_free(struct snd_dma_buffer *dmab)
|
||||
{
|
||||
dma_free_noncoherent(dmab->dev.dev, dmab->bytes, dmab->area,
|
||||
dmab->addr, dmab->dev.dir);
|
||||
}
|
||||
|
||||
static int snd_dma_noncoherent_mmap(struct snd_dma_buffer *dmab,
|
||||
struct vm_area_struct *area)
|
||||
{
|
||||
area->vm_page_prot = vm_get_page_prot(area->vm_flags);
|
||||
return dma_mmap_pages(dmab->dev.dev, area,
|
||||
area->vm_end - area->vm_start,
|
||||
virt_to_page(dmab->area));
|
||||
}
|
||||
|
||||
static void snd_dma_noncoherent_sync(struct snd_dma_buffer *dmab,
|
||||
enum snd_dma_sync_mode mode)
|
||||
{
|
||||
if (mode == SNDRV_DMA_SYNC_CPU) {
|
||||
if (dmab->dev.dir != DMA_TO_DEVICE)
|
||||
dma_sync_single_for_cpu(dmab->dev.dev, dmab->addr,
|
||||
dmab->bytes, dmab->dev.dir);
|
||||
} else {
|
||||
if (dmab->dev.dir != DMA_FROM_DEVICE)
|
||||
dma_sync_single_for_device(dmab->dev.dev, dmab->addr,
|
||||
dmab->bytes, dmab->dev.dir);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct snd_malloc_ops snd_dma_noncoherent_ops = {
|
||||
.alloc = snd_dma_noncoherent_alloc,
|
||||
.free = snd_dma_noncoherent_free,
|
||||
.mmap = snd_dma_noncoherent_mmap,
|
||||
.sync = snd_dma_noncoherent_sync,
|
||||
};
|
||||
|
||||
#endif /* CONFIG_HAS_DMA */
|
||||
|
||||
/*
|
||||
@ -479,14 +661,15 @@ static const struct snd_malloc_ops *dma_ops[] = {
|
||||
#ifdef CONFIG_HAS_DMA
|
||||
[SNDRV_DMA_TYPE_DEV] = &snd_dma_dev_ops,
|
||||
[SNDRV_DMA_TYPE_DEV_WC] = &snd_dma_wc_ops,
|
||||
[SNDRV_DMA_TYPE_NONCONTIG] = &snd_dma_noncontig_ops,
|
||||
[SNDRV_DMA_TYPE_NONCOHERENT] = &snd_dma_noncoherent_ops,
|
||||
#ifdef CONFIG_SND_DMA_SGBUF
|
||||
[SNDRV_DMA_TYPE_DEV_WC_SG] = &snd_dma_sg_wc_ops,
|
||||
#endif
|
||||
#ifdef CONFIG_GENERIC_ALLOCATOR
|
||||
[SNDRV_DMA_TYPE_DEV_IRAM] = &snd_dma_iram_ops,
|
||||
#endif /* CONFIG_GENERIC_ALLOCATOR */
|
||||
#endif /* CONFIG_HAS_DMA */
|
||||
#ifdef CONFIG_SND_DMA_SGBUF
|
||||
[SNDRV_DMA_TYPE_DEV_SG] = &snd_dma_sg_ops,
|
||||
[SNDRV_DMA_TYPE_DEV_WC_SG] = &snd_dma_sg_ops,
|
||||
#endif
|
||||
};
|
||||
|
||||
static const struct snd_malloc_ops *snd_dma_get_ops(struct snd_dma_buffer *dmab)
|
||||
|
@ -10,6 +10,7 @@ struct snd_malloc_ops {
|
||||
unsigned int (*get_chunk_size)(struct snd_dma_buffer *dmab,
|
||||
unsigned int ofs, unsigned int size);
|
||||
int (*mmap)(struct snd_dma_buffer *dmab, struct vm_area_struct *area);
|
||||
void (*sync)(struct snd_dma_buffer *dmab, enum snd_dma_sync_mode mode);
|
||||
};
|
||||
|
||||
#ifdef CONFIG_SND_DMA_SGBUF
|
||||
|
@ -453,6 +453,8 @@ static int snd_pcm_ioctl_sync_ptr_x32(struct snd_pcm_substream *substream,
|
||||
sstatus.suspended_state = status->suspended_state;
|
||||
sstatus.audio_tstamp = status->audio_tstamp;
|
||||
snd_pcm_stream_unlock_irq(substream);
|
||||
if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL))
|
||||
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE);
|
||||
if (put_user(sstatus.state, &src->s.status.state) ||
|
||||
put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) ||
|
||||
put_user(sstatus.tstamp.tv_sec, &src->s.status.tstamp_sec) ||
|
||||
@ -533,6 +535,8 @@ static int snd_pcm_ioctl_sync_ptr_buggy(struct snd_pcm_substream *substream,
|
||||
sync_ptr.s.status.suspended_state = status->suspended_state;
|
||||
sync_ptr.s.status.audio_tstamp = status->audio_tstamp;
|
||||
snd_pcm_stream_unlock_irq(substream);
|
||||
if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_APPL))
|
||||
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE);
|
||||
if (copy_to_user(_sync_ptr, &sync_ptr, sizeof(sync_ptr)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
|
@ -106,6 +106,7 @@ void snd_pcm_playback_silence(struct snd_pcm_substream *substream, snd_pcm_ufram
|
||||
frames -= transfer;
|
||||
ofs = 0;
|
||||
}
|
||||
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SND_DEBUG
|
||||
@ -2256,8 +2257,12 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,
|
||||
goto _end_unlock;
|
||||
}
|
||||
snd_pcm_stream_unlock_irq(substream);
|
||||
if (!is_playback)
|
||||
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_CPU);
|
||||
err = writer(substream, appl_ofs, data, offset, frames,
|
||||
transfer);
|
||||
if (is_playback)
|
||||
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE);
|
||||
snd_pcm_stream_lock_irq(substream);
|
||||
if (err < 0)
|
||||
goto _end_unlock;
|
||||
|
@ -73,4 +73,11 @@ void snd_pcm_sync_stop(struct snd_pcm_substream *substream, bool sync_irq);
|
||||
for ((subs) = (pcm)->streams[str].substream; (subs); \
|
||||
(subs) = (subs)->next)
|
||||
|
||||
static inline void snd_pcm_dma_buffer_sync(struct snd_pcm_substream *substream,
|
||||
enum snd_dma_sync_mode mode)
|
||||
{
|
||||
if (substream->runtime->info & SNDRV_PCM_INFO_EXPLICIT_SYNC)
|
||||
snd_dma_buffer_sync(snd_pcm_get_dma_buf(substream), mode);
|
||||
}
|
||||
|
||||
#endif /* __SOUND_CORE_PCM_LOCAL_H */
|
||||
|
@ -32,15 +32,20 @@ module_param(max_alloc_per_card, ulong, 0644);
|
||||
MODULE_PARM_DESC(max_alloc_per_card, "Max total allocation bytes per card.");
|
||||
|
||||
static int do_alloc_pages(struct snd_card *card, int type, struct device *dev,
|
||||
size_t size, struct snd_dma_buffer *dmab)
|
||||
int str, size_t size, struct snd_dma_buffer *dmab)
|
||||
{
|
||||
enum dma_data_direction dir;
|
||||
int err;
|
||||
|
||||
if (max_alloc_per_card &&
|
||||
card->total_pcm_alloc_bytes + size > max_alloc_per_card)
|
||||
return -ENOMEM;
|
||||
|
||||
err = snd_dma_alloc_pages(type, dev, size, dmab);
|
||||
if (str == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
dir = DMA_TO_DEVICE;
|
||||
else
|
||||
dir = DMA_FROM_DEVICE;
|
||||
err = snd_dma_alloc_dir_pages(type, dev, dir, size, dmab);
|
||||
if (!err) {
|
||||
mutex_lock(&card->memory_mutex);
|
||||
card->total_pcm_alloc_bytes += dmab->bytes;
|
||||
@ -77,7 +82,7 @@ static int preallocate_pcm_pages(struct snd_pcm_substream *substream,
|
||||
|
||||
do {
|
||||
err = do_alloc_pages(card, dmab->dev.type, dmab->dev.dev,
|
||||
size, dmab);
|
||||
substream->stream, size, dmab);
|
||||
if (err != -ENOMEM)
|
||||
return err;
|
||||
if (no_fallback)
|
||||
@ -177,6 +182,7 @@ static void snd_pcm_lib_preallocate_proc_write(struct snd_info_entry *entry,
|
||||
if (do_alloc_pages(card,
|
||||
substream->dma_buffer.dev.type,
|
||||
substream->dma_buffer.dev.dev,
|
||||
substream->stream,
|
||||
size, &new_dmab) < 0) {
|
||||
buffer->error = -ENOMEM;
|
||||
pr_debug("ALSA pcmC%dD%d%c,%d:%s: cannot preallocate for size %zu\n",
|
||||
@ -418,6 +424,7 @@ int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size)
|
||||
if (do_alloc_pages(card,
|
||||
substream->dma_buffer.dev.type,
|
||||
substream->dma_buffer.dev.dev,
|
||||
substream->stream,
|
||||
size, dmab) < 0) {
|
||||
kfree(dmab);
|
||||
pr_debug("ALSA pcmC%dD%d%c,%d:%s: cannot preallocate for size %zu\n",
|
||||
|
@ -2685,6 +2685,13 @@ int snd_pcm_open_substream(struct snd_pcm *pcm, int stream,
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* automatically set EXPLICIT_SYNC flag in the managed mode whenever
|
||||
* the DMA buffer requires it
|
||||
*/
|
||||
if (substream->managed_buffer_alloc &&
|
||||
substream->dma_buffer.dev.need_sync)
|
||||
substream->runtime->hw.info |= SNDRV_PCM_INFO_EXPLICIT_SYNC;
|
||||
|
||||
*rsubstream = substream;
|
||||
return 0;
|
||||
|
||||
@ -2912,6 +2919,8 @@ static snd_pcm_sframes_t snd_pcm_rewind(struct snd_pcm_substream *substream,
|
||||
ret = rewind_appl_ptr(substream, frames,
|
||||
snd_pcm_hw_avail(substream));
|
||||
snd_pcm_stream_unlock_irq(substream);
|
||||
if (ret >= 0)
|
||||
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -2929,35 +2938,31 @@ static snd_pcm_sframes_t snd_pcm_forward(struct snd_pcm_substream *substream,
|
||||
ret = forward_appl_ptr(substream, frames,
|
||||
snd_pcm_avail(substream));
|
||||
snd_pcm_stream_unlock_irq(substream);
|
||||
if (ret >= 0)
|
||||
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int snd_pcm_hwsync(struct snd_pcm_substream *substream)
|
||||
{
|
||||
int err;
|
||||
|
||||
snd_pcm_stream_lock_irq(substream);
|
||||
err = do_pcm_hwsync(substream);
|
||||
snd_pcm_stream_unlock_irq(substream);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int snd_pcm_delay(struct snd_pcm_substream *substream,
|
||||
snd_pcm_sframes_t *delay)
|
||||
{
|
||||
int err;
|
||||
snd_pcm_sframes_t n = 0;
|
||||
|
||||
snd_pcm_stream_lock_irq(substream);
|
||||
err = do_pcm_hwsync(substream);
|
||||
if (!err)
|
||||
n = snd_pcm_calc_delay(substream);
|
||||
if (delay && !err)
|
||||
*delay = snd_pcm_calc_delay(substream);
|
||||
snd_pcm_stream_unlock_irq(substream);
|
||||
if (!err)
|
||||
*delay = n;
|
||||
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_CPU);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static inline int snd_pcm_hwsync(struct snd_pcm_substream *substream)
|
||||
{
|
||||
return snd_pcm_delay(substream, NULL);
|
||||
}
|
||||
|
||||
static int snd_pcm_sync_ptr(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_sync_ptr __user *_sync_ptr)
|
||||
{
|
||||
@ -3000,6 +3005,8 @@ static int snd_pcm_sync_ptr(struct snd_pcm_substream *substream,
|
||||
sync_ptr.s.status.suspended_state = status->suspended_state;
|
||||
sync_ptr.s.status.audio_tstamp = status->audio_tstamp;
|
||||
snd_pcm_stream_unlock_irq(substream);
|
||||
if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_APPL))
|
||||
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE);
|
||||
if (copy_to_user(_sync_ptr, &sync_ptr, sizeof(sync_ptr)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
@ -3096,6 +3103,8 @@ static int snd_pcm_ioctl_sync_ptr_compat(struct snd_pcm_substream *substream,
|
||||
sstatus.suspended_state = status->suspended_state;
|
||||
sstatus.audio_tstamp = status->audio_tstamp;
|
||||
snd_pcm_stream_unlock_irq(substream);
|
||||
if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL))
|
||||
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE);
|
||||
if (put_user(sstatus.state, &src->s.status.state) ||
|
||||
put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) ||
|
||||
put_user(sstatus.tstamp.tv_sec, &src->s.status.tstamp_sec) ||
|
||||
@ -3218,6 +3227,9 @@ static int snd_pcm_common_ioctl(struct file *file,
|
||||
if (PCM_RUNTIME_CHECK(substream))
|
||||
return -ENXIO;
|
||||
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED)
|
||||
return -EBADFD;
|
||||
|
||||
res = snd_power_wait(substream->pcm->card);
|
||||
if (res < 0)
|
||||
return res;
|
||||
@ -3272,7 +3284,7 @@ static int snd_pcm_common_ioctl(struct file *file,
|
||||
return snd_pcm_hwsync(substream);
|
||||
case SNDRV_PCM_IOCTL_DELAY:
|
||||
{
|
||||
snd_pcm_sframes_t delay;
|
||||
snd_pcm_sframes_t delay = 0;
|
||||
snd_pcm_sframes_t __user *res = arg;
|
||||
int err;
|
||||
|
||||
@ -3344,6 +3356,9 @@ int snd_pcm_kernel_ioctl(struct snd_pcm_substream *substream,
|
||||
snd_pcm_uframes_t *frames = arg;
|
||||
snd_pcm_sframes_t result;
|
||||
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED)
|
||||
return -EBADFD;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_IOCTL_FORWARD:
|
||||
{
|
||||
@ -3386,7 +3401,8 @@ static ssize_t snd_pcm_read(struct file *file, char __user *buf, size_t count,
|
||||
if (PCM_RUNTIME_CHECK(substream))
|
||||
return -ENXIO;
|
||||
runtime = substream->runtime;
|
||||
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
|
||||
if (runtime->status->state == SNDRV_PCM_STATE_OPEN ||
|
||||
runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED)
|
||||
return -EBADFD;
|
||||
if (!frame_aligned(runtime, count))
|
||||
return -EINVAL;
|
||||
@ -3410,7 +3426,8 @@ static ssize_t snd_pcm_write(struct file *file, const char __user *buf,
|
||||
if (PCM_RUNTIME_CHECK(substream))
|
||||
return -ENXIO;
|
||||
runtime = substream->runtime;
|
||||
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
|
||||
if (runtime->status->state == SNDRV_PCM_STATE_OPEN ||
|
||||
runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED)
|
||||
return -EBADFD;
|
||||
if (!frame_aligned(runtime, count))
|
||||
return -EINVAL;
|
||||
@ -3436,7 +3453,8 @@ static ssize_t snd_pcm_readv(struct kiocb *iocb, struct iov_iter *to)
|
||||
if (PCM_RUNTIME_CHECK(substream))
|
||||
return -ENXIO;
|
||||
runtime = substream->runtime;
|
||||
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
|
||||
if (runtime->status->state == SNDRV_PCM_STATE_OPEN ||
|
||||
runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED)
|
||||
return -EBADFD;
|
||||
if (!iter_is_iovec(to))
|
||||
return -EINVAL;
|
||||
@ -3472,7 +3490,8 @@ static ssize_t snd_pcm_writev(struct kiocb *iocb, struct iov_iter *from)
|
||||
if (PCM_RUNTIME_CHECK(substream))
|
||||
return -ENXIO;
|
||||
runtime = substream->runtime;
|
||||
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
|
||||
if (runtime->status->state == SNDRV_PCM_STATE_OPEN ||
|
||||
runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED)
|
||||
return -EBADFD;
|
||||
if (!iter_is_iovec(from))
|
||||
return -EINVAL;
|
||||
@ -3511,6 +3530,9 @@ static __poll_t snd_pcm_poll(struct file *file, poll_table *wait)
|
||||
return ok | EPOLLERR;
|
||||
|
||||
runtime = substream->runtime;
|
||||
if (runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED)
|
||||
return ok | EPOLLERR;
|
||||
|
||||
poll_wait(file, &runtime->sleep, wait);
|
||||
|
||||
mask = 0;
|
||||
@ -3820,6 +3842,8 @@ static int snd_pcm_mmap(struct file *file, struct vm_area_struct *area)
|
||||
substream = pcm_file->substream;
|
||||
if (PCM_RUNTIME_CHECK(substream))
|
||||
return -ENXIO;
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED)
|
||||
return -EBADFD;
|
||||
|
||||
offset = area->vm_pgoff << PAGE_SHIFT;
|
||||
switch (offset) {
|
||||
@ -3856,6 +3880,8 @@ static int snd_pcm_fasync(int fd, struct file * file, int on)
|
||||
if (PCM_RUNTIME_CHECK(substream))
|
||||
return -ENXIO;
|
||||
runtime = substream->runtime;
|
||||
if (runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED)
|
||||
return -EBADFD;
|
||||
return fasync_helper(fd, file, on, &runtime->fasync);
|
||||
}
|
||||
|
||||
|
@ -1,201 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Scatter-Gather buffer
|
||||
*
|
||||
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/export.h>
|
||||
#include <sound/memalloc.h>
|
||||
#include "memalloc_local.h"
|
||||
|
||||
struct snd_sg_page {
|
||||
void *buf;
|
||||
dma_addr_t addr;
|
||||
};
|
||||
|
||||
struct snd_sg_buf {
|
||||
int size; /* allocated byte size */
|
||||
int pages; /* allocated pages */
|
||||
int tblsize; /* allocated table size */
|
||||
struct snd_sg_page *table; /* address table */
|
||||
struct page **page_table; /* page table (for vmap/vunmap) */
|
||||
struct device *dev;
|
||||
};
|
||||
|
||||
/* table entries are align to 32 */
|
||||
#define SGBUF_TBL_ALIGN 32
|
||||
#define sgbuf_align_table(tbl) ALIGN((tbl), SGBUF_TBL_ALIGN)
|
||||
|
||||
static void snd_dma_sg_free(struct snd_dma_buffer *dmab)
|
||||
{
|
||||
struct snd_sg_buf *sgbuf = dmab->private_data;
|
||||
struct snd_dma_buffer tmpb;
|
||||
int i;
|
||||
|
||||
if (!sgbuf)
|
||||
return;
|
||||
|
||||
vunmap(dmab->area);
|
||||
dmab->area = NULL;
|
||||
|
||||
tmpb.dev.type = SNDRV_DMA_TYPE_DEV;
|
||||
if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_WC_SG)
|
||||
tmpb.dev.type = SNDRV_DMA_TYPE_DEV_WC;
|
||||
tmpb.dev.dev = sgbuf->dev;
|
||||
for (i = 0; i < sgbuf->pages; i++) {
|
||||
if (!(sgbuf->table[i].addr & ~PAGE_MASK))
|
||||
continue; /* continuous pages */
|
||||
tmpb.area = sgbuf->table[i].buf;
|
||||
tmpb.addr = sgbuf->table[i].addr & PAGE_MASK;
|
||||
tmpb.bytes = (sgbuf->table[i].addr & ~PAGE_MASK) << PAGE_SHIFT;
|
||||
snd_dma_free_pages(&tmpb);
|
||||
}
|
||||
|
||||
kfree(sgbuf->table);
|
||||
kfree(sgbuf->page_table);
|
||||
kfree(sgbuf);
|
||||
dmab->private_data = NULL;
|
||||
}
|
||||
|
||||
#define MAX_ALLOC_PAGES 32
|
||||
|
||||
static void *snd_dma_sg_alloc(struct snd_dma_buffer *dmab, size_t size)
|
||||
{
|
||||
struct snd_sg_buf *sgbuf;
|
||||
unsigned int i, pages, chunk, maxpages;
|
||||
struct snd_dma_buffer tmpb;
|
||||
struct snd_sg_page *table;
|
||||
struct page **pgtable;
|
||||
int type = SNDRV_DMA_TYPE_DEV;
|
||||
pgprot_t prot = PAGE_KERNEL;
|
||||
void *area;
|
||||
|
||||
dmab->private_data = sgbuf = kzalloc(sizeof(*sgbuf), GFP_KERNEL);
|
||||
if (!sgbuf)
|
||||
return NULL;
|
||||
if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_WC_SG) {
|
||||
type = SNDRV_DMA_TYPE_DEV_WC;
|
||||
#ifdef pgprot_noncached
|
||||
prot = pgprot_noncached(PAGE_KERNEL);
|
||||
#endif
|
||||
}
|
||||
sgbuf->dev = dmab->dev.dev;
|
||||
pages = snd_sgbuf_aligned_pages(size);
|
||||
sgbuf->tblsize = sgbuf_align_table(pages);
|
||||
table = kcalloc(sgbuf->tblsize, sizeof(*table), GFP_KERNEL);
|
||||
if (!table)
|
||||
goto _failed;
|
||||
sgbuf->table = table;
|
||||
pgtable = kcalloc(sgbuf->tblsize, sizeof(*pgtable), GFP_KERNEL);
|
||||
if (!pgtable)
|
||||
goto _failed;
|
||||
sgbuf->page_table = pgtable;
|
||||
|
||||
/* allocate pages */
|
||||
maxpages = MAX_ALLOC_PAGES;
|
||||
while (pages > 0) {
|
||||
chunk = pages;
|
||||
/* don't be too eager to take a huge chunk */
|
||||
if (chunk > maxpages)
|
||||
chunk = maxpages;
|
||||
chunk <<= PAGE_SHIFT;
|
||||
if (snd_dma_alloc_pages_fallback(type, dmab->dev.dev,
|
||||
chunk, &tmpb) < 0) {
|
||||
if (!sgbuf->pages)
|
||||
goto _failed;
|
||||
size = sgbuf->pages * PAGE_SIZE;
|
||||
break;
|
||||
}
|
||||
chunk = tmpb.bytes >> PAGE_SHIFT;
|
||||
for (i = 0; i < chunk; i++) {
|
||||
table->buf = tmpb.area;
|
||||
table->addr = tmpb.addr;
|
||||
if (!i)
|
||||
table->addr |= chunk; /* mark head */
|
||||
table++;
|
||||
*pgtable++ = virt_to_page(tmpb.area);
|
||||
tmpb.area += PAGE_SIZE;
|
||||
tmpb.addr += PAGE_SIZE;
|
||||
}
|
||||
sgbuf->pages += chunk;
|
||||
pages -= chunk;
|
||||
if (chunk < maxpages)
|
||||
maxpages = chunk;
|
||||
}
|
||||
|
||||
sgbuf->size = size;
|
||||
area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, prot);
|
||||
if (!area)
|
||||
goto _failed;
|
||||
return area;
|
||||
|
||||
_failed:
|
||||
snd_dma_sg_free(dmab); /* free the table */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static dma_addr_t snd_dma_sg_get_addr(struct snd_dma_buffer *dmab,
|
||||
size_t offset)
|
||||
{
|
||||
struct snd_sg_buf *sgbuf = dmab->private_data;
|
||||
dma_addr_t addr;
|
||||
|
||||
addr = sgbuf->table[offset >> PAGE_SHIFT].addr;
|
||||
addr &= ~((dma_addr_t)PAGE_SIZE - 1);
|
||||
return addr + offset % PAGE_SIZE;
|
||||
}
|
||||
|
||||
static struct page *snd_dma_sg_get_page(struct snd_dma_buffer *dmab,
|
||||
size_t offset)
|
||||
{
|
||||
struct snd_sg_buf *sgbuf = dmab->private_data;
|
||||
unsigned int idx = offset >> PAGE_SHIFT;
|
||||
|
||||
if (idx >= (unsigned int)sgbuf->pages)
|
||||
return NULL;
|
||||
return sgbuf->page_table[idx];
|
||||
}
|
||||
|
||||
static unsigned int snd_dma_sg_get_chunk_size(struct snd_dma_buffer *dmab,
|
||||
unsigned int ofs,
|
||||
unsigned int size)
|
||||
{
|
||||
struct snd_sg_buf *sg = dmab->private_data;
|
||||
unsigned int start, end, pg;
|
||||
|
||||
start = ofs >> PAGE_SHIFT;
|
||||
end = (ofs + size - 1) >> PAGE_SHIFT;
|
||||
/* check page continuity */
|
||||
pg = sg->table[start].addr >> PAGE_SHIFT;
|
||||
for (;;) {
|
||||
start++;
|
||||
if (start > end)
|
||||
break;
|
||||
pg++;
|
||||
if ((sg->table[start].addr >> PAGE_SHIFT) != pg)
|
||||
return (start << PAGE_SHIFT) - ofs;
|
||||
}
|
||||
/* ok, all on continuous pages */
|
||||
return size;
|
||||
}
|
||||
|
||||
static int snd_dma_sg_mmap(struct snd_dma_buffer *dmab,
|
||||
struct vm_area_struct *area)
|
||||
{
|
||||
if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_WC_SG)
|
||||
area->vm_page_prot = pgprot_writecombine(area->vm_page_prot);
|
||||
return -ENOENT; /* continue with the default mmap handler */
|
||||
}
|
||||
|
||||
const struct snd_malloc_ops snd_dma_sg_ops = {
|
||||
.alloc = snd_dma_sg_alloc,
|
||||
.free = snd_dma_sg_free,
|
||||
.get_addr = snd_dma_sg_get_addr,
|
||||
.get_page = snd_dma_sg_get_page,
|
||||
.get_chunk_size = snd_dma_sg_get_chunk_size,
|
||||
.mmap = snd_dma_sg_mmap,
|
||||
};
|
@ -4,5 +4,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 \
|
||||
motu-protocol-v1.o
|
||||
motu-protocol-v1.o motu-register-dsp-message-parser.o \
|
||||
motu-command-dsp-message-parser.o
|
||||
obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o
|
||||
|
@ -333,6 +333,7 @@ static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s,
|
||||
unsigned int packets,
|
||||
struct snd_pcm_substream *pcm)
|
||||
{
|
||||
struct snd_motu *motu = container_of(s, struct snd_motu, tx_stream);
|
||||
struct amdtp_motu *p = s->protocol;
|
||||
unsigned int pcm_frames = 0;
|
||||
int i;
|
||||
@ -357,6 +358,14 @@ static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s,
|
||||
read_midi_messages(s, buf, data_blocks);
|
||||
}
|
||||
|
||||
if (motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP) {
|
||||
snd_motu_register_dsp_message_parser_parse(motu, descs, packets,
|
||||
s->data_block_quadlets);
|
||||
} else if (motu->spec->flags & SND_MOTU_SPEC_COMMAND_DSP) {
|
||||
snd_motu_command_dsp_message_parser_parse(motu, descs, packets,
|
||||
s->data_block_quadlets);
|
||||
}
|
||||
|
||||
// For tracepoints.
|
||||
if (trace_data_block_sph_enabled() ||
|
||||
trace_data_block_message_enabled())
|
||||
@ -415,8 +424,6 @@ static unsigned int process_it_ctx_payloads(struct amdtp_stream *s,
|
||||
if (p->midi_ports)
|
||||
write_midi_messages(s, buf, data_blocks);
|
||||
|
||||
// TODO: how to interact control messages between userspace?
|
||||
|
||||
write_sph(p->cache, buf, data_blocks, s->data_block_quadlets);
|
||||
}
|
||||
|
||||
|
181
sound/firewire/motu/motu-command-dsp-message-parser.c
Normal file
181
sound/firewire/motu/motu-command-dsp-message-parser.c
Normal file
@ -0,0 +1,181 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
//
|
||||
// motu-command-dsp-message-parser.c - a part of driver for MOTU FireWire series
|
||||
//
|
||||
// Copyright (c) 2021 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
||||
|
||||
// Below models allow software to configure their DSP function by command transferred in
|
||||
// asynchronous transaction:
|
||||
// * 828 mk3 (FireWire only and Hybrid)
|
||||
// * 896 mk3 (FireWire only and Hybrid)
|
||||
// * Ultralite mk3 (FireWire only and Hybrid)
|
||||
// * Traveler mk3
|
||||
// * Track 16
|
||||
//
|
||||
// Isochronous packets from the above models includes messages to report state of hardware meter.
|
||||
|
||||
#include "motu.h"
|
||||
|
||||
enum msg_parser_state {
|
||||
INITIALIZED,
|
||||
FRAGMENT_DETECTED,
|
||||
AVAILABLE,
|
||||
};
|
||||
|
||||
struct msg_parser {
|
||||
spinlock_t lock;
|
||||
enum msg_parser_state state;
|
||||
unsigned int interval;
|
||||
unsigned int message_count;
|
||||
unsigned int fragment_pos;
|
||||
unsigned int value_index;
|
||||
u64 value;
|
||||
struct snd_firewire_motu_command_dsp_meter meter;
|
||||
};
|
||||
|
||||
int snd_motu_command_dsp_message_parser_new(struct snd_motu *motu)
|
||||
{
|
||||
struct msg_parser *parser;
|
||||
|
||||
parser = devm_kzalloc(&motu->card->card_dev, sizeof(*parser), GFP_KERNEL);
|
||||
if (!parser)
|
||||
return -ENOMEM;
|
||||
spin_lock_init(&parser->lock);
|
||||
motu->message_parser = parser;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int snd_motu_command_dsp_message_parser_init(struct snd_motu *motu, enum cip_sfc sfc)
|
||||
{
|
||||
struct msg_parser *parser = motu->message_parser;
|
||||
|
||||
parser->state = INITIALIZED;
|
||||
|
||||
// All of data blocks don't have messages with meaningful information.
|
||||
switch (sfc) {
|
||||
case CIP_SFC_176400:
|
||||
case CIP_SFC_192000:
|
||||
parser->interval = 4;
|
||||
break;
|
||||
case CIP_SFC_88200:
|
||||
case CIP_SFC_96000:
|
||||
parser->interval = 2;
|
||||
break;
|
||||
case CIP_SFC_32000:
|
||||
case CIP_SFC_44100:
|
||||
case CIP_SFC_48000:
|
||||
default:
|
||||
parser->interval = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define FRAGMENT_POS 6
|
||||
#define MIDI_BYTE_POS 7
|
||||
#define MIDI_FLAG_POS 8
|
||||
// One value of hardware meter consists of 4 messages.
|
||||
#define FRAGMENTS_PER_VALUE 4
|
||||
#define VALUES_AT_IMAGE_END 0xffffffffffffffff
|
||||
|
||||
void snd_motu_command_dsp_message_parser_parse(struct snd_motu *motu, const struct pkt_desc *descs,
|
||||
unsigned int desc_count, unsigned int data_block_quadlets)
|
||||
{
|
||||
struct msg_parser *parser = motu->message_parser;
|
||||
unsigned int interval = parser->interval;
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
spin_lock_irqsave(&parser->lock, flags);
|
||||
|
||||
for (i = 0; i < desc_count; ++i) {
|
||||
const struct pkt_desc *desc = descs + i;
|
||||
__be32 *buffer = desc->ctx_payload;
|
||||
unsigned int data_blocks = desc->data_blocks;
|
||||
int j;
|
||||
|
||||
for (j = 0; j < data_blocks; ++j) {
|
||||
u8 *b = (u8 *)buffer;
|
||||
buffer += data_block_quadlets;
|
||||
|
||||
switch (parser->state) {
|
||||
case INITIALIZED:
|
||||
{
|
||||
u8 fragment = b[FRAGMENT_POS];
|
||||
|
||||
if (fragment > 0) {
|
||||
parser->value = fragment;
|
||||
parser->message_count = 1;
|
||||
parser->state = FRAGMENT_DETECTED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FRAGMENT_DETECTED:
|
||||
{
|
||||
if (parser->message_count % interval == 0) {
|
||||
u8 fragment = b[FRAGMENT_POS];
|
||||
|
||||
parser->value >>= 8;
|
||||
parser->value |= (u64)fragment << 56;
|
||||
|
||||
if (parser->value == VALUES_AT_IMAGE_END) {
|
||||
parser->state = AVAILABLE;
|
||||
parser->fragment_pos = 0;
|
||||
parser->value_index = 0;
|
||||
parser->message_count = 0;
|
||||
}
|
||||
}
|
||||
++parser->message_count;
|
||||
break;
|
||||
}
|
||||
case AVAILABLE:
|
||||
default:
|
||||
{
|
||||
if (parser->message_count % interval == 0) {
|
||||
u8 fragment = b[FRAGMENT_POS];
|
||||
|
||||
parser->value >>= 8;
|
||||
parser->value |= (u64)fragment << 56;
|
||||
++parser->fragment_pos;
|
||||
|
||||
if (parser->fragment_pos == 4) {
|
||||
// Skip the last two quadlets since they could be
|
||||
// invalid value (0xffffffff) as floating point
|
||||
// number.
|
||||
if (parser->value_index <
|
||||
SNDRV_FIREWIRE_MOTU_COMMAND_DSP_METER_COUNT - 2) {
|
||||
u32 val = (u32)(parser->value >> 32);
|
||||
parser->meter.data[parser->value_index] = val;
|
||||
}
|
||||
++parser->value_index;
|
||||
parser->fragment_pos = 0;
|
||||
}
|
||||
|
||||
if (parser->value == VALUES_AT_IMAGE_END) {
|
||||
parser->value_index = 0;
|
||||
parser->fragment_pos = 0;
|
||||
parser->message_count = 0;
|
||||
}
|
||||
}
|
||||
++parser->message_count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&parser->lock, flags);
|
||||
}
|
||||
|
||||
void snd_motu_command_dsp_message_parser_copy_meter(struct snd_motu *motu,
|
||||
struct snd_firewire_motu_command_dsp_meter *meter)
|
||||
{
|
||||
struct msg_parser *parser = motu->message_parser;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&parser->lock, flags);
|
||||
memcpy(meter, &parser->meter, sizeof(*meter));
|
||||
spin_unlock_irqrestore(&parser->lock, flags);
|
||||
}
|
@ -16,6 +16,14 @@
|
||||
|
||||
#include "motu.h"
|
||||
|
||||
static bool has_dsp_event(struct snd_motu *motu)
|
||||
{
|
||||
if (motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP)
|
||||
return (snd_motu_register_dsp_message_parser_count_event(motu) > 0);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
|
||||
loff_t *offset)
|
||||
{
|
||||
@ -25,7 +33,7 @@ static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
|
||||
|
||||
spin_lock_irq(&motu->lock);
|
||||
|
||||
while (!motu->dev_lock_changed && motu->msg == 0) {
|
||||
while (!motu->dev_lock_changed && motu->msg == 0 && !has_dsp_event(motu)) {
|
||||
prepare_to_wait(&motu->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
|
||||
spin_unlock_irq(&motu->lock);
|
||||
schedule();
|
||||
@ -40,21 +48,47 @@ static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
|
||||
event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
|
||||
event.lock_status.status = (motu->dev_lock_count > 0);
|
||||
motu->dev_lock_changed = false;
|
||||
spin_unlock_irq(&motu->lock);
|
||||
|
||||
count = min_t(long, count, sizeof(event.lock_status));
|
||||
} else {
|
||||
count = min_t(long, count, sizeof(event));
|
||||
if (copy_to_user(buf, &event, count))
|
||||
return -EFAULT;
|
||||
} else if (motu->msg > 0) {
|
||||
event.motu_notification.type = SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION;
|
||||
event.motu_notification.message = motu->msg;
|
||||
motu->msg = 0;
|
||||
spin_unlock_irq(&motu->lock);
|
||||
|
||||
count = min_t(long, count, sizeof(event.motu_notification));
|
||||
count = min_t(long, count, sizeof(event));
|
||||
if (copy_to_user(buf, &event, count))
|
||||
return -EFAULT;
|
||||
} else if (has_dsp_event(motu)) {
|
||||
size_t consumed = 0;
|
||||
u32 __user *ptr;
|
||||
u32 ev;
|
||||
|
||||
spin_unlock_irq(&motu->lock);
|
||||
|
||||
// Header is filled later.
|
||||
consumed += sizeof(event.motu_register_dsp_change);
|
||||
|
||||
while (consumed < count &&
|
||||
snd_motu_register_dsp_message_parser_copy_event(motu, &ev)) {
|
||||
ptr = (u32 __user *)(buf + consumed);
|
||||
if (put_user(ev, ptr))
|
||||
return -EFAULT;
|
||||
consumed += sizeof(ev);
|
||||
}
|
||||
|
||||
event.motu_register_dsp_change.type = SNDRV_FIREWIRE_EVENT_MOTU_REGISTER_DSP_CHANGE;
|
||||
event.motu_register_dsp_change.count =
|
||||
(consumed - sizeof(event.motu_register_dsp_change)) / 4;
|
||||
if (copy_to_user(buf, &event, sizeof(event.motu_register_dsp_change)))
|
||||
return -EFAULT;
|
||||
|
||||
count = consumed;
|
||||
}
|
||||
|
||||
spin_unlock_irq(&motu->lock);
|
||||
|
||||
if (copy_to_user(buf, &event, count))
|
||||
return -EFAULT;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
@ -67,7 +101,7 @@ static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
|
||||
poll_wait(file, &motu->hwdep_wait, wait);
|
||||
|
||||
spin_lock_irq(&motu->lock);
|
||||
if (motu->dev_lock_changed || motu->msg)
|
||||
if (motu->dev_lock_changed || motu->msg || has_dsp_event(motu))
|
||||
events = EPOLLIN | EPOLLRDNORM;
|
||||
else
|
||||
events = 0;
|
||||
@ -155,6 +189,71 @@ static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
|
||||
return hwdep_lock(motu);
|
||||
case SNDRV_FIREWIRE_IOCTL_UNLOCK:
|
||||
return hwdep_unlock(motu);
|
||||
case SNDRV_FIREWIRE_IOCTL_MOTU_REGISTER_DSP_METER:
|
||||
{
|
||||
struct snd_firewire_motu_register_dsp_meter *meter;
|
||||
int err;
|
||||
|
||||
if (!(motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP))
|
||||
return -ENXIO;
|
||||
|
||||
meter = kzalloc(sizeof(*meter), GFP_KERNEL);
|
||||
if (!meter)
|
||||
return -ENOMEM;
|
||||
|
||||
snd_motu_register_dsp_message_parser_copy_meter(motu, meter);
|
||||
|
||||
err = copy_to_user((void __user *)arg, meter, sizeof(*meter));
|
||||
kfree(meter);
|
||||
|
||||
if (err)
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
case SNDRV_FIREWIRE_IOCTL_MOTU_COMMAND_DSP_METER:
|
||||
{
|
||||
struct snd_firewire_motu_command_dsp_meter *meter;
|
||||
int err;
|
||||
|
||||
if (!(motu->spec->flags & SND_MOTU_SPEC_COMMAND_DSP))
|
||||
return -ENXIO;
|
||||
|
||||
meter = kzalloc(sizeof(*meter), GFP_KERNEL);
|
||||
if (!meter)
|
||||
return -ENOMEM;
|
||||
|
||||
snd_motu_command_dsp_message_parser_copy_meter(motu, meter);
|
||||
|
||||
err = copy_to_user((void __user *)arg, meter, sizeof(*meter));
|
||||
kfree(meter);
|
||||
|
||||
if (err)
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
case SNDRV_FIREWIRE_IOCTL_MOTU_REGISTER_DSP_PARAMETER:
|
||||
{
|
||||
struct snd_firewire_motu_register_dsp_parameter *param;
|
||||
int err;
|
||||
|
||||
if (!(motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP))
|
||||
return -ENXIO;
|
||||
|
||||
param = kzalloc(sizeof(*param), GFP_KERNEL);
|
||||
if (!param)
|
||||
return -ENOMEM;
|
||||
|
||||
snd_motu_register_dsp_message_parser_copy_parameter(motu, param);
|
||||
|
||||
err = copy_to_user((void __user *)arg, param, sizeof(*param));
|
||||
kfree(param);
|
||||
if (err)
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
@ -193,5 +292,7 @@ int snd_motu_create_hwdep_device(struct snd_motu *motu)
|
||||
hwdep->private_data = motu;
|
||||
hwdep->exclusive = true;
|
||||
|
||||
motu->hwdep = hwdep;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -275,7 +275,8 @@ const struct snd_motu_spec snd_motu_spec_828mk2 = {
|
||||
.name = "828mk2",
|
||||
.protocol_version = SND_MOTU_PROTOCOL_V2,
|
||||
.flags = SND_MOTU_SPEC_RX_MIDI_2ND_Q |
|
||||
SND_MOTU_SPEC_TX_MIDI_2ND_Q,
|
||||
SND_MOTU_SPEC_TX_MIDI_2ND_Q |
|
||||
SND_MOTU_SPEC_REGISTER_DSP,
|
||||
.tx_fixed_pcm_chunks = {14, 14, 0},
|
||||
.rx_fixed_pcm_chunks = {14, 14, 0},
|
||||
};
|
||||
@ -283,7 +284,7 @@ const struct snd_motu_spec snd_motu_spec_828mk2 = {
|
||||
const struct snd_motu_spec snd_motu_spec_896hd = {
|
||||
.name = "896HD",
|
||||
.protocol_version = SND_MOTU_PROTOCOL_V2,
|
||||
// No support for MIDI.
|
||||
.flags = SND_MOTU_SPEC_REGISTER_DSP,
|
||||
.tx_fixed_pcm_chunks = {14, 14, 8},
|
||||
.rx_fixed_pcm_chunks = {14, 14, 8},
|
||||
};
|
||||
@ -292,7 +293,8 @@ const struct snd_motu_spec snd_motu_spec_traveler = {
|
||||
.name = "Traveler",
|
||||
.protocol_version = SND_MOTU_PROTOCOL_V2,
|
||||
.flags = SND_MOTU_SPEC_RX_MIDI_2ND_Q |
|
||||
SND_MOTU_SPEC_TX_MIDI_2ND_Q,
|
||||
SND_MOTU_SPEC_TX_MIDI_2ND_Q |
|
||||
SND_MOTU_SPEC_REGISTER_DSP,
|
||||
.tx_fixed_pcm_chunks = {14, 14, 8},
|
||||
.rx_fixed_pcm_chunks = {14, 14, 8},
|
||||
};
|
||||
@ -301,7 +303,8 @@ const struct snd_motu_spec snd_motu_spec_ultralite = {
|
||||
.name = "UltraLite",
|
||||
.protocol_version = SND_MOTU_PROTOCOL_V2,
|
||||
.flags = SND_MOTU_SPEC_RX_MIDI_2ND_Q |
|
||||
SND_MOTU_SPEC_TX_MIDI_2ND_Q,
|
||||
SND_MOTU_SPEC_TX_MIDI_2ND_Q |
|
||||
SND_MOTU_SPEC_REGISTER_DSP,
|
||||
.tx_fixed_pcm_chunks = {14, 14, 0},
|
||||
.rx_fixed_pcm_chunks = {14, 14, 0},
|
||||
};
|
||||
@ -310,7 +313,8 @@ const struct snd_motu_spec snd_motu_spec_8pre = {
|
||||
.name = "8pre",
|
||||
.protocol_version = SND_MOTU_PROTOCOL_V2,
|
||||
.flags = SND_MOTU_SPEC_RX_MIDI_2ND_Q |
|
||||
SND_MOTU_SPEC_TX_MIDI_2ND_Q,
|
||||
SND_MOTU_SPEC_TX_MIDI_2ND_Q |
|
||||
SND_MOTU_SPEC_REGISTER_DSP,
|
||||
// Two dummy chunks always in the end of data block.
|
||||
.tx_fixed_pcm_chunks = {10, 10, 0},
|
||||
.rx_fixed_pcm_chunks = {6, 6, 0},
|
||||
|
@ -261,12 +261,12 @@ int snd_motu_protocol_v3_cache_packet_formats(struct snd_motu *motu)
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
const struct snd_motu_spec snd_motu_spec_828mk3_fw = {
|
||||
.name = "828mk3",
|
||||
.protocol_version = SND_MOTU_PROTOCOL_V3,
|
||||
.flags = SND_MOTU_SPEC_RX_MIDI_3RD_Q |
|
||||
SND_MOTU_SPEC_TX_MIDI_3RD_Q,
|
||||
SND_MOTU_SPEC_TX_MIDI_3RD_Q |
|
||||
SND_MOTU_SPEC_COMMAND_DSP,
|
||||
.tx_fixed_pcm_chunks = {18, 18, 14},
|
||||
.rx_fixed_pcm_chunks = {14, 14, 10},
|
||||
};
|
||||
@ -275,7 +275,8 @@ const struct snd_motu_spec snd_motu_spec_828mk3_hybrid = {
|
||||
.name = "828mk3",
|
||||
.protocol_version = SND_MOTU_PROTOCOL_V3,
|
||||
.flags = SND_MOTU_SPEC_RX_MIDI_3RD_Q |
|
||||
SND_MOTU_SPEC_TX_MIDI_3RD_Q,
|
||||
SND_MOTU_SPEC_TX_MIDI_3RD_Q |
|
||||
SND_MOTU_SPEC_COMMAND_DSP,
|
||||
.tx_fixed_pcm_chunks = {18, 18, 14},
|
||||
.rx_fixed_pcm_chunks = {14, 14, 14}, // Additional 4 dummy chunks at higher rate.
|
||||
};
|
||||
@ -284,7 +285,8 @@ const struct snd_motu_spec snd_motu_spec_ultralite_mk3 = {
|
||||
.name = "UltraLiteMk3",
|
||||
.protocol_version = SND_MOTU_PROTOCOL_V3,
|
||||
.flags = SND_MOTU_SPEC_RX_MIDI_3RD_Q |
|
||||
SND_MOTU_SPEC_TX_MIDI_3RD_Q,
|
||||
SND_MOTU_SPEC_TX_MIDI_3RD_Q |
|
||||
SND_MOTU_SPEC_COMMAND_DSP,
|
||||
.tx_fixed_pcm_chunks = {18, 14, 10},
|
||||
.rx_fixed_pcm_chunks = {14, 14, 14},
|
||||
};
|
||||
@ -293,7 +295,8 @@ const struct snd_motu_spec snd_motu_spec_audio_express = {
|
||||
.name = "AudioExpress",
|
||||
.protocol_version = SND_MOTU_PROTOCOL_V3,
|
||||
.flags = SND_MOTU_SPEC_RX_MIDI_2ND_Q |
|
||||
SND_MOTU_SPEC_TX_MIDI_3RD_Q,
|
||||
SND_MOTU_SPEC_TX_MIDI_3RD_Q |
|
||||
SND_MOTU_SPEC_REGISTER_DSP,
|
||||
.tx_fixed_pcm_chunks = {10, 10, 0},
|
||||
.rx_fixed_pcm_chunks = {10, 10, 0},
|
||||
};
|
||||
@ -301,6 +304,7 @@ const struct snd_motu_spec snd_motu_spec_audio_express = {
|
||||
const struct snd_motu_spec snd_motu_spec_4pre = {
|
||||
.name = "4pre",
|
||||
.protocol_version = SND_MOTU_PROTOCOL_V3,
|
||||
.flags = SND_MOTU_SPEC_REGISTER_DSP,
|
||||
.tx_fixed_pcm_chunks = {10, 10, 0},
|
||||
.rx_fixed_pcm_chunks = {10, 10, 0},
|
||||
};
|
||||
|
420
sound/firewire/motu/motu-register-dsp-message-parser.c
Normal file
420
sound/firewire/motu/motu-register-dsp-message-parser.c
Normal file
@ -0,0 +1,420 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
//
|
||||
// motu-register-dsp-message-parser.c - a part of driver for MOTU FireWire series
|
||||
//
|
||||
// Copyright (c) 2021 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
||||
|
||||
// Below models allow software to configure their DSP functions by asynchronous transaction
|
||||
// to access their internal registers.
|
||||
// * 828 mk2
|
||||
// * 896hd
|
||||
// * Traveler
|
||||
// * 8 pre
|
||||
// * Ultralite
|
||||
// * 4 pre
|
||||
// * Audio Express
|
||||
//
|
||||
// Additionally, isochronous packets from the above models include messages to notify state of
|
||||
// DSP. The messages are two set of 3 byte data in 2nd and 3rd quadlet of data block. When user
|
||||
// operates hardware components such as dial and switch, corresponding messages are transferred.
|
||||
// The messages include Hardware metering and MIDI messages as well.
|
||||
|
||||
#include "motu.h"
|
||||
|
||||
#define MSG_FLAG_POS 4
|
||||
#define MSG_FLAG_TYPE_MASK 0xf8
|
||||
#define MSG_FLAG_MIDI_MASK 0x01
|
||||
#define MSG_FLAG_MODEL_SPECIFIC_MASK 0x06
|
||||
#define MSG_FLAG_8PRE 0x00
|
||||
#define MSG_FLAG_ULTRALITE 0x04
|
||||
#define MSG_FLAG_TRAVELER 0x04
|
||||
#define MSG_FLAG_828MK2 0x04
|
||||
#define MSG_FLAG_896HD 0x04
|
||||
#define MSG_FLAG_4PRE 0x05 // MIDI mask is in 8th byte.
|
||||
#define MSG_FLAG_AUDIOEXPRESS 0x05 // MIDI mask is in 8th byte.
|
||||
#define MSG_FLAG_TYPE_SHIFT 3
|
||||
#define MSG_VALUE_POS 5
|
||||
#define MSG_MIDI_BYTE_POS 6
|
||||
#define MSG_METER_IDX_POS 7
|
||||
|
||||
// In 4 pre and Audio express, meter index is in 6th byte. MIDI flag is in 8th byte and MIDI byte
|
||||
// is in 7th byte.
|
||||
#define MSG_METER_IDX_POS_4PRE_AE 6
|
||||
#define MSG_MIDI_BYTE_POS_4PRE_AE 7
|
||||
#define MSG_FLAG_MIDI_POS_4PRE_AE 8
|
||||
|
||||
enum register_dsp_msg_type {
|
||||
// Used for messages with no information.
|
||||
INVALID = 0x00,
|
||||
MIXER_SELECT = 0x01,
|
||||
MIXER_SRC_GAIN = 0x02,
|
||||
MIXER_SRC_PAN = 0x03,
|
||||
MIXER_SRC_FLAG = 0x04,
|
||||
MIXER_OUTPUT_PAIRED_VOLUME = 0x05,
|
||||
MIXER_OUTPUT_PAIRED_FLAG = 0x06,
|
||||
MAIN_OUTPUT_PAIRED_VOLUME = 0x07,
|
||||
HP_OUTPUT_PAIRED_VOLUME = 0x08,
|
||||
HP_OUTPUT_PAIRED_ASSIGNMENT = 0x09,
|
||||
// Transferred by all models but the purpose is still unknown.
|
||||
UNKNOWN_0 = 0x0a,
|
||||
// Specific to 828mk2, 896hd, Traveler.
|
||||
UNKNOWN_2 = 0x0c,
|
||||
// Specific to 828mk2, Traveler, and 896hd (not functional).
|
||||
LINE_INPUT_BOOST = 0x0d,
|
||||
// Specific to 828mk2, Traveler, and 896hd (not functional).
|
||||
LINE_INPUT_NOMINAL_LEVEL = 0x0e,
|
||||
// Specific to Ultralite, 4 pre, Audio express, and 8 pre (not functional).
|
||||
INPUT_GAIN_AND_INVERT = 0x15,
|
||||
// Specific to 4 pre, and Audio express.
|
||||
INPUT_FLAG = 0x16,
|
||||
// Specific to 4 pre, and Audio express.
|
||||
MIXER_SRC_PAIRED_BALANCE = 0x17,
|
||||
// Specific to 4 pre, and Audio express.
|
||||
MIXER_SRC_PAIRED_WIDTH = 0x18,
|
||||
// Transferred by all models. This type of message interposes the series of the other
|
||||
// messages. The message delivers signal level up to 96.0 kHz. In 828mk2, 896hd, and
|
||||
// Traveler, one of physical outputs is selected for the message. The selection is done
|
||||
// by LSB one byte in asynchronous write quadlet transaction to 0x'ffff'f000'0b2c.
|
||||
METER = 0x1f,
|
||||
};
|
||||
|
||||
#define EVENT_QUEUE_SIZE 16
|
||||
|
||||
struct msg_parser {
|
||||
spinlock_t lock;
|
||||
struct snd_firewire_motu_register_dsp_meter meter;
|
||||
bool meter_pos_quirk;
|
||||
|
||||
struct snd_firewire_motu_register_dsp_parameter param;
|
||||
u8 prev_mixer_src_type;
|
||||
u8 mixer_ch;
|
||||
u8 mixer_src_ch;
|
||||
|
||||
u8 input_ch;
|
||||
u8 prev_msg_type;
|
||||
|
||||
u32 event_queue[EVENT_QUEUE_SIZE];
|
||||
unsigned int push_pos;
|
||||
unsigned int pull_pos;
|
||||
};
|
||||
|
||||
int snd_motu_register_dsp_message_parser_new(struct snd_motu *motu)
|
||||
{
|
||||
struct msg_parser *parser;
|
||||
parser = devm_kzalloc(&motu->card->card_dev, sizeof(*parser), GFP_KERNEL);
|
||||
if (!parser)
|
||||
return -ENOMEM;
|
||||
spin_lock_init(&parser->lock);
|
||||
if (motu->spec == &snd_motu_spec_4pre || motu->spec == &snd_motu_spec_audio_express)
|
||||
parser->meter_pos_quirk = true;
|
||||
motu->message_parser = parser;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int snd_motu_register_dsp_message_parser_init(struct snd_motu *motu)
|
||||
{
|
||||
struct msg_parser *parser = motu->message_parser;
|
||||
|
||||
parser->prev_mixer_src_type = INVALID;
|
||||
parser->mixer_ch = 0xff;
|
||||
parser->mixer_src_ch = 0xff;
|
||||
parser->prev_msg_type = INVALID;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Rough implementaion of queue without overrun check.
|
||||
static void queue_event(struct snd_motu *motu, u8 msg_type, u8 identifier0, u8 identifier1, u8 val)
|
||||
{
|
||||
struct msg_parser *parser = motu->message_parser;
|
||||
unsigned int pos = parser->push_pos;
|
||||
u32 entry;
|
||||
|
||||
if (!motu->hwdep || motu->hwdep->used == 0)
|
||||
return;
|
||||
|
||||
entry = (msg_type << 24) | (identifier0 << 16) | (identifier1 << 8) | val;
|
||||
parser->event_queue[pos] = entry;
|
||||
|
||||
++pos;
|
||||
if (pos >= EVENT_QUEUE_SIZE)
|
||||
pos = 0;
|
||||
parser->push_pos = pos;
|
||||
}
|
||||
|
||||
void snd_motu_register_dsp_message_parser_parse(struct snd_motu *motu, const struct pkt_desc *descs,
|
||||
unsigned int desc_count, unsigned int data_block_quadlets)
|
||||
{
|
||||
struct msg_parser *parser = motu->message_parser;
|
||||
bool meter_pos_quirk = parser->meter_pos_quirk;
|
||||
unsigned int pos = parser->push_pos;
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
spin_lock_irqsave(&parser->lock, flags);
|
||||
|
||||
for (i = 0; i < desc_count; ++i) {
|
||||
const struct pkt_desc *desc = descs + i;
|
||||
__be32 *buffer = desc->ctx_payload;
|
||||
unsigned int data_blocks = desc->data_blocks;
|
||||
int j;
|
||||
|
||||
for (j = 0; j < data_blocks; ++j) {
|
||||
u8 *b = (u8 *)buffer;
|
||||
u8 msg_type = (b[MSG_FLAG_POS] & MSG_FLAG_TYPE_MASK) >> MSG_FLAG_TYPE_SHIFT;
|
||||
u8 val = b[MSG_VALUE_POS];
|
||||
|
||||
buffer += data_block_quadlets;
|
||||
|
||||
switch (msg_type) {
|
||||
case MIXER_SELECT:
|
||||
{
|
||||
u8 mixer_ch = val / 0x20;
|
||||
if (mixer_ch < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_COUNT) {
|
||||
parser->mixer_src_ch = 0;
|
||||
parser->mixer_ch = mixer_ch;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MIXER_SRC_GAIN:
|
||||
case MIXER_SRC_PAN:
|
||||
case MIXER_SRC_FLAG:
|
||||
case MIXER_SRC_PAIRED_BALANCE:
|
||||
case MIXER_SRC_PAIRED_WIDTH:
|
||||
{
|
||||
struct snd_firewire_motu_register_dsp_parameter *param = &parser->param;
|
||||
u8 mixer_ch = parser->mixer_ch;
|
||||
u8 mixer_src_ch = parser->mixer_src_ch;
|
||||
|
||||
if (msg_type != parser->prev_mixer_src_type)
|
||||
mixer_src_ch = 0;
|
||||
else
|
||||
++mixer_src_ch;
|
||||
parser->prev_mixer_src_type = msg_type;
|
||||
|
||||
if (mixer_ch < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_COUNT &&
|
||||
mixer_src_ch < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_SRC_COUNT) {
|
||||
u8 mixer_ch = parser->mixer_ch;
|
||||
|
||||
switch (msg_type) {
|
||||
case MIXER_SRC_GAIN:
|
||||
if (param->mixer.source[mixer_ch].gain[mixer_src_ch] != val) {
|
||||
queue_event(motu, msg_type, mixer_ch, mixer_src_ch, val);
|
||||
param->mixer.source[mixer_ch].gain[mixer_src_ch] = val;
|
||||
}
|
||||
break;
|
||||
case MIXER_SRC_PAN:
|
||||
if (param->mixer.source[mixer_ch].pan[mixer_src_ch] != val) {
|
||||
queue_event(motu, msg_type, mixer_ch, mixer_src_ch, val);
|
||||
param->mixer.source[mixer_ch].pan[mixer_src_ch] = val;
|
||||
}
|
||||
break;
|
||||
case MIXER_SRC_FLAG:
|
||||
if (param->mixer.source[mixer_ch].flag[mixer_src_ch] != val) {
|
||||
queue_event(motu, msg_type, mixer_ch, mixer_src_ch, val);
|
||||
param->mixer.source[mixer_ch].flag[mixer_src_ch] = val;
|
||||
}
|
||||
break;
|
||||
case MIXER_SRC_PAIRED_BALANCE:
|
||||
if (param->mixer.source[mixer_ch].paired_balance[mixer_src_ch] != val) {
|
||||
queue_event(motu, msg_type, mixer_ch, mixer_src_ch, val);
|
||||
param->mixer.source[mixer_ch].paired_balance[mixer_src_ch] = val;
|
||||
}
|
||||
break;
|
||||
case MIXER_SRC_PAIRED_WIDTH:
|
||||
if (param->mixer.source[mixer_ch].paired_width[mixer_src_ch] != val) {
|
||||
queue_event(motu, msg_type, mixer_ch, mixer_src_ch, val);
|
||||
param->mixer.source[mixer_ch].paired_width[mixer_src_ch] = val;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
parser->mixer_src_ch = mixer_src_ch;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MIXER_OUTPUT_PAIRED_VOLUME:
|
||||
case MIXER_OUTPUT_PAIRED_FLAG:
|
||||
{
|
||||
struct snd_firewire_motu_register_dsp_parameter *param = &parser->param;
|
||||
u8 mixer_ch = parser->mixer_ch;
|
||||
|
||||
if (mixer_ch < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_COUNT) {
|
||||
switch (msg_type) {
|
||||
case MIXER_OUTPUT_PAIRED_VOLUME:
|
||||
if (param->mixer.output.paired_volume[mixer_ch] != val) {
|
||||
queue_event(motu, msg_type, mixer_ch, 0, val);
|
||||
param->mixer.output.paired_volume[mixer_ch] = val;
|
||||
}
|
||||
break;
|
||||
case MIXER_OUTPUT_PAIRED_FLAG:
|
||||
if (param->mixer.output.paired_flag[mixer_ch] != val) {
|
||||
queue_event(motu, msg_type, mixer_ch, 0, val);
|
||||
param->mixer.output.paired_flag[mixer_ch] = val;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MAIN_OUTPUT_PAIRED_VOLUME:
|
||||
if (parser->param.output.main_paired_volume != val) {
|
||||
queue_event(motu, msg_type, 0, 0, val);
|
||||
parser->param.output.main_paired_volume = val;
|
||||
}
|
||||
break;
|
||||
case HP_OUTPUT_PAIRED_VOLUME:
|
||||
if (parser->param.output.hp_paired_volume != val) {
|
||||
queue_event(motu, msg_type, 0, 0, val);
|
||||
parser->param.output.hp_paired_volume = val;
|
||||
}
|
||||
break;
|
||||
case HP_OUTPUT_PAIRED_ASSIGNMENT:
|
||||
if (parser->param.output.hp_paired_assignment != val) {
|
||||
queue_event(motu, msg_type, 0, 0, val);
|
||||
parser->param.output.hp_paired_assignment = val;
|
||||
}
|
||||
break;
|
||||
case LINE_INPUT_BOOST:
|
||||
if (parser->param.line_input.boost_flag != val) {
|
||||
queue_event(motu, msg_type, 0, 0, val);
|
||||
parser->param.line_input.boost_flag = val;
|
||||
}
|
||||
break;
|
||||
case LINE_INPUT_NOMINAL_LEVEL:
|
||||
if (parser->param.line_input.nominal_level_flag != val) {
|
||||
queue_event(motu, msg_type, 0, 0, val);
|
||||
parser->param.line_input.nominal_level_flag = val;
|
||||
}
|
||||
break;
|
||||
case INPUT_GAIN_AND_INVERT:
|
||||
case INPUT_FLAG:
|
||||
{
|
||||
struct snd_firewire_motu_register_dsp_parameter *param = &parser->param;
|
||||
u8 input_ch = parser->input_ch;
|
||||
|
||||
if (parser->prev_msg_type != msg_type)
|
||||
input_ch = 0;
|
||||
else
|
||||
++input_ch;
|
||||
|
||||
if (input_ch < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_INPUT_COUNT) {
|
||||
switch (msg_type) {
|
||||
case INPUT_GAIN_AND_INVERT:
|
||||
if (param->input.gain_and_invert[input_ch] != val) {
|
||||
queue_event(motu, msg_type, input_ch, 0, val);
|
||||
param->input.gain_and_invert[input_ch] = val;
|
||||
}
|
||||
break;
|
||||
case INPUT_FLAG:
|
||||
if (param->input.flag[input_ch] != val) {
|
||||
queue_event(motu, msg_type, input_ch, 0, val);
|
||||
param->input.flag[input_ch] = val;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
parser->input_ch = input_ch;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UNKNOWN_0:
|
||||
case UNKNOWN_2:
|
||||
break;
|
||||
case METER:
|
||||
{
|
||||
u8 pos;
|
||||
|
||||
if (!meter_pos_quirk)
|
||||
pos = b[MSG_METER_IDX_POS];
|
||||
else
|
||||
pos = b[MSG_METER_IDX_POS_4PRE_AE];
|
||||
|
||||
if (pos < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_INPUT_COUNT) {
|
||||
parser->meter.data[pos] = val;
|
||||
} else if (pos >= 0x80) {
|
||||
pos -= (0x80 - SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_INPUT_COUNT);
|
||||
|
||||
if (pos < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_COUNT)
|
||||
parser->meter.data[pos] = val;
|
||||
}
|
||||
|
||||
// The message for meter is interruptible to the series of other
|
||||
// types of messages. Don't cache it.
|
||||
fallthrough;
|
||||
}
|
||||
case INVALID:
|
||||
default:
|
||||
// Don't cache it.
|
||||
continue;
|
||||
}
|
||||
|
||||
parser->prev_msg_type = msg_type;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos != parser->push_pos)
|
||||
wake_up(&motu->hwdep_wait);
|
||||
|
||||
spin_unlock_irqrestore(&parser->lock, flags);
|
||||
}
|
||||
|
||||
void snd_motu_register_dsp_message_parser_copy_meter(struct snd_motu *motu,
|
||||
struct snd_firewire_motu_register_dsp_meter *meter)
|
||||
{
|
||||
struct msg_parser *parser = motu->message_parser;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&parser->lock, flags);
|
||||
memcpy(meter, &parser->meter, sizeof(*meter));
|
||||
spin_unlock_irqrestore(&parser->lock, flags);
|
||||
}
|
||||
|
||||
void snd_motu_register_dsp_message_parser_copy_parameter(struct snd_motu *motu,
|
||||
struct snd_firewire_motu_register_dsp_parameter *param)
|
||||
{
|
||||
struct msg_parser *parser = motu->message_parser;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&parser->lock, flags);
|
||||
memcpy(param, &parser->param, sizeof(*param));
|
||||
spin_unlock_irqrestore(&parser->lock, flags);
|
||||
}
|
||||
|
||||
unsigned int snd_motu_register_dsp_message_parser_count_event(struct snd_motu *motu)
|
||||
{
|
||||
struct msg_parser *parser = motu->message_parser;
|
||||
|
||||
if (parser->pull_pos > parser->push_pos)
|
||||
return EVENT_QUEUE_SIZE - parser->pull_pos + parser->push_pos;
|
||||
else
|
||||
return parser->push_pos - parser->pull_pos;
|
||||
}
|
||||
|
||||
bool snd_motu_register_dsp_message_parser_copy_event(struct snd_motu *motu, u32 *event)
|
||||
{
|
||||
struct msg_parser *parser = motu->message_parser;
|
||||
unsigned int pos = parser->pull_pos;
|
||||
unsigned long flags;
|
||||
|
||||
if (pos == parser->push_pos)
|
||||
return false;
|
||||
|
||||
spin_lock_irqsave(&parser->lock, flags);
|
||||
|
||||
*event = parser->event_queue[pos];
|
||||
|
||||
++pos;
|
||||
if (pos >= EVENT_QUEUE_SIZE)
|
||||
pos = 0;
|
||||
parser->pull_pos = pos;
|
||||
|
||||
spin_unlock_irqrestore(&parser->lock, flags);
|
||||
|
||||
return true;
|
||||
}
|
@ -255,6 +255,16 @@ int snd_motu_stream_start_duplex(struct snd_motu *motu)
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP) {
|
||||
err = snd_motu_register_dsp_message_parser_init(motu);
|
||||
if (err < 0)
|
||||
return err;
|
||||
} else if (motu->spec->flags & SND_MOTU_SPEC_COMMAND_DSP) {
|
||||
err = snd_motu_command_dsp_message_parser_init(motu, motu->tx_stream.sfc);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
err = begin_session(motu);
|
||||
if (err < 0) {
|
||||
dev_err(&motu->unit->device,
|
||||
|
@ -112,6 +112,16 @@ static int motu_probe(struct fw_unit *unit, const struct ieee1394_device_id *ent
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
if (motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP) {
|
||||
err = snd_motu_register_dsp_message_parser_new(motu);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
} else if (motu->spec->flags & SND_MOTU_SPEC_COMMAND_DSP) {
|
||||
err = snd_motu_command_dsp_message_parser_new(motu);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
err = snd_card_register(card);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
@ -74,10 +74,13 @@ struct snd_motu {
|
||||
int dev_lock_count;
|
||||
bool dev_lock_changed;
|
||||
wait_queue_head_t hwdep_wait;
|
||||
struct snd_hwdep *hwdep;
|
||||
|
||||
struct amdtp_domain domain;
|
||||
|
||||
struct amdtp_motu_cache cache;
|
||||
|
||||
void *message_parser;
|
||||
};
|
||||
|
||||
enum snd_motu_spec_flags {
|
||||
@ -85,6 +88,8 @@ enum snd_motu_spec_flags {
|
||||
SND_MOTU_SPEC_RX_MIDI_3RD_Q = 0x0002,
|
||||
SND_MOTU_SPEC_TX_MIDI_2ND_Q = 0x0004,
|
||||
SND_MOTU_SPEC_TX_MIDI_3RD_Q = 0x0008,
|
||||
SND_MOTU_SPEC_REGISTER_DSP = 0x0010,
|
||||
SND_MOTU_SPEC_COMMAND_DSP = 0x0020,
|
||||
};
|
||||
|
||||
#define SND_MOTU_CLOCK_RATE_COUNT 6
|
||||
@ -270,4 +275,22 @@ static inline int snd_motu_protocol_cache_packet_formats(struct snd_motu *motu)
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
int snd_motu_register_dsp_message_parser_new(struct snd_motu *motu);
|
||||
int snd_motu_register_dsp_message_parser_init(struct snd_motu *motu);
|
||||
void snd_motu_register_dsp_message_parser_parse(struct snd_motu *motu, const struct pkt_desc *descs,
|
||||
unsigned int desc_count, unsigned int data_block_quadlets);
|
||||
void snd_motu_register_dsp_message_parser_copy_meter(struct snd_motu *motu,
|
||||
struct snd_firewire_motu_register_dsp_meter *meter);
|
||||
void snd_motu_register_dsp_message_parser_copy_parameter(struct snd_motu *motu,
|
||||
struct snd_firewire_motu_register_dsp_parameter *params);
|
||||
unsigned int snd_motu_register_dsp_message_parser_count_event(struct snd_motu *motu);
|
||||
bool snd_motu_register_dsp_message_parser_copy_event(struct snd_motu *motu, u32 *event);
|
||||
|
||||
int snd_motu_command_dsp_message_parser_new(struct snd_motu *motu);
|
||||
int snd_motu_command_dsp_message_parser_init(struct snd_motu *motu, enum cip_sfc sfc);
|
||||
void snd_motu_command_dsp_message_parser_parse(struct snd_motu *motu, const struct pkt_desc *descs,
|
||||
unsigned int desc_count, unsigned int data_block_quadlets);
|
||||
void snd_motu_command_dsp_message_parser_copy_meter(struct snd_motu *motu,
|
||||
struct snd_firewire_motu_command_dsp_meter *meter);
|
||||
|
||||
#endif
|
||||
|
@ -9,7 +9,7 @@
|
||||
#include <linux/delay.h>
|
||||
|
||||
#define AVC_GENERIC_FRAME_MAXIMUM_BYTES 512
|
||||
#define READY_TIMEOUT_MS 200
|
||||
#define READY_TIMEOUT_MS 600
|
||||
|
||||
/*
|
||||
* According to datasheet of Oxford Semiconductor:
|
||||
@ -367,6 +367,11 @@ int snd_oxfw_stream_start_duplex(struct snd_oxfw *oxfw)
|
||||
// Just after changing sampling transfer frequency, many cycles are
|
||||
// skipped for packet transmission.
|
||||
tx_init_skip_cycles = 400;
|
||||
} else if (oxfw->quirks & SND_OXFW_QUIRK_VOLUNTARY_RECOVERY) {
|
||||
// It takes a bit time for target device to adjust event frequency
|
||||
// according to nominal event frequency in isochronous packets from
|
||||
// ALSA oxfw driver.
|
||||
tx_init_skip_cycles = 4000;
|
||||
} else {
|
||||
replay_seq = true;
|
||||
}
|
||||
|
@ -25,6 +25,7 @@
|
||||
#define MODEL_SATELLITE 0x00200f
|
||||
#define MODEL_SCS1M 0x001000
|
||||
#define MODEL_DUET_FW 0x01dddd
|
||||
#define MODEL_ONYX_1640I 0x001640
|
||||
|
||||
#define SPECIFIER_1394TA 0x00a02d
|
||||
#define VERSION_AVC 0x010001
|
||||
@ -192,6 +193,13 @@ static int detect_quirks(struct snd_oxfw *oxfw, const struct ieee1394_device_id
|
||||
// OXFW971-based models may transfer events by blocking method.
|
||||
if (!(oxfw->quirks & SND_OXFW_QUIRK_JUMBO_PAYLOAD))
|
||||
oxfw->quirks |= SND_OXFW_QUIRK_BLOCKING_TRANSMISSION;
|
||||
|
||||
if (model == MODEL_ONYX_1640I) {
|
||||
//Unless receiving packets without NOINFO packet, the device transfers
|
||||
//mostly half of events in packets than expected.
|
||||
oxfw->quirks |= SND_OXFW_QUIRK_IGNORE_NO_INFO_PACKET |
|
||||
SND_OXFW_QUIRK_VOLUNTARY_RECOVERY;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -47,6 +47,11 @@ enum snd_oxfw_quirk {
|
||||
// the device to process audio data even if the value is invalid in a point of
|
||||
// IEC 61883-1/6.
|
||||
SND_OXFW_QUIRK_IGNORE_NO_INFO_PACKET = 0x10,
|
||||
// Loud Technologies Mackie Onyx 1640i seems to configure OXFW971 ASIC so that it decides
|
||||
// event frequency according to events in received isochronous packets. The device looks to
|
||||
// performs media clock recovery voluntarily. In the recovery, the packets with NO_INFO
|
||||
// are ignored, thus driver should transfer packets with timestamp.
|
||||
SND_OXFW_QUIRK_VOLUNTARY_RECOVERY = 0x20,
|
||||
};
|
||||
|
||||
/* This is an arbitrary number for convinience. */
|
||||
|
@ -106,20 +106,14 @@ void snd_hdac_stream_free_all(struct hdac_bus *bus)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_stream_free_all);
|
||||
|
||||
/**
|
||||
* snd_hdac_ext_stream_decouple - decouple the hdac stream
|
||||
* @bus: HD-audio core bus
|
||||
* @stream: HD-audio ext core stream object to initialize
|
||||
* @decouple: flag to decouple
|
||||
*/
|
||||
void snd_hdac_ext_stream_decouple(struct hdac_bus *bus,
|
||||
struct hdac_ext_stream *stream, bool decouple)
|
||||
void snd_hdac_ext_stream_decouple_locked(struct hdac_bus *bus,
|
||||
struct hdac_ext_stream *stream,
|
||||
bool decouple)
|
||||
{
|
||||
struct hdac_stream *hstream = &stream->hstream;
|
||||
u32 val;
|
||||
int mask = AZX_PPCTL_PROCEN(hstream->index);
|
||||
|
||||
spin_lock_irq(&bus->reg_lock);
|
||||
val = readw(bus->ppcap + AZX_REG_PP_PPCTL) & mask;
|
||||
|
||||
if (decouple && !val)
|
||||
@ -128,6 +122,20 @@ void snd_hdac_ext_stream_decouple(struct hdac_bus *bus,
|
||||
snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, 0);
|
||||
|
||||
stream->decoupled = decouple;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple_locked);
|
||||
|
||||
/**
|
||||
* snd_hdac_ext_stream_decouple - decouple the hdac stream
|
||||
* @bus: HD-audio core bus
|
||||
* @stream: HD-audio ext core stream object to initialize
|
||||
* @decouple: flag to decouple
|
||||
*/
|
||||
void snd_hdac_ext_stream_decouple(struct hdac_bus *bus,
|
||||
struct hdac_ext_stream *stream, bool decouple)
|
||||
{
|
||||
spin_lock_irq(&bus->reg_lock);
|
||||
snd_hdac_ext_stream_decouple_locked(bus, stream, decouple);
|
||||
spin_unlock_irq(&bus->reg_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple);
|
||||
@ -252,6 +260,7 @@ hdac_ext_link_stream_assign(struct hdac_bus *bus,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
spin_lock_irq(&bus->reg_lock);
|
||||
list_for_each_entry(stream, &bus->stream_list, list) {
|
||||
struct hdac_ext_stream *hstream = container_of(stream,
|
||||
struct hdac_ext_stream,
|
||||
@ -266,17 +275,16 @@ hdac_ext_link_stream_assign(struct hdac_bus *bus,
|
||||
}
|
||||
|
||||
if (!hstream->link_locked) {
|
||||
snd_hdac_ext_stream_decouple(bus, hstream, true);
|
||||
snd_hdac_ext_stream_decouple_locked(bus, hstream, true);
|
||||
res = hstream;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (res) {
|
||||
spin_lock_irq(&bus->reg_lock);
|
||||
res->link_locked = 1;
|
||||
res->link_substream = substream;
|
||||
spin_unlock_irq(&bus->reg_lock);
|
||||
}
|
||||
spin_unlock_irq(&bus->reg_lock);
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -292,6 +300,7 @@ hdac_ext_host_stream_assign(struct hdac_bus *bus,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
spin_lock_irq(&bus->reg_lock);
|
||||
list_for_each_entry(stream, &bus->stream_list, list) {
|
||||
struct hdac_ext_stream *hstream = container_of(stream,
|
||||
struct hdac_ext_stream,
|
||||
@ -301,18 +310,17 @@ hdac_ext_host_stream_assign(struct hdac_bus *bus,
|
||||
|
||||
if (!stream->opened) {
|
||||
if (!hstream->decoupled)
|
||||
snd_hdac_ext_stream_decouple(bus, hstream, true);
|
||||
snd_hdac_ext_stream_decouple_locked(bus, hstream, true);
|
||||
res = hstream;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (res) {
|
||||
spin_lock_irq(&bus->reg_lock);
|
||||
res->hstream.opened = 1;
|
||||
res->hstream.running = 0;
|
||||
res->hstream.substream = substream;
|
||||
spin_unlock_irq(&bus->reg_lock);
|
||||
}
|
||||
spin_unlock_irq(&bus->reg_lock);
|
||||
|
||||
return res;
|
||||
}
|
||||
@ -378,15 +386,17 @@ void snd_hdac_ext_stream_release(struct hdac_ext_stream *stream, int type)
|
||||
break;
|
||||
|
||||
case HDAC_EXT_STREAM_TYPE_HOST:
|
||||
spin_lock_irq(&bus->reg_lock);
|
||||
if (stream->decoupled && !stream->link_locked)
|
||||
snd_hdac_ext_stream_decouple(bus, stream, false);
|
||||
snd_hdac_ext_stream_decouple_locked(bus, stream, false);
|
||||
spin_unlock_irq(&bus->reg_lock);
|
||||
snd_hdac_stream_release(&stream->hstream);
|
||||
break;
|
||||
|
||||
case HDAC_EXT_STREAM_TYPE_LINK:
|
||||
if (stream->decoupled && !stream->hstream.opened)
|
||||
snd_hdac_ext_stream_decouple(bus, stream, false);
|
||||
spin_lock_irq(&bus->reg_lock);
|
||||
if (stream->decoupled && !stream->hstream.opened)
|
||||
snd_hdac_ext_stream_decouple_locked(bus, stream, false);
|
||||
stream->link_locked = 0;
|
||||
stream->link_substream = NULL;
|
||||
spin_unlock_irq(&bus->reg_lock);
|
||||
|
@ -296,6 +296,7 @@ struct hdac_stream *snd_hdac_stream_assign(struct hdac_bus *bus,
|
||||
int key = (substream->pcm->device << 16) | (substream->number << 2) |
|
||||
(substream->stream + 1);
|
||||
|
||||
spin_lock_irq(&bus->reg_lock);
|
||||
list_for_each_entry(azx_dev, &bus->stream_list, list) {
|
||||
if (azx_dev->direction != substream->stream)
|
||||
continue;
|
||||
@ -309,13 +310,12 @@ struct hdac_stream *snd_hdac_stream_assign(struct hdac_bus *bus,
|
||||
res = azx_dev;
|
||||
}
|
||||
if (res) {
|
||||
spin_lock_irq(&bus->reg_lock);
|
||||
res->opened = 1;
|
||||
res->running = 0;
|
||||
res->assigned_key = key;
|
||||
res->substream = substream;
|
||||
spin_unlock_irq(&bus->reg_lock);
|
||||
}
|
||||
spin_unlock_irq(&bus->reg_lock);
|
||||
return res;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_stream_assign);
|
||||
|
@ -22,7 +22,7 @@ config SND_SB16_DSP
|
||||
menuconfig SND_ISA
|
||||
bool "ISA sound devices"
|
||||
depends on ISA || COMPILE_TEST
|
||||
depends on ISA_DMA_API
|
||||
depends on ISA_DMA_API && !M68K
|
||||
default y
|
||||
help
|
||||
Support for sound devices connected via the ISA bus.
|
||||
|
@ -126,6 +126,8 @@ static void snd_gf1_dma_interrupt(struct snd_gus_card * gus)
|
||||
}
|
||||
block = snd_gf1_dma_next_block(gus);
|
||||
spin_unlock(&gus->dma_lock);
|
||||
if (!block)
|
||||
return;
|
||||
snd_gf1_dma_program(gus, block->addr, block->buf_addr, block->count, (unsigned short) block->cmd);
|
||||
kfree(block);
|
||||
#if 0
|
||||
|
@ -279,6 +279,7 @@ config SND_CS46XX_NEW_DSP
|
||||
config SND_CS5530
|
||||
tristate "CS5530 Audio"
|
||||
depends on ISA_DMA_API && (X86_32 || COMPILE_TEST)
|
||||
depends on !M68K
|
||||
select SND_SB16_DSP
|
||||
help
|
||||
Say Y here to include support for audio on Cyrix/NatSemi CS5530 chips.
|
||||
|
@ -638,13 +638,17 @@ static int azx_position_check(struct azx *chip, struct azx_dev *azx_dev)
|
||||
* the update-IRQ timing. The IRQ is issued before actually the
|
||||
* data is processed. So, we need to process it afterwords in a
|
||||
* workqueue.
|
||||
*
|
||||
* Returns 1 if OK to proceed, 0 for delay handling, -1 for skipping update
|
||||
*/
|
||||
static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev)
|
||||
{
|
||||
struct snd_pcm_substream *substream = azx_dev->core.substream;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
int stream = substream->stream;
|
||||
u32 wallclk;
|
||||
unsigned int pos;
|
||||
snd_pcm_uframes_t hwptr, target;
|
||||
|
||||
wallclk = azx_readl(chip, WALLCLK) - azx_dev->core.start_wallclk;
|
||||
if (wallclk < (azx_dev->core.period_wallclk * 2) / 3)
|
||||
@ -681,6 +685,24 @@ static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev)
|
||||
/* NG - it's below the first next period boundary */
|
||||
return chip->bdl_pos_adj ? 0 : -1;
|
||||
azx_dev->core.start_wallclk += wallclk;
|
||||
|
||||
if (azx_dev->core.no_period_wakeup)
|
||||
return 1; /* OK, no need to check period boundary */
|
||||
|
||||
if (runtime->hw_ptr_base != runtime->hw_ptr_interrupt)
|
||||
return 1; /* OK, already in hwptr updating process */
|
||||
|
||||
/* check whether the period gets really elapsed */
|
||||
pos = bytes_to_frames(runtime, pos);
|
||||
hwptr = runtime->hw_ptr_base + pos;
|
||||
if (hwptr < runtime->status->hw_ptr)
|
||||
hwptr += runtime->buffer_size;
|
||||
target = runtime->hw_ptr_interrupt + runtime->period_size;
|
||||
if (hwptr < target) {
|
||||
/* too early wakeup, process it later */
|
||||
return chip->bdl_pos_adj ? 0 : -1;
|
||||
}
|
||||
|
||||
return 1; /* OK, it's fine */
|
||||
}
|
||||
|
||||
@ -859,31 +881,6 @@ static int azx_get_delay_from_fifo(struct azx *chip, struct azx_dev *azx_dev,
|
||||
return substream->runtime->delay;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static void __azx_shutdown_chip(struct azx *chip, bool skip_link_reset)
|
||||
{
|
||||
azx_stop_chip(chip);
|
||||
@ -1573,7 +1570,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,
|
||||
[POS_FIX_SKL] = azx_get_pos_posbuf,
|
||||
[POS_FIX_FIFO] = azx_get_pos_fifo,
|
||||
};
|
||||
|
||||
|
@ -8634,6 +8634,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
|
||||
ALC285_FIXUP_HP_GPIO_AMP_INIT),
|
||||
SND_PCI_QUIRK(0x103c, 0x8783, "HP ZBook Fury 15 G7 Mobile Workstation",
|
||||
ALC285_FIXUP_HP_GPIO_AMP_INIT),
|
||||
SND_PCI_QUIRK(0x103c, 0x8788, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED),
|
||||
SND_PCI_QUIRK(0x103c, 0x87c8, "HP", ALC287_FIXUP_HP_GPIO_LED),
|
||||
SND_PCI_QUIRK(0x103c, 0x87e5, "HP ProBook 440 G8 Notebook PC", ALC236_FIXUP_HP_GPIO_LED),
|
||||
SND_PCI_QUIRK(0x103c, 0x87e7, "HP ProBook 450 G8 Notebook PC", ALC236_FIXUP_HP_GPIO_LED),
|
||||
|
@ -68,6 +68,7 @@ static struct hdac_ext_stream *
|
||||
return NULL;
|
||||
}
|
||||
|
||||
spin_lock_irq(&bus->reg_lock);
|
||||
list_for_each_entry(stream, &bus->stream_list, list) {
|
||||
struct hdac_ext_stream *hstream =
|
||||
stream_to_hdac_ext_stream(stream);
|
||||
@ -107,12 +108,12 @@ static struct hdac_ext_stream *
|
||||
* is updated in snd_hdac_ext_stream_decouple().
|
||||
*/
|
||||
if (!res->decoupled)
|
||||
snd_hdac_ext_stream_decouple(bus, res, true);
|
||||
spin_lock_irq(&bus->reg_lock);
|
||||
snd_hdac_ext_stream_decouple_locked(bus, res, true);
|
||||
|
||||
res->link_locked = 1;
|
||||
res->link_substream = substream;
|
||||
spin_unlock_irq(&bus->reg_lock);
|
||||
}
|
||||
spin_unlock_irq(&bus->reg_lock);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ static int usb6fire_comm_send_buffer(u8 *buffer, struct usb_device *dev)
|
||||
int actual_len;
|
||||
|
||||
ret = usb_interrupt_msg(dev, usb_sndintpipe(dev, COMM_EP),
|
||||
buffer, buffer[1] + 2, &actual_len, HZ);
|
||||
buffer, buffer[1] + 2, &actual_len, 1000);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else if (actual_len != buffer[1] + 2)
|
||||
|
@ -160,7 +160,7 @@ static int usb6fire_fw_ezusb_write(struct usb_device *device,
|
||||
{
|
||||
return usb_control_msg_send(device, 0, type,
|
||||
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
|
||||
value, 0, data, len, HZ, GFP_KERNEL);
|
||||
value, 0, data, len, 1000, GFP_KERNEL);
|
||||
}
|
||||
|
||||
static int usb6fire_fw_ezusb_read(struct usb_device *device,
|
||||
@ -168,7 +168,7 @@ static int usb6fire_fw_ezusb_read(struct usb_device *device,
|
||||
{
|
||||
return usb_control_msg_recv(device, 0, type,
|
||||
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
|
||||
value, 0, data, len, HZ, GFP_KERNEL);
|
||||
value, 0, data, len, 1000, GFP_KERNEL);
|
||||
}
|
||||
|
||||
static int usb6fire_fw_fpga_write(struct usb_device *device,
|
||||
@ -178,7 +178,7 @@ static int usb6fire_fw_fpga_write(struct usb_device *device,
|
||||
int ret;
|
||||
|
||||
ret = usb_bulk_msg(device, usb_sndbulkpipe(device, FPGA_EP), data, len,
|
||||
&actual_len, HZ);
|
||||
&actual_len, 1000);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else if (actual_len != len)
|
||||
|
@ -74,8 +74,9 @@ struct snd_usb_endpoint {
|
||||
|
||||
atomic_t state; /* running state */
|
||||
|
||||
void (*prepare_data_urb) (struct snd_usb_substream *subs,
|
||||
struct urb *urb);
|
||||
int (*prepare_data_urb) (struct snd_usb_substream *subs,
|
||||
struct urb *urb,
|
||||
bool in_stream_lock);
|
||||
void (*retire_data_urb) (struct snd_usb_substream *subs,
|
||||
struct urb *urb);
|
||||
|
||||
@ -94,9 +95,9 @@ struct snd_usb_endpoint {
|
||||
struct list_head ready_playback_urbs; /* playback URB FIFO for implicit fb */
|
||||
|
||||
unsigned int nurbs; /* # urbs */
|
||||
unsigned int nominal_queue_size; /* total buffer sizes in URBs */
|
||||
unsigned long active_mask; /* bitmask of active urbs */
|
||||
unsigned long unlink_mask; /* bitmask of unlinked urbs */
|
||||
atomic_t submitted_urbs; /* currently submitted urbs */
|
||||
char *syncbuf; /* sync buffer for all sync URBs */
|
||||
dma_addr_t sync_dma; /* DMA address of syncbuf */
|
||||
|
||||
@ -125,6 +126,7 @@ struct snd_usb_endpoint {
|
||||
int skip_packets; /* quirks for devices to ignore the first n packets
|
||||
in a stream */
|
||||
bool implicit_fb_sync; /* syncs with implicit feedback */
|
||||
bool lowlatency_playback; /* low-latency playback mode */
|
||||
bool need_setup; /* (re-)need for configure? */
|
||||
|
||||
/* for hw constraints */
|
||||
@ -136,6 +138,7 @@ struct snd_usb_endpoint {
|
||||
unsigned int cur_period_frames;
|
||||
unsigned int cur_period_bytes;
|
||||
unsigned int cur_buffer_periods;
|
||||
unsigned char cur_clock;
|
||||
|
||||
spinlock_t lock;
|
||||
struct list_head list;
|
||||
@ -188,7 +191,7 @@ struct snd_usb_substream {
|
||||
} dsd_dop;
|
||||
|
||||
bool trigger_tstamp_pending_update; /* trigger timestamp being updated from initial estimate */
|
||||
bool early_playback_start; /* early start needed for playback? */
|
||||
bool lowlatency_playback; /* low-latency playback mode */
|
||||
struct media_ctl *media_ctl;
|
||||
};
|
||||
|
||||
|
@ -271,7 +271,7 @@ static int __uac_clock_find_source(struct snd_usb_audio *chip,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* first, see if the ID we're looking for is a clock source already */
|
||||
/* first, see if the ID we're looking at is a clock source already */
|
||||
source = snd_usb_find_clock_source(chip, entity_id, proto);
|
||||
if (source) {
|
||||
entity_id = GET_VAL(source, proto, bClockID);
|
||||
@ -297,7 +297,7 @@ static int __uac_clock_find_source(struct snd_usb_audio *chip,
|
||||
goto find_source;
|
||||
}
|
||||
|
||||
/* the entity ID we are looking for is a selector.
|
||||
/* the entity ID we are looking at is a selector.
|
||||
* find out what it currently selects */
|
||||
ret = uac_clock_selector_get_val(chip, clock_id);
|
||||
if (ret < 0) {
|
||||
@ -496,6 +496,10 @@ int snd_usb_set_sample_rate_v2v3(struct snd_usb_audio *chip,
|
||||
union uac23_clock_source_desc *cs_desc;
|
||||
|
||||
cs_desc = snd_usb_find_clock_source(chip, clock, fmt->protocol);
|
||||
|
||||
if (!cs_desc)
|
||||
return 0;
|
||||
|
||||
if (fmt->protocol == UAC_VERSION_3)
|
||||
bmControls = le32_to_cpu(cs_desc->v3.bmControls);
|
||||
else
|
||||
|
@ -148,18 +148,23 @@ int snd_usb_endpoint_implicit_feedback_sink(struct snd_usb_endpoint *ep)
|
||||
* This won't be used for implicit feedback which takes the packet size
|
||||
* returned from the sync source
|
||||
*/
|
||||
static int slave_next_packet_size(struct snd_usb_endpoint *ep)
|
||||
static int slave_next_packet_size(struct snd_usb_endpoint *ep,
|
||||
unsigned int avail)
|
||||
{
|
||||
unsigned long flags;
|
||||
unsigned int phase;
|
||||
int ret;
|
||||
|
||||
if (ep->fill_max)
|
||||
return ep->maxframesize;
|
||||
|
||||
spin_lock_irqsave(&ep->lock, flags);
|
||||
ep->phase = (ep->phase & 0xffff)
|
||||
+ (ep->freqm << ep->datainterval);
|
||||
ret = min(ep->phase >> 16, ep->maxframesize);
|
||||
phase = (ep->phase & 0xffff) + (ep->freqm << ep->datainterval);
|
||||
ret = min(phase >> 16, ep->maxframesize);
|
||||
if (avail && ret >= avail)
|
||||
ret = -EAGAIN;
|
||||
else
|
||||
ep->phase = phase;
|
||||
spin_unlock_irqrestore(&ep->lock, flags);
|
||||
|
||||
return ret;
|
||||
@ -169,20 +174,25 @@ static int slave_next_packet_size(struct snd_usb_endpoint *ep)
|
||||
* Return the number of samples to be sent in the next packet
|
||||
* for adaptive and synchronous endpoints
|
||||
*/
|
||||
static int next_packet_size(struct snd_usb_endpoint *ep)
|
||||
static int next_packet_size(struct snd_usb_endpoint *ep, unsigned int avail)
|
||||
{
|
||||
unsigned int sample_accum;
|
||||
int ret;
|
||||
|
||||
if (ep->fill_max)
|
||||
return ep->maxframesize;
|
||||
|
||||
ep->sample_accum += ep->sample_rem;
|
||||
if (ep->sample_accum >= ep->pps) {
|
||||
ep->sample_accum -= ep->pps;
|
||||
sample_accum = ep->sample_accum + ep->sample_rem;
|
||||
if (sample_accum >= ep->pps) {
|
||||
sample_accum -= ep->pps;
|
||||
ret = ep->packsize[1];
|
||||
} else {
|
||||
ret = ep->packsize[0];
|
||||
}
|
||||
if (avail && ret >= avail)
|
||||
ret = -EAGAIN;
|
||||
else
|
||||
ep->sample_accum = sample_accum;
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -190,16 +200,27 @@ static int next_packet_size(struct snd_usb_endpoint *ep)
|
||||
/*
|
||||
* snd_usb_endpoint_next_packet_size: Return the number of samples to be sent
|
||||
* in the next packet
|
||||
*
|
||||
* If the size is equal or exceeds @avail, don't proceed but return -EAGAIN
|
||||
* Exception: @avail = 0 for skipping the check.
|
||||
*/
|
||||
int snd_usb_endpoint_next_packet_size(struct snd_usb_endpoint *ep,
|
||||
struct snd_urb_ctx *ctx, int idx)
|
||||
struct snd_urb_ctx *ctx, int idx,
|
||||
unsigned int avail)
|
||||
{
|
||||
if (ctx->packet_size[idx])
|
||||
return ctx->packet_size[idx];
|
||||
else if (ep->sync_source)
|
||||
return slave_next_packet_size(ep);
|
||||
unsigned int packet;
|
||||
|
||||
packet = ctx->packet_size[idx];
|
||||
if (packet) {
|
||||
if (avail && packet >= avail)
|
||||
return -EAGAIN;
|
||||
return packet;
|
||||
}
|
||||
|
||||
if (ep->sync_source)
|
||||
return slave_next_packet_size(ep, avail);
|
||||
else
|
||||
return next_packet_size(ep);
|
||||
return next_packet_size(ep, avail);
|
||||
}
|
||||
|
||||
static void call_retire_callback(struct snd_usb_endpoint *ep,
|
||||
@ -263,7 +284,7 @@ static void prepare_silent_urb(struct snd_usb_endpoint *ep,
|
||||
unsigned int length;
|
||||
int counts;
|
||||
|
||||
counts = snd_usb_endpoint_next_packet_size(ep, ctx, i);
|
||||
counts = snd_usb_endpoint_next_packet_size(ep, ctx, i, 0);
|
||||
length = counts * ep->stride; /* number of silent bytes */
|
||||
offset = offs * ep->stride + extra * i;
|
||||
urb->iso_frame_desc[i].offset = offset;
|
||||
@ -286,8 +307,9 @@ static void prepare_silent_urb(struct snd_usb_endpoint *ep,
|
||||
/*
|
||||
* Prepare a PLAYBACK urb for submission to the bus.
|
||||
*/
|
||||
static void prepare_outbound_urb(struct snd_usb_endpoint *ep,
|
||||
struct snd_urb_ctx *ctx)
|
||||
static int prepare_outbound_urb(struct snd_usb_endpoint *ep,
|
||||
struct snd_urb_ctx *ctx,
|
||||
bool in_stream_lock)
|
||||
{
|
||||
struct urb *urb = ctx->urb;
|
||||
unsigned char *cp = urb->transfer_buffer;
|
||||
@ -299,9 +321,9 @@ static void prepare_outbound_urb(struct snd_usb_endpoint *ep,
|
||||
case SND_USB_ENDPOINT_TYPE_DATA:
|
||||
data_subs = READ_ONCE(ep->data_subs);
|
||||
if (data_subs && ep->prepare_data_urb)
|
||||
ep->prepare_data_urb(data_subs, urb);
|
||||
else /* no data provider, so send silence */
|
||||
prepare_silent_urb(ep, ctx);
|
||||
return ep->prepare_data_urb(data_subs, urb, in_stream_lock);
|
||||
/* no data provider, so send silence */
|
||||
prepare_silent_urb(ep, ctx);
|
||||
break;
|
||||
|
||||
case SND_USB_ENDPOINT_TYPE_SYNC:
|
||||
@ -330,13 +352,14 @@ static void prepare_outbound_urb(struct snd_usb_endpoint *ep,
|
||||
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prepare a CAPTURE or SYNC urb for submission to the bus.
|
||||
*/
|
||||
static inline void prepare_inbound_urb(struct snd_usb_endpoint *ep,
|
||||
struct snd_urb_ctx *urb_ctx)
|
||||
static int prepare_inbound_urb(struct snd_usb_endpoint *ep,
|
||||
struct snd_urb_ctx *urb_ctx)
|
||||
{
|
||||
int i, offs;
|
||||
struct urb *urb = urb_ctx->urb;
|
||||
@ -361,6 +384,7 @@ static inline void prepare_inbound_urb(struct snd_usb_endpoint *ep,
|
||||
urb->iso_frame_desc[0].offset = 0;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* notify an error as XRUN to the assigned PCM data substream */
|
||||
@ -396,6 +420,16 @@ next_packet_fifo_dequeue(struct snd_usb_endpoint *ep)
|
||||
return p;
|
||||
}
|
||||
|
||||
static void push_back_to_ready_list(struct snd_usb_endpoint *ep,
|
||||
struct snd_urb_ctx *ctx)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&ep->lock, flags);
|
||||
list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs);
|
||||
spin_unlock_irqrestore(&ep->lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Send output urbs that have been prepared previously. URBs are dequeued
|
||||
* from ep->ready_playback_urbs and in case there aren't any available
|
||||
@ -406,12 +440,14 @@ next_packet_fifo_dequeue(struct snd_usb_endpoint *ep)
|
||||
* is that host controllers don't guarantee the order in which they return
|
||||
* inbound and outbound packets to their submitters.
|
||||
*
|
||||
* This function is only used for implicit feedback endpoints. For endpoints
|
||||
* driven by dedicated sync endpoints, URBs are immediately re-submitted
|
||||
* from their completion handler.
|
||||
* This function is used both for implicit feedback endpoints and in low-
|
||||
* latency playback mode.
|
||||
*/
|
||||
static void queue_pending_output_urbs(struct snd_usb_endpoint *ep)
|
||||
void snd_usb_queue_pending_output_urbs(struct snd_usb_endpoint *ep,
|
||||
bool in_stream_lock)
|
||||
{
|
||||
bool implicit_fb = snd_usb_endpoint_implicit_feedback_sink(ep);
|
||||
|
||||
while (ep_state_running(ep)) {
|
||||
|
||||
unsigned long flags;
|
||||
@ -420,14 +456,14 @@ static void queue_pending_output_urbs(struct snd_usb_endpoint *ep)
|
||||
int err, i;
|
||||
|
||||
spin_lock_irqsave(&ep->lock, flags);
|
||||
if (ep->next_packet_queued > 0 &&
|
||||
if ((!implicit_fb || ep->next_packet_queued > 0) &&
|
||||
!list_empty(&ep->ready_playback_urbs)) {
|
||||
/* take URB out of FIFO */
|
||||
ctx = list_first_entry(&ep->ready_playback_urbs,
|
||||
struct snd_urb_ctx, ready_list);
|
||||
list_del_init(&ctx->ready_list);
|
||||
|
||||
packet = next_packet_fifo_dequeue(ep);
|
||||
if (implicit_fb)
|
||||
packet = next_packet_fifo_dequeue(ep);
|
||||
}
|
||||
spin_unlock_irqrestore(&ep->lock, flags);
|
||||
|
||||
@ -435,11 +471,24 @@ static void queue_pending_output_urbs(struct snd_usb_endpoint *ep)
|
||||
return;
|
||||
|
||||
/* copy over the length information */
|
||||
for (i = 0; i < packet->packets; i++)
|
||||
ctx->packet_size[i] = packet->packet_size[i];
|
||||
if (implicit_fb) {
|
||||
for (i = 0; i < packet->packets; i++)
|
||||
ctx->packet_size[i] = packet->packet_size[i];
|
||||
}
|
||||
|
||||
/* call the data handler to fill in playback data */
|
||||
prepare_outbound_urb(ep, ctx);
|
||||
err = prepare_outbound_urb(ep, ctx, in_stream_lock);
|
||||
/* can be stopped during prepare callback */
|
||||
if (unlikely(!ep_state_running(ep)))
|
||||
break;
|
||||
if (err < 0) {
|
||||
/* push back to ready list again for -EAGAIN */
|
||||
if (err == -EAGAIN)
|
||||
push_back_to_ready_list(ep, ctx);
|
||||
else
|
||||
notify_xrun(ep);
|
||||
return;
|
||||
}
|
||||
|
||||
err = usb_submit_urb(ctx->urb, GFP_ATOMIC);
|
||||
if (err < 0) {
|
||||
@ -451,6 +500,7 @@ static void queue_pending_output_urbs(struct snd_usb_endpoint *ep)
|
||||
}
|
||||
|
||||
set_bit(ctx->index, &ep->active_mask);
|
||||
atomic_inc(&ep->submitted_urbs);
|
||||
}
|
||||
}
|
||||
|
||||
@ -461,7 +511,6 @@ static void snd_complete_urb(struct urb *urb)
|
||||
{
|
||||
struct snd_urb_ctx *ctx = urb->context;
|
||||
struct snd_usb_endpoint *ep = ctx->ep;
|
||||
unsigned long flags;
|
||||
int err;
|
||||
|
||||
if (unlikely(urb->status == -ENOENT || /* unlinked */
|
||||
@ -482,16 +531,20 @@ static void snd_complete_urb(struct urb *urb)
|
||||
if (unlikely(!ep_state_running(ep)))
|
||||
goto exit_clear;
|
||||
|
||||
if (snd_usb_endpoint_implicit_feedback_sink(ep)) {
|
||||
spin_lock_irqsave(&ep->lock, flags);
|
||||
list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs);
|
||||
/* in low-latency and implicit-feedback modes, push back the
|
||||
* URB to ready list at first, then process as much as possible
|
||||
*/
|
||||
if (ep->lowlatency_playback ||
|
||||
snd_usb_endpoint_implicit_feedback_sink(ep)) {
|
||||
push_back_to_ready_list(ep, ctx);
|
||||
clear_bit(ctx->index, &ep->active_mask);
|
||||
spin_unlock_irqrestore(&ep->lock, flags);
|
||||
queue_pending_output_urbs(ep);
|
||||
snd_usb_queue_pending_output_urbs(ep, false);
|
||||
atomic_dec(&ep->submitted_urbs); /* decrement at last */
|
||||
return;
|
||||
}
|
||||
|
||||
prepare_outbound_urb(ep, ctx);
|
||||
/* in non-lowlatency mode, no error handling for prepare */
|
||||
prepare_outbound_urb(ep, ctx, false);
|
||||
/* can be stopped during prepare callback */
|
||||
if (unlikely(!ep_state_running(ep)))
|
||||
goto exit_clear;
|
||||
@ -513,6 +566,7 @@ static void snd_complete_urb(struct urb *urb)
|
||||
|
||||
exit_clear:
|
||||
clear_bit(ctx->index, &ep->active_mask);
|
||||
atomic_dec(&ep->submitted_urbs);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -596,6 +650,7 @@ int snd_usb_add_endpoint(struct snd_usb_audio *chip, int ep_num, int type)
|
||||
ep->type = type;
|
||||
ep->ep_num = ep_num;
|
||||
INIT_LIST_HEAD(&ep->ready_playback_urbs);
|
||||
atomic_set(&ep->submitted_urbs, 0);
|
||||
|
||||
is_playback = ((ep_num & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT);
|
||||
ep_num &= USB_ENDPOINT_NUMBER_MASK;
|
||||
@ -722,6 +777,7 @@ snd_usb_endpoint_open(struct snd_usb_audio *chip,
|
||||
ep->cur_period_frames = params_period_size(params);
|
||||
ep->cur_period_bytes = ep->cur_period_frames * ep->cur_frame_bytes;
|
||||
ep->cur_buffer_periods = params_periods(params);
|
||||
ep->cur_clock = fp->clock;
|
||||
|
||||
if (ep->type == SND_USB_ENDPOINT_TYPE_SYNC)
|
||||
endpoint_set_syncinterval(chip, ep);
|
||||
@ -781,14 +837,19 @@ void snd_usb_endpoint_set_sync(struct snd_usb_audio *chip,
|
||||
* Pass NULL to deactivate each callback.
|
||||
*/
|
||||
void snd_usb_endpoint_set_callback(struct snd_usb_endpoint *ep,
|
||||
void (*prepare)(struct snd_usb_substream *subs,
|
||||
struct urb *urb),
|
||||
int (*prepare)(struct snd_usb_substream *subs,
|
||||
struct urb *urb,
|
||||
bool in_stream_lock),
|
||||
void (*retire)(struct snd_usb_substream *subs,
|
||||
struct urb *urb),
|
||||
struct snd_usb_substream *data_subs)
|
||||
{
|
||||
ep->prepare_data_urb = prepare;
|
||||
ep->retire_data_urb = retire;
|
||||
if (data_subs)
|
||||
ep->lowlatency_playback = data_subs->lowlatency_playback;
|
||||
else
|
||||
ep->lowlatency_playback = false;
|
||||
WRITE_ONCE(ep->data_subs, data_subs);
|
||||
}
|
||||
|
||||
@ -833,6 +894,7 @@ void snd_usb_endpoint_close(struct snd_usb_audio *chip,
|
||||
ep->altsetting = 0;
|
||||
ep->cur_audiofmt = NULL;
|
||||
ep->cur_rate = 0;
|
||||
ep->cur_clock = 0;
|
||||
ep->iface_ref = NULL;
|
||||
usb_audio_dbg(chip, "EP 0x%x closed\n", ep->ep_num);
|
||||
}
|
||||
@ -859,7 +921,7 @@ static int wait_clear_urbs(struct snd_usb_endpoint *ep)
|
||||
return 0;
|
||||
|
||||
do {
|
||||
alive = bitmap_weight(&ep->active_mask, ep->nurbs);
|
||||
alive = atomic_read(&ep->submitted_urbs);
|
||||
if (!alive)
|
||||
break;
|
||||
|
||||
@ -893,9 +955,10 @@ void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep)
|
||||
*
|
||||
* This function moves the EP to STOPPING state if it's being RUNNING.
|
||||
*/
|
||||
static int stop_urbs(struct snd_usb_endpoint *ep, bool force)
|
||||
static int stop_urbs(struct snd_usb_endpoint *ep, bool force, bool keep_pending)
|
||||
{
|
||||
unsigned int i;
|
||||
unsigned long flags;
|
||||
|
||||
if (!force && atomic_read(&ep->running))
|
||||
return -EBUSY;
|
||||
@ -903,9 +966,14 @@ static int stop_urbs(struct snd_usb_endpoint *ep, bool force)
|
||||
if (!ep_state_update(ep, EP_STATE_RUNNING, EP_STATE_STOPPING))
|
||||
return 0;
|
||||
|
||||
spin_lock_irqsave(&ep->lock, flags);
|
||||
INIT_LIST_HEAD(&ep->ready_playback_urbs);
|
||||
ep->next_packet_head = 0;
|
||||
ep->next_packet_queued = 0;
|
||||
spin_unlock_irqrestore(&ep->lock, flags);
|
||||
|
||||
if (keep_pending)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < ep->nurbs; i++) {
|
||||
if (test_bit(i, &ep->active_mask)) {
|
||||
@ -930,7 +998,7 @@ static int release_urbs(struct snd_usb_endpoint *ep, bool force)
|
||||
snd_usb_endpoint_set_callback(ep, NULL, NULL, NULL);
|
||||
|
||||
/* stop and unlink urbs */
|
||||
err = stop_urbs(ep, force);
|
||||
err = stop_urbs(ep, force, false);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
@ -1132,10 +1200,6 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep)
|
||||
INIT_LIST_HEAD(&u->ready_list);
|
||||
}
|
||||
|
||||
/* total buffer bytes of all URBs plus the next queue;
|
||||
* referred in pcm.c
|
||||
*/
|
||||
ep->nominal_queue_size = maxsize * urb_packs * (ep->nurbs + 1);
|
||||
return 0;
|
||||
|
||||
out_of_memory:
|
||||
@ -1340,6 +1404,25 @@ unlock:
|
||||
return err;
|
||||
}
|
||||
|
||||
/* get the current rate set to the given clock by any endpoint */
|
||||
int snd_usb_endpoint_get_clock_rate(struct snd_usb_audio *chip, int clock)
|
||||
{
|
||||
struct snd_usb_endpoint *ep;
|
||||
int rate = 0;
|
||||
|
||||
if (!clock)
|
||||
return 0;
|
||||
mutex_lock(&chip->mutex);
|
||||
list_for_each_entry(ep, &chip->ep_list, list) {
|
||||
if (ep->cur_clock == clock && ep->cur_rate) {
|
||||
rate = ep->cur_rate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&chip->mutex);
|
||||
return rate;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_usb_endpoint_start: start an snd_usb_endpoint
|
||||
*
|
||||
@ -1355,6 +1438,7 @@ unlock:
|
||||
*/
|
||||
int snd_usb_endpoint_start(struct snd_usb_endpoint *ep)
|
||||
{
|
||||
bool is_playback = usb_pipeout(ep->pipe);
|
||||
int err;
|
||||
unsigned int i;
|
||||
|
||||
@ -1391,13 +1475,9 @@ int snd_usb_endpoint_start(struct snd_usb_endpoint *ep)
|
||||
|
||||
if (snd_usb_endpoint_implicit_feedback_sink(ep) &&
|
||||
!(ep->chip->quirk_flags & QUIRK_FLAG_PLAYBACK_FIRST)) {
|
||||
for (i = 0; i < ep->nurbs; i++) {
|
||||
struct snd_urb_ctx *ctx = ep->urb + i;
|
||||
list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs);
|
||||
}
|
||||
|
||||
usb_audio_dbg(ep->chip, "No URB submission due to implicit fb sync\n");
|
||||
return 0;
|
||||
i = 0;
|
||||
goto fill_rest;
|
||||
}
|
||||
|
||||
for (i = 0; i < ep->nurbs; i++) {
|
||||
@ -1406,10 +1486,18 @@ int snd_usb_endpoint_start(struct snd_usb_endpoint *ep)
|
||||
if (snd_BUG_ON(!urb))
|
||||
goto __error;
|
||||
|
||||
if (usb_pipeout(ep->pipe)) {
|
||||
prepare_outbound_urb(ep, urb->context);
|
||||
} else {
|
||||
prepare_inbound_urb(ep, urb->context);
|
||||
if (is_playback)
|
||||
err = prepare_outbound_urb(ep, urb->context, true);
|
||||
else
|
||||
err = prepare_inbound_urb(ep, urb->context);
|
||||
if (err < 0) {
|
||||
/* stop filling at applptr */
|
||||
if (err == -EAGAIN)
|
||||
break;
|
||||
usb_audio_dbg(ep->chip,
|
||||
"EP 0x%x: failed to prepare urb: %d\n",
|
||||
ep->ep_num, err);
|
||||
goto __error;
|
||||
}
|
||||
|
||||
err = usb_submit_urb(urb, GFP_ATOMIC);
|
||||
@ -1420,14 +1508,29 @@ int snd_usb_endpoint_start(struct snd_usb_endpoint *ep)
|
||||
goto __error;
|
||||
}
|
||||
set_bit(i, &ep->active_mask);
|
||||
atomic_inc(&ep->submitted_urbs);
|
||||
}
|
||||
|
||||
if (!i) {
|
||||
usb_audio_dbg(ep->chip, "XRUN at starting EP 0x%x\n",
|
||||
ep->ep_num);
|
||||
goto __error;
|
||||
}
|
||||
|
||||
usb_audio_dbg(ep->chip, "%d URBs submitted for EP 0x%x\n",
|
||||
ep->nurbs, ep->ep_num);
|
||||
i, ep->ep_num);
|
||||
|
||||
fill_rest:
|
||||
/* put the remaining URBs to ready list */
|
||||
if (is_playback) {
|
||||
for (; i < ep->nurbs; i++)
|
||||
push_back_to_ready_list(ep, ep->urb + i);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
__error:
|
||||
snd_usb_endpoint_stop(ep);
|
||||
snd_usb_endpoint_stop(ep, false);
|
||||
return -EPIPE;
|
||||
}
|
||||
|
||||
@ -1435,6 +1538,7 @@ __error:
|
||||
* snd_usb_endpoint_stop: stop an snd_usb_endpoint
|
||||
*
|
||||
* @ep: the endpoint to stop (may be NULL)
|
||||
* @keep_pending: keep in-flight URBs
|
||||
*
|
||||
* A call to this function will decrement the running count of the endpoint.
|
||||
* In case the last user has requested the endpoint stop, the URBs will
|
||||
@ -1445,7 +1549,7 @@ __error:
|
||||
* The caller needs to synchronize the pending stop operation via
|
||||
* snd_usb_endpoint_sync_pending_stop().
|
||||
*/
|
||||
void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep)
|
||||
void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep, bool keep_pending)
|
||||
{
|
||||
if (!ep)
|
||||
return;
|
||||
@ -1460,7 +1564,7 @@ void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep)
|
||||
if (!atomic_dec_return(&ep->running)) {
|
||||
if (ep->sync_source)
|
||||
WRITE_ONCE(ep->sync_source->sync_sink, NULL);
|
||||
stop_urbs(ep, false);
|
||||
stop_urbs(ep, false, keep_pending);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1575,7 +1679,7 @@ static void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep,
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&ep->lock, flags);
|
||||
queue_pending_output_urbs(ep);
|
||||
snd_usb_queue_pending_output_urbs(ep, false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ void snd_usb_endpoint_close(struct snd_usb_audio *chip,
|
||||
struct snd_usb_endpoint *ep);
|
||||
int snd_usb_endpoint_configure(struct snd_usb_audio *chip,
|
||||
struct snd_usb_endpoint *ep);
|
||||
int snd_usb_endpoint_get_clock_rate(struct snd_usb_audio *chip, int clock);
|
||||
|
||||
bool snd_usb_endpoint_compatible(struct snd_usb_audio *chip,
|
||||
struct snd_usb_endpoint *ep,
|
||||
@ -29,14 +30,15 @@ void snd_usb_endpoint_set_sync(struct snd_usb_audio *chip,
|
||||
struct snd_usb_endpoint *data_ep,
|
||||
struct snd_usb_endpoint *sync_ep);
|
||||
void snd_usb_endpoint_set_callback(struct snd_usb_endpoint *ep,
|
||||
void (*prepare)(struct snd_usb_substream *subs,
|
||||
struct urb *urb),
|
||||
int (*prepare)(struct snd_usb_substream *subs,
|
||||
struct urb *urb,
|
||||
bool in_stream_lock),
|
||||
void (*retire)(struct snd_usb_substream *subs,
|
||||
struct urb *urb),
|
||||
struct snd_usb_substream *data_subs);
|
||||
|
||||
int snd_usb_endpoint_start(struct snd_usb_endpoint *ep);
|
||||
void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep);
|
||||
void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep, bool keep_pending);
|
||||
void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep);
|
||||
void snd_usb_endpoint_suspend(struct snd_usb_endpoint *ep);
|
||||
int snd_usb_endpoint_activate(struct snd_usb_endpoint *ep);
|
||||
@ -45,6 +47,9 @@ void snd_usb_endpoint_free_all(struct snd_usb_audio *chip);
|
||||
|
||||
int snd_usb_endpoint_implicit_feedback_sink(struct snd_usb_endpoint *ep);
|
||||
int snd_usb_endpoint_next_packet_size(struct snd_usb_endpoint *ep,
|
||||
struct snd_urb_ctx *ctx, int idx);
|
||||
struct snd_urb_ctx *ctx, int idx,
|
||||
unsigned int avail);
|
||||
void snd_usb_queue_pending_output_urbs(struct snd_usb_endpoint *ep,
|
||||
bool in_stream_lock);
|
||||
|
||||
#endif /* __USBAUDIO_ENDPOINT_H */
|
||||
|
@ -54,8 +54,6 @@ static const struct snd_usb_implicit_fb_match playback_implicit_fb_quirks[] = {
|
||||
|
||||
/* Fixed EP */
|
||||
/* FIXME: check the availability of generic matching */
|
||||
IMPLICIT_FB_FIXED_DEV(0x1397, 0x0001, 0x81, 1), /* Behringer UFX1604 */
|
||||
IMPLICIT_FB_FIXED_DEV(0x1397, 0x0002, 0x81, 1), /* Behringer UFX1204 */
|
||||
IMPLICIT_FB_FIXED_DEV(0x2466, 0x8010, 0x81, 2), /* Fractal Audio Axe-Fx III */
|
||||
IMPLICIT_FB_FIXED_DEV(0x31e9, 0x0001, 0x81, 2), /* Solid State Logic SSL2 */
|
||||
IMPLICIT_FB_FIXED_DEV(0x31e9, 0x0002, 0x81, 2), /* Solid State Logic SSL2+ */
|
||||
|
@ -113,12 +113,12 @@ int line6_send_raw_message(struct usb_line6 *line6, const char *buffer,
|
||||
retval = usb_interrupt_msg(line6->usbdev,
|
||||
usb_sndintpipe(line6->usbdev, properties->ep_ctrl_w),
|
||||
(char *)frag_buf, frag_size,
|
||||
&partial, LINE6_TIMEOUT * HZ);
|
||||
&partial, LINE6_TIMEOUT);
|
||||
} else {
|
||||
retval = usb_bulk_msg(line6->usbdev,
|
||||
usb_sndbulkpipe(line6->usbdev, properties->ep_ctrl_w),
|
||||
(char *)frag_buf, frag_size,
|
||||
&partial, LINE6_TIMEOUT * HZ);
|
||||
&partial, LINE6_TIMEOUT);
|
||||
}
|
||||
|
||||
if (retval) {
|
||||
@ -347,7 +347,7 @@ int line6_read_data(struct usb_line6 *line6, unsigned address, void *data,
|
||||
ret = usb_control_msg_send(usbdev, 0, 0x67,
|
||||
USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
|
||||
(datalen << 8) | 0x21, address, NULL, 0,
|
||||
LINE6_TIMEOUT * HZ, GFP_KERNEL);
|
||||
LINE6_TIMEOUT, GFP_KERNEL);
|
||||
if (ret) {
|
||||
dev_err(line6->ifcdev, "read request failed (error %d)\n", ret);
|
||||
goto exit;
|
||||
@ -360,7 +360,7 @@ int line6_read_data(struct usb_line6 *line6, unsigned address, void *data,
|
||||
ret = usb_control_msg_recv(usbdev, 0, 0x67,
|
||||
USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
|
||||
0x0012, 0x0000, &len, 1,
|
||||
LINE6_TIMEOUT * HZ, GFP_KERNEL);
|
||||
LINE6_TIMEOUT, GFP_KERNEL);
|
||||
if (ret) {
|
||||
dev_err(line6->ifcdev,
|
||||
"receive length failed (error %d)\n", ret);
|
||||
@ -387,7 +387,7 @@ int line6_read_data(struct usb_line6 *line6, unsigned address, void *data,
|
||||
/* receive the result: */
|
||||
ret = usb_control_msg_recv(usbdev, 0, 0x67,
|
||||
USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
|
||||
0x0013, 0x0000, data, datalen, LINE6_TIMEOUT * HZ,
|
||||
0x0013, 0x0000, data, datalen, LINE6_TIMEOUT,
|
||||
GFP_KERNEL);
|
||||
if (ret)
|
||||
dev_err(line6->ifcdev, "read failed (error %d)\n", ret);
|
||||
@ -417,7 +417,7 @@ int line6_write_data(struct usb_line6 *line6, unsigned address, void *data,
|
||||
|
||||
ret = usb_control_msg_send(usbdev, 0, 0x67,
|
||||
USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
|
||||
0x0022, address, data, datalen, LINE6_TIMEOUT * HZ,
|
||||
0x0022, address, data, datalen, LINE6_TIMEOUT,
|
||||
GFP_KERNEL);
|
||||
if (ret) {
|
||||
dev_err(line6->ifcdev,
|
||||
@ -430,7 +430,7 @@ int line6_write_data(struct usb_line6 *line6, unsigned address, void *data,
|
||||
|
||||
ret = usb_control_msg_recv(usbdev, 0, 0x67,
|
||||
USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
|
||||
0x0012, 0x0000, status, 1, LINE6_TIMEOUT * HZ,
|
||||
0x0012, 0x0000, status, 1, LINE6_TIMEOUT,
|
||||
GFP_KERNEL);
|
||||
if (ret) {
|
||||
dev_err(line6->ifcdev,
|
||||
|
@ -27,7 +27,7 @@
|
||||
#define LINE6_FALLBACK_INTERVAL 10
|
||||
#define LINE6_FALLBACK_MAXPACKETSIZE 16
|
||||
|
||||
#define LINE6_TIMEOUT 1
|
||||
#define LINE6_TIMEOUT 1000
|
||||
#define LINE6_BUFSIZE_LISTEN 64
|
||||
#define LINE6_MIDI_MESSAGE_MAXLEN 256
|
||||
|
||||
|
@ -190,7 +190,7 @@ static int podhd_dev_start(struct usb_line6_podhd *pod)
|
||||
ret = usb_control_msg_send(usbdev, 0,
|
||||
0x67, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
|
||||
0x11, 0,
|
||||
NULL, 0, LINE6_TIMEOUT * HZ, GFP_KERNEL);
|
||||
NULL, 0, LINE6_TIMEOUT, GFP_KERNEL);
|
||||
if (ret) {
|
||||
dev_err(pod->line6.ifcdev, "read request failed (error %d)\n", ret);
|
||||
goto exit;
|
||||
@ -200,7 +200,7 @@ static int podhd_dev_start(struct usb_line6_podhd *pod)
|
||||
ret = usb_control_msg_recv(usbdev, 0, 0x67,
|
||||
USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
|
||||
0x11, 0x0,
|
||||
init_bytes, 3, LINE6_TIMEOUT * HZ, GFP_KERNEL);
|
||||
init_bytes, 3, LINE6_TIMEOUT, GFP_KERNEL);
|
||||
if (ret) {
|
||||
dev_err(pod->line6.ifcdev,
|
||||
"receive length failed (error %d)\n", ret);
|
||||
@ -220,7 +220,7 @@ static int podhd_dev_start(struct usb_line6_podhd *pod)
|
||||
USB_REQ_SET_FEATURE,
|
||||
USB_TYPE_STANDARD | USB_RECIP_DEVICE | USB_DIR_OUT,
|
||||
1, 0,
|
||||
NULL, 0, LINE6_TIMEOUT * HZ, GFP_KERNEL);
|
||||
NULL, 0, LINE6_TIMEOUT, GFP_KERNEL);
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ static int toneport_send_cmd(struct usb_device *usbdev, int cmd1, int cmd2)
|
||||
|
||||
ret = usb_control_msg_send(usbdev, 0, 0x67,
|
||||
USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
|
||||
cmd1, cmd2, NULL, 0, LINE6_TIMEOUT * HZ,
|
||||
cmd1, cmd2, NULL, 0, LINE6_TIMEOUT,
|
||||
GFP_KERNEL);
|
||||
|
||||
if (ret) {
|
||||
|
@ -1000,7 +1000,7 @@ static int detect_usb_format(struct ua101 *ua)
|
||||
fmt_playback->bSubframeSize * ua->playback.channels;
|
||||
|
||||
epd = &ua->intf[INTF_CAPTURE]->altsetting[1].endpoint[0].desc;
|
||||
if (!usb_endpoint_is_isoc_in(epd)) {
|
||||
if (!usb_endpoint_is_isoc_in(epd) || usb_endpoint_maxp(epd) == 0) {
|
||||
dev_err(&ua->dev->dev, "invalid capture endpoint\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
@ -1008,7 +1008,7 @@ static int detect_usb_format(struct ua101 *ua)
|
||||
ua->capture.max_packet_bytes = usb_endpoint_maxp(epd);
|
||||
|
||||
epd = &ua->intf[INTF_PLAYBACK]->altsetting[1].endpoint[0].desc;
|
||||
if (!usb_endpoint_is_isoc_out(epd)) {
|
||||
if (!usb_endpoint_is_isoc_out(epd) || usb_endpoint_maxp(epd) == 0) {
|
||||
dev_err(&ua->dev->dev, "invalid playback endpoint\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
@ -361,9 +361,8 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request,
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
|
||||
ret = snd_usb_lock_shutdown(chip) ? -EIO : 0;
|
||||
if (ret)
|
||||
goto error;
|
||||
if (snd_usb_lock_shutdown(chip))
|
||||
return -EIO;
|
||||
|
||||
idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8);
|
||||
ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest,
|
||||
@ -372,8 +371,7 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request,
|
||||
snd_usb_unlock_shutdown(chip);
|
||||
|
||||
if (ret < 0) {
|
||||
error:
|
||||
usb_audio_err(chip,
|
||||
usb_audio_dbg(chip,
|
||||
"cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n",
|
||||
request, validx, idx, cval->val_type);
|
||||
return ret;
|
||||
@ -1208,12 +1206,32 @@ static void volume_control_quirks(struct usb_mixer_elem_info *cval,
|
||||
}
|
||||
}
|
||||
|
||||
/* forcibly initialize the current mixer value; if GET_CUR fails, set to
|
||||
* the minimum as default
|
||||
*/
|
||||
static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx)
|
||||
{
|
||||
int val, err;
|
||||
|
||||
err = snd_usb_get_cur_mix_value(cval, ch, idx, &val);
|
||||
if (!err)
|
||||
return;
|
||||
if (!cval->head.mixer->ignore_ctl_error)
|
||||
usb_audio_warn(cval->head.mixer->chip,
|
||||
"%d:%d: failed to get current value for ch %d (%d)\n",
|
||||
cval->head.id, mixer_ctrl_intf(cval->head.mixer),
|
||||
ch, err);
|
||||
snd_usb_set_cur_mix_value(cval, ch, idx, cval->min);
|
||||
}
|
||||
|
||||
/*
|
||||
* retrieve the minimum and maximum values for the specified control
|
||||
*/
|
||||
static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval,
|
||||
int default_min, struct snd_kcontrol *kctl)
|
||||
{
|
||||
int i, idx;
|
||||
|
||||
/* for failsafe */
|
||||
cval->min = default_min;
|
||||
cval->max = cval->min + 1;
|
||||
@ -1226,7 +1244,6 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval,
|
||||
} else {
|
||||
int minchn = 0;
|
||||
if (cval->cmask) {
|
||||
int i;
|
||||
for (i = 0; i < MAX_CHANNELS; i++)
|
||||
if (cval->cmask & (1 << i)) {
|
||||
minchn = i + 1;
|
||||
@ -1327,6 +1344,19 @@ no_res_check:
|
||||
}
|
||||
}
|
||||
|
||||
/* initialize all elements */
|
||||
if (!cval->cmask) {
|
||||
init_cur_mix_raw(cval, 0, 0);
|
||||
} else {
|
||||
idx = 0;
|
||||
for (i = 0; i < MAX_CHANNELS; i++) {
|
||||
if (cval->cmask & (1 << i)) {
|
||||
init_cur_mix_raw(cval, i + 1, idx);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -2795,6 +2795,7 @@ static int snd_bbfpro_controls_create(struct usb_mixer_interface *mixer)
|
||||
#define SND_DJM_750_IDX 0x1
|
||||
#define SND_DJM_850_IDX 0x2
|
||||
#define SND_DJM_900NXS2_IDX 0x3
|
||||
#define SND_DJM_750MK2_IDX 0x4
|
||||
|
||||
|
||||
#define SND_DJM_CTL(_name, suffix, _default_value, _windex) { \
|
||||
@ -2984,10 +2985,40 @@ static const struct snd_djm_ctl snd_djm_ctls_900nxs2[] = {
|
||||
SND_DJM_CTL("Ch5 Input", 900nxs2_cap5, 3, SND_DJM_WINDEX_CAP)
|
||||
};
|
||||
|
||||
// DJM-750MK2
|
||||
static const u16 snd_djm_opts_750mk2_cap1[] = {
|
||||
0x0100, 0x0102, 0x0103, 0x0106, 0x0107, 0x0108, 0x0109, 0x010a };
|
||||
static const u16 snd_djm_opts_750mk2_cap2[] = {
|
||||
0x0200, 0x0202, 0x0203, 0x0206, 0x0207, 0x0208, 0x0209, 0x020a };
|
||||
static const u16 snd_djm_opts_750mk2_cap3[] = {
|
||||
0x0300, 0x0302, 0x0303, 0x0306, 0x0307, 0x0308, 0x0309, 0x030a };
|
||||
static const u16 snd_djm_opts_750mk2_cap4[] = {
|
||||
0x0400, 0x0402, 0x0403, 0x0406, 0x0407, 0x0408, 0x0409, 0x040a };
|
||||
static const u16 snd_djm_opts_750mk2_cap5[] = {
|
||||
0x0507, 0x0508, 0x0509, 0x050a, 0x0511, 0x0512, 0x0513, 0x0514 };
|
||||
|
||||
static const u16 snd_djm_opts_750mk2_pb1[] = { 0x0100, 0x0101, 0x0104 };
|
||||
static const u16 snd_djm_opts_750mk2_pb2[] = { 0x0200, 0x0201, 0x0204 };
|
||||
static const u16 snd_djm_opts_750mk2_pb3[] = { 0x0300, 0x0301, 0x0304 };
|
||||
|
||||
|
||||
static const struct snd_djm_ctl snd_djm_ctls_750mk2[] = {
|
||||
SND_DJM_CTL("Capture Level", cap_level, 0, SND_DJM_WINDEX_CAPLVL),
|
||||
SND_DJM_CTL("Ch1 Input", 750mk2_cap1, 2, SND_DJM_WINDEX_CAP),
|
||||
SND_DJM_CTL("Ch2 Input", 750mk2_cap2, 2, SND_DJM_WINDEX_CAP),
|
||||
SND_DJM_CTL("Ch3 Input", 750mk2_cap3, 2, SND_DJM_WINDEX_CAP),
|
||||
SND_DJM_CTL("Ch4 Input", 750mk2_cap4, 2, SND_DJM_WINDEX_CAP),
|
||||
SND_DJM_CTL("Ch5 Input", 750mk2_cap5, 3, SND_DJM_WINDEX_CAP),
|
||||
SND_DJM_CTL("Ch1 Output", 750mk2_pb1, 0, SND_DJM_WINDEX_PB),
|
||||
SND_DJM_CTL("Ch2 Output", 750mk2_pb2, 1, SND_DJM_WINDEX_PB),
|
||||
SND_DJM_CTL("Ch3 Output", 750mk2_pb3, 2, SND_DJM_WINDEX_PB)
|
||||
};
|
||||
|
||||
|
||||
static const struct snd_djm_device snd_djm_devices[] = {
|
||||
SND_DJM_DEVICE(250mk2),
|
||||
SND_DJM_DEVICE(750),
|
||||
SND_DJM_DEVICE(750mk2),
|
||||
SND_DJM_DEVICE(850),
|
||||
SND_DJM_DEVICE(900nxs2)
|
||||
};
|
||||
@ -3235,6 +3266,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
|
||||
case USB_ID(0x08e4, 0x017f): /* Pioneer DJ DJM-750 */
|
||||
err = snd_djm_controls_create(mixer, SND_DJM_750_IDX);
|
||||
break;
|
||||
case USB_ID(0x2b73, 0x001b): /* Pioneer DJ DJM-750MK2 */
|
||||
err = snd_djm_controls_create(mixer, SND_DJM_750MK2_IDX);
|
||||
break;
|
||||
case USB_ID(0x08e4, 0x0163): /* Pioneer DJ DJM-850 */
|
||||
err = snd_djm_controls_create(mixer, SND_DJM_850_IDX);
|
||||
break;
|
||||
|
164
sound/usb/pcm.c
164
sound/usb/pcm.c
@ -219,16 +219,16 @@ int snd_usb_init_pitch(struct snd_usb_audio *chip,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool stop_endpoints(struct snd_usb_substream *subs)
|
||||
static bool stop_endpoints(struct snd_usb_substream *subs, bool keep_pending)
|
||||
{
|
||||
bool stopped = 0;
|
||||
|
||||
if (test_and_clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags)) {
|
||||
snd_usb_endpoint_stop(subs->sync_endpoint);
|
||||
snd_usb_endpoint_stop(subs->sync_endpoint, keep_pending);
|
||||
stopped = true;
|
||||
}
|
||||
if (test_and_clear_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags)) {
|
||||
snd_usb_endpoint_stop(subs->data_endpoint);
|
||||
snd_usb_endpoint_stop(subs->data_endpoint, keep_pending);
|
||||
stopped = true;
|
||||
}
|
||||
return stopped;
|
||||
@ -261,7 +261,7 @@ static int start_endpoints(struct snd_usb_substream *subs)
|
||||
return 0;
|
||||
|
||||
error:
|
||||
stop_endpoints(subs);
|
||||
stop_endpoints(subs, false);
|
||||
return err;
|
||||
}
|
||||
|
||||
@ -437,7 +437,7 @@ static int configure_endpoints(struct snd_usb_audio *chip,
|
||||
|
||||
if (subs->data_endpoint->need_setup) {
|
||||
/* stop any running stream beforehand */
|
||||
if (stop_endpoints(subs))
|
||||
if (stop_endpoints(subs, false))
|
||||
sync_pending_stops(subs);
|
||||
err = snd_usb_endpoint_configure(chip, subs->data_endpoint);
|
||||
if (err < 0)
|
||||
@ -572,7 +572,7 @@ static int snd_usb_hw_free(struct snd_pcm_substream *substream)
|
||||
subs->cur_audiofmt = NULL;
|
||||
mutex_unlock(&chip->mutex);
|
||||
if (!snd_usb_lock_shutdown(chip)) {
|
||||
if (stop_endpoints(subs))
|
||||
if (stop_endpoints(subs, false))
|
||||
sync_pending_stops(subs);
|
||||
close_endpoints(chip, subs);
|
||||
snd_usb_unlock_shutdown(chip);
|
||||
@ -581,6 +581,26 @@ static int snd_usb_hw_free(struct snd_pcm_substream *substream)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* check whether early start is needed for playback stream */
|
||||
static int lowlatency_playback_available(struct snd_pcm_runtime *runtime,
|
||||
struct snd_usb_substream *subs)
|
||||
{
|
||||
struct snd_usb_audio *chip = subs->stream->chip;
|
||||
|
||||
if (subs->direction == SNDRV_PCM_STREAM_CAPTURE)
|
||||
return false;
|
||||
/* disabled via module option? */
|
||||
if (!chip->lowlatency)
|
||||
return false;
|
||||
/* free-wheeling mode? (e.g. dmix) */
|
||||
if (runtime->stop_threshold > runtime->buffer_size)
|
||||
return false;
|
||||
/* implicit feedback mode has own operation mode */
|
||||
if (snd_usb_endpoint_implicit_feedback_sink(subs->data_endpoint))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* prepare callback
|
||||
*
|
||||
@ -614,13 +634,8 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
subs->period_elapsed_pending = 0;
|
||||
runtime->delay = 0;
|
||||
|
||||
/* check whether early start is needed for playback stream */
|
||||
subs->early_playback_start =
|
||||
subs->direction == SNDRV_PCM_STREAM_PLAYBACK &&
|
||||
(!chip->lowlatency ||
|
||||
(subs->data_endpoint->nominal_queue_size >= subs->buffer_bytes));
|
||||
|
||||
if (subs->early_playback_start)
|
||||
subs->lowlatency_playback = lowlatency_playback_available(runtime, subs);
|
||||
if (!subs->lowlatency_playback)
|
||||
ret = start_endpoints(subs);
|
||||
|
||||
unlock:
|
||||
@ -734,6 +749,7 @@ static int hw_rule_rate(struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_rule *rule)
|
||||
{
|
||||
struct snd_usb_substream *subs = rule->private;
|
||||
struct snd_usb_audio *chip = subs->stream->chip;
|
||||
const struct audioformat *fp;
|
||||
struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
|
||||
unsigned int rmin, rmax, r;
|
||||
@ -745,6 +761,14 @@ static int hw_rule_rate(struct snd_pcm_hw_params *params,
|
||||
list_for_each_entry(fp, &subs->fmt_list, list) {
|
||||
if (!hw_check_valid_format(subs, params, fp))
|
||||
continue;
|
||||
r = snd_usb_endpoint_get_clock_rate(chip, fp->clock);
|
||||
if (r > 0) {
|
||||
if (!snd_interval_test(it, r))
|
||||
continue;
|
||||
rmin = min(rmin, r);
|
||||
rmax = max(rmax, r);
|
||||
continue;
|
||||
}
|
||||
if (fp->rate_table && fp->nr_rates) {
|
||||
for (i = 0; i < fp->nr_rates; i++) {
|
||||
r = fp->rate_table[i];
|
||||
@ -1056,6 +1080,13 @@ static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substre
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
list_for_each_entry(fp, &subs->fmt_list, list) {
|
||||
if (fp->implicit_fb) {
|
||||
runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1068,6 +1099,10 @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream)
|
||||
int ret;
|
||||
|
||||
runtime->hw = snd_usb_hardware;
|
||||
/* need an explicit sync to catch applptr update in low-latency mode */
|
||||
if (direction == SNDRV_PCM_STREAM_PLAYBACK &&
|
||||
as->chip->lowlatency)
|
||||
runtime->hw.info |= SNDRV_PCM_INFO_SYNC_APPLPTR;
|
||||
runtime->private_data = subs;
|
||||
subs->pcm_substream = substream;
|
||||
/* runtime PM is also done there */
|
||||
@ -1320,44 +1355,66 @@ static unsigned int copy_to_urb_quirk(struct snd_usb_substream *subs,
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static void prepare_playback_urb(struct snd_usb_substream *subs,
|
||||
struct urb *urb)
|
||||
static int prepare_playback_urb(struct snd_usb_substream *subs,
|
||||
struct urb *urb,
|
||||
bool in_stream_lock)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
|
||||
struct snd_usb_endpoint *ep = subs->data_endpoint;
|
||||
struct snd_urb_ctx *ctx = urb->context;
|
||||
unsigned int counts, frames, bytes;
|
||||
unsigned int frames, bytes;
|
||||
int counts;
|
||||
unsigned int transfer_done, frame_limit, avail = 0;
|
||||
int i, stride, period_elapsed = 0;
|
||||
unsigned long flags;
|
||||
int err = 0;
|
||||
|
||||
stride = ep->stride;
|
||||
|
||||
frames = 0;
|
||||
ctx->queued = 0;
|
||||
urb->number_of_packets = 0;
|
||||
|
||||
spin_lock_irqsave(&subs->lock, flags);
|
||||
subs->frame_limit += ep->max_urb_frames;
|
||||
frame_limit = subs->frame_limit + ep->max_urb_frames;
|
||||
transfer_done = subs->transfer_done;
|
||||
|
||||
if (subs->lowlatency_playback &&
|
||||
runtime->status->state != SNDRV_PCM_STATE_DRAINING) {
|
||||
unsigned int hwptr = subs->hwptr_done / stride;
|
||||
|
||||
/* calculate the byte offset-in-buffer of the appl_ptr */
|
||||
avail = (runtime->control->appl_ptr - runtime->hw_ptr_base)
|
||||
% runtime->buffer_size;
|
||||
if (avail <= hwptr)
|
||||
avail += runtime->buffer_size;
|
||||
avail -= hwptr;
|
||||
}
|
||||
|
||||
for (i = 0; i < ctx->packets; i++) {
|
||||
counts = snd_usb_endpoint_next_packet_size(ep, ctx, i);
|
||||
counts = snd_usb_endpoint_next_packet_size(ep, ctx, i, avail);
|
||||
if (counts < 0)
|
||||
break;
|
||||
/* set up descriptor */
|
||||
urb->iso_frame_desc[i].offset = frames * stride;
|
||||
urb->iso_frame_desc[i].length = counts * stride;
|
||||
frames += counts;
|
||||
avail -= counts;
|
||||
urb->number_of_packets++;
|
||||
subs->transfer_done += counts;
|
||||
if (subs->transfer_done >= runtime->period_size) {
|
||||
subs->transfer_done -= runtime->period_size;
|
||||
subs->frame_limit = 0;
|
||||
transfer_done += counts;
|
||||
if (transfer_done >= runtime->period_size) {
|
||||
transfer_done -= runtime->period_size;
|
||||
frame_limit = 0;
|
||||
period_elapsed = 1;
|
||||
if (subs->fmt_type == UAC_FORMAT_TYPE_II) {
|
||||
if (subs->transfer_done > 0) {
|
||||
if (transfer_done > 0) {
|
||||
/* FIXME: fill-max mode is not
|
||||
* supported yet */
|
||||
frames -= subs->transfer_done;
|
||||
counts -= subs->transfer_done;
|
||||
frames -= transfer_done;
|
||||
counts -= transfer_done;
|
||||
urb->iso_frame_desc[i].length =
|
||||
counts * stride;
|
||||
subs->transfer_done = 0;
|
||||
transfer_done = 0;
|
||||
}
|
||||
i++;
|
||||
if (i < ctx->packets) {
|
||||
@ -1371,13 +1428,19 @@ static void prepare_playback_urb(struct snd_usb_substream *subs,
|
||||
}
|
||||
}
|
||||
/* finish at the period boundary or after enough frames */
|
||||
if ((period_elapsed ||
|
||||
subs->transfer_done >= subs->frame_limit) &&
|
||||
if ((period_elapsed || transfer_done >= frame_limit) &&
|
||||
!snd_usb_endpoint_implicit_feedback_sink(ep))
|
||||
break;
|
||||
}
|
||||
bytes = frames * stride;
|
||||
|
||||
if (!frames) {
|
||||
err = -EAGAIN;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
bytes = frames * stride;
|
||||
subs->transfer_done = transfer_done;
|
||||
subs->frame_limit = frame_limit;
|
||||
if (unlikely(ep->cur_format == SNDRV_PCM_FORMAT_DSD_U16_LE &&
|
||||
subs->cur_audiofmt->dsd_dop)) {
|
||||
fill_playback_urb_dsd_dop(subs, urb, bytes);
|
||||
@ -1403,14 +1466,23 @@ static void prepare_playback_urb(struct snd_usb_substream *subs,
|
||||
subs->trigger_tstamp_pending_update = false;
|
||||
}
|
||||
|
||||
if (period_elapsed && !subs->running && !subs->early_playback_start) {
|
||||
if (period_elapsed && !subs->running && subs->lowlatency_playback) {
|
||||
subs->period_elapsed_pending = 1;
|
||||
period_elapsed = 0;
|
||||
}
|
||||
|
||||
unlock:
|
||||
spin_unlock_irqrestore(&subs->lock, flags);
|
||||
if (err < 0)
|
||||
return err;
|
||||
urb->transfer_buffer_length = bytes;
|
||||
if (period_elapsed)
|
||||
snd_pcm_period_elapsed(subs->pcm_substream);
|
||||
if (period_elapsed) {
|
||||
if (in_stream_lock)
|
||||
snd_pcm_period_elapsed_under_stream_lock(subs->pcm_substream);
|
||||
else
|
||||
snd_pcm_period_elapsed(subs->pcm_substream);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1442,6 +1514,27 @@ static void retire_playback_urb(struct snd_usb_substream *subs,
|
||||
snd_pcm_period_elapsed(subs->pcm_substream);
|
||||
}
|
||||
|
||||
/* PCM ack callback for the playback stream;
|
||||
* this plays a role only when the stream is running in low-latency mode.
|
||||
*/
|
||||
static int snd_usb_pcm_playback_ack(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_usb_substream *subs = substream->runtime->private_data;
|
||||
struct snd_usb_endpoint *ep;
|
||||
|
||||
if (!subs->lowlatency_playback || !subs->running)
|
||||
return 0;
|
||||
ep = subs->data_endpoint;
|
||||
if (!ep)
|
||||
return 0;
|
||||
/* When no more in-flight URBs available, try to process the pending
|
||||
* outputs here
|
||||
*/
|
||||
if (!ep->active_mask)
|
||||
snd_usb_queue_pending_output_urbs(ep, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd)
|
||||
{
|
||||
@ -1457,7 +1550,7 @@ static int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substrea
|
||||
prepare_playback_urb,
|
||||
retire_playback_urb,
|
||||
subs);
|
||||
if (!subs->early_playback_start &&
|
||||
if (subs->lowlatency_playback &&
|
||||
cmd == SNDRV_PCM_TRIGGER_START) {
|
||||
err = start_endpoints(subs);
|
||||
if (err < 0) {
|
||||
@ -1473,7 +1566,7 @@ static int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substrea
|
||||
return 0;
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
stop_endpoints(subs);
|
||||
stop_endpoints(subs, substream->runtime->status->state == SNDRV_PCM_STATE_DRAINING);
|
||||
snd_usb_endpoint_set_callback(subs->data_endpoint,
|
||||
NULL, NULL, NULL);
|
||||
subs->running = 0;
|
||||
@ -1521,7 +1614,7 @@ static int snd_usb_substream_capture_trigger(struct snd_pcm_substream *substream
|
||||
return 0;
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
stop_endpoints(subs);
|
||||
stop_endpoints(subs, false);
|
||||
fallthrough;
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
snd_usb_endpoint_set_callback(subs->data_endpoint,
|
||||
@ -1545,6 +1638,7 @@ static const struct snd_pcm_ops snd_usb_playback_ops = {
|
||||
.trigger = snd_usb_substream_playback_trigger,
|
||||
.sync_stop = snd_usb_pcm_sync_stop,
|
||||
.pointer = snd_usb_pcm_pointer,
|
||||
.ack = snd_usb_pcm_playback_ack,
|
||||
};
|
||||
|
||||
static const struct snd_pcm_ops snd_usb_capture_ops = {
|
||||
|
@ -3892,6 +3892,64 @@ YAMAHA_DEVICE(0x7010, "UB99"),
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Pioneer DJ DJM-750MK2
|
||||
* 10 channels playback & 12 channels capture @ 48kHz S24LE
|
||||
*/
|
||||
USB_DEVICE_VENDOR_SPEC(0x2b73, 0x001b),
|
||||
.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
|
||||
.ifnum = QUIRK_ANY_INTERFACE,
|
||||
.type = QUIRK_COMPOSITE,
|
||||
.data = (const struct snd_usb_audio_quirk[]) {
|
||||
{
|
||||
.ifnum = 0,
|
||||
.type = QUIRK_AUDIO_FIXED_ENDPOINT,
|
||||
.data = &(const struct audioformat) {
|
||||
.formats = SNDRV_PCM_FMTBIT_S24_3LE,
|
||||
.channels = 10,
|
||||
.iface = 0,
|
||||
.altsetting = 1,
|
||||
.altset_idx = 1,
|
||||
.endpoint = 0x01,
|
||||
.ep_attr = USB_ENDPOINT_XFER_ISOC|
|
||||
USB_ENDPOINT_SYNC_ASYNC,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
.rate_min = 48000,
|
||||
.rate_max = 48000,
|
||||
.nr_rates = 1,
|
||||
.rate_table = (unsigned int[]) {
|
||||
48000
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
.ifnum = 0,
|
||||
.type = QUIRK_AUDIO_FIXED_ENDPOINT,
|
||||
.data = &(const struct audioformat) {
|
||||
.formats = SNDRV_PCM_FMTBIT_S24_3LE,
|
||||
.channels = 12,
|
||||
.iface = 0,
|
||||
.altsetting = 1,
|
||||
.altset_idx = 1,
|
||||
.endpoint = 0x82,
|
||||
.ep_idx = 1,
|
||||
.ep_attr = USB_ENDPOINT_XFER_ISOC|
|
||||
USB_ENDPOINT_SYNC_ASYNC|
|
||||
USB_ENDPOINT_USAGE_IMPLICIT_FB,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
.rate_min = 48000,
|
||||
.rate_max = 48000,
|
||||
.nr_rates = 1,
|
||||
.rate_table = (unsigned int[]) { 48000 }
|
||||
}
|
||||
},
|
||||
{
|
||||
.ifnum = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Pioneer DJ DJM-850
|
||||
|
@ -668,14 +668,15 @@ static void i_usx2y_04int(struct urb *urb)
|
||||
|
||||
static int usx2y_rate_set(struct usx2ydev *usx2y, int rate)
|
||||
{
|
||||
int err = 0, i;
|
||||
struct snd_usx2y_urb_seq *us = NULL;
|
||||
int *usbdata = NULL;
|
||||
const struct s_c2 *ra = rate == 48000 ? setrate_48000 : setrate_44100;
|
||||
int err = 0, i;
|
||||
struct snd_usx2y_urb_seq *us = NULL;
|
||||
int *usbdata = NULL;
|
||||
const struct s_c2 *ra = rate == 48000 ? setrate_48000 : setrate_44100;
|
||||
struct urb *urb;
|
||||
|
||||
if (usx2y->rate != rate) {
|
||||
us = kzalloc(sizeof(*us) + sizeof(struct urb *) * NOOF_SETRATE_URBS, GFP_KERNEL);
|
||||
us = kzalloc(struct_size(us, urb, NOOF_SETRATE_URBS),
|
||||
GFP_KERNEL);
|
||||
if (!us) {
|
||||
err = -ENOMEM;
|
||||
goto cleanup;
|
||||
|
@ -20,7 +20,7 @@ struct virtio_pcm_msg {
|
||||
struct virtio_snd_pcm_xfer xfer;
|
||||
struct virtio_snd_pcm_status status;
|
||||
size_t length;
|
||||
struct scatterlist sgs[0];
|
||||
struct scatterlist sgs[];
|
||||
};
|
||||
|
||||
/**
|
||||
@ -146,8 +146,7 @@ int virtsnd_pcm_msg_alloc(struct virtio_pcm_substream *vss,
|
||||
int sg_num = virtsnd_pcm_sg_num(data, period_bytes);
|
||||
struct virtio_pcm_msg *msg;
|
||||
|
||||
msg = kzalloc(sizeof(*msg) + sizeof(*msg->sgs) * (sg_num + 2),
|
||||
GFP_KERNEL);
|
||||
msg = kzalloc(struct_size(msg, sgs, sg_num + 2), GFP_KERNEL);
|
||||
if (!msg)
|
||||
return -ENOMEM;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user