Merge branch 'for-next' into for-linus
For 4.12 merge.
This commit is contained in:
Коммит
d7dc450d5a
|
@ -494,6 +494,8 @@ add_hp_mic (bool)
|
|||
hp_mic_detect (bool)
|
||||
enable/disable the hp/mic shared input for a single built-in mic
|
||||
case; default true
|
||||
vmaster (bool)
|
||||
enable/disable the virtual Master control; default true
|
||||
mixer_nid (int)
|
||||
specifies the widget NID of the analog-loopback mixer
|
||||
|
||||
|
|
|
@ -106,8 +106,26 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 };
|
|||
#define AZX_REG_HSW_EM4 0x100c
|
||||
#define AZX_REG_HSW_EM5 0x1010
|
||||
|
||||
/* Skylake/Broxton display HD-A controller Extended Mode registers */
|
||||
#define AZX_REG_SKL_EM4L 0x1040
|
||||
/* Skylake/Broxton vendor-specific registers */
|
||||
#define AZX_REG_VS_EM1 0x1000
|
||||
#define AZX_REG_VS_INRC 0x1004
|
||||
#define AZX_REG_VS_OUTRC 0x1008
|
||||
#define AZX_REG_VS_FIFOTRK 0x100C
|
||||
#define AZX_REG_VS_FIFOTRK2 0x1010
|
||||
#define AZX_REG_VS_EM2 0x1030
|
||||
#define AZX_REG_VS_EM3L 0x1038
|
||||
#define AZX_REG_VS_EM3U 0x103C
|
||||
#define AZX_REG_VS_EM4L 0x1040
|
||||
#define AZX_REG_VS_EM4U 0x1044
|
||||
#define AZX_REG_VS_LTRC 0x1048
|
||||
#define AZX_REG_VS_D0I3C 0x104A
|
||||
#define AZX_REG_VS_PCE 0x104B
|
||||
#define AZX_REG_VS_L2MAGC 0x1050
|
||||
#define AZX_REG_VS_L2LAHPT 0x1054
|
||||
#define AZX_REG_VS_SDXDPIB_XBASE 0x1084
|
||||
#define AZX_REG_VS_SDXDPIB_XINTERVAL 0x20
|
||||
#define AZX_REG_VS_SDXEFIFOS_XBASE 0x1094
|
||||
#define AZX_REG_VS_SDXEFIFOS_XINTERVAL 0x20
|
||||
|
||||
/* PCI space */
|
||||
#define AZX_PCIREG_TCSEL 0x44
|
||||
|
@ -243,9 +261,11 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 };
|
|||
#define AZX_REG_ML_LOUTPAY 0x20
|
||||
#define AZX_REG_ML_LINPAY 0x30
|
||||
|
||||
#define AZX_MLCTL_SPA (1<<16)
|
||||
#define AZX_MLCTL_CPA 23
|
||||
|
||||
#define ML_LCTL_SCF_MASK 0xF
|
||||
#define AZX_MLCTL_SPA (0x1 << 16)
|
||||
#define AZX_MLCTL_CPA (0x1 << 23)
|
||||
#define AZX_MLCTL_SPA_SHIFT 16
|
||||
#define AZX_MLCTL_CPA_SHIFT 23
|
||||
|
||||
/* registers for DMA Resume Capability Structure */
|
||||
#define AZX_DRSM_CAP_ID 0x5
|
||||
|
|
|
@ -368,24 +368,32 @@ void snd_hdac_bus_free_stream_pages(struct hdac_bus *bus);
|
|||
/*
|
||||
* macros for easy use
|
||||
*/
|
||||
#define _snd_hdac_chip_write(type, chip, reg, value) \
|
||||
((chip)->io_ops->reg_write ## type(value, (chip)->remap_addr + (reg)))
|
||||
#define _snd_hdac_chip_read(type, chip, reg) \
|
||||
((chip)->io_ops->reg_read ## type((chip)->remap_addr + (reg)))
|
||||
#define _snd_hdac_chip_writeb(chip, reg, value) \
|
||||
((chip)->io_ops->reg_writeb(value, (chip)->remap_addr + (reg)))
|
||||
#define _snd_hdac_chip_readb(chip, reg) \
|
||||
((chip)->io_ops->reg_readb((chip)->remap_addr + (reg)))
|
||||
#define _snd_hdac_chip_writew(chip, reg, value) \
|
||||
((chip)->io_ops->reg_writew(value, (chip)->remap_addr + (reg)))
|
||||
#define _snd_hdac_chip_readw(chip, reg) \
|
||||
((chip)->io_ops->reg_readw((chip)->remap_addr + (reg)))
|
||||
#define _snd_hdac_chip_writel(chip, reg, value) \
|
||||
((chip)->io_ops->reg_writel(value, (chip)->remap_addr + (reg)))
|
||||
#define _snd_hdac_chip_readl(chip, reg) \
|
||||
((chip)->io_ops->reg_readl((chip)->remap_addr + (reg)))
|
||||
|
||||
/* read/write a register, pass without AZX_REG_ prefix */
|
||||
#define snd_hdac_chip_writel(chip, reg, value) \
|
||||
_snd_hdac_chip_write(l, chip, AZX_REG_ ## reg, value)
|
||||
_snd_hdac_chip_writel(chip, AZX_REG_ ## reg, value)
|
||||
#define snd_hdac_chip_writew(chip, reg, value) \
|
||||
_snd_hdac_chip_write(w, chip, AZX_REG_ ## reg, value)
|
||||
_snd_hdac_chip_writew(chip, AZX_REG_ ## reg, value)
|
||||
#define snd_hdac_chip_writeb(chip, reg, value) \
|
||||
_snd_hdac_chip_write(b, chip, AZX_REG_ ## reg, value)
|
||||
_snd_hdac_chip_writeb(chip, AZX_REG_ ## reg, value)
|
||||
#define snd_hdac_chip_readl(chip, reg) \
|
||||
_snd_hdac_chip_read(l, chip, AZX_REG_ ## reg)
|
||||
_snd_hdac_chip_readl(chip, AZX_REG_ ## reg)
|
||||
#define snd_hdac_chip_readw(chip, reg) \
|
||||
_snd_hdac_chip_read(w, chip, AZX_REG_ ## reg)
|
||||
_snd_hdac_chip_readw(chip, AZX_REG_ ## reg)
|
||||
#define snd_hdac_chip_readb(chip, reg) \
|
||||
_snd_hdac_chip_read(b, chip, AZX_REG_ ## reg)
|
||||
_snd_hdac_chip_readb(chip, AZX_REG_ ## reg)
|
||||
|
||||
/* update a register, pass without AZX_REG_ prefix */
|
||||
#define snd_hdac_chip_updatel(chip, reg, mask, val) \
|
||||
|
|
|
@ -107,9 +107,11 @@ enum {
|
|||
SNDRV_HWDEP_IFACE_FW_DIGI00X, /* Digidesign Digi 002/003 family */
|
||||
SNDRV_HWDEP_IFACE_FW_TASCAM, /* TASCAM FireWire series */
|
||||
SNDRV_HWDEP_IFACE_LINE6, /* Line6 USB processors */
|
||||
SNDRV_HWDEP_IFACE_FW_MOTU, /* MOTU FireWire series */
|
||||
SNDRV_HWDEP_IFACE_FW_FIREFACE, /* RME Fireface series */
|
||||
|
||||
/* Don't forget to change the following: */
|
||||
SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_LINE6
|
||||
SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_FIREFACE
|
||||
};
|
||||
|
||||
struct snd_hwdep_info {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#define SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION 0xd1ce004e
|
||||
#define SNDRV_FIREWIRE_EVENT_EFW_RESPONSE 0x4e617475
|
||||
#define SNDRV_FIREWIRE_EVENT_DIGI00X_MESSAGE 0x746e736c
|
||||
#define SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION 0x64776479
|
||||
|
||||
struct snd_firewire_event_common {
|
||||
unsigned int type; /* SNDRV_FIREWIRE_EVENT_xxx */
|
||||
|
@ -46,12 +47,18 @@ struct snd_firewire_event_digi00x_message {
|
|||
__u32 message; /* Digi00x-specific message */
|
||||
};
|
||||
|
||||
struct snd_firewire_event_motu_notification {
|
||||
unsigned int type;
|
||||
__u32 message; /* MOTU-specific bits. */
|
||||
};
|
||||
|
||||
union snd_firewire_event {
|
||||
struct snd_firewire_event_common common;
|
||||
struct snd_firewire_event_lock_status lock_status;
|
||||
struct snd_firewire_event_dice_notification dice_notification;
|
||||
struct snd_firewire_event_efw_response efw_response;
|
||||
struct snd_firewire_event_digi00x_message digi00x_message;
|
||||
struct snd_firewire_event_motu_notification motu_notification;
|
||||
};
|
||||
|
||||
|
||||
|
@ -65,7 +72,8 @@ union snd_firewire_event {
|
|||
#define SNDRV_FIREWIRE_TYPE_OXFW 4
|
||||
#define SNDRV_FIREWIRE_TYPE_DIGI00X 5
|
||||
#define SNDRV_FIREWIRE_TYPE_TASCAM 6
|
||||
/* RME, MOTU, ... */
|
||||
#define SNDRV_FIREWIRE_TYPE_MOTU 7
|
||||
#define SNDRV_FIREWIRE_TYPE_FIREFACE 8
|
||||
|
||||
struct snd_firewire_get_info {
|
||||
unsigned int type; /* SNDRV_FIREWIRE_TYPE_xxx */
|
||||
|
|
|
@ -1277,6 +1277,7 @@ static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri,
|
|||
struct timespec tstamp;
|
||||
int prev, append = 0;
|
||||
|
||||
memset(&r1, 0, sizeof(r1));
|
||||
memset(&tstamp, 0, sizeof(tstamp));
|
||||
spin_lock(&tu->qlock);
|
||||
if ((tu->filter & ((1 << SNDRV_TIMER_EVENT_RESOLUTION) |
|
||||
|
@ -1292,7 +1293,6 @@ static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri,
|
|||
}
|
||||
if ((tu->filter & (1 << SNDRV_TIMER_EVENT_RESOLUTION)) &&
|
||||
tu->last_resolution != resolution) {
|
||||
memset(&r1, 0, sizeof(r1));
|
||||
r1.event = SNDRV_TIMER_EVENT_RESOLUTION;
|
||||
r1.tstamp = tstamp;
|
||||
r1.val = resolution;
|
||||
|
@ -1430,18 +1430,13 @@ static int snd_timer_user_next_device(struct snd_timer_id __user *_tid)
|
|||
if (id.card < 0) {
|
||||
id.card = 0;
|
||||
} else {
|
||||
if (id.card < 0) {
|
||||
id.card = 0;
|
||||
if (id.device < 0) {
|
||||
id.device = 0;
|
||||
} else {
|
||||
if (id.device < 0) {
|
||||
id.device = 0;
|
||||
} else {
|
||||
if (id.subdevice < 0) {
|
||||
id.subdevice = 0;
|
||||
} else {
|
||||
id.subdevice++;
|
||||
}
|
||||
}
|
||||
if (id.subdevice < 0)
|
||||
id.subdevice = 0;
|
||||
else
|
||||
id.subdevice++;
|
||||
}
|
||||
}
|
||||
list_for_each(p, &snd_timer_list) {
|
||||
|
|
|
@ -795,10 +795,8 @@ struct vx_core *snd_vx_create(struct snd_card *card, struct snd_vx_hardware *hw,
|
|||
return NULL;
|
||||
|
||||
chip = kzalloc(sizeof(*chip) + extra_size, GFP_KERNEL);
|
||||
if (! chip) {
|
||||
snd_printk(KERN_ERR "vx_core: no memory\n");
|
||||
if (! chip)
|
||||
return NULL;
|
||||
}
|
||||
mutex_init(&chip->lock);
|
||||
chip->irq = -1;
|
||||
chip->hw = hw;
|
||||
|
|
|
@ -140,4 +140,24 @@ config SND_FIREWIRE_TASCAM
|
|||
To compile this driver as a module, choose M here: the module
|
||||
will be called snd-firewire-tascam.
|
||||
|
||||
config SND_FIREWIRE_MOTU
|
||||
tristate "Mark of the unicorn FireWire series support"
|
||||
select SND_FIREWIRE_LIB
|
||||
select SND_HWDEP
|
||||
help
|
||||
Say Y here to enable support for FireWire devices which MOTU produced:
|
||||
* 828mk2
|
||||
* 828mk3
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called snd-firewire-motu.
|
||||
|
||||
config SND_FIREFACE
|
||||
tristate "RME Fireface series support"
|
||||
select SND_FIREWIRE_LIB
|
||||
select SND_HWDEP
|
||||
help
|
||||
Say Y here to include support for RME fireface series.
|
||||
* Fireface 400
|
||||
|
||||
endif # SND_FIREWIRE
|
||||
|
|
|
@ -13,3 +13,5 @@ obj-$(CONFIG_SND_FIREWORKS) += fireworks/
|
|||
obj-$(CONFIG_SND_BEBOB) += bebob/
|
||||
obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += digi00x/
|
||||
obj-$(CONFIG_SND_FIREWIRE_TASCAM) += tascam/
|
||||
obj-$(CONFIG_SND_FIREWIRE_MOTU) += motu/
|
||||
obj-$(CONFIG_SND_FIREFACE) += fireface/
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
#include <linux/tracepoint.h>
|
||||
|
||||
TRACE_EVENT(in_packet,
|
||||
TP_PROTO(const struct amdtp_stream *s, u32 cycles, u32 cip_header[2], unsigned int payload_quadlets, unsigned int index),
|
||||
TP_ARGS(s, cycles, cip_header, payload_quadlets, index),
|
||||
TP_PROTO(const struct amdtp_stream *s, u32 cycles, u32 cip_header[2], unsigned int payload_length, unsigned int index),
|
||||
TP_ARGS(s, cycles, cip_header, payload_length, index),
|
||||
TP_STRUCT__entry(
|
||||
__field(unsigned int, second)
|
||||
__field(unsigned int, cycle)
|
||||
|
@ -37,7 +37,7 @@ TRACE_EVENT(in_packet,
|
|||
__entry->dest = fw_parent_device(s->unit)->card->node_id;
|
||||
__entry->cip_header0 = cip_header[0];
|
||||
__entry->cip_header1 = cip_header[1];
|
||||
__entry->payload_quadlets = payload_quadlets;
|
||||
__entry->payload_quadlets = payload_length / 4;
|
||||
__entry->packet_index = s->packet_index;
|
||||
__entry->irq = !!in_interrupt();
|
||||
__entry->index = index;
|
||||
|
@ -101,6 +101,94 @@ TRACE_EVENT(out_packet,
|
|||
__entry->index)
|
||||
);
|
||||
|
||||
TRACE_EVENT(in_packet_without_header,
|
||||
TP_PROTO(const struct amdtp_stream *s, u32 cycles, unsigned int payload_quadlets, unsigned int data_blocks, unsigned int index),
|
||||
TP_ARGS(s, cycles, payload_quadlets, data_blocks, index),
|
||||
TP_STRUCT__entry(
|
||||
__field(unsigned int, second)
|
||||
__field(unsigned int, cycle)
|
||||
__field(int, channel)
|
||||
__field(int, src)
|
||||
__field(int, dest)
|
||||
__field(unsigned int, payload_quadlets)
|
||||
__field(unsigned int, data_blocks)
|
||||
__field(unsigned int, data_block_counter)
|
||||
__field(unsigned int, packet_index)
|
||||
__field(unsigned int, irq)
|
||||
__field(unsigned int, index)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->second = cycles / CYCLES_PER_SECOND;
|
||||
__entry->cycle = cycles % CYCLES_PER_SECOND;
|
||||
__entry->channel = s->context->channel;
|
||||
__entry->src = fw_parent_device(s->unit)->node_id;
|
||||
__entry->dest = fw_parent_device(s->unit)->card->node_id;
|
||||
__entry->payload_quadlets = payload_quadlets;
|
||||
__entry->data_blocks = data_blocks,
|
||||
__entry->data_block_counter = s->data_block_counter,
|
||||
__entry->packet_index = s->packet_index;
|
||||
__entry->irq = !!in_interrupt();
|
||||
__entry->index = index;
|
||||
),
|
||||
TP_printk(
|
||||
"%02u %04u %04x %04x %02d %03u %3u %3u %02u %01u %02u",
|
||||
__entry->second,
|
||||
__entry->cycle,
|
||||
__entry->src,
|
||||
__entry->dest,
|
||||
__entry->channel,
|
||||
__entry->payload_quadlets,
|
||||
__entry->data_blocks,
|
||||
__entry->data_block_counter,
|
||||
__entry->packet_index,
|
||||
__entry->irq,
|
||||
__entry->index)
|
||||
);
|
||||
|
||||
TRACE_EVENT(out_packet_without_header,
|
||||
TP_PROTO(const struct amdtp_stream *s, u32 cycles, unsigned int payload_length, unsigned int data_blocks, unsigned int index),
|
||||
TP_ARGS(s, cycles, payload_length, data_blocks, index),
|
||||
TP_STRUCT__entry(
|
||||
__field(unsigned int, second)
|
||||
__field(unsigned int, cycle)
|
||||
__field(int, channel)
|
||||
__field(int, src)
|
||||
__field(int, dest)
|
||||
__field(unsigned int, payload_quadlets)
|
||||
__field(unsigned int, data_blocks)
|
||||
__field(unsigned int, data_block_counter)
|
||||
__field(unsigned int, packet_index)
|
||||
__field(unsigned int, irq)
|
||||
__field(unsigned int, index)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->second = cycles / CYCLES_PER_SECOND;
|
||||
__entry->cycle = cycles % CYCLES_PER_SECOND;
|
||||
__entry->channel = s->context->channel;
|
||||
__entry->src = fw_parent_device(s->unit)->card->node_id;
|
||||
__entry->dest = fw_parent_device(s->unit)->node_id;
|
||||
__entry->payload_quadlets = payload_length / 4;
|
||||
__entry->data_blocks = data_blocks,
|
||||
__entry->data_blocks = s->data_block_counter,
|
||||
__entry->packet_index = s->packet_index;
|
||||
__entry->irq = !!in_interrupt();
|
||||
__entry->index = index;
|
||||
),
|
||||
TP_printk(
|
||||
"%02u %04u %04x %04x %02d %03u %02u %03u %02u %01u %02u",
|
||||
__entry->second,
|
||||
__entry->cycle,
|
||||
__entry->src,
|
||||
__entry->dest,
|
||||
__entry->channel,
|
||||
__entry->payload_quadlets,
|
||||
__entry->data_blocks,
|
||||
__entry->data_block_counter,
|
||||
__entry->packet_index,
|
||||
__entry->irq,
|
||||
__entry->index)
|
||||
);
|
||||
|
||||
#endif
|
||||
|
||||
#undef TRACE_INCLUDE_PATH
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
|
||||
/* isochronous header parameters */
|
||||
#define ISO_DATA_LENGTH_SHIFT 16
|
||||
#define TAG_NO_CIP_HEADER 0
|
||||
#define TAG_CIP 1
|
||||
|
||||
/* common isochronous packet header parameters */
|
||||
|
@ -37,6 +38,8 @@
|
|||
#define CIP_SID_MASK 0x3f000000
|
||||
#define CIP_DBS_MASK 0x00ff0000
|
||||
#define CIP_DBS_SHIFT 16
|
||||
#define CIP_SPH_MASK 0x00000400
|
||||
#define CIP_SPH_SHIFT 10
|
||||
#define CIP_DBC_MASK 0x000000ff
|
||||
#define CIP_FMT_SHIFT 24
|
||||
#define CIP_FMT_MASK 0x3f000000
|
||||
|
@ -232,11 +235,15 @@ EXPORT_SYMBOL(amdtp_stream_set_parameters);
|
|||
unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s)
|
||||
{
|
||||
unsigned int multiplier = 1;
|
||||
unsigned int header_size = 0;
|
||||
|
||||
if (s->flags & CIP_JUMBO_PAYLOAD)
|
||||
multiplier = 5;
|
||||
if (!(s->flags & CIP_NO_HEADER))
|
||||
header_size = 8;
|
||||
|
||||
return 8 + s->syt_interval * s->data_block_quadlets * 4 * multiplier;
|
||||
return header_size +
|
||||
s->syt_interval * s->data_block_quadlets * 4 * multiplier;
|
||||
}
|
||||
EXPORT_SYMBOL(amdtp_stream_get_max_payload);
|
||||
|
||||
|
@ -378,7 +385,7 @@ static int queue_packet(struct amdtp_stream *s, unsigned int header_length,
|
|||
goto end;
|
||||
|
||||
p.interrupt = IS_ALIGNED(s->packet_index + 1, INTERRUPT_INTERVAL);
|
||||
p.tag = TAG_CIP;
|
||||
p.tag = s->tag;
|
||||
p.header_length = header_length;
|
||||
if (payload_length > 0)
|
||||
p.payload_length = payload_length;
|
||||
|
@ -405,17 +412,16 @@ static inline int queue_out_packet(struct amdtp_stream *s,
|
|||
|
||||
static inline int queue_in_packet(struct amdtp_stream *s)
|
||||
{
|
||||
return queue_packet(s, IN_PACKET_HEADER_SIZE,
|
||||
amdtp_stream_get_max_payload(s));
|
||||
return queue_packet(s, IN_PACKET_HEADER_SIZE, s->max_payload_length);
|
||||
}
|
||||
|
||||
static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle,
|
||||
static int handle_out_packet(struct amdtp_stream *s,
|
||||
unsigned int payload_length, unsigned int cycle,
|
||||
unsigned int index)
|
||||
{
|
||||
__be32 *buffer;
|
||||
unsigned int syt;
|
||||
unsigned int data_blocks;
|
||||
unsigned int payload_length;
|
||||
unsigned int pcm_frames;
|
||||
struct snd_pcm_substream *pcm;
|
||||
|
||||
|
@ -424,15 +430,22 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle,
|
|||
data_blocks = calculate_data_blocks(s, syt);
|
||||
pcm_frames = s->process_data_blocks(s, buffer + 2, data_blocks, &syt);
|
||||
|
||||
if (s->flags & CIP_DBC_IS_END_EVENT)
|
||||
s->data_block_counter =
|
||||
(s->data_block_counter + data_blocks) & 0xff;
|
||||
|
||||
buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) |
|
||||
(s->data_block_quadlets << CIP_DBS_SHIFT) |
|
||||
((s->sph << CIP_SPH_SHIFT) & CIP_SPH_MASK) |
|
||||
s->data_block_counter);
|
||||
buffer[1] = cpu_to_be32(CIP_EOH |
|
||||
((s->fmt << CIP_FMT_SHIFT) & CIP_FMT_MASK) |
|
||||
((s->fdf << CIP_FDF_SHIFT) & CIP_FDF_MASK) |
|
||||
(syt & CIP_SYT_MASK));
|
||||
|
||||
s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff;
|
||||
if (!(s->flags & CIP_DBC_IS_END_EVENT))
|
||||
s->data_block_counter =
|
||||
(s->data_block_counter + data_blocks) & 0xff;
|
||||
payload_length = 8 + data_blocks * 4 * s->data_block_quadlets;
|
||||
|
||||
trace_out_packet(s, cycle, buffer, payload_length, index);
|
||||
|
@ -448,13 +461,45 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int handle_out_packet_without_header(struct amdtp_stream *s,
|
||||
unsigned int payload_length, unsigned int cycle,
|
||||
unsigned int index)
|
||||
{
|
||||
__be32 *buffer;
|
||||
unsigned int syt;
|
||||
unsigned int data_blocks;
|
||||
unsigned int pcm_frames;
|
||||
struct snd_pcm_substream *pcm;
|
||||
|
||||
buffer = s->buffer.packets[s->packet_index].buffer;
|
||||
syt = calculate_syt(s, cycle);
|
||||
data_blocks = calculate_data_blocks(s, syt);
|
||||
pcm_frames = s->process_data_blocks(s, buffer, data_blocks, &syt);
|
||||
s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff;
|
||||
|
||||
payload_length = data_blocks * 4 * s->data_block_quadlets;
|
||||
|
||||
trace_out_packet_without_header(s, cycle, payload_length, data_blocks,
|
||||
index);
|
||||
|
||||
if (queue_out_packet(s, payload_length) < 0)
|
||||
return -EIO;
|
||||
|
||||
pcm = ACCESS_ONCE(s->pcm);
|
||||
if (pcm && pcm_frames > 0)
|
||||
update_pcm_pointers(s, pcm, pcm_frames);
|
||||
|
||||
/* No need to return the number of handled data blocks. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_in_packet(struct amdtp_stream *s,
|
||||
unsigned int payload_quadlets, unsigned int cycle,
|
||||
unsigned int payload_length, unsigned int cycle,
|
||||
unsigned int index)
|
||||
{
|
||||
__be32 *buffer;
|
||||
u32 cip_header[2];
|
||||
unsigned int fmt, fdf, syt;
|
||||
unsigned int sph, fmt, fdf, syt;
|
||||
unsigned int data_block_quadlets, data_block_counter, dbc_interval;
|
||||
unsigned int data_blocks;
|
||||
struct snd_pcm_substream *pcm;
|
||||
|
@ -465,14 +510,15 @@ static int handle_in_packet(struct amdtp_stream *s,
|
|||
cip_header[0] = be32_to_cpu(buffer[0]);
|
||||
cip_header[1] = be32_to_cpu(buffer[1]);
|
||||
|
||||
trace_in_packet(s, cycle, cip_header, payload_quadlets, index);
|
||||
trace_in_packet(s, cycle, cip_header, payload_length, index);
|
||||
|
||||
/*
|
||||
* This module supports 'Two-quadlet CIP header with SYT field'.
|
||||
* For convenience, also check FMT field is AM824 or not.
|
||||
*/
|
||||
if (((cip_header[0] & CIP_EOH_MASK) == CIP_EOH) ||
|
||||
((cip_header[1] & CIP_EOH_MASK) != CIP_EOH)) {
|
||||
if ((((cip_header[0] & CIP_EOH_MASK) == CIP_EOH) ||
|
||||
((cip_header[1] & CIP_EOH_MASK) != CIP_EOH)) &&
|
||||
(!(s->flags & CIP_HEADER_WITHOUT_EOH))) {
|
||||
dev_info_ratelimited(&s->unit->device,
|
||||
"Invalid CIP header for AMDTP: %08X:%08X\n",
|
||||
cip_header[0], cip_header[1]);
|
||||
|
@ -482,8 +528,9 @@ static int handle_in_packet(struct amdtp_stream *s,
|
|||
}
|
||||
|
||||
/* Check valid protocol or not. */
|
||||
sph = (cip_header[0] & CIP_SPH_MASK) >> CIP_SPH_SHIFT;
|
||||
fmt = (cip_header[1] & CIP_FMT_MASK) >> CIP_FMT_SHIFT;
|
||||
if (fmt != s->fmt) {
|
||||
if (sph != s->sph || fmt != s->fmt) {
|
||||
dev_info_ratelimited(&s->unit->device,
|
||||
"Detect unexpected protocol: %08x %08x\n",
|
||||
cip_header[0], cip_header[1]);
|
||||
|
@ -494,7 +541,7 @@ static int handle_in_packet(struct amdtp_stream *s,
|
|||
|
||||
/* Calculate data blocks */
|
||||
fdf = (cip_header[1] & CIP_FDF_MASK) >> CIP_FDF_SHIFT;
|
||||
if (payload_quadlets < 3 ||
|
||||
if (payload_length < 12 ||
|
||||
(fmt == CIP_FMT_AM && fdf == AMDTP_FDF_NO_DATA)) {
|
||||
data_blocks = 0;
|
||||
} else {
|
||||
|
@ -510,7 +557,8 @@ static int handle_in_packet(struct amdtp_stream *s,
|
|||
if (s->flags & CIP_WRONG_DBS)
|
||||
data_block_quadlets = s->data_block_quadlets;
|
||||
|
||||
data_blocks = (payload_quadlets - 2) / data_block_quadlets;
|
||||
data_blocks = (payload_length / 4 - 2) /
|
||||
data_block_quadlets;
|
||||
}
|
||||
|
||||
/* Check data block counter continuity */
|
||||
|
@ -561,6 +609,34 @@ end:
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int handle_in_packet_without_header(struct amdtp_stream *s,
|
||||
unsigned int payload_quadlets, unsigned int cycle,
|
||||
unsigned int index)
|
||||
{
|
||||
__be32 *buffer;
|
||||
unsigned int data_blocks;
|
||||
struct snd_pcm_substream *pcm;
|
||||
unsigned int pcm_frames;
|
||||
|
||||
buffer = s->buffer.packets[s->packet_index].buffer;
|
||||
data_blocks = payload_quadlets / s->data_block_quadlets;
|
||||
|
||||
trace_in_packet_without_header(s, cycle, payload_quadlets, data_blocks,
|
||||
index);
|
||||
|
||||
pcm_frames = s->process_data_blocks(s, buffer, data_blocks, NULL);
|
||||
s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff;
|
||||
|
||||
if (queue_in_packet(s) < 0)
|
||||
return -EIO;
|
||||
|
||||
pcm = ACCESS_ONCE(s->pcm);
|
||||
if (pcm && pcm_frames > 0)
|
||||
update_pcm_pointers(s, pcm, pcm_frames);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* In CYCLE_TIMER register of IEEE 1394, 7 bits are used to represent second. On
|
||||
* the other hand, in DMA descriptors of 1394 OHCI, 3 bits are used to represent
|
||||
|
@ -604,7 +680,7 @@ static void out_stream_callback(struct fw_iso_context *context, u32 tstamp,
|
|||
|
||||
for (i = 0; i < packets; ++i) {
|
||||
cycle = increment_cycle_count(cycle, 1);
|
||||
if (handle_out_packet(s, cycle, i) < 0) {
|
||||
if (s->handle_packet(s, 0, cycle, i) < 0) {
|
||||
s->packet_index = -1;
|
||||
amdtp_stream_pcm_abort(s);
|
||||
return;
|
||||
|
@ -620,7 +696,7 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp,
|
|||
{
|
||||
struct amdtp_stream *s = private_data;
|
||||
unsigned int i, packets;
|
||||
unsigned int payload_quadlets, max_payload_quadlets;
|
||||
unsigned int payload_length, max_payload_length;
|
||||
__be32 *headers = header;
|
||||
u32 cycle;
|
||||
|
||||
|
@ -636,22 +712,22 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp,
|
|||
cycle = decrement_cycle_count(cycle, packets);
|
||||
|
||||
/* For buffer-over-run prevention. */
|
||||
max_payload_quadlets = amdtp_stream_get_max_payload(s) / 4;
|
||||
max_payload_length = s->max_payload_length;
|
||||
|
||||
for (i = 0; i < packets; i++) {
|
||||
cycle = increment_cycle_count(cycle, 1);
|
||||
|
||||
/* The number of quadlets in this packet */
|
||||
payload_quadlets =
|
||||
(be32_to_cpu(headers[i]) >> ISO_DATA_LENGTH_SHIFT) / 4;
|
||||
if (payload_quadlets > max_payload_quadlets) {
|
||||
/* The number of bytes in this packet */
|
||||
payload_length =
|
||||
(be32_to_cpu(headers[i]) >> ISO_DATA_LENGTH_SHIFT);
|
||||
if (payload_length > max_payload_length) {
|
||||
dev_err(&s->unit->device,
|
||||
"Detect jumbo payload: %02x %02x\n",
|
||||
payload_quadlets, max_payload_quadlets);
|
||||
"Detect jumbo payload: %04x %04x\n",
|
||||
payload_length, max_payload_length);
|
||||
break;
|
||||
}
|
||||
|
||||
if (handle_in_packet(s, payload_quadlets, cycle, i) < 0)
|
||||
if (s->handle_packet(s, payload_length, cycle, i) < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -671,6 +747,10 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context,
|
|||
void *header, void *private_data)
|
||||
{
|
||||
struct amdtp_stream *s = private_data;
|
||||
u32 cycle;
|
||||
unsigned int packets;
|
||||
|
||||
s->max_payload_length = amdtp_stream_get_max_payload(s);
|
||||
|
||||
/*
|
||||
* For in-stream, first packet has come.
|
||||
|
@ -679,10 +759,27 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context,
|
|||
s->callbacked = true;
|
||||
wake_up(&s->callback_wait);
|
||||
|
||||
if (s->direction == AMDTP_IN_STREAM)
|
||||
cycle = compute_cycle_count(tstamp);
|
||||
|
||||
if (s->direction == AMDTP_IN_STREAM) {
|
||||
packets = header_length / IN_PACKET_HEADER_SIZE;
|
||||
cycle = decrement_cycle_count(cycle, packets);
|
||||
context->callback.sc = in_stream_callback;
|
||||
else
|
||||
if (s->flags & CIP_NO_HEADER)
|
||||
s->handle_packet = handle_in_packet_without_header;
|
||||
else
|
||||
s->handle_packet = handle_in_packet;
|
||||
} else {
|
||||
packets = header_length / 4;
|
||||
cycle = increment_cycle_count(cycle, QUEUE_LENGTH - packets);
|
||||
context->callback.sc = out_stream_callback;
|
||||
if (s->flags & CIP_NO_HEADER)
|
||||
s->handle_packet = handle_out_packet_without_header;
|
||||
else
|
||||
s->handle_packet = handle_out_packet;
|
||||
}
|
||||
|
||||
s->start_cycle = cycle;
|
||||
|
||||
context->callback.sc(context, tstamp, header_length, header, s);
|
||||
}
|
||||
|
@ -759,6 +856,11 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
|
|||
|
||||
amdtp_stream_update(s);
|
||||
|
||||
if (s->flags & CIP_NO_HEADER)
|
||||
s->tag = TAG_NO_CIP_HEADER;
|
||||
else
|
||||
s->tag = TAG_CIP;
|
||||
|
||||
s->packet_index = 0;
|
||||
do {
|
||||
if (s->direction == AMDTP_IN_STREAM)
|
||||
|
@ -771,7 +873,7 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
|
|||
|
||||
/* NOTE: TAG1 matches CIP. This just affects in stream. */
|
||||
tag = FW_ISO_CONTEXT_MATCH_TAG1;
|
||||
if (s->flags & CIP_EMPTY_WITH_TAG0)
|
||||
if ((s->flags & CIP_EMPTY_WITH_TAG0) || (s->flags & CIP_NO_HEADER))
|
||||
tag |= FW_ISO_CONTEXT_MATCH_TAG0;
|
||||
|
||||
s->callbacked = false;
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
* SYT_INTERVAL samples, with these two types alternating so that
|
||||
* the overall sample rate comes out right.
|
||||
* @CIP_EMPTY_WITH_TAG0: Only for in-stream. Empty in-packets have TAG0.
|
||||
* @CIP_DBC_IS_END_EVENT: Only for in-stream. The value of dbc in an in-packet
|
||||
* corresponds to the end of event in the packet. Out of IEC 61883.
|
||||
* @CIP_DBC_IS_END_EVENT: The value of dbc in an packet corresponds to the end
|
||||
* of event in the packet. Out of IEC 61883.
|
||||
* @CIP_WRONG_DBS: Only for in-stream. The value of dbs is wrong in in-packets.
|
||||
* The value of data_block_quadlets is used instead of reported value.
|
||||
* @CIP_SKIP_DBC_ZERO_CHECK: Only for in-stream. Packets with zero in dbc is
|
||||
|
@ -29,6 +29,9 @@
|
|||
* @CIP_JUMBO_PAYLOAD: Only for in-stream. The number of data blocks in an
|
||||
* packet is larger than IEC 61883-6 defines. Current implementation
|
||||
* allows 5 times as large as IEC 61883-6 defines.
|
||||
* @CIP_HEADER_WITHOUT_EOH: Only for in-stream. CIP Header doesn't include
|
||||
* valid EOH.
|
||||
* @CIP_NO_HEADERS: a lack of headers in packets
|
||||
*/
|
||||
enum cip_flags {
|
||||
CIP_NONBLOCKING = 0x00,
|
||||
|
@ -39,6 +42,8 @@ enum cip_flags {
|
|||
CIP_SKIP_DBC_ZERO_CHECK = 0x10,
|
||||
CIP_EMPTY_HAS_WRONG_DBC = 0x20,
|
||||
CIP_JUMBO_PAYLOAD = 0x40,
|
||||
CIP_HEADER_WITHOUT_EOH = 0x80,
|
||||
CIP_NO_HEADER = 0x100,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -101,11 +106,17 @@ struct amdtp_stream {
|
|||
struct fw_iso_context *context;
|
||||
struct iso_packets_buffer buffer;
|
||||
int packet_index;
|
||||
int tag;
|
||||
int (*handle_packet)(struct amdtp_stream *s,
|
||||
unsigned int payload_quadlets, unsigned int cycle,
|
||||
unsigned int index);
|
||||
unsigned int max_payload_length;
|
||||
|
||||
/* For CIP headers. */
|
||||
unsigned int source_node_id_field;
|
||||
unsigned int data_block_quadlets;
|
||||
unsigned int data_block_counter;
|
||||
unsigned int sph;
|
||||
unsigned int fmt;
|
||||
unsigned int fdf;
|
||||
/* quirk: fixed interval of dbc between previos/current packets. */
|
||||
|
@ -130,6 +141,7 @@ struct amdtp_stream {
|
|||
/* To wait for first packet. */
|
||||
bool callbacked;
|
||||
wait_queue_head_t callback_wait;
|
||||
u32 start_cycle;
|
||||
|
||||
/* For backends to process data blocks. */
|
||||
void *protocol;
|
||||
|
|
|
@ -31,13 +31,15 @@ int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id,
|
|||
err = fcp_avc_transaction(unit, buf, 12, buf, 12,
|
||||
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
|
||||
BIT(6) | BIT(7) | BIT(8));
|
||||
if (err > 0 && err < 9)
|
||||
if (err < 0)
|
||||
;
|
||||
else if (err < 9)
|
||||
err = -EIO;
|
||||
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
|
||||
err = -ENOSYS;
|
||||
else if (buf[0] == 0x0a) /* REJECTED */
|
||||
err = -EINVAL;
|
||||
else if (err > 0)
|
||||
else
|
||||
err = 0;
|
||||
|
||||
kfree(buf);
|
||||
|
@ -67,7 +69,9 @@ int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id,
|
|||
err = fcp_avc_transaction(unit, buf, 12, buf, 12,
|
||||
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
|
||||
BIT(6) | BIT(8));
|
||||
if (err > 0 && err < 9)
|
||||
if (err < 0)
|
||||
;
|
||||
else if (err < 9)
|
||||
err = -EIO;
|
||||
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
|
||||
err = -ENOSYS;
|
||||
|
@ -120,7 +124,9 @@ int avc_bridgeco_get_plug_type(struct fw_unit *unit,
|
|||
err = fcp_avc_transaction(unit, buf, 12, buf, 12,
|
||||
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
|
||||
BIT(6) | BIT(7) | BIT(9));
|
||||
if ((err >= 0) && (err < 8))
|
||||
if (err < 0)
|
||||
;
|
||||
else if (err < 11)
|
||||
err = -EIO;
|
||||
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
|
||||
err = -ENOSYS;
|
||||
|
@ -150,7 +156,9 @@ int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit,
|
|||
err = fcp_avc_transaction(unit, buf, 12, buf, 256,
|
||||
BIT(1) | BIT(2) | BIT(3) | BIT(4) |
|
||||
BIT(5) | BIT(6) | BIT(7) | BIT(9));
|
||||
if ((err >= 0) && (err < 8))
|
||||
if (err < 0)
|
||||
;
|
||||
else if (err < 11)
|
||||
err = -EIO;
|
||||
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
|
||||
err = -ENOSYS;
|
||||
|
@ -187,7 +195,9 @@ int avc_bridgeco_get_plug_section_type(struct fw_unit *unit,
|
|||
err = fcp_avc_transaction(unit, buf, 12, buf, 12,
|
||||
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
|
||||
BIT(6) | BIT(7) | BIT(9) | BIT(10));
|
||||
if ((err >= 0) && (err < 8))
|
||||
if (err < 0)
|
||||
;
|
||||
else if (err < 12)
|
||||
err = -EIO;
|
||||
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
|
||||
err = -ENOSYS;
|
||||
|
@ -221,7 +231,9 @@ int avc_bridgeco_get_plug_input(struct fw_unit *unit,
|
|||
err = fcp_avc_transaction(unit, buf, 16, buf, 16,
|
||||
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
|
||||
BIT(6) | BIT(7));
|
||||
if ((err >= 0) && (err < 8))
|
||||
if (err < 0)
|
||||
;
|
||||
else if (err < 16)
|
||||
err = -EIO;
|
||||
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
|
||||
err = -ENOSYS;
|
||||
|
@ -260,7 +272,9 @@ int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit,
|
|||
err = fcp_avc_transaction(unit, buf, 12, buf, *len,
|
||||
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
|
||||
BIT(6) | BIT(7) | BIT(10));
|
||||
if ((err >= 0) && (err < 12))
|
||||
if (err < 0)
|
||||
;
|
||||
else if (err < 12)
|
||||
err = -EIO;
|
||||
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
|
||||
err = -ENOSYS;
|
||||
|
|
|
@ -28,6 +28,9 @@
|
|||
*/
|
||||
#define MAX_MIDI_RX_BLOCKS 8
|
||||
|
||||
/* 3 = MAX(DOT_MIDI_IN_PORTS, DOT_MIDI_OUT_PORTS) + 1. */
|
||||
#define MAX_MIDI_PORTS 3
|
||||
|
||||
/*
|
||||
* The double-oh-three algorithm was discovered by Robin Gareus and Damien
|
||||
* Zammit in 2012, with reverse-engineering for Digi 003 Rack.
|
||||
|
@ -42,10 +45,8 @@ struct amdtp_dot {
|
|||
unsigned int pcm_channels;
|
||||
struct dot_state state;
|
||||
|
||||
unsigned int midi_ports;
|
||||
/* 2 = MAX(DOT_MIDI_IN_PORTS, DOT_MIDI_OUT_PORTS) */
|
||||
struct snd_rawmidi_substream *midi[2];
|
||||
int midi_fifo_used[2];
|
||||
struct snd_rawmidi_substream *midi[MAX_MIDI_PORTS];
|
||||
int midi_fifo_used[MAX_MIDI_PORTS];
|
||||
int midi_fifo_limit;
|
||||
|
||||
void (*transfer_samples)(struct amdtp_stream *s,
|
||||
|
@ -124,8 +125,8 @@ int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate,
|
|||
return -EBUSY;
|
||||
|
||||
/*
|
||||
* A first data channel is for MIDI conformant data channel, the rest is
|
||||
* Multi Bit Linear Audio data channel.
|
||||
* A first data channel is for MIDI messages, the rest is Multi Bit
|
||||
* Linear Audio data channel.
|
||||
*/
|
||||
err = amdtp_stream_set_parameters(s, rate, pcm_channels + 1);
|
||||
if (err < 0)
|
||||
|
@ -135,11 +136,6 @@ int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate,
|
|||
|
||||
p->pcm_channels = pcm_channels;
|
||||
|
||||
if (s->direction == AMDTP_IN_STREAM)
|
||||
p->midi_ports = DOT_MIDI_IN_PORTS;
|
||||
else
|
||||
p->midi_ports = DOT_MIDI_OUT_PORTS;
|
||||
|
||||
/*
|
||||
* We do not know the actual MIDI FIFO size of most devices. Just
|
||||
* assume two bytes, i.e., one byte can be received over the bus while
|
||||
|
@ -281,13 +277,25 @@ static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer,
|
|||
b = (u8 *)&buffer[0];
|
||||
|
||||
len = 0;
|
||||
if (port < p->midi_ports &&
|
||||
if (port < MAX_MIDI_PORTS &&
|
||||
midi_ratelimit_per_packet(s, port) &&
|
||||
p->midi[port] != NULL)
|
||||
len = snd_rawmidi_transmit(p->midi[port], b + 1, 2);
|
||||
|
||||
if (len > 0) {
|
||||
b[3] = (0x10 << port) | len;
|
||||
/*
|
||||
* Upper 4 bits of LSB represent port number.
|
||||
* - 0000b: physical MIDI port 1.
|
||||
* - 0010b: physical MIDI port 2.
|
||||
* - 1110b: console MIDI port.
|
||||
*/
|
||||
if (port == 2)
|
||||
b[3] = 0xe0;
|
||||
else if (port == 1)
|
||||
b[3] = 0x20;
|
||||
else
|
||||
b[3] = 0x00;
|
||||
b[3] |= len;
|
||||
midi_use_bytes(s, port, len);
|
||||
} else {
|
||||
b[1] = 0;
|
||||
|
@ -309,11 +317,22 @@ static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer,
|
|||
|
||||
for (f = 0; f < data_blocks; f++) {
|
||||
b = (u8 *)&buffer[0];
|
||||
port = b[3] >> 4;
|
||||
len = b[3] & 0x0f;
|
||||
|
||||
if (port < p->midi_ports && p->midi[port] && len > 0)
|
||||
snd_rawmidi_receive(p->midi[port], b + 1, len);
|
||||
len = b[3] & 0x0f;
|
||||
if (len > 0) {
|
||||
/*
|
||||
* Upper 4 bits of LSB represent port number.
|
||||
* - 0000b: physical MIDI port 1. Use port 0.
|
||||
* - 1110b: console MIDI port. Use port 2.
|
||||
*/
|
||||
if (b[3] >> 4 > 0)
|
||||
port = 2;
|
||||
else
|
||||
port = 0;
|
||||
|
||||
if (port < MAX_MIDI_PORTS && p->midi[port])
|
||||
snd_rawmidi_receive(p->midi[port], b + 1, len);
|
||||
}
|
||||
|
||||
buffer += s->data_block_quadlets;
|
||||
}
|
||||
|
@ -364,7 +383,7 @@ void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port,
|
|||
{
|
||||
struct amdtp_dot *p = s->protocol;
|
||||
|
||||
if (port < p->midi_ports)
|
||||
if (port < MAX_MIDI_PORTS)
|
||||
ACCESS_ONCE(p->midi[port]) = midi;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include "digi00x.h"
|
||||
|
||||
static int midi_phys_open(struct snd_rawmidi_substream *substream)
|
||||
static int midi_open(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->rmidi->private_data;
|
||||
int err;
|
||||
|
@ -27,7 +27,7 @@ static int midi_phys_open(struct snd_rawmidi_substream *substream)
|
|||
return err;
|
||||
}
|
||||
|
||||
static int midi_phys_close(struct snd_rawmidi_substream *substream)
|
||||
static int midi_close(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->rmidi->private_data;
|
||||
|
||||
|
@ -40,180 +40,130 @@ static int midi_phys_close(struct snd_rawmidi_substream *substream)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void midi_phys_capture_trigger(struct snd_rawmidi_substream *substream,
|
||||
int up)
|
||||
static void midi_capture_trigger(struct snd_rawmidi_substream *substream,
|
||||
int up)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->rmidi->private_data;
|
||||
unsigned int port;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dg00x->lock, flags);
|
||||
|
||||
if (up)
|
||||
amdtp_dot_midi_trigger(&dg00x->tx_stream, substream->number,
|
||||
substream);
|
||||
if (substream->rmidi->device == 0)
|
||||
port = substream->number;
|
||||
else
|
||||
amdtp_dot_midi_trigger(&dg00x->tx_stream, substream->number,
|
||||
NULL);
|
||||
|
||||
spin_unlock_irqrestore(&dg00x->lock, flags);
|
||||
}
|
||||
|
||||
static void midi_phys_playback_trigger(struct snd_rawmidi_substream *substream,
|
||||
int up)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->rmidi->private_data;
|
||||
unsigned long flags;
|
||||
port = 2;
|
||||
|
||||
spin_lock_irqsave(&dg00x->lock, flags);
|
||||
|
||||
if (up)
|
||||
amdtp_dot_midi_trigger(&dg00x->rx_stream, substream->number,
|
||||
substream);
|
||||
amdtp_dot_midi_trigger(&dg00x->tx_stream, port, substream);
|
||||
else
|
||||
amdtp_dot_midi_trigger(&dg00x->rx_stream, substream->number,
|
||||
NULL);
|
||||
amdtp_dot_midi_trigger(&dg00x->tx_stream, port, NULL);
|
||||
|
||||
spin_unlock_irqrestore(&dg00x->lock, flags);
|
||||
}
|
||||
|
||||
static int midi_ctl_open(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
/* Do nothing. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int midi_ctl_capture_close(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
/* Do nothing. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int midi_ctl_playback_close(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->rmidi->private_data;
|
||||
|
||||
snd_fw_async_midi_port_finish(&dg00x->out_control);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void midi_ctl_capture_trigger(struct snd_rawmidi_substream *substream,
|
||||
int up)
|
||||
static void midi_playback_trigger(struct snd_rawmidi_substream *substream,
|
||||
int up)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->rmidi->private_data;
|
||||
unsigned int port;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dg00x->lock, flags);
|
||||
|
||||
if (up)
|
||||
dg00x->in_control = substream;
|
||||
if (substream->rmidi->device == 0)
|
||||
port = substream->number;
|
||||
else
|
||||
dg00x->in_control = NULL;
|
||||
|
||||
spin_unlock_irqrestore(&dg00x->lock, flags);
|
||||
}
|
||||
|
||||
static void midi_ctl_playback_trigger(struct snd_rawmidi_substream *substream,
|
||||
int up)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->rmidi->private_data;
|
||||
unsigned long flags;
|
||||
port = 2;
|
||||
|
||||
spin_lock_irqsave(&dg00x->lock, flags);
|
||||
|
||||
if (up)
|
||||
snd_fw_async_midi_port_run(&dg00x->out_control, substream);
|
||||
amdtp_dot_midi_trigger(&dg00x->rx_stream, port, substream);
|
||||
else
|
||||
amdtp_dot_midi_trigger(&dg00x->rx_stream, port, NULL);
|
||||
|
||||
spin_unlock_irqrestore(&dg00x->lock, flags);
|
||||
}
|
||||
|
||||
static void set_midi_substream_names(struct snd_dg00x *dg00x,
|
||||
struct snd_rawmidi_str *str,
|
||||
bool is_ctl)
|
||||
static void set_substream_names(struct snd_dg00x *dg00x,
|
||||
struct snd_rawmidi *rmidi, bool is_console)
|
||||
{
|
||||
struct snd_rawmidi_substream *subs;
|
||||
struct snd_rawmidi_str *str;
|
||||
int i;
|
||||
|
||||
list_for_each_entry(subs, &str->substreams, list) {
|
||||
if (!is_ctl)
|
||||
snprintf(subs->name, sizeof(subs->name),
|
||||
"%s MIDI %d",
|
||||
dg00x->card->shortname, subs->number + 1);
|
||||
else
|
||||
/* This port is for asynchronous transaction. */
|
||||
snprintf(subs->name, sizeof(subs->name),
|
||||
"%s control",
|
||||
dg00x->card->shortname);
|
||||
for (i = 0; i < 2; ++i) {
|
||||
str = &rmidi->streams[i];
|
||||
|
||||
list_for_each_entry(subs, &str->substreams, list) {
|
||||
if (!is_console) {
|
||||
snprintf(subs->name, sizeof(subs->name),
|
||||
"%s MIDI %d",
|
||||
dg00x->card->shortname,
|
||||
subs->number + 1);
|
||||
} else {
|
||||
snprintf(subs->name, sizeof(subs->name),
|
||||
"%s control",
|
||||
dg00x->card->shortname);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int add_substream_pair(struct snd_dg00x *dg00x, unsigned int out_ports,
|
||||
unsigned int in_ports, bool is_console)
|
||||
{
|
||||
static const struct snd_rawmidi_ops capture_ops = {
|
||||
.open = midi_open,
|
||||
.close = midi_close,
|
||||
.trigger = midi_capture_trigger,
|
||||
};
|
||||
static const struct snd_rawmidi_ops playback_ops = {
|
||||
.open = midi_open,
|
||||
.close = midi_close,
|
||||
.trigger = midi_playback_trigger,
|
||||
};
|
||||
const char *label;
|
||||
struct snd_rawmidi *rmidi;
|
||||
int err;
|
||||
|
||||
/* Add physical midi ports. */
|
||||
err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, is_console,
|
||||
out_ports, in_ports, &rmidi);
|
||||
if (err < 0)
|
||||
return err;
|
||||
rmidi->private_data = dg00x;
|
||||
|
||||
if (!is_console)
|
||||
label = "%s control";
|
||||
else
|
||||
label = "%s MIDI";
|
||||
snprintf(rmidi->name, sizeof(rmidi->name), label,
|
||||
dg00x->card->shortname);
|
||||
|
||||
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &playback_ops);
|
||||
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &capture_ops);
|
||||
|
||||
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT |
|
||||
SNDRV_RAWMIDI_INFO_OUTPUT |
|
||||
SNDRV_RAWMIDI_INFO_DUPLEX;
|
||||
|
||||
set_substream_names(dg00x, rmidi, is_console);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x)
|
||||
{
|
||||
static const struct snd_rawmidi_ops phys_capture_ops = {
|
||||
.open = midi_phys_open,
|
||||
.close = midi_phys_close,
|
||||
.trigger = midi_phys_capture_trigger,
|
||||
};
|
||||
static const struct snd_rawmidi_ops phys_playback_ops = {
|
||||
.open = midi_phys_open,
|
||||
.close = midi_phys_close,
|
||||
.trigger = midi_phys_playback_trigger,
|
||||
};
|
||||
static const struct snd_rawmidi_ops ctl_capture_ops = {
|
||||
.open = midi_ctl_open,
|
||||
.close = midi_ctl_capture_close,
|
||||
.trigger = midi_ctl_capture_trigger,
|
||||
};
|
||||
static const struct snd_rawmidi_ops ctl_playback_ops = {
|
||||
.open = midi_ctl_open,
|
||||
.close = midi_ctl_playback_close,
|
||||
.trigger = midi_ctl_playback_trigger,
|
||||
};
|
||||
struct snd_rawmidi *rmidi[2];
|
||||
struct snd_rawmidi_str *str;
|
||||
unsigned int i;
|
||||
int err;
|
||||
|
||||
/* Add physical midi ports. */
|
||||
err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 0,
|
||||
DOT_MIDI_OUT_PORTS, DOT_MIDI_IN_PORTS, &rmidi[0]);
|
||||
err = add_substream_pair(dg00x, DOT_MIDI_OUT_PORTS, DOT_MIDI_IN_PORTS,
|
||||
false);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
snprintf(rmidi[0]->name, sizeof(rmidi[0]->name),
|
||||
"%s MIDI", dg00x->card->shortname);
|
||||
if (dg00x->is_console)
|
||||
err = add_substream_pair(dg00x, 1, 1, true);
|
||||
|
||||
snd_rawmidi_set_ops(rmidi[0], SNDRV_RAWMIDI_STREAM_INPUT,
|
||||
&phys_capture_ops);
|
||||
snd_rawmidi_set_ops(rmidi[0], SNDRV_RAWMIDI_STREAM_OUTPUT,
|
||||
&phys_playback_ops);
|
||||
|
||||
/* Add a pair of control midi ports. */
|
||||
err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 1,
|
||||
1, 1, &rmidi[1]);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
snprintf(rmidi[1]->name, sizeof(rmidi[1]->name),
|
||||
"%s control", dg00x->card->shortname);
|
||||
|
||||
snd_rawmidi_set_ops(rmidi[1], SNDRV_RAWMIDI_STREAM_INPUT,
|
||||
&ctl_capture_ops);
|
||||
snd_rawmidi_set_ops(rmidi[1], SNDRV_RAWMIDI_STREAM_OUTPUT,
|
||||
&ctl_playback_ops);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(rmidi); i++) {
|
||||
rmidi[i]->private_data = dg00x;
|
||||
|
||||
rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
|
||||
str = &rmidi[i]->streams[SNDRV_RAWMIDI_STREAM_INPUT];
|
||||
set_midi_substream_names(dg00x, str, i);
|
||||
|
||||
rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
|
||||
str = &rmidi[i]->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
|
||||
set_midi_substream_names(dg00x, str, i);
|
||||
|
||||
rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return err;
|
||||
}
|
||||
|
|
|
@ -9,40 +9,6 @@
|
|||
#include <sound/asound.h>
|
||||
#include "digi00x.h"
|
||||
|
||||
static int fill_midi_message(struct snd_rawmidi_substream *substream, u8 *buf)
|
||||
{
|
||||
int bytes;
|
||||
|
||||
buf[0] = 0x80;
|
||||
bytes = snd_rawmidi_transmit_peek(substream, buf + 1, 2);
|
||||
if (bytes >= 0)
|
||||
buf[3] = 0xc0 | bytes;
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static void handle_midi_control(struct snd_dg00x *dg00x, __be32 *buf,
|
||||
unsigned int length)
|
||||
{
|
||||
struct snd_rawmidi_substream *substream;
|
||||
unsigned int i;
|
||||
unsigned int len;
|
||||
u8 *b;
|
||||
|
||||
substream = ACCESS_ONCE(dg00x->in_control);
|
||||
if (substream == NULL)
|
||||
return;
|
||||
|
||||
length /= 4;
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
b = (u8 *)&buf[i];
|
||||
len = b[3] & 0xf;
|
||||
if (len > 0)
|
||||
snd_rawmidi_receive(dg00x->in_control, b + 1, len);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_unknown_message(struct snd_dg00x *dg00x,
|
||||
unsigned long long offset, __be32 *buf)
|
||||
{
|
||||
|
@ -63,39 +29,36 @@ static void handle_message(struct fw_card *card, struct fw_request *request,
|
|||
struct snd_dg00x *dg00x = callback_data;
|
||||
__be32 *buf = (__be32 *)data;
|
||||
|
||||
fw_send_response(card, request, RCODE_COMPLETE);
|
||||
|
||||
if (offset == dg00x->async_handler.offset)
|
||||
handle_unknown_message(dg00x, offset, buf);
|
||||
else if (offset == dg00x->async_handler.offset + 4)
|
||||
handle_midi_control(dg00x, buf, length);
|
||||
|
||||
fw_send_response(card, request, RCODE_COMPLETE);
|
||||
}
|
||||
|
||||
int snd_dg00x_transaction_reregister(struct snd_dg00x *dg00x)
|
||||
{
|
||||
struct fw_device *device = fw_parent_device(dg00x->unit);
|
||||
__be32 data[2];
|
||||
int err;
|
||||
|
||||
/* Unknown. 4bytes. */
|
||||
data[0] = cpu_to_be32((device->card->node_id << 16) |
|
||||
(dg00x->async_handler.offset >> 32));
|
||||
data[1] = cpu_to_be32(dg00x->async_handler.offset);
|
||||
err = snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST,
|
||||
DG00X_ADDR_BASE + DG00X_OFFSET_MESSAGE_ADDR,
|
||||
&data, sizeof(data), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Asynchronous transactions for MIDI control message. */
|
||||
data[0] = cpu_to_be32((device->card->node_id << 16) |
|
||||
(dg00x->async_handler.offset >> 32));
|
||||
data[1] = cpu_to_be32(dg00x->async_handler.offset + 4);
|
||||
return snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST,
|
||||
DG00X_ADDR_BASE + DG00X_OFFSET_MIDI_CTL_ADDR,
|
||||
DG00X_ADDR_BASE + DG00X_OFFSET_MESSAGE_ADDR,
|
||||
&data, sizeof(data), 0);
|
||||
}
|
||||
|
||||
void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x)
|
||||
{
|
||||
if (dg00x->async_handler.callback_data == NULL)
|
||||
return;
|
||||
|
||||
fw_core_remove_address_handler(&dg00x->async_handler);
|
||||
|
||||
dg00x->async_handler.callback_data = NULL;
|
||||
}
|
||||
|
||||
int snd_dg00x_transaction_register(struct snd_dg00x *dg00x)
|
||||
{
|
||||
static const struct fw_address_region resp_register_region = {
|
||||
|
@ -104,7 +67,7 @@ int snd_dg00x_transaction_register(struct snd_dg00x *dg00x)
|
|||
};
|
||||
int err;
|
||||
|
||||
dg00x->async_handler.length = 12;
|
||||
dg00x->async_handler.length = 4;
|
||||
dg00x->async_handler.address_callback = handle_message;
|
||||
dg00x->async_handler.callback_data = dg00x;
|
||||
|
||||
|
@ -115,28 +78,7 @@ int snd_dg00x_transaction_register(struct snd_dg00x *dg00x)
|
|||
|
||||
err = snd_dg00x_transaction_reregister(dg00x);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = snd_fw_async_midi_port_init(&dg00x->out_control, dg00x->unit,
|
||||
DG00X_ADDR_BASE + DG00X_OFFSET_MMC,
|
||||
4, fill_midi_message);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
snd_dg00x_transaction_unregister(dg00x);
|
||||
|
||||
return err;
|
||||
error:
|
||||
fw_core_remove_address_handler(&dg00x->async_handler);
|
||||
dg00x->async_handler.callback_data = NULL;
|
||||
return err;
|
||||
}
|
||||
|
||||
void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x)
|
||||
{
|
||||
if (dg00x->async_handler.callback_data == NULL)
|
||||
return;
|
||||
|
||||
snd_fw_async_midi_port_destroy(&dg00x->out_control);
|
||||
fw_core_remove_address_handler(&dg00x->async_handler);
|
||||
|
||||
dg00x->async_handler.callback_data = NULL;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,8 @@ MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
|
|||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
#define VENDOR_DIGIDESIGN 0x00a07e
|
||||
#define MODEL_DIGI00X 0x000002
|
||||
#define MODEL_CONSOLE 0x000001
|
||||
#define MODEL_RACK 0x000002
|
||||
|
||||
static int name_card(struct snd_dg00x *dg00x)
|
||||
{
|
||||
|
@ -129,6 +130,8 @@ static int snd_dg00x_probe(struct fw_unit *unit,
|
|||
spin_lock_init(&dg00x->lock);
|
||||
init_waitqueue_head(&dg00x->hwdep_wait);
|
||||
|
||||
dg00x->is_console = entry->model_id == MODEL_CONSOLE;
|
||||
|
||||
/* Allocate and register this sound card later. */
|
||||
INIT_DEFERRABLE_WORK(&dg00x->dwork, do_registration);
|
||||
snd_fw_schedule_registration(unit, &dg00x->dwork);
|
||||
|
@ -183,7 +186,13 @@ static const struct ieee1394_device_id snd_dg00x_id_table[] = {
|
|||
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
||||
IEEE1394_MATCH_MODEL_ID,
|
||||
.vendor_id = VENDOR_DIGIDESIGN,
|
||||
.model_id = MODEL_DIGI00X,
|
||||
.model_id = MODEL_CONSOLE,
|
||||
},
|
||||
{
|
||||
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
||||
IEEE1394_MATCH_MODEL_ID,
|
||||
.vendor_id = VENDOR_DIGIDESIGN,
|
||||
.model_id = MODEL_RACK,
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
|
|
@ -58,16 +58,15 @@ struct snd_dg00x {
|
|||
struct fw_address_handler async_handler;
|
||||
u32 msg;
|
||||
|
||||
/* For asynchronous MIDI controls. */
|
||||
struct snd_rawmidi_substream *in_control;
|
||||
struct snd_fw_async_midi_port out_control;
|
||||
/* Console models have additional MIDI ports for control surface. */
|
||||
bool is_console;
|
||||
};
|
||||
|
||||
#define DG00X_ADDR_BASE 0xffffe0000000ull
|
||||
|
||||
#define DG00X_OFFSET_STREAMING_STATE 0x0000
|
||||
#define DG00X_OFFSET_STREAMING_SET 0x0004
|
||||
#define DG00X_OFFSET_MIDI_CTL_ADDR 0x0008
|
||||
/* unknown but address in host space 0x0008 */
|
||||
/* For LSB of the address 0x000c */
|
||||
/* unknown 0x0010 */
|
||||
#define DG00X_OFFSET_MESSAGE_ADDR 0x0014
|
||||
|
|
|
@ -63,7 +63,9 @@ int avc_general_set_sig_fmt(struct fw_unit *unit, unsigned int rate,
|
|||
/* do transaction and check buf[1-5] are the same against command */
|
||||
err = fcp_avc_transaction(unit, buf, 8, buf, 8,
|
||||
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5));
|
||||
if (err >= 0 && err < 8)
|
||||
if (err < 0)
|
||||
;
|
||||
else if (err < 8)
|
||||
err = -EIO;
|
||||
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
|
||||
err = -ENOSYS;
|
||||
|
@ -106,7 +108,9 @@ int avc_general_get_sig_fmt(struct fw_unit *unit, unsigned int *rate,
|
|||
/* do transaction and check buf[1-4] are the same against command */
|
||||
err = fcp_avc_transaction(unit, buf, 8, buf, 8,
|
||||
BIT(1) | BIT(2) | BIT(3) | BIT(4));
|
||||
if (err >= 0 && err < 8)
|
||||
if (err < 0)
|
||||
;
|
||||
else if (err < 8)
|
||||
err = -EIO;
|
||||
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
|
||||
err = -ENOSYS;
|
||||
|
@ -154,7 +158,9 @@ int avc_general_get_plug_info(struct fw_unit *unit, unsigned int subunit_type,
|
|||
buf[3] = 0xff & subfunction;
|
||||
|
||||
err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2));
|
||||
if (err >= 0 && err < 8)
|
||||
if (err < 0)
|
||||
;
|
||||
else if (err < 8)
|
||||
err = -EIO;
|
||||
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
|
||||
err = -ENOSYS;
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \
|
||||
ff-stream.o ff-pcm.o ff-hwdep.o ff-protocol-ff400.o
|
||||
obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* amdtp-ff.c - a part of driver for RME Fireface series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include <sound/pcm.h>
|
||||
#include "ff.h"
|
||||
|
||||
struct amdtp_ff {
|
||||
unsigned int pcm_channels;
|
||||
};
|
||||
|
||||
int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate,
|
||||
unsigned int pcm_channels)
|
||||
{
|
||||
struct amdtp_ff *p = s->protocol;
|
||||
unsigned int data_channels;
|
||||
|
||||
if (amdtp_stream_running(s))
|
||||
return -EBUSY;
|
||||
|
||||
p->pcm_channels = pcm_channels;
|
||||
data_channels = pcm_channels;
|
||||
|
||||
return amdtp_stream_set_parameters(s, rate, data_channels);
|
||||
}
|
||||
|
||||
static void write_pcm_s32(struct amdtp_stream *s,
|
||||
struct snd_pcm_substream *pcm,
|
||||
__le32 *buffer, unsigned int frames)
|
||||
{
|
||||
struct amdtp_ff *p = s->protocol;
|
||||
struct snd_pcm_runtime *runtime = pcm->runtime;
|
||||
unsigned int channels, remaining_frames, i, c;
|
||||
const u32 *src;
|
||||
|
||||
channels = p->pcm_channels;
|
||||
src = (void *)runtime->dma_area +
|
||||
frames_to_bytes(runtime, s->pcm_buffer_pointer);
|
||||
remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
|
||||
|
||||
for (i = 0; i < frames; ++i) {
|
||||
for (c = 0; c < channels; ++c) {
|
||||
buffer[c] = cpu_to_le32(*src);
|
||||
src++;
|
||||
}
|
||||
buffer += s->data_block_quadlets;
|
||||
if (--remaining_frames == 0)
|
||||
src = (void *)runtime->dma_area;
|
||||
}
|
||||
}
|
||||
|
||||
static void read_pcm_s32(struct amdtp_stream *s,
|
||||
struct snd_pcm_substream *pcm,
|
||||
__le32 *buffer, unsigned int frames)
|
||||
{
|
||||
struct amdtp_ff *p = s->protocol;
|
||||
struct snd_pcm_runtime *runtime = pcm->runtime;
|
||||
unsigned int channels, remaining_frames, i, c;
|
||||
u32 *dst;
|
||||
|
||||
channels = p->pcm_channels;
|
||||
dst = (void *)runtime->dma_area +
|
||||
frames_to_bytes(runtime, s->pcm_buffer_pointer);
|
||||
remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
|
||||
|
||||
for (i = 0; i < frames; ++i) {
|
||||
for (c = 0; c < channels; ++c) {
|
||||
*dst = le32_to_cpu(buffer[c]) & 0xffffff00;
|
||||
dst++;
|
||||
}
|
||||
buffer += s->data_block_quadlets;
|
||||
if (--remaining_frames == 0)
|
||||
dst = (void *)runtime->dma_area;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_pcm_silence(struct amdtp_stream *s,
|
||||
__le32 *buffer, unsigned int frames)
|
||||
{
|
||||
struct amdtp_ff *p = s->protocol;
|
||||
unsigned int i, c, channels = p->pcm_channels;
|
||||
|
||||
for (i = 0; i < frames; ++i) {
|
||||
for (c = 0; c < channels; ++c)
|
||||
buffer[c] = cpu_to_le32(0x00000000);
|
||||
buffer += s->data_block_quadlets;
|
||||
}
|
||||
}
|
||||
|
||||
int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s,
|
||||
struct snd_pcm_runtime *runtime)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return amdtp_stream_add_pcm_hw_constraints(s, runtime);
|
||||
}
|
||||
|
||||
static unsigned int process_rx_data_blocks(struct amdtp_stream *s,
|
||||
__be32 *buffer,
|
||||
unsigned int data_blocks,
|
||||
unsigned int *syt)
|
||||
{
|
||||
struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm);
|
||||
unsigned int pcm_frames;
|
||||
|
||||
if (pcm) {
|
||||
write_pcm_s32(s, pcm, (__le32 *)buffer, data_blocks);
|
||||
pcm_frames = data_blocks;
|
||||
} else {
|
||||
write_pcm_silence(s, (__le32 *)buffer, data_blocks);
|
||||
pcm_frames = 0;
|
||||
}
|
||||
|
||||
return pcm_frames;
|
||||
}
|
||||
|
||||
static unsigned int process_tx_data_blocks(struct amdtp_stream *s,
|
||||
__be32 *buffer,
|
||||
unsigned int data_blocks,
|
||||
unsigned int *syt)
|
||||
{
|
||||
struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm);
|
||||
unsigned int pcm_frames;
|
||||
|
||||
if (pcm) {
|
||||
read_pcm_s32(s, pcm, (__le32 *)buffer, data_blocks);
|
||||
pcm_frames = data_blocks;
|
||||
} else {
|
||||
pcm_frames = 0;
|
||||
}
|
||||
|
||||
return pcm_frames;
|
||||
}
|
||||
|
||||
int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit,
|
||||
enum amdtp_stream_direction dir)
|
||||
{
|
||||
amdtp_stream_process_data_blocks_t process_data_blocks;
|
||||
|
||||
if (dir == AMDTP_IN_STREAM)
|
||||
process_data_blocks = process_tx_data_blocks;
|
||||
else
|
||||
process_data_blocks = process_rx_data_blocks;
|
||||
|
||||
return amdtp_stream_init(s, unit, dir, CIP_NO_HEADER, 0,
|
||||
process_data_blocks, sizeof(struct amdtp_ff));
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* ff-hwdep.c - a part of driver for RME Fireface series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This codes give three functionality.
|
||||
*
|
||||
* 1.get firewire node information
|
||||
* 2.get notification about starting/stopping stream
|
||||
* 3.lock/unlock stream
|
||||
*/
|
||||
|
||||
#include "ff.h"
|
||||
|
||||
static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
|
||||
loff_t *offset)
|
||||
{
|
||||
struct snd_ff *ff = hwdep->private_data;
|
||||
DEFINE_WAIT(wait);
|
||||
union snd_firewire_event event;
|
||||
|
||||
spin_lock_irq(&ff->lock);
|
||||
|
||||
while (!ff->dev_lock_changed) {
|
||||
prepare_to_wait(&ff->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
|
||||
spin_unlock_irq(&ff->lock);
|
||||
schedule();
|
||||
finish_wait(&ff->hwdep_wait, &wait);
|
||||
if (signal_pending(current))
|
||||
return -ERESTARTSYS;
|
||||
spin_lock_irq(&ff->lock);
|
||||
}
|
||||
|
||||
memset(&event, 0, sizeof(event));
|
||||
if (ff->dev_lock_changed) {
|
||||
event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
|
||||
event.lock_status.status = (ff->dev_lock_count > 0);
|
||||
ff->dev_lock_changed = false;
|
||||
|
||||
count = min_t(long, count, sizeof(event.lock_status));
|
||||
}
|
||||
|
||||
spin_unlock_irq(&ff->lock);
|
||||
|
||||
if (copy_to_user(buf, &event, count))
|
||||
return -EFAULT;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
|
||||
poll_table *wait)
|
||||
{
|
||||
struct snd_ff *ff = hwdep->private_data;
|
||||
unsigned int events;
|
||||
|
||||
poll_wait(file, &ff->hwdep_wait, wait);
|
||||
|
||||
spin_lock_irq(&ff->lock);
|
||||
if (ff->dev_lock_changed)
|
||||
events = POLLIN | POLLRDNORM;
|
||||
else
|
||||
events = 0;
|
||||
spin_unlock_irq(&ff->lock);
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
static int hwdep_get_info(struct snd_ff *ff, void __user *arg)
|
||||
{
|
||||
struct fw_device *dev = fw_parent_device(ff->unit);
|
||||
struct snd_firewire_get_info info;
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.type = SNDRV_FIREWIRE_TYPE_FIREFACE;
|
||||
info.card = dev->card->index;
|
||||
*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
|
||||
*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
|
||||
strlcpy(info.device_name, dev_name(&dev->device),
|
||||
sizeof(info.device_name));
|
||||
|
||||
if (copy_to_user(arg, &info, sizeof(info)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hwdep_lock(struct snd_ff *ff)
|
||||
{
|
||||
int err;
|
||||
|
||||
spin_lock_irq(&ff->lock);
|
||||
|
||||
if (ff->dev_lock_count == 0) {
|
||||
ff->dev_lock_count = -1;
|
||||
err = 0;
|
||||
} else {
|
||||
err = -EBUSY;
|
||||
}
|
||||
|
||||
spin_unlock_irq(&ff->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int hwdep_unlock(struct snd_ff *ff)
|
||||
{
|
||||
int err;
|
||||
|
||||
spin_lock_irq(&ff->lock);
|
||||
|
||||
if (ff->dev_lock_count == -1) {
|
||||
ff->dev_lock_count = 0;
|
||||
err = 0;
|
||||
} else {
|
||||
err = -EBADFD;
|
||||
}
|
||||
|
||||
spin_unlock_irq(&ff->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
|
||||
{
|
||||
struct snd_ff *ff = hwdep->private_data;
|
||||
|
||||
spin_lock_irq(&ff->lock);
|
||||
if (ff->dev_lock_count == -1)
|
||||
ff->dev_lock_count = 0;
|
||||
spin_unlock_irq(&ff->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct snd_ff *ff = hwdep->private_data;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_FIREWIRE_IOCTL_GET_INFO:
|
||||
return hwdep_get_info(ff, (void __user *)arg);
|
||||
case SNDRV_FIREWIRE_IOCTL_LOCK:
|
||||
return hwdep_lock(ff);
|
||||
case SNDRV_FIREWIRE_IOCTL_UNLOCK:
|
||||
return hwdep_unlock(ff);
|
||||
default:
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
return hwdep_ioctl(hwdep, file, cmd,
|
||||
(unsigned long)compat_ptr(arg));
|
||||
}
|
||||
#else
|
||||
#define hwdep_compat_ioctl NULL
|
||||
#endif
|
||||
|
||||
int snd_ff_create_hwdep_devices(struct snd_ff *ff)
|
||||
{
|
||||
static const struct snd_hwdep_ops hwdep_ops = {
|
||||
.read = hwdep_read,
|
||||
.release = hwdep_release,
|
||||
.poll = hwdep_poll,
|
||||
.ioctl = hwdep_ioctl,
|
||||
.ioctl_compat = hwdep_compat_ioctl,
|
||||
};
|
||||
struct snd_hwdep *hwdep;
|
||||
int err;
|
||||
|
||||
err = snd_hwdep_new(ff->card, ff->card->driver, 0, &hwdep);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
strcpy(hwdep->name, ff->card->driver);
|
||||
hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREFACE;
|
||||
hwdep->ops = hwdep_ops;
|
||||
hwdep->private_data = ff;
|
||||
hwdep->exclusive = true;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* ff-midi.c - a part of driver for RME Fireface series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "ff.h"
|
||||
|
||||
static int midi_capture_open(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
/* Do nothing. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int midi_playback_open(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_ff *ff = substream->rmidi->private_data;
|
||||
|
||||
/* Initialize internal status. */
|
||||
ff->running_status[substream->number] = 0;
|
||||
ff->rx_midi_error[substream->number] = false;
|
||||
|
||||
ACCESS_ONCE(ff->rx_midi_substreams[substream->number]) = substream;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int midi_capture_close(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
/* Do nothing. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int midi_playback_close(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_ff *ff = substream->rmidi->private_data;
|
||||
|
||||
cancel_work_sync(&ff->rx_midi_work[substream->number]);
|
||||
ACCESS_ONCE(ff->rx_midi_substreams[substream->number]) = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void midi_capture_trigger(struct snd_rawmidi_substream *substream,
|
||||
int up)
|
||||
{
|
||||
struct snd_ff *ff = substream->rmidi->private_data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&ff->lock, flags);
|
||||
|
||||
if (up)
|
||||
ACCESS_ONCE(ff->tx_midi_substreams[substream->number]) =
|
||||
substream;
|
||||
else
|
||||
ACCESS_ONCE(ff->tx_midi_substreams[substream->number]) = NULL;
|
||||
|
||||
spin_unlock_irqrestore(&ff->lock, flags);
|
||||
}
|
||||
|
||||
static void midi_playback_trigger(struct snd_rawmidi_substream *substream,
|
||||
int up)
|
||||
{
|
||||
struct snd_ff *ff = substream->rmidi->private_data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&ff->lock, flags);
|
||||
|
||||
if (up || !ff->rx_midi_error[substream->number])
|
||||
schedule_work(&ff->rx_midi_work[substream->number]);
|
||||
|
||||
spin_unlock_irqrestore(&ff->lock, flags);
|
||||
}
|
||||
|
||||
static struct snd_rawmidi_ops midi_capture_ops = {
|
||||
.open = midi_capture_open,
|
||||
.close = midi_capture_close,
|
||||
.trigger = midi_capture_trigger,
|
||||
};
|
||||
|
||||
static struct snd_rawmidi_ops midi_playback_ops = {
|
||||
.open = midi_playback_open,
|
||||
.close = midi_playback_close,
|
||||
.trigger = midi_playback_trigger,
|
||||
};
|
||||
|
||||
static void set_midi_substream_names(struct snd_rawmidi_str *stream,
|
||||
const char *const name)
|
||||
{
|
||||
struct snd_rawmidi_substream *substream;
|
||||
|
||||
list_for_each_entry(substream, &stream->substreams, list) {
|
||||
snprintf(substream->name, sizeof(substream->name),
|
||||
"%s MIDI %d", name, substream->number + 1);
|
||||
}
|
||||
}
|
||||
|
||||
int snd_ff_create_midi_devices(struct snd_ff *ff)
|
||||
{
|
||||
struct snd_rawmidi *rmidi;
|
||||
struct snd_rawmidi_str *stream;
|
||||
int err;
|
||||
|
||||
err = snd_rawmidi_new(ff->card, ff->card->driver, 0,
|
||||
ff->spec->midi_out_ports, ff->spec->midi_in_ports,
|
||||
&rmidi);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
snprintf(rmidi->name, sizeof(rmidi->name),
|
||||
"%s MIDI", ff->card->shortname);
|
||||
rmidi->private_data = ff;
|
||||
|
||||
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
|
||||
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
|
||||
&midi_capture_ops);
|
||||
stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT];
|
||||
set_midi_substream_names(stream, ff->card->shortname);
|
||||
|
||||
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
|
||||
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
|
||||
&midi_playback_ops);
|
||||
stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
|
||||
set_midi_substream_names(stream, ff->card->shortname);
|
||||
|
||||
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,409 @@
|
|||
/*
|
||||
* ff-pcm.c - a part of driver for RME Fireface series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "ff.h"
|
||||
|
||||
static inline unsigned int get_multiplier_mode_with_index(unsigned int index)
|
||||
{
|
||||
return ((int)index - 1) / 2;
|
||||
}
|
||||
|
||||
static int hw_rule_rate(struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_rule *rule)
|
||||
{
|
||||
const unsigned int *pcm_channels = rule->private;
|
||||
struct snd_interval *r =
|
||||
hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
|
||||
const struct snd_interval *c =
|
||||
hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
|
||||
struct snd_interval t = {
|
||||
.min = UINT_MAX, .max = 0, .integer = 1
|
||||
};
|
||||
unsigned int i, mode;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) {
|
||||
mode = get_multiplier_mode_with_index(i);
|
||||
if (!snd_interval_test(c, pcm_channels[mode]))
|
||||
continue;
|
||||
|
||||
t.min = min(t.min, amdtp_rate_table[i]);
|
||||
t.max = max(t.max, amdtp_rate_table[i]);
|
||||
}
|
||||
|
||||
return snd_interval_refine(r, &t);
|
||||
}
|
||||
|
||||
static int hw_rule_channels(struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_rule *rule)
|
||||
{
|
||||
const unsigned int *pcm_channels = rule->private;
|
||||
struct snd_interval *c =
|
||||
hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
|
||||
const struct snd_interval *r =
|
||||
hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE);
|
||||
struct snd_interval t = {
|
||||
.min = UINT_MAX, .max = 0, .integer = 1
|
||||
};
|
||||
unsigned int i, mode;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) {
|
||||
mode = get_multiplier_mode_with_index(i);
|
||||
if (!snd_interval_test(r, amdtp_rate_table[i]))
|
||||
continue;
|
||||
|
||||
t.min = min(t.min, pcm_channels[mode]);
|
||||
t.max = max(t.max, pcm_channels[mode]);
|
||||
}
|
||||
|
||||
return snd_interval_refine(c, &t);
|
||||
}
|
||||
|
||||
static void limit_channels_and_rates(struct snd_pcm_hardware *hw,
|
||||
const unsigned int *pcm_channels)
|
||||
{
|
||||
unsigned int mode;
|
||||
unsigned int rate, channels;
|
||||
int i;
|
||||
|
||||
hw->channels_min = UINT_MAX;
|
||||
hw->channels_max = 0;
|
||||
hw->rate_min = UINT_MAX;
|
||||
hw->rate_max = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) {
|
||||
mode = get_multiplier_mode_with_index(i);
|
||||
|
||||
channels = pcm_channels[mode];
|
||||
if (pcm_channels[mode] == 0)
|
||||
continue;
|
||||
hw->channels_min = min(hw->channels_min, channels);
|
||||
hw->channels_max = max(hw->channels_max, channels);
|
||||
|
||||
rate = amdtp_rate_table[i];
|
||||
hw->rates |= snd_pcm_rate_to_rate_bit(rate);
|
||||
hw->rate_min = min(hw->rate_min, rate);
|
||||
hw->rate_max = max(hw->rate_max, rate);
|
||||
}
|
||||
}
|
||||
|
||||
static void limit_period_and_buffer(struct snd_pcm_hardware *hw)
|
||||
{
|
||||
hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */
|
||||
hw->periods_max = UINT_MAX;
|
||||
|
||||
hw->period_bytes_min = 4 * hw->channels_max; /* bytes for a frame */
|
||||
|
||||
/* Just to prevent from allocating much pages. */
|
||||
hw->period_bytes_max = hw->period_bytes_min * 2048;
|
||||
hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min;
|
||||
}
|
||||
|
||||
static int pcm_init_hw_params(struct snd_ff *ff,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct amdtp_stream *s;
|
||||
const unsigned int *pcm_channels;
|
||||
int err;
|
||||
|
||||
runtime->hw.info = SNDRV_PCM_INFO_BATCH |
|
||||
SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
||||
SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_JOINT_DUPLEX |
|
||||
SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
runtime->hw.formats = SNDRV_PCM_FMTBIT_S32;
|
||||
s = &ff->tx_stream;
|
||||
pcm_channels = ff->spec->pcm_capture_channels;
|
||||
} else {
|
||||
runtime->hw.formats = SNDRV_PCM_FMTBIT_S32;
|
||||
s = &ff->rx_stream;
|
||||
pcm_channels = ff->spec->pcm_playback_channels;
|
||||
}
|
||||
|
||||
/* limit rates */
|
||||
limit_channels_and_rates(&runtime->hw, pcm_channels);
|
||||
limit_period_and_buffer(&runtime->hw);
|
||||
|
||||
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
|
||||
hw_rule_channels, (void *)pcm_channels,
|
||||
SNDRV_PCM_HW_PARAM_RATE, -1);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
|
||||
hw_rule_rate, (void *)pcm_channels,
|
||||
SNDRV_PCM_HW_PARAM_CHANNELS, -1);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return amdtp_ff_add_pcm_hw_constraints(s, runtime);
|
||||
}
|
||||
|
||||
static int pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_ff *ff = substream->private_data;
|
||||
unsigned int rate;
|
||||
enum snd_ff_clock_src src;
|
||||
int i, err;
|
||||
|
||||
err = snd_ff_stream_lock_try(ff);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = pcm_init_hw_params(ff, substream);
|
||||
if (err < 0) {
|
||||
snd_ff_stream_lock_release(ff);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = ff->spec->protocol->get_clock(ff, &rate, &src);
|
||||
if (err < 0) {
|
||||
snd_ff_stream_lock_release(ff);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (src != SND_FF_CLOCK_SRC_INTERNAL) {
|
||||
for (i = 0; i < CIP_SFC_COUNT; ++i) {
|
||||
if (amdtp_rate_table[i] == rate)
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* The unit is configured at sampling frequency which packet
|
||||
* streaming engine can't support.
|
||||
*/
|
||||
if (i >= CIP_SFC_COUNT) {
|
||||
snd_ff_stream_lock_release(ff);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
substream->runtime->hw.rate_min = rate;
|
||||
substream->runtime->hw.rate_max = rate;
|
||||
} else {
|
||||
if (amdtp_stream_pcm_running(&ff->rx_stream) ||
|
||||
amdtp_stream_pcm_running(&ff->tx_stream)) {
|
||||
rate = amdtp_rate_table[ff->rx_stream.sfc];
|
||||
substream->runtime->hw.rate_min = rate;
|
||||
substream->runtime->hw.rate_max = rate;
|
||||
}
|
||||
}
|
||||
|
||||
snd_pcm_set_sync(substream);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_ff *ff = substream->private_data;
|
||||
|
||||
snd_ff_stream_lock_release(ff);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcm_capture_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct snd_ff *ff = substream->private_data;
|
||||
int err;
|
||||
|
||||
err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
||||
mutex_lock(&ff->mutex);
|
||||
ff->substreams_counter++;
|
||||
mutex_unlock(&ff->mutex);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcm_playback_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct snd_ff *ff = substream->private_data;
|
||||
int err;
|
||||
|
||||
err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
||||
mutex_lock(&ff->mutex);
|
||||
ff->substreams_counter++;
|
||||
mutex_unlock(&ff->mutex);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcm_capture_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_ff *ff = substream->private_data;
|
||||
|
||||
mutex_lock(&ff->mutex);
|
||||
|
||||
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
|
||||
ff->substreams_counter--;
|
||||
|
||||
snd_ff_stream_stop_duplex(ff);
|
||||
|
||||
mutex_unlock(&ff->mutex);
|
||||
|
||||
return snd_pcm_lib_free_vmalloc_buffer(substream);
|
||||
}
|
||||
|
||||
static int pcm_playback_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_ff *ff = substream->private_data;
|
||||
|
||||
mutex_lock(&ff->mutex);
|
||||
|
||||
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
|
||||
ff->substreams_counter--;
|
||||
|
||||
snd_ff_stream_stop_duplex(ff);
|
||||
|
||||
mutex_unlock(&ff->mutex);
|
||||
|
||||
return snd_pcm_lib_free_vmalloc_buffer(substream);
|
||||
}
|
||||
|
||||
static int pcm_capture_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_ff *ff = substream->private_data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
int err;
|
||||
|
||||
mutex_lock(&ff->mutex);
|
||||
|
||||
err = snd_ff_stream_start_duplex(ff, runtime->rate);
|
||||
if (err >= 0)
|
||||
amdtp_stream_pcm_prepare(&ff->tx_stream);
|
||||
|
||||
mutex_unlock(&ff->mutex);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int pcm_playback_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_ff *ff = substream->private_data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
int err;
|
||||
|
||||
mutex_lock(&ff->mutex);
|
||||
|
||||
err = snd_ff_stream_start_duplex(ff, runtime->rate);
|
||||
if (err >= 0)
|
||||
amdtp_stream_pcm_prepare(&ff->rx_stream);
|
||||
|
||||
mutex_unlock(&ff->mutex);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_ff *ff = substream->private_data;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
amdtp_stream_pcm_trigger(&ff->tx_stream, substream);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
amdtp_stream_pcm_trigger(&ff->tx_stream, NULL);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_ff *ff = substream->private_data;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
amdtp_stream_pcm_trigger(&ff->rx_stream, substream);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
amdtp_stream_pcm_trigger(&ff->rx_stream, NULL);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
|
||||
{
|
||||
struct snd_ff *ff = sbstrm->private_data;
|
||||
|
||||
return amdtp_stream_pcm_pointer(&ff->tx_stream);
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
|
||||
{
|
||||
struct snd_ff *ff = sbstrm->private_data;
|
||||
|
||||
return amdtp_stream_pcm_pointer(&ff->rx_stream);
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops pcm_capture_ops = {
|
||||
.open = pcm_open,
|
||||
.close = pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = pcm_capture_hw_params,
|
||||
.hw_free = pcm_capture_hw_free,
|
||||
.prepare = pcm_capture_prepare,
|
||||
.trigger = pcm_capture_trigger,
|
||||
.pointer = pcm_capture_pointer,
|
||||
.page = snd_pcm_lib_get_vmalloc_page,
|
||||
};
|
||||
|
||||
static struct snd_pcm_ops pcm_playback_ops = {
|
||||
.open = pcm_open,
|
||||
.close = pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = pcm_playback_hw_params,
|
||||
.hw_free = pcm_playback_hw_free,
|
||||
.prepare = pcm_playback_prepare,
|
||||
.trigger = pcm_playback_trigger,
|
||||
.pointer = pcm_playback_pointer,
|
||||
.page = snd_pcm_lib_get_vmalloc_page,
|
||||
.mmap = snd_pcm_lib_mmap_vmalloc,
|
||||
};
|
||||
|
||||
int snd_ff_create_pcm_devices(struct snd_ff *ff)
|
||||
{
|
||||
struct snd_pcm *pcm;
|
||||
int err;
|
||||
|
||||
err = snd_pcm_new(ff->card, ff->card->driver, 0, 1, 1, &pcm);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
pcm->private_data = ff;
|
||||
snprintf(pcm->name, sizeof(pcm->name),
|
||||
"%s PCM", ff->card->shortname);
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops);
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* ff-proc.c - a part of driver for RME Fireface series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "./ff.h"
|
||||
|
||||
static void proc_dump_clock_config(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct snd_ff *ff = entry->private_data;
|
||||
|
||||
ff->spec->protocol->dump_clock_config(ff, buffer);
|
||||
}
|
||||
|
||||
static void proc_dump_sync_status(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct snd_ff *ff = entry->private_data;
|
||||
|
||||
ff->spec->protocol->dump_sync_status(ff, buffer);
|
||||
}
|
||||
|
||||
static void add_node(struct snd_ff *ff, struct snd_info_entry *root,
|
||||
const char *name,
|
||||
void (*op)(struct snd_info_entry *e,
|
||||
struct snd_info_buffer *b))
|
||||
{
|
||||
struct snd_info_entry *entry;
|
||||
|
||||
entry = snd_info_create_card_entry(ff->card, name, root);
|
||||
if (entry == NULL)
|
||||
return;
|
||||
|
||||
snd_info_set_text_ops(entry, ff, op);
|
||||
if (snd_info_register(entry) < 0)
|
||||
snd_info_free_entry(entry);
|
||||
}
|
||||
|
||||
void snd_ff_proc_init(struct snd_ff *ff)
|
||||
{
|
||||
struct snd_info_entry *root;
|
||||
|
||||
/*
|
||||
* All nodes are automatically removed at snd_card_disconnect(),
|
||||
* by following to link list.
|
||||
*/
|
||||
root = snd_info_create_card_entry(ff->card, "firewire",
|
||||
ff->card->proc_root);
|
||||
if (root == NULL)
|
||||
return;
|
||||
root->mode = S_IFDIR | S_IRUGO | S_IXUGO;
|
||||
if (snd_info_register(root) < 0) {
|
||||
snd_info_free_entry(root);
|
||||
return;
|
||||
}
|
||||
|
||||
add_node(ff, root, "clock-config", proc_dump_clock_config);
|
||||
add_node(ff, root, "sync-status", proc_dump_sync_status);
|
||||
}
|
|
@ -0,0 +1,371 @@
|
|||
/*
|
||||
* ff-protocol-ff400.c - a part of driver for RME Fireface series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include "ff.h"
|
||||
|
||||
#define FF400_STF 0x000080100500ull
|
||||
#define FF400_RX_PACKET_FORMAT 0x000080100504ull
|
||||
#define FF400_ISOC_COMM_START 0x000080100508ull
|
||||
#define FF400_TX_PACKET_FORMAT 0x00008010050cull
|
||||
#define FF400_ISOC_COMM_STOP 0x000080100510ull
|
||||
#define FF400_SYNC_STATUS 0x0000801c0000ull
|
||||
#define FF400_FETCH_PCM_FRAMES 0x0000801c0000ull /* For block request. */
|
||||
#define FF400_CLOCK_CONFIG 0x0000801c0004ull
|
||||
|
||||
#define FF400_MIDI_HIGH_ADDR 0x0000801003f4ull
|
||||
#define FF400_MIDI_RX_PORT_0 0x000080180000ull
|
||||
#define FF400_MIDI_RX_PORT_1 0x000080190000ull
|
||||
|
||||
static int ff400_get_clock(struct snd_ff *ff, unsigned int *rate,
|
||||
enum snd_ff_clock_src *src)
|
||||
{
|
||||
__le32 reg;
|
||||
u32 data;
|
||||
int err;
|
||||
|
||||
err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
|
||||
FF400_SYNC_STATUS, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
data = le32_to_cpu(reg);
|
||||
|
||||
/* Calculate sampling rate. */
|
||||
switch ((data >> 1) & 0x03) {
|
||||
case 0x01:
|
||||
*rate = 32000;
|
||||
break;
|
||||
case 0x00:
|
||||
*rate = 44100;
|
||||
break;
|
||||
case 0x03:
|
||||
*rate = 48000;
|
||||
break;
|
||||
case 0x02:
|
||||
default:
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (data & 0x08)
|
||||
*rate *= 2;
|
||||
else if (data & 0x10)
|
||||
*rate *= 4;
|
||||
|
||||
/* Calculate source of clock. */
|
||||
if (data & 0x01) {
|
||||
*src = SND_FF_CLOCK_SRC_INTERNAL;
|
||||
} else {
|
||||
/* TODO: 0x00, 0x01, 0x02, 0x06, 0x07? */
|
||||
switch ((data >> 10) & 0x07) {
|
||||
case 0x03:
|
||||
*src = SND_FF_CLOCK_SRC_SPDIF;
|
||||
break;
|
||||
case 0x04:
|
||||
*src = SND_FF_CLOCK_SRC_WORD;
|
||||
break;
|
||||
case 0x05:
|
||||
*src = SND_FF_CLOCK_SRC_LTC;
|
||||
break;
|
||||
case 0x00:
|
||||
default:
|
||||
*src = SND_FF_CLOCK_SRC_ADAT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ff400_begin_session(struct snd_ff *ff, unsigned int rate)
|
||||
{
|
||||
__le32 reg;
|
||||
int i, err;
|
||||
|
||||
/* Check whether the given value is supported or not. */
|
||||
for (i = 0; i < CIP_SFC_COUNT; i++) {
|
||||
if (amdtp_rate_table[i] == rate)
|
||||
break;
|
||||
}
|
||||
if (i == CIP_SFC_COUNT)
|
||||
return -EINVAL;
|
||||
|
||||
/* Set the number of data blocks transferred in a second. */
|
||||
reg = cpu_to_le32(rate);
|
||||
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF400_STF, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
msleep(100);
|
||||
|
||||
/*
|
||||
* Set isochronous channel and the number of quadlets of received
|
||||
* packets.
|
||||
*/
|
||||
reg = cpu_to_le32(((ff->rx_stream.data_block_quadlets << 3) << 8) |
|
||||
ff->rx_resources.channel);
|
||||
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF400_RX_PACKET_FORMAT, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/*
|
||||
* Set isochronous channel and the number of quadlets of transmitted
|
||||
* packet.
|
||||
*/
|
||||
/* TODO: investigate the purpose of this 0x80. */
|
||||
reg = cpu_to_le32((0x80 << 24) |
|
||||
(ff->tx_resources.channel << 5) |
|
||||
(ff->tx_stream.data_block_quadlets));
|
||||
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF400_TX_PACKET_FORMAT, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Allow to transmit packets. */
|
||||
reg = cpu_to_le32(0x00000001);
|
||||
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF400_ISOC_COMM_START, ®, sizeof(reg), 0);
|
||||
}
|
||||
|
||||
static void ff400_finish_session(struct snd_ff *ff)
|
||||
{
|
||||
__le32 reg;
|
||||
|
||||
reg = cpu_to_le32(0x80000000);
|
||||
snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF400_ISOC_COMM_STOP, ®, sizeof(reg), 0);
|
||||
}
|
||||
|
||||
static int ff400_switch_fetching_mode(struct snd_ff *ff, bool enable)
|
||||
{
|
||||
__le32 *reg;
|
||||
int i;
|
||||
|
||||
reg = kzalloc(sizeof(__le32) * 18, GFP_KERNEL);
|
||||
if (reg == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
if (enable) {
|
||||
/*
|
||||
* Each quadlet is corresponding to data channels in a data
|
||||
* blocks in reverse order. Precisely, quadlets for available
|
||||
* data channels should be enabled. Here, I take second best
|
||||
* to fetch PCM frames from all of data channels regardless of
|
||||
* stf.
|
||||
*/
|
||||
for (i = 0; i < 18; ++i)
|
||||
reg[i] = cpu_to_le32(0x00000001);
|
||||
}
|
||||
|
||||
return snd_fw_transaction(ff->unit, TCODE_WRITE_BLOCK_REQUEST,
|
||||
FF400_FETCH_PCM_FRAMES, reg,
|
||||
sizeof(__le32) * 18, 0);
|
||||
}
|
||||
|
||||
static void ff400_dump_sync_status(struct snd_ff *ff,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
__le32 reg;
|
||||
u32 data;
|
||||
int err;
|
||||
|
||||
err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
|
||||
FF400_SYNC_STATUS, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return;
|
||||
|
||||
data = le32_to_cpu(reg);
|
||||
|
||||
snd_iprintf(buffer, "External source detection:\n");
|
||||
|
||||
snd_iprintf(buffer, "Word Clock:");
|
||||
if ((data >> 24) & 0x20) {
|
||||
if ((data >> 24) & 0x40)
|
||||
snd_iprintf(buffer, "sync\n");
|
||||
else
|
||||
snd_iprintf(buffer, "lock\n");
|
||||
} else {
|
||||
snd_iprintf(buffer, "none\n");
|
||||
}
|
||||
|
||||
snd_iprintf(buffer, "S/PDIF:");
|
||||
if ((data >> 16) & 0x10) {
|
||||
if ((data >> 16) & 0x04)
|
||||
snd_iprintf(buffer, "sync\n");
|
||||
else
|
||||
snd_iprintf(buffer, "lock\n");
|
||||
} else {
|
||||
snd_iprintf(buffer, "none\n");
|
||||
}
|
||||
|
||||
snd_iprintf(buffer, "ADAT:");
|
||||
if ((data >> 8) & 0x04) {
|
||||
if ((data >> 8) & 0x10)
|
||||
snd_iprintf(buffer, "sync\n");
|
||||
else
|
||||
snd_iprintf(buffer, "lock\n");
|
||||
} else {
|
||||
snd_iprintf(buffer, "none\n");
|
||||
}
|
||||
|
||||
snd_iprintf(buffer, "\nUsed external source:\n");
|
||||
|
||||
if (((data >> 22) & 0x07) == 0x07) {
|
||||
snd_iprintf(buffer, "None\n");
|
||||
} else {
|
||||
switch ((data >> 22) & 0x07) {
|
||||
case 0x00:
|
||||
snd_iprintf(buffer, "ADAT:");
|
||||
break;
|
||||
case 0x03:
|
||||
snd_iprintf(buffer, "S/PDIF:");
|
||||
break;
|
||||
case 0x04:
|
||||
snd_iprintf(buffer, "Word:");
|
||||
break;
|
||||
case 0x07:
|
||||
snd_iprintf(buffer, "Nothing:");
|
||||
break;
|
||||
case 0x01:
|
||||
case 0x02:
|
||||
case 0x05:
|
||||
case 0x06:
|
||||
default:
|
||||
snd_iprintf(buffer, "unknown:");
|
||||
break;
|
||||
}
|
||||
|
||||
if ((data >> 25) & 0x07) {
|
||||
switch ((data >> 25) & 0x07) {
|
||||
case 0x01:
|
||||
snd_iprintf(buffer, "32000\n");
|
||||
break;
|
||||
case 0x02:
|
||||
snd_iprintf(buffer, "44100\n");
|
||||
break;
|
||||
case 0x03:
|
||||
snd_iprintf(buffer, "48000\n");
|
||||
break;
|
||||
case 0x04:
|
||||
snd_iprintf(buffer, "64000\n");
|
||||
break;
|
||||
case 0x05:
|
||||
snd_iprintf(buffer, "88200\n");
|
||||
break;
|
||||
case 0x06:
|
||||
snd_iprintf(buffer, "96000\n");
|
||||
break;
|
||||
case 0x07:
|
||||
snd_iprintf(buffer, "128000\n");
|
||||
break;
|
||||
case 0x08:
|
||||
snd_iprintf(buffer, "176400\n");
|
||||
break;
|
||||
case 0x09:
|
||||
snd_iprintf(buffer, "192000\n");
|
||||
break;
|
||||
case 0x00:
|
||||
snd_iprintf(buffer, "unknown\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
snd_iprintf(buffer, "Multiplied:");
|
||||
snd_iprintf(buffer, "%d\n", (data & 0x3ff) * 250);
|
||||
}
|
||||
|
||||
static void ff400_dump_clock_config(struct snd_ff *ff,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
__le32 reg;
|
||||
u32 data;
|
||||
unsigned int rate;
|
||||
const char *src;
|
||||
int err;
|
||||
|
||||
err = snd_fw_transaction(ff->unit, TCODE_READ_BLOCK_REQUEST,
|
||||
FF400_CLOCK_CONFIG, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return;
|
||||
|
||||
data = le32_to_cpu(reg);
|
||||
|
||||
snd_iprintf(buffer, "Output S/PDIF format: %s (Emphasis: %s)\n",
|
||||
(data & 0x20) ? "Professional" : "Consumer",
|
||||
(data & 0x40) ? "on" : "off");
|
||||
|
||||
snd_iprintf(buffer, "Optical output interface format: %s\n",
|
||||
((data >> 8) & 0x01) ? "S/PDIF" : "ADAT");
|
||||
|
||||
snd_iprintf(buffer, "Word output single speed: %s\n",
|
||||
((data >> 8) & 0x20) ? "on" : "off");
|
||||
|
||||
snd_iprintf(buffer, "S/PDIF input interface: %s\n",
|
||||
((data >> 8) & 0x02) ? "Optical" : "Coaxial");
|
||||
|
||||
switch ((data >> 1) & 0x03) {
|
||||
case 0x01:
|
||||
rate = 32000;
|
||||
break;
|
||||
case 0x00:
|
||||
rate = 44100;
|
||||
break;
|
||||
case 0x03:
|
||||
rate = 48000;
|
||||
break;
|
||||
case 0x02:
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (data & 0x08)
|
||||
rate *= 2;
|
||||
else if (data & 0x10)
|
||||
rate *= 4;
|
||||
|
||||
snd_iprintf(buffer, "Sampling rate: %d\n", rate);
|
||||
|
||||
if (data & 0x01) {
|
||||
src = "Internal";
|
||||
} else {
|
||||
switch ((data >> 10) & 0x07) {
|
||||
case 0x00:
|
||||
src = "ADAT";
|
||||
break;
|
||||
case 0x03:
|
||||
src = "S/PDIF";
|
||||
break;
|
||||
case 0x04:
|
||||
src = "Word";
|
||||
break;
|
||||
case 0x05:
|
||||
src = "LTC";
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
snd_iprintf(buffer, "Sync to clock source: %s\n", src);
|
||||
}
|
||||
|
||||
struct snd_ff_protocol snd_ff_protocol_ff400 = {
|
||||
.get_clock = ff400_get_clock,
|
||||
.begin_session = ff400_begin_session,
|
||||
.finish_session = ff400_finish_session,
|
||||
.switch_fetching_mode = ff400_switch_fetching_mode,
|
||||
|
||||
.dump_sync_status = ff400_dump_sync_status,
|
||||
.dump_clock_config = ff400_dump_clock_config,
|
||||
|
||||
.midi_high_addr_reg = FF400_MIDI_HIGH_ADDR,
|
||||
.midi_rx_port_0_reg = FF400_MIDI_RX_PORT_0,
|
||||
.midi_rx_port_1_reg = FF400_MIDI_RX_PORT_1,
|
||||
};
|
|
@ -0,0 +1,282 @@
|
|||
/*
|
||||
* ff-stream.c - a part of driver for RME Fireface series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "ff.h"
|
||||
|
||||
#define CALLBACK_TIMEOUT_MS 200
|
||||
|
||||
static int get_rate_mode(unsigned int rate, unsigned int *mode)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < CIP_SFC_COUNT; i++) {
|
||||
if (amdtp_rate_table[i] == rate)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == CIP_SFC_COUNT)
|
||||
return -EINVAL;
|
||||
|
||||
*mode = ((int)i - 1) / 2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fireface 400 manages isochronous channel number in 3 bit field. Therefore,
|
||||
* we can allocate between 0 and 7 channel.
|
||||
*/
|
||||
static int keep_resources(struct snd_ff *ff, unsigned int rate)
|
||||
{
|
||||
int mode;
|
||||
int err;
|
||||
|
||||
err = get_rate_mode(rate, &mode);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Keep resources for in-stream. */
|
||||
err = amdtp_ff_set_parameters(&ff->tx_stream, rate,
|
||||
ff->spec->pcm_capture_channels[mode]);
|
||||
if (err < 0)
|
||||
return err;
|
||||
ff->tx_resources.channels_mask = 0x00000000000000ffuLL;
|
||||
err = fw_iso_resources_allocate(&ff->tx_resources,
|
||||
amdtp_stream_get_max_payload(&ff->tx_stream),
|
||||
fw_parent_device(ff->unit)->max_speed);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Keep resources for out-stream. */
|
||||
err = amdtp_ff_set_parameters(&ff->rx_stream, rate,
|
||||
ff->spec->pcm_playback_channels[mode]);
|
||||
if (err < 0)
|
||||
return err;
|
||||
ff->rx_resources.channels_mask = 0x00000000000000ffuLL;
|
||||
err = fw_iso_resources_allocate(&ff->rx_resources,
|
||||
amdtp_stream_get_max_payload(&ff->rx_stream),
|
||||
fw_parent_device(ff->unit)->max_speed);
|
||||
if (err < 0)
|
||||
fw_iso_resources_free(&ff->tx_resources);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void release_resources(struct snd_ff *ff)
|
||||
{
|
||||
fw_iso_resources_free(&ff->tx_resources);
|
||||
fw_iso_resources_free(&ff->rx_resources);
|
||||
}
|
||||
|
||||
static inline void finish_session(struct snd_ff *ff)
|
||||
{
|
||||
ff->spec->protocol->finish_session(ff);
|
||||
ff->spec->protocol->switch_fetching_mode(ff, false);
|
||||
}
|
||||
|
||||
static int init_stream(struct snd_ff *ff, enum amdtp_stream_direction dir)
|
||||
{
|
||||
int err;
|
||||
struct fw_iso_resources *resources;
|
||||
struct amdtp_stream *stream;
|
||||
|
||||
if (dir == AMDTP_IN_STREAM) {
|
||||
resources = &ff->tx_resources;
|
||||
stream = &ff->tx_stream;
|
||||
} else {
|
||||
resources = &ff->rx_resources;
|
||||
stream = &ff->rx_stream;
|
||||
}
|
||||
|
||||
err = fw_iso_resources_init(resources, ff->unit);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = amdtp_ff_init(stream, ff->unit, dir);
|
||||
if (err < 0)
|
||||
fw_iso_resources_destroy(resources);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void destroy_stream(struct snd_ff *ff, enum amdtp_stream_direction dir)
|
||||
{
|
||||
if (dir == AMDTP_IN_STREAM) {
|
||||
amdtp_stream_destroy(&ff->tx_stream);
|
||||
fw_iso_resources_destroy(&ff->tx_resources);
|
||||
} else {
|
||||
amdtp_stream_destroy(&ff->rx_stream);
|
||||
fw_iso_resources_destroy(&ff->rx_resources);
|
||||
}
|
||||
}
|
||||
|
||||
int snd_ff_stream_init_duplex(struct snd_ff *ff)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = init_stream(ff, AMDTP_OUT_STREAM);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
err = init_stream(ff, AMDTP_IN_STREAM);
|
||||
if (err < 0)
|
||||
destroy_stream(ff, AMDTP_OUT_STREAM);
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function should be called before starting streams or after stopping
|
||||
* streams.
|
||||
*/
|
||||
void snd_ff_stream_destroy_duplex(struct snd_ff *ff)
|
||||
{
|
||||
destroy_stream(ff, AMDTP_IN_STREAM);
|
||||
destroy_stream(ff, AMDTP_OUT_STREAM);
|
||||
}
|
||||
|
||||
int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate)
|
||||
{
|
||||
unsigned int curr_rate;
|
||||
enum snd_ff_clock_src src;
|
||||
int err;
|
||||
|
||||
if (ff->substreams_counter == 0)
|
||||
return 0;
|
||||
|
||||
err = ff->spec->protocol->get_clock(ff, &curr_rate, &src);
|
||||
if (err < 0)
|
||||
return err;
|
||||
if (curr_rate != rate ||
|
||||
amdtp_streaming_error(&ff->tx_stream) ||
|
||||
amdtp_streaming_error(&ff->rx_stream)) {
|
||||
finish_session(ff);
|
||||
|
||||
amdtp_stream_stop(&ff->tx_stream);
|
||||
amdtp_stream_stop(&ff->rx_stream);
|
||||
|
||||
release_resources(ff);
|
||||
}
|
||||
|
||||
/*
|
||||
* Regardless of current source of clock signal, drivers transfer some
|
||||
* packets. Then, the device transfers packets.
|
||||
*/
|
||||
if (!amdtp_stream_running(&ff->rx_stream)) {
|
||||
err = keep_resources(ff, rate);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = ff->spec->protocol->begin_session(ff, rate);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = amdtp_stream_start(&ff->rx_stream,
|
||||
ff->rx_resources.channel,
|
||||
fw_parent_device(ff->unit)->max_speed);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
if (!amdtp_stream_wait_callback(&ff->rx_stream,
|
||||
CALLBACK_TIMEOUT_MS)) {
|
||||
err = -ETIMEDOUT;
|
||||
goto error;
|
||||
}
|
||||
|
||||
err = ff->spec->protocol->switch_fetching_mode(ff, true);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!amdtp_stream_running(&ff->tx_stream)) {
|
||||
err = amdtp_stream_start(&ff->tx_stream,
|
||||
ff->tx_resources.channel,
|
||||
fw_parent_device(ff->unit)->max_speed);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
if (!amdtp_stream_wait_callback(&ff->tx_stream,
|
||||
CALLBACK_TIMEOUT_MS)) {
|
||||
err = -ETIMEDOUT;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
amdtp_stream_stop(&ff->tx_stream);
|
||||
amdtp_stream_stop(&ff->rx_stream);
|
||||
|
||||
finish_session(ff);
|
||||
release_resources(ff);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
void snd_ff_stream_stop_duplex(struct snd_ff *ff)
|
||||
{
|
||||
if (ff->substreams_counter > 0)
|
||||
return;
|
||||
|
||||
amdtp_stream_stop(&ff->tx_stream);
|
||||
amdtp_stream_stop(&ff->rx_stream);
|
||||
finish_session(ff);
|
||||
release_resources(ff);
|
||||
}
|
||||
|
||||
void snd_ff_stream_update_duplex(struct snd_ff *ff)
|
||||
{
|
||||
/* The device discontinue to transfer packets. */
|
||||
amdtp_stream_pcm_abort(&ff->tx_stream);
|
||||
amdtp_stream_stop(&ff->tx_stream);
|
||||
|
||||
amdtp_stream_pcm_abort(&ff->rx_stream);
|
||||
amdtp_stream_stop(&ff->rx_stream);
|
||||
|
||||
fw_iso_resources_update(&ff->tx_resources);
|
||||
fw_iso_resources_update(&ff->rx_resources);
|
||||
}
|
||||
|
||||
void snd_ff_stream_lock_changed(struct snd_ff *ff)
|
||||
{
|
||||
ff->dev_lock_changed = true;
|
||||
wake_up(&ff->hwdep_wait);
|
||||
}
|
||||
|
||||
int snd_ff_stream_lock_try(struct snd_ff *ff)
|
||||
{
|
||||
int err;
|
||||
|
||||
spin_lock_irq(&ff->lock);
|
||||
|
||||
/* user land lock this */
|
||||
if (ff->dev_lock_count < 0) {
|
||||
err = -EBUSY;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* this is the first time */
|
||||
if (ff->dev_lock_count++ == 0)
|
||||
snd_ff_stream_lock_changed(ff);
|
||||
err = 0;
|
||||
end:
|
||||
spin_unlock_irq(&ff->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
void snd_ff_stream_lock_release(struct snd_ff *ff)
|
||||
{
|
||||
spin_lock_irq(&ff->lock);
|
||||
|
||||
if (WARN_ON(ff->dev_lock_count <= 0))
|
||||
goto end;
|
||||
if (--ff->dev_lock_count == 0)
|
||||
snd_ff_stream_lock_changed(ff);
|
||||
end:
|
||||
spin_unlock_irq(&ff->lock);
|
||||
}
|
|
@ -0,0 +1,295 @@
|
|||
/*
|
||||
* ff-transaction.c - a part of driver for RME Fireface series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "ff.h"
|
||||
|
||||
static void finish_transmit_midi_msg(struct snd_ff *ff, unsigned int port,
|
||||
int rcode)
|
||||
{
|
||||
struct snd_rawmidi_substream *substream =
|
||||
ACCESS_ONCE(ff->rx_midi_substreams[port]);
|
||||
|
||||
if (rcode_is_permanent_error(rcode)) {
|
||||
ff->rx_midi_error[port] = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (rcode != RCODE_COMPLETE) {
|
||||
/* Transfer the message again, immediately. */
|
||||
ff->next_ktime[port] = 0;
|
||||
schedule_work(&ff->rx_midi_work[port]);
|
||||
return;
|
||||
}
|
||||
|
||||
snd_rawmidi_transmit_ack(substream, ff->rx_bytes[port]);
|
||||
ff->rx_bytes[port] = 0;
|
||||
|
||||
if (!snd_rawmidi_transmit_empty(substream))
|
||||
schedule_work(&ff->rx_midi_work[port]);
|
||||
}
|
||||
|
||||
static void finish_transmit_midi0_msg(struct fw_card *card, int rcode,
|
||||
void *data, size_t length,
|
||||
void *callback_data)
|
||||
{
|
||||
struct snd_ff *ff =
|
||||
container_of(callback_data, struct snd_ff, transactions[0]);
|
||||
finish_transmit_midi_msg(ff, 0, rcode);
|
||||
}
|
||||
|
||||
static void finish_transmit_midi1_msg(struct fw_card *card, int rcode,
|
||||
void *data, size_t length,
|
||||
void *callback_data)
|
||||
{
|
||||
struct snd_ff *ff =
|
||||
container_of(callback_data, struct snd_ff, transactions[1]);
|
||||
finish_transmit_midi_msg(ff, 1, rcode);
|
||||
}
|
||||
|
||||
static inline void fill_midi_buf(struct snd_ff *ff, unsigned int port,
|
||||
unsigned int index, u8 byte)
|
||||
{
|
||||
ff->msg_buf[port][index] = cpu_to_le32(byte);
|
||||
}
|
||||
|
||||
static void transmit_midi_msg(struct snd_ff *ff, unsigned int port)
|
||||
{
|
||||
struct snd_rawmidi_substream *substream =
|
||||
ACCESS_ONCE(ff->rx_midi_substreams[port]);
|
||||
u8 *buf = (u8 *)ff->msg_buf[port];
|
||||
int i, len;
|
||||
|
||||
struct fw_device *fw_dev = fw_parent_device(ff->unit);
|
||||
unsigned long long addr;
|
||||
int generation;
|
||||
fw_transaction_callback_t callback;
|
||||
|
||||
if (substream == NULL || snd_rawmidi_transmit_empty(substream))
|
||||
return;
|
||||
|
||||
if (ff->rx_bytes[port] > 0 || ff->rx_midi_error[port])
|
||||
return;
|
||||
|
||||
/* Do it in next chance. */
|
||||
if (ktime_after(ff->next_ktime[port], ktime_get())) {
|
||||
schedule_work(&ff->rx_midi_work[port]);
|
||||
return;
|
||||
}
|
||||
|
||||
len = snd_rawmidi_transmit_peek(substream, buf,
|
||||
SND_FF_MAXIMIM_MIDI_QUADS);
|
||||
if (len <= 0)
|
||||
return;
|
||||
|
||||
for (i = len - 1; i >= 0; i--)
|
||||
fill_midi_buf(ff, port, i, buf[i]);
|
||||
|
||||
if (port == 0) {
|
||||
addr = ff->spec->protocol->midi_rx_port_0_reg;
|
||||
callback = finish_transmit_midi0_msg;
|
||||
} else {
|
||||
addr = ff->spec->protocol->midi_rx_port_1_reg;
|
||||
callback = finish_transmit_midi1_msg;
|
||||
}
|
||||
|
||||
/* Set interval to next transaction. */
|
||||
ff->next_ktime[port] = ktime_add_ns(ktime_get(),
|
||||
len * 8 * NSEC_PER_SEC / 31250);
|
||||
ff->rx_bytes[port] = len;
|
||||
|
||||
/*
|
||||
* In Linux FireWire core, when generation is updated with memory
|
||||
* barrier, node id has already been updated. In this module, After
|
||||
* this smp_rmb(), load/store instructions to memory are completed.
|
||||
* Thus, both of generation and node id are available with recent
|
||||
* values. This is a light-serialization solution to handle bus reset
|
||||
* events on IEEE 1394 bus.
|
||||
*/
|
||||
generation = fw_dev->generation;
|
||||
smp_rmb();
|
||||
fw_send_request(fw_dev->card, &ff->transactions[port],
|
||||
TCODE_WRITE_BLOCK_REQUEST,
|
||||
fw_dev->node_id, generation, fw_dev->max_speed,
|
||||
addr, &ff->msg_buf[port], len * 4,
|
||||
callback, &ff->transactions[port]);
|
||||
}
|
||||
|
||||
static void transmit_midi0_msg(struct work_struct *work)
|
||||
{
|
||||
struct snd_ff *ff = container_of(work, struct snd_ff, rx_midi_work[0]);
|
||||
|
||||
transmit_midi_msg(ff, 0);
|
||||
}
|
||||
|
||||
static void transmit_midi1_msg(struct work_struct *work)
|
||||
{
|
||||
struct snd_ff *ff = container_of(work, struct snd_ff, rx_midi_work[1]);
|
||||
|
||||
transmit_midi_msg(ff, 1);
|
||||
}
|
||||
|
||||
static void handle_midi_msg(struct fw_card *card, struct fw_request *request,
|
||||
int tcode, int destination, int source,
|
||||
int generation, unsigned long long offset,
|
||||
void *data, size_t length, void *callback_data)
|
||||
{
|
||||
struct snd_ff *ff = callback_data;
|
||||
__le32 *buf = data;
|
||||
u32 quad;
|
||||
u8 byte;
|
||||
unsigned int index;
|
||||
struct snd_rawmidi_substream *substream;
|
||||
int i;
|
||||
|
||||
fw_send_response(card, request, RCODE_COMPLETE);
|
||||
|
||||
for (i = 0; i < length / 4; i++) {
|
||||
quad = le32_to_cpu(buf[i]);
|
||||
|
||||
/* Message in first port. */
|
||||
/*
|
||||
* This value may represent the index of this unit when the same
|
||||
* units are on the same IEEE 1394 bus. This driver doesn't use
|
||||
* it.
|
||||
*/
|
||||
index = (quad >> 8) & 0xff;
|
||||
if (index > 0) {
|
||||
substream = ACCESS_ONCE(ff->tx_midi_substreams[0]);
|
||||
if (substream != NULL) {
|
||||
byte = quad & 0xff;
|
||||
snd_rawmidi_receive(substream, &byte, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Message in second port. */
|
||||
index = (quad >> 24) & 0xff;
|
||||
if (index > 0) {
|
||||
substream = ACCESS_ONCE(ff->tx_midi_substreams[1]);
|
||||
if (substream != NULL) {
|
||||
byte = (quad >> 16) & 0xff;
|
||||
snd_rawmidi_receive(substream, &byte, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int allocate_own_address(struct snd_ff *ff, int i)
|
||||
{
|
||||
struct fw_address_region midi_msg_region;
|
||||
int err;
|
||||
|
||||
ff->async_handler.length = SND_FF_MAXIMIM_MIDI_QUADS * 4;
|
||||
ff->async_handler.address_callback = handle_midi_msg;
|
||||
ff->async_handler.callback_data = ff;
|
||||
|
||||
midi_msg_region.start = 0x000100000000ull * i;
|
||||
midi_msg_region.end = midi_msg_region.start + ff->async_handler.length;
|
||||
|
||||
err = fw_core_add_address_handler(&ff->async_handler, &midi_msg_region);
|
||||
if (err >= 0) {
|
||||
/* Controllers are allowed to register this region. */
|
||||
if (ff->async_handler.offset & 0x0000ffffffff) {
|
||||
fw_core_remove_address_handler(&ff->async_handler);
|
||||
err = -EAGAIN;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* The configuration to start asynchronous transactions for MIDI messages is in
|
||||
* 0x'0000'8010'051c. This register includes the other options, thus this driver
|
||||
* doesn't touch it and leaves the decision to userspace. The userspace MUST add
|
||||
* 0x04000000 to write transactions to the register to receive any MIDI
|
||||
* messages.
|
||||
*
|
||||
* Here, I just describe MIDI-related offsets of the register, in little-endian
|
||||
* order.
|
||||
*
|
||||
* Controllers are allowed to register higher 4 bytes of address to receive
|
||||
* the transactions. The register is 0x'0000'8010'03f4. On the other hand, the
|
||||
* controllers are not allowed to register lower 4 bytes of the address. They
|
||||
* are forced to select from 4 options by writing corresponding bits to
|
||||
* 0x'0000'8010'051c.
|
||||
*
|
||||
* The 3rd-6th bits in MSB of this register are used to indicate lower 4 bytes
|
||||
* of address to which the device transferrs the transactions.
|
||||
* - 6th: 0x'....'....'0000'0180
|
||||
* - 5th: 0x'....'....'0000'0100
|
||||
* - 4th: 0x'....'....'0000'0080
|
||||
* - 3rd: 0x'....'....'0000'0000
|
||||
*
|
||||
* This driver configure 0x'....'....'0000'0000 for units to receive MIDI
|
||||
* messages. 3rd bit of the register should be configured, however this driver
|
||||
* deligates this task to user space applications due to a restriction that
|
||||
* this register is write-only and the other bits have own effects.
|
||||
*
|
||||
* The 1st and 2nd bits in LSB of this register are used to cancel transferring
|
||||
* asynchronous transactions. These two bits have the same effect.
|
||||
* - 1st/2nd: cancel transferring
|
||||
*/
|
||||
int snd_ff_transaction_reregister(struct snd_ff *ff)
|
||||
{
|
||||
struct fw_card *fw_card = fw_parent_device(ff->unit)->card;
|
||||
u32 addr;
|
||||
__le32 reg;
|
||||
|
||||
/*
|
||||
* Controllers are allowed to register its node ID and upper 2 byte of
|
||||
* local address to listen asynchronous transactions.
|
||||
*/
|
||||
addr = (fw_card->node_id << 16) | (ff->async_handler.offset >> 32);
|
||||
reg = cpu_to_le32(addr);
|
||||
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
ff->spec->protocol->midi_high_addr_reg,
|
||||
®, sizeof(reg), 0);
|
||||
}
|
||||
|
||||
int snd_ff_transaction_register(struct snd_ff *ff)
|
||||
{
|
||||
int i, err;
|
||||
|
||||
/*
|
||||
* Allocate in Memory Space of IEC 13213, but lower 4 byte in LSB should
|
||||
* be zero due to device specification.
|
||||
*/
|
||||
for (i = 0; i < 0xffff; i++) {
|
||||
err = allocate_own_address(ff, i);
|
||||
if (err != -EBUSY && err != -EAGAIN)
|
||||
break;
|
||||
}
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = snd_ff_transaction_reregister(ff);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
INIT_WORK(&ff->rx_midi_work[0], transmit_midi0_msg);
|
||||
INIT_WORK(&ff->rx_midi_work[1], transmit_midi1_msg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void snd_ff_transaction_unregister(struct snd_ff *ff)
|
||||
{
|
||||
__le32 reg;
|
||||
|
||||
if (ff->async_handler.callback_data == NULL)
|
||||
return;
|
||||
ff->async_handler.callback_data = NULL;
|
||||
|
||||
/* Release higher 4 bytes of address. */
|
||||
reg = cpu_to_le32(0x00000000);
|
||||
snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
ff->spec->protocol->midi_high_addr_reg,
|
||||
®, sizeof(reg), 0);
|
||||
|
||||
fw_core_remove_address_handler(&ff->async_handler);
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* ff.c - a part of driver for RME Fireface series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "ff.h"
|
||||
|
||||
#define OUI_RME 0x000a35
|
||||
|
||||
MODULE_DESCRIPTION("RME Fireface series Driver");
|
||||
MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
static void name_card(struct snd_ff *ff)
|
||||
{
|
||||
struct fw_device *fw_dev = fw_parent_device(ff->unit);
|
||||
|
||||
strcpy(ff->card->driver, "Fireface");
|
||||
strcpy(ff->card->shortname, ff->spec->name);
|
||||
strcpy(ff->card->mixername, ff->spec->name);
|
||||
snprintf(ff->card->longname, sizeof(ff->card->longname),
|
||||
"RME %s, GUID %08x%08x at %s, S%d", ff->spec->name,
|
||||
fw_dev->config_rom[3], fw_dev->config_rom[4],
|
||||
dev_name(&ff->unit->device), 100 << fw_dev->max_speed);
|
||||
}
|
||||
|
||||
static void ff_free(struct snd_ff *ff)
|
||||
{
|
||||
snd_ff_stream_destroy_duplex(ff);
|
||||
snd_ff_transaction_unregister(ff);
|
||||
|
||||
fw_unit_put(ff->unit);
|
||||
|
||||
mutex_destroy(&ff->mutex);
|
||||
kfree(ff);
|
||||
}
|
||||
|
||||
static void ff_card_free(struct snd_card *card)
|
||||
{
|
||||
ff_free(card->private_data);
|
||||
}
|
||||
|
||||
static void do_registration(struct work_struct *work)
|
||||
{
|
||||
struct snd_ff *ff = container_of(work, struct snd_ff, dwork.work);
|
||||
int err;
|
||||
|
||||
if (ff->registered)
|
||||
return;
|
||||
|
||||
err = snd_card_new(&ff->unit->device, -1, NULL, THIS_MODULE, 0,
|
||||
&ff->card);
|
||||
if (err < 0)
|
||||
return;
|
||||
|
||||
err = snd_ff_transaction_register(ff);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
name_card(ff);
|
||||
|
||||
err = snd_ff_stream_init_duplex(ff);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
snd_ff_proc_init(ff);
|
||||
|
||||
err = snd_ff_create_midi_devices(ff);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = snd_ff_create_pcm_devices(ff);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = snd_ff_create_hwdep_devices(ff);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = snd_card_register(ff->card);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
ff->card->private_free = ff_card_free;
|
||||
ff->card->private_data = ff;
|
||||
ff->registered = true;
|
||||
|
||||
return;
|
||||
error:
|
||||
snd_ff_transaction_unregister(ff);
|
||||
snd_ff_stream_destroy_duplex(ff);
|
||||
snd_card_free(ff->card);
|
||||
dev_info(&ff->unit->device,
|
||||
"Sound card registration failed: %d\n", err);
|
||||
}
|
||||
|
||||
static int snd_ff_probe(struct fw_unit *unit,
|
||||
const struct ieee1394_device_id *entry)
|
||||
{
|
||||
struct snd_ff *ff;
|
||||
|
||||
ff = kzalloc(sizeof(struct snd_ff), GFP_KERNEL);
|
||||
if (ff == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
/* initialize myself */
|
||||
ff->unit = fw_unit_get(unit);
|
||||
dev_set_drvdata(&unit->device, ff);
|
||||
|
||||
mutex_init(&ff->mutex);
|
||||
spin_lock_init(&ff->lock);
|
||||
init_waitqueue_head(&ff->hwdep_wait);
|
||||
|
||||
ff->spec = (const struct snd_ff_spec *)entry->driver_data;
|
||||
|
||||
/* Register this sound card later. */
|
||||
INIT_DEFERRABLE_WORK(&ff->dwork, do_registration);
|
||||
snd_fw_schedule_registration(unit, &ff->dwork);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void snd_ff_update(struct fw_unit *unit)
|
||||
{
|
||||
struct snd_ff *ff = dev_get_drvdata(&unit->device);
|
||||
|
||||
/* Postpone a workqueue for deferred registration. */
|
||||
if (!ff->registered)
|
||||
snd_fw_schedule_registration(unit, &ff->dwork);
|
||||
|
||||
snd_ff_transaction_reregister(ff);
|
||||
|
||||
if (ff->registered)
|
||||
snd_ff_stream_update_duplex(ff);
|
||||
}
|
||||
|
||||
static void snd_ff_remove(struct fw_unit *unit)
|
||||
{
|
||||
struct snd_ff *ff = dev_get_drvdata(&unit->device);
|
||||
|
||||
/*
|
||||
* Confirm to stop the work for registration before the sound card is
|
||||
* going to be released. The work is not scheduled again because bus
|
||||
* reset handler is not called anymore.
|
||||
*/
|
||||
cancel_work_sync(&ff->dwork.work);
|
||||
|
||||
if (ff->registered) {
|
||||
/* No need to wait for releasing card object in this context. */
|
||||
snd_card_free_when_closed(ff->card);
|
||||
} else {
|
||||
/* Don't forget this case. */
|
||||
ff_free(ff);
|
||||
}
|
||||
}
|
||||
|
||||
static struct snd_ff_spec spec_ff400 = {
|
||||
.name = "Fireface400",
|
||||
.pcm_capture_channels = {18, 14, 10},
|
||||
.pcm_playback_channels = {18, 14, 10},
|
||||
.midi_in_ports = 2,
|
||||
.midi_out_ports = 2,
|
||||
.protocol = &snd_ff_protocol_ff400,
|
||||
};
|
||||
|
||||
static const struct ieee1394_device_id snd_ff_id_table[] = {
|
||||
/* Fireface 400 */
|
||||
{
|
||||
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
||||
IEEE1394_MATCH_SPECIFIER_ID |
|
||||
IEEE1394_MATCH_VERSION |
|
||||
IEEE1394_MATCH_MODEL_ID,
|
||||
.vendor_id = OUI_RME,
|
||||
.specifier_id = 0x000a35,
|
||||
.version = 0x000002,
|
||||
.model_id = 0x101800,
|
||||
.driver_data = (kernel_ulong_t)&spec_ff400,
|
||||
},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(ieee1394, snd_ff_id_table);
|
||||
|
||||
static struct fw_driver ff_driver = {
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "snd-fireface",
|
||||
.bus = &fw_bus_type,
|
||||
},
|
||||
.probe = snd_ff_probe,
|
||||
.update = snd_ff_update,
|
||||
.remove = snd_ff_remove,
|
||||
.id_table = snd_ff_id_table,
|
||||
};
|
||||
|
||||
static int __init snd_ff_init(void)
|
||||
{
|
||||
return driver_register(&ff_driver.driver);
|
||||
}
|
||||
|
||||
static void __exit snd_ff_exit(void)
|
||||
{
|
||||
driver_unregister(&ff_driver.driver);
|
||||
}
|
||||
|
||||
module_init(snd_ff_init);
|
||||
module_exit(snd_ff_exit);
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* ff.h - a part of driver for RME Fireface series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#ifndef SOUND_FIREFACE_H_INCLUDED
|
||||
#define SOUND_FIREFACE_H_INCLUDED
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/firewire.h>
|
||||
#include <linux/firewire-constants.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/compat.h>
|
||||
#include <linux/sched/signal.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/info.h>
|
||||
#include <sound/rawmidi.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/hwdep.h>
|
||||
#include <sound/firewire.h>
|
||||
|
||||
#include "../lib.h"
|
||||
#include "../amdtp-stream.h"
|
||||
#include "../iso-resources.h"
|
||||
|
||||
#define SND_FF_STREAM_MODES 3
|
||||
|
||||
#define SND_FF_MAXIMIM_MIDI_QUADS 9
|
||||
#define SND_FF_IN_MIDI_PORTS 2
|
||||
#define SND_FF_OUT_MIDI_PORTS 2
|
||||
|
||||
struct snd_ff_protocol;
|
||||
struct snd_ff_spec {
|
||||
const char *const name;
|
||||
|
||||
const unsigned int pcm_capture_channels[SND_FF_STREAM_MODES];
|
||||
const unsigned int pcm_playback_channels[SND_FF_STREAM_MODES];
|
||||
|
||||
unsigned int midi_in_ports;
|
||||
unsigned int midi_out_ports;
|
||||
|
||||
struct snd_ff_protocol *protocol;
|
||||
};
|
||||
|
||||
struct snd_ff {
|
||||
struct snd_card *card;
|
||||
struct fw_unit *unit;
|
||||
struct mutex mutex;
|
||||
spinlock_t lock;
|
||||
|
||||
bool registered;
|
||||
struct delayed_work dwork;
|
||||
|
||||
const struct snd_ff_spec *spec;
|
||||
|
||||
/* To handle MIDI tx. */
|
||||
struct snd_rawmidi_substream *tx_midi_substreams[SND_FF_IN_MIDI_PORTS];
|
||||
struct fw_address_handler async_handler;
|
||||
|
||||
/* TO handle MIDI rx. */
|
||||
struct snd_rawmidi_substream *rx_midi_substreams[SND_FF_OUT_MIDI_PORTS];
|
||||
u8 running_status[SND_FF_OUT_MIDI_PORTS];
|
||||
__le32 msg_buf[SND_FF_OUT_MIDI_PORTS][SND_FF_MAXIMIM_MIDI_QUADS];
|
||||
struct work_struct rx_midi_work[SND_FF_OUT_MIDI_PORTS];
|
||||
struct fw_transaction transactions[SND_FF_OUT_MIDI_PORTS];
|
||||
ktime_t next_ktime[SND_FF_OUT_MIDI_PORTS];
|
||||
bool rx_midi_error[SND_FF_OUT_MIDI_PORTS];
|
||||
unsigned int rx_bytes[SND_FF_OUT_MIDI_PORTS];
|
||||
|
||||
unsigned int substreams_counter;
|
||||
struct amdtp_stream tx_stream;
|
||||
struct amdtp_stream rx_stream;
|
||||
struct fw_iso_resources tx_resources;
|
||||
struct fw_iso_resources rx_resources;
|
||||
|
||||
int dev_lock_count;
|
||||
bool dev_lock_changed;
|
||||
wait_queue_head_t hwdep_wait;
|
||||
};
|
||||
|
||||
enum snd_ff_clock_src {
|
||||
SND_FF_CLOCK_SRC_INTERNAL,
|
||||
SND_FF_CLOCK_SRC_SPDIF,
|
||||
SND_FF_CLOCK_SRC_ADAT,
|
||||
SND_FF_CLOCK_SRC_WORD,
|
||||
SND_FF_CLOCK_SRC_LTC,
|
||||
/* TODO: perhaps ADAT2 and TCO exists. */
|
||||
};
|
||||
|
||||
struct snd_ff_protocol {
|
||||
int (*get_clock)(struct snd_ff *ff, unsigned int *rate,
|
||||
enum snd_ff_clock_src *src);
|
||||
int (*begin_session)(struct snd_ff *ff, unsigned int rate);
|
||||
void (*finish_session)(struct snd_ff *ff);
|
||||
int (*switch_fetching_mode)(struct snd_ff *ff, bool enable);
|
||||
|
||||
void (*dump_sync_status)(struct snd_ff *ff,
|
||||
struct snd_info_buffer *buffer);
|
||||
void (*dump_clock_config)(struct snd_ff *ff,
|
||||
struct snd_info_buffer *buffer);
|
||||
|
||||
u64 midi_high_addr_reg;
|
||||
u64 midi_rx_port_0_reg;
|
||||
u64 midi_rx_port_1_reg;
|
||||
};
|
||||
|
||||
extern struct snd_ff_protocol snd_ff_protocol_ff400;
|
||||
|
||||
int snd_ff_transaction_register(struct snd_ff *ff);
|
||||
int snd_ff_transaction_reregister(struct snd_ff *ff);
|
||||
void snd_ff_transaction_unregister(struct snd_ff *ff);
|
||||
|
||||
int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate,
|
||||
unsigned int pcm_channels);
|
||||
int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s,
|
||||
struct snd_pcm_runtime *runtime);
|
||||
int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit,
|
||||
enum amdtp_stream_direction dir);
|
||||
|
||||
int snd_ff_stream_init_duplex(struct snd_ff *ff);
|
||||
void snd_ff_stream_destroy_duplex(struct snd_ff *ff);
|
||||
int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate);
|
||||
void snd_ff_stream_stop_duplex(struct snd_ff *ff);
|
||||
void snd_ff_stream_update_duplex(struct snd_ff *ff);
|
||||
|
||||
void snd_ff_stream_lock_changed(struct snd_ff *ff);
|
||||
int snd_ff_stream_lock_try(struct snd_ff *ff);
|
||||
void snd_ff_stream_lock_release(struct snd_ff *ff);
|
||||
|
||||
void snd_ff_proc_init(struct snd_ff *ff);
|
||||
|
||||
int snd_ff_create_midi_devices(struct snd_ff *ff);
|
||||
|
||||
int snd_ff_create_pcm_devices(struct snd_ff *ff);
|
||||
|
||||
int snd_ff_create_hwdep_devices(struct snd_ff *ff);
|
||||
|
||||
#endif
|
|
@ -99,147 +99,6 @@ void snd_fw_schedule_registration(struct fw_unit *unit,
|
|||
}
|
||||
EXPORT_SYMBOL(snd_fw_schedule_registration);
|
||||
|
||||
static void async_midi_port_callback(struct fw_card *card, int rcode,
|
||||
void *data, size_t length,
|
||||
void *callback_data)
|
||||
{
|
||||
struct snd_fw_async_midi_port *port = callback_data;
|
||||
struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream);
|
||||
|
||||
/* This port is closed. */
|
||||
if (substream == NULL)
|
||||
return;
|
||||
|
||||
if (rcode == RCODE_COMPLETE)
|
||||
snd_rawmidi_transmit_ack(substream, port->consume_bytes);
|
||||
else if (!rcode_is_permanent_error(rcode))
|
||||
/* To start next transaction immediately for recovery. */
|
||||
port->next_ktime = 0;
|
||||
else
|
||||
/* Don't continue processing. */
|
||||
port->error = true;
|
||||
|
||||
port->idling = true;
|
||||
|
||||
if (!snd_rawmidi_transmit_empty(substream))
|
||||
schedule_work(&port->work);
|
||||
}
|
||||
|
||||
static void midi_port_work(struct work_struct *work)
|
||||
{
|
||||
struct snd_fw_async_midi_port *port =
|
||||
container_of(work, struct snd_fw_async_midi_port, work);
|
||||
struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream);
|
||||
int generation;
|
||||
int type;
|
||||
|
||||
/* Under transacting or error state. */
|
||||
if (!port->idling || port->error)
|
||||
return;
|
||||
|
||||
/* Nothing to do. */
|
||||
if (substream == NULL || snd_rawmidi_transmit_empty(substream))
|
||||
return;
|
||||
|
||||
/* Do it in next chance. */
|
||||
if (ktime_after(port->next_ktime, ktime_get())) {
|
||||
schedule_work(&port->work);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fill the buffer. The callee must use snd_rawmidi_transmit_peek().
|
||||
* Later, snd_rawmidi_transmit_ack() is called.
|
||||
*/
|
||||
memset(port->buf, 0, port->len);
|
||||
port->consume_bytes = port->fill(substream, port->buf);
|
||||
if (port->consume_bytes <= 0) {
|
||||
/* Do it in next chance, immediately. */
|
||||
if (port->consume_bytes == 0) {
|
||||
port->next_ktime = 0;
|
||||
schedule_work(&port->work);
|
||||
} else {
|
||||
/* Fatal error. */
|
||||
port->error = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Calculate type of transaction. */
|
||||
if (port->len == 4)
|
||||
type = TCODE_WRITE_QUADLET_REQUEST;
|
||||
else
|
||||
type = TCODE_WRITE_BLOCK_REQUEST;
|
||||
|
||||
/* Set interval to next transaction. */
|
||||
port->next_ktime = ktime_add_ns(ktime_get(),
|
||||
port->consume_bytes * 8 * NSEC_PER_SEC / 31250);
|
||||
|
||||
/* Start this transaction. */
|
||||
port->idling = false;
|
||||
|
||||
/*
|
||||
* In Linux FireWire core, when generation is updated with memory
|
||||
* barrier, node id has already been updated. In this module, After
|
||||
* this smp_rmb(), load/store instructions to memory are completed.
|
||||
* Thus, both of generation and node id are available with recent
|
||||
* values. This is a light-serialization solution to handle bus reset
|
||||
* events on IEEE 1394 bus.
|
||||
*/
|
||||
generation = port->parent->generation;
|
||||
smp_rmb();
|
||||
|
||||
fw_send_request(port->parent->card, &port->transaction, type,
|
||||
port->parent->node_id, generation,
|
||||
port->parent->max_speed, port->addr,
|
||||
port->buf, port->len, async_midi_port_callback,
|
||||
port);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_fw_async_midi_port_init - initialize asynchronous MIDI port structure
|
||||
* @port: the asynchronous MIDI port to initialize
|
||||
* @unit: the target of the asynchronous transaction
|
||||
* @addr: the address to which transactions are transferred
|
||||
* @len: the length of transaction
|
||||
* @fill: the callback function to fill given buffer, and returns the
|
||||
* number of consumed bytes for MIDI message.
|
||||
*
|
||||
*/
|
||||
int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port,
|
||||
struct fw_unit *unit, u64 addr, unsigned int len,
|
||||
snd_fw_async_midi_port_fill fill)
|
||||
{
|
||||
port->len = DIV_ROUND_UP(len, 4) * 4;
|
||||
port->buf = kzalloc(port->len, GFP_KERNEL);
|
||||
if (port->buf == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
port->parent = fw_parent_device(unit);
|
||||
port->addr = addr;
|
||||
port->fill = fill;
|
||||
port->idling = true;
|
||||
port->next_ktime = 0;
|
||||
port->error = false;
|
||||
|
||||
INIT_WORK(&port->work, midi_port_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_fw_async_midi_port_init);
|
||||
|
||||
/**
|
||||
* snd_fw_async_midi_port_destroy - free asynchronous MIDI port structure
|
||||
* @port: the asynchronous MIDI port structure
|
||||
*/
|
||||
void snd_fw_async_midi_port_destroy(struct snd_fw_async_midi_port *port)
|
||||
{
|
||||
snd_fw_async_midi_port_finish(port);
|
||||
cancel_work_sync(&port->work);
|
||||
kfree(port->buf);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_fw_async_midi_port_destroy);
|
||||
|
||||
MODULE_DESCRIPTION("FireWire audio helper functions");
|
||||
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
|
|
@ -25,58 +25,4 @@ static inline bool rcode_is_permanent_error(int rcode)
|
|||
void snd_fw_schedule_registration(struct fw_unit *unit,
|
||||
struct delayed_work *dwork);
|
||||
|
||||
struct snd_fw_async_midi_port;
|
||||
typedef int (*snd_fw_async_midi_port_fill)(
|
||||
struct snd_rawmidi_substream *substream,
|
||||
u8 *buf);
|
||||
|
||||
struct snd_fw_async_midi_port {
|
||||
struct fw_device *parent;
|
||||
struct work_struct work;
|
||||
bool idling;
|
||||
ktime_t next_ktime;
|
||||
bool error;
|
||||
|
||||
u64 addr;
|
||||
struct fw_transaction transaction;
|
||||
|
||||
u8 *buf;
|
||||
unsigned int len;
|
||||
|
||||
struct snd_rawmidi_substream *substream;
|
||||
snd_fw_async_midi_port_fill fill;
|
||||
int consume_bytes;
|
||||
};
|
||||
|
||||
int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port,
|
||||
struct fw_unit *unit, u64 addr, unsigned int len,
|
||||
snd_fw_async_midi_port_fill fill);
|
||||
void snd_fw_async_midi_port_destroy(struct snd_fw_async_midi_port *port);
|
||||
|
||||
/**
|
||||
* snd_fw_async_midi_port_run - run transactions for the async MIDI port
|
||||
* @port: the asynchronous MIDI port
|
||||
* @substream: the MIDI substream
|
||||
*/
|
||||
static inline void
|
||||
snd_fw_async_midi_port_run(struct snd_fw_async_midi_port *port,
|
||||
struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
if (!port->error) {
|
||||
port->substream = substream;
|
||||
schedule_work(&port->work);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_fw_async_midi_port_finish - finish the asynchronous MIDI port
|
||||
* @port: the asynchronous MIDI port
|
||||
*/
|
||||
static inline void
|
||||
snd_fw_async_midi_port_finish(struct snd_fw_async_midi_port *port)
|
||||
{
|
||||
port->substream = NULL;
|
||||
port->error = false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
CFLAGS_amdtp-motu.o := -I$(src)
|
||||
|
||||
snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \
|
||||
motu-proc.o motu-pcm.o motu-midi.o motu-hwdep.o \
|
||||
motu-protocol-v2.o motu-protocol-v3.o
|
||||
obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* amdtp-motu-trace.h - tracepoint definitions to dump a part of packet data
|
||||
*
|
||||
* Copyright (c) 2017 Takashi Sakamoto
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#undef TRACE_SYSTEM
|
||||
#define TRACE_SYSTEM snd_firewire_motu
|
||||
|
||||
#if !defined(_SND_FIREWIRE_MOTU_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
|
||||
#define _SND_FIREWIRE_MOTU_TRACE_H
|
||||
|
||||
#include <linux/tracepoint.h>
|
||||
|
||||
static void copy_sph(u32 *frame, __be32 *buffer, unsigned int data_blocks,
|
||||
unsigned int data_block_quadlets);
|
||||
static void copy_message(u64 *frames, __be32 *buffer, unsigned int data_blocks,
|
||||
unsigned int data_block_quadlets);
|
||||
|
||||
TRACE_EVENT(in_data_block_sph,
|
||||
TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer),
|
||||
TP_ARGS(s, data_blocks, buffer),
|
||||
TP_STRUCT__entry(
|
||||
__field(int, src)
|
||||
__field(int, dst)
|
||||
__field(unsigned int, data_blocks)
|
||||
__dynamic_array(u32, tstamps, data_blocks)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->src = fw_parent_device(s->unit)->node_id;
|
||||
__entry->dst = fw_parent_device(s->unit)->card->node_id;
|
||||
__entry->data_blocks = data_blocks;
|
||||
copy_sph(__get_dynamic_array(tstamps), buffer, data_blocks, s->data_block_quadlets);
|
||||
),
|
||||
TP_printk(
|
||||
"%04x %04x %u %s",
|
||||
__entry->src,
|
||||
__entry->dst,
|
||||
__entry->data_blocks,
|
||||
__print_array(__get_dynamic_array(tstamps), __entry->data_blocks, 4)
|
||||
)
|
||||
);
|
||||
|
||||
TRACE_EVENT(out_data_block_sph,
|
||||
TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer),
|
||||
TP_ARGS(s, data_blocks, buffer),
|
||||
TP_STRUCT__entry(
|
||||
__field(int, src)
|
||||
__field(int, dst)
|
||||
__field(unsigned int, data_blocks)
|
||||
__dynamic_array(u32, tstamps, data_blocks)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->src = fw_parent_device(s->unit)->card->node_id;
|
||||
__entry->dst = fw_parent_device(s->unit)->node_id;
|
||||
__entry->data_blocks = data_blocks;
|
||||
copy_sph(__get_dynamic_array(tstamps), buffer, data_blocks, s->data_block_quadlets);
|
||||
),
|
||||
TP_printk(
|
||||
"%04x %04x %u %s",
|
||||
__entry->src,
|
||||
__entry->dst,
|
||||
__entry->data_blocks,
|
||||
__print_array(__get_dynamic_array(tstamps), __entry->data_blocks, 4)
|
||||
)
|
||||
);
|
||||
|
||||
TRACE_EVENT(in_data_block_message,
|
||||
TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer),
|
||||
TP_ARGS(s, data_blocks, buffer),
|
||||
TP_STRUCT__entry(
|
||||
__field(int, src)
|
||||
__field(int, dst)
|
||||
__field(unsigned int, data_blocks)
|
||||
__dynamic_array(u64, messages, data_blocks)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->src = fw_parent_device(s->unit)->node_id;
|
||||
__entry->dst = fw_parent_device(s->unit)->card->node_id;
|
||||
__entry->data_blocks = data_blocks;
|
||||
copy_message(__get_dynamic_array(messages), buffer, data_blocks, s->data_block_quadlets);
|
||||
),
|
||||
TP_printk(
|
||||
"%04x %04x %u %s",
|
||||
__entry->src,
|
||||
__entry->dst,
|
||||
__entry->data_blocks,
|
||||
__print_array(__get_dynamic_array(messages), __entry->data_blocks, 8)
|
||||
)
|
||||
);
|
||||
|
||||
TRACE_EVENT(out_data_block_message,
|
||||
TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer),
|
||||
TP_ARGS(s, data_blocks, buffer),
|
||||
TP_STRUCT__entry(
|
||||
__field(int, src)
|
||||
__field(int, dst)
|
||||
__field(unsigned int, data_blocks)
|
||||
__dynamic_array(u64, messages, data_blocks)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->src = fw_parent_device(s->unit)->card->node_id;
|
||||
__entry->dst = fw_parent_device(s->unit)->node_id;
|
||||
__entry->data_blocks = data_blocks;
|
||||
copy_message(__get_dynamic_array(messages), buffer, data_blocks, s->data_block_quadlets);
|
||||
),
|
||||
TP_printk(
|
||||
"%04x %04x %u %s",
|
||||
__entry->src,
|
||||
__entry->dst,
|
||||
__entry->data_blocks,
|
||||
__print_array(__get_dynamic_array(messages), __entry->data_blocks, 8)
|
||||
)
|
||||
);
|
||||
|
||||
#endif
|
||||
|
||||
#undef TRACE_INCLUDE_PATH
|
||||
#define TRACE_INCLUDE_PATH .
|
||||
#undef TRACE_INCLUDE_FILE
|
||||
#define TRACE_INCLUDE_FILE amdtp-motu-trace
|
||||
#include <trace/define_trace.h>
|
|
@ -0,0 +1,427 @@
|
|||
/*
|
||||
* amdtp-motu.c - a part of driver for MOTU FireWire series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <sound/pcm.h>
|
||||
#include "motu.h"
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include "amdtp-motu-trace.h"
|
||||
|
||||
#define CIP_FMT_MOTU 0x02
|
||||
#define CIP_FMT_MOTU_TX_V3 0x22
|
||||
#define MOTU_FDF_AM824 0x22
|
||||
|
||||
/*
|
||||
* Nominally 3125 bytes/second, but the MIDI port's clock might be
|
||||
* 1% too slow, and the bus clock 100 ppm too fast.
|
||||
*/
|
||||
#define MIDI_BYTES_PER_SECOND 3093
|
||||
|
||||
struct amdtp_motu {
|
||||
/* For timestamp processing. */
|
||||
unsigned int quotient_ticks_per_event;
|
||||
unsigned int remainder_ticks_per_event;
|
||||
unsigned int next_ticks;
|
||||
unsigned int next_accumulated;
|
||||
unsigned int next_cycles;
|
||||
unsigned int next_seconds;
|
||||
|
||||
unsigned int pcm_chunks;
|
||||
unsigned int pcm_byte_offset;
|
||||
|
||||
struct snd_rawmidi_substream *midi;
|
||||
unsigned int midi_ports;
|
||||
unsigned int midi_flag_offset;
|
||||
unsigned int midi_byte_offset;
|
||||
|
||||
int midi_db_count;
|
||||
unsigned int midi_db_interval;
|
||||
};
|
||||
|
||||
int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate,
|
||||
unsigned int midi_ports,
|
||||
struct snd_motu_packet_format *formats)
|
||||
{
|
||||
static const struct {
|
||||
unsigned int quotient_ticks_per_event;
|
||||
unsigned int remainder_ticks_per_event;
|
||||
} params[] = {
|
||||
[CIP_SFC_44100] = { 557, 123 },
|
||||
[CIP_SFC_48000] = { 512, 0 },
|
||||
[CIP_SFC_88200] = { 278, 282 },
|
||||
[CIP_SFC_96000] = { 256, 0 },
|
||||
[CIP_SFC_176400] = { 139, 141 },
|
||||
[CIP_SFC_192000] = { 128, 0 },
|
||||
};
|
||||
struct amdtp_motu *p = s->protocol;
|
||||
unsigned int pcm_chunks, data_chunks, data_block_quadlets;
|
||||
unsigned int delay;
|
||||
unsigned int mode;
|
||||
int i, err;
|
||||
|
||||
if (amdtp_stream_running(s))
|
||||
return -EBUSY;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) {
|
||||
if (snd_motu_clock_rates[i] == rate) {
|
||||
mode = i >> 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == ARRAY_SIZE(snd_motu_clock_rates))
|
||||
return -EINVAL;
|
||||
|
||||
pcm_chunks = formats->fixed_part_pcm_chunks[mode] +
|
||||
formats->differed_part_pcm_chunks[mode];
|
||||
data_chunks = formats->msg_chunks + pcm_chunks;
|
||||
|
||||
/*
|
||||
* Each data block includes SPH in its head. Data chunks follow with
|
||||
* 3 byte alignment. Padding follows with zero to conform to quadlet
|
||||
* alignment.
|
||||
*/
|
||||
data_block_quadlets = 1 + DIV_ROUND_UP(data_chunks * 3, 4);
|
||||
|
||||
err = amdtp_stream_set_parameters(s, rate, data_block_quadlets);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
p->pcm_chunks = pcm_chunks;
|
||||
p->pcm_byte_offset = formats->pcm_byte_offset;
|
||||
|
||||
p->midi_ports = midi_ports;
|
||||
p->midi_flag_offset = formats->midi_flag_offset;
|
||||
p->midi_byte_offset = formats->midi_byte_offset;
|
||||
|
||||
p->midi_db_count = 0;
|
||||
p->midi_db_interval = rate / MIDI_BYTES_PER_SECOND;
|
||||
|
||||
/* IEEE 1394 bus requires. */
|
||||
delay = 0x2e00;
|
||||
|
||||
/* For no-data or empty packets to adjust PCM sampling frequency. */
|
||||
delay += 8000 * 3072 * s->syt_interval / rate;
|
||||
|
||||
p->next_seconds = 0;
|
||||
p->next_cycles = delay / 3072;
|
||||
p->quotient_ticks_per_event = params[s->sfc].quotient_ticks_per_event;
|
||||
p->remainder_ticks_per_event = params[s->sfc].remainder_ticks_per_event;
|
||||
p->next_ticks = delay % 3072;
|
||||
p->next_accumulated = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void read_pcm_s32(struct amdtp_stream *s,
|
||||
struct snd_pcm_runtime *runtime,
|
||||
__be32 *buffer, unsigned int data_blocks)
|
||||
{
|
||||
struct amdtp_motu *p = s->protocol;
|
||||
unsigned int channels, remaining_frames, i, c;
|
||||
u8 *byte;
|
||||
u32 *dst;
|
||||
|
||||
channels = p->pcm_chunks;
|
||||
dst = (void *)runtime->dma_area +
|
||||
frames_to_bytes(runtime, s->pcm_buffer_pointer);
|
||||
remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
|
||||
|
||||
for (i = 0; i < data_blocks; ++i) {
|
||||
byte = (u8 *)buffer + p->pcm_byte_offset;
|
||||
|
||||
for (c = 0; c < channels; ++c) {
|
||||
*dst = (byte[0] << 24) | (byte[1] << 16) | byte[2];
|
||||
byte += 3;
|
||||
dst++;
|
||||
}
|
||||
buffer += s->data_block_quadlets;
|
||||
if (--remaining_frames == 0)
|
||||
dst = (void *)runtime->dma_area;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_pcm_s32(struct amdtp_stream *s,
|
||||
struct snd_pcm_runtime *runtime,
|
||||
__be32 *buffer, unsigned int data_blocks)
|
||||
{
|
||||
struct amdtp_motu *p = s->protocol;
|
||||
unsigned int channels, remaining_frames, i, c;
|
||||
u8 *byte;
|
||||
const u32 *src;
|
||||
|
||||
channels = p->pcm_chunks;
|
||||
src = (void *)runtime->dma_area +
|
||||
frames_to_bytes(runtime, s->pcm_buffer_pointer);
|
||||
remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
|
||||
|
||||
for (i = 0; i < data_blocks; ++i) {
|
||||
byte = (u8 *)buffer + p->pcm_byte_offset;
|
||||
|
||||
for (c = 0; c < channels; ++c) {
|
||||
byte[0] = (*src >> 24) & 0xff;
|
||||
byte[1] = (*src >> 16) & 0xff;
|
||||
byte[2] = (*src >> 8) & 0xff;
|
||||
byte += 3;
|
||||
src++;
|
||||
}
|
||||
|
||||
buffer += s->data_block_quadlets;
|
||||
if (--remaining_frames == 0)
|
||||
src = (void *)runtime->dma_area;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_pcm_silence(struct amdtp_stream *s, __be32 *buffer,
|
||||
unsigned int data_blocks)
|
||||
{
|
||||
struct amdtp_motu *p = s->protocol;
|
||||
unsigned int channels, i, c;
|
||||
u8 *byte;
|
||||
|
||||
channels = p->pcm_chunks;
|
||||
|
||||
for (i = 0; i < data_blocks; ++i) {
|
||||
byte = (u8 *)buffer + p->pcm_byte_offset;
|
||||
|
||||
for (c = 0; c < channels; ++c) {
|
||||
byte[0] = 0;
|
||||
byte[1] = 0;
|
||||
byte[2] = 0;
|
||||
byte += 3;
|
||||
}
|
||||
|
||||
buffer += s->data_block_quadlets;
|
||||
}
|
||||
}
|
||||
|
||||
int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s,
|
||||
struct snd_pcm_runtime *runtime)
|
||||
{
|
||||
int err;
|
||||
|
||||
/* TODO: how to set an constraint for exactly 24bit PCM sample? */
|
||||
err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return amdtp_stream_add_pcm_hw_constraints(s, runtime);
|
||||
}
|
||||
|
||||
void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port,
|
||||
struct snd_rawmidi_substream *midi)
|
||||
{
|
||||
struct amdtp_motu *p = s->protocol;
|
||||
|
||||
if (port < p->midi_ports)
|
||||
WRITE_ONCE(p->midi, midi);
|
||||
}
|
||||
|
||||
static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer,
|
||||
unsigned int data_blocks)
|
||||
{
|
||||
struct amdtp_motu *p = s->protocol;
|
||||
struct snd_rawmidi_substream *midi = READ_ONCE(p->midi);
|
||||
u8 *b;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < data_blocks; i++) {
|
||||
b = (u8 *)buffer;
|
||||
|
||||
if (midi && p->midi_db_count == 0 &&
|
||||
snd_rawmidi_transmit(midi, b + p->midi_byte_offset, 1) == 1) {
|
||||
b[p->midi_flag_offset] = 0x01;
|
||||
} else {
|
||||
b[p->midi_byte_offset] = 0x00;
|
||||
b[p->midi_flag_offset] = 0x00;
|
||||
}
|
||||
|
||||
buffer += s->data_block_quadlets;
|
||||
|
||||
if (--p->midi_db_count < 0)
|
||||
p->midi_db_count = p->midi_db_interval;
|
||||
}
|
||||
}
|
||||
|
||||
static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer,
|
||||
unsigned int data_blocks)
|
||||
{
|
||||
struct amdtp_motu *p = s->protocol;
|
||||
struct snd_rawmidi_substream *midi;
|
||||
u8 *b;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < data_blocks; i++) {
|
||||
b = (u8 *)buffer;
|
||||
midi = READ_ONCE(p->midi);
|
||||
|
||||
if (midi && (b[p->midi_flag_offset] & 0x01))
|
||||
snd_rawmidi_receive(midi, b + p->midi_byte_offset, 1);
|
||||
|
||||
buffer += s->data_block_quadlets;
|
||||
}
|
||||
}
|
||||
|
||||
/* For tracepoints. */
|
||||
static void __maybe_unused copy_sph(u32 *frames, __be32 *buffer,
|
||||
unsigned int data_blocks,
|
||||
unsigned int data_block_quadlets)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < data_blocks; ++i) {
|
||||
*frames = be32_to_cpu(*buffer);
|
||||
buffer += data_block_quadlets;
|
||||
frames++;
|
||||
}
|
||||
}
|
||||
|
||||
/* For tracepoints. */
|
||||
static void __maybe_unused copy_message(u64 *frames, __be32 *buffer,
|
||||
unsigned int data_blocks,
|
||||
unsigned int data_block_quadlets)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
/* This is just for v2/v3 protocol. */
|
||||
for (i = 0; i < data_blocks; ++i) {
|
||||
*frames = (be32_to_cpu(buffer[1]) << 16) |
|
||||
(be32_to_cpu(buffer[2]) >> 16);
|
||||
buffer += data_block_quadlets;
|
||||
frames++;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int process_tx_data_blocks(struct amdtp_stream *s,
|
||||
__be32 *buffer, unsigned int data_blocks,
|
||||
unsigned int *syt)
|
||||
{
|
||||
struct amdtp_motu *p = s->protocol;
|
||||
struct snd_pcm_substream *pcm;
|
||||
|
||||
trace_in_data_block_sph(s, data_blocks, buffer);
|
||||
trace_in_data_block_message(s, data_blocks, buffer);
|
||||
|
||||
if (p->midi_ports)
|
||||
read_midi_messages(s, buffer, data_blocks);
|
||||
|
||||
pcm = ACCESS_ONCE(s->pcm);
|
||||
if (data_blocks > 0 && pcm)
|
||||
read_pcm_s32(s, pcm->runtime, buffer, data_blocks);
|
||||
|
||||
return data_blocks;
|
||||
}
|
||||
|
||||
static inline void compute_next_elapse_from_start(struct amdtp_motu *p)
|
||||
{
|
||||
p->next_accumulated += p->remainder_ticks_per_event;
|
||||
if (p->next_accumulated >= 441) {
|
||||
p->next_accumulated -= 441;
|
||||
p->next_ticks++;
|
||||
}
|
||||
|
||||
p->next_ticks += p->quotient_ticks_per_event;
|
||||
if (p->next_ticks >= 3072) {
|
||||
p->next_ticks -= 3072;
|
||||
p->next_cycles++;
|
||||
}
|
||||
|
||||
if (p->next_cycles >= 8000) {
|
||||
p->next_cycles -= 8000;
|
||||
p->next_seconds++;
|
||||
}
|
||||
|
||||
if (p->next_seconds >= 128)
|
||||
p->next_seconds -= 128;
|
||||
}
|
||||
|
||||
static void write_sph(struct amdtp_stream *s, __be32 *buffer,
|
||||
unsigned int data_blocks)
|
||||
{
|
||||
struct amdtp_motu *p = s->protocol;
|
||||
unsigned int next_cycles;
|
||||
unsigned int i;
|
||||
u32 sph;
|
||||
|
||||
for (i = 0; i < data_blocks; i++) {
|
||||
next_cycles = (s->start_cycle + p->next_cycles) % 8000;
|
||||
sph = ((next_cycles << 12) | p->next_ticks) & 0x01ffffff;
|
||||
*buffer = cpu_to_be32(sph);
|
||||
|
||||
compute_next_elapse_from_start(p);
|
||||
|
||||
buffer += s->data_block_quadlets;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int process_rx_data_blocks(struct amdtp_stream *s,
|
||||
__be32 *buffer, unsigned int data_blocks,
|
||||
unsigned int *syt)
|
||||
{
|
||||
struct amdtp_motu *p = (struct amdtp_motu *)s->protocol;
|
||||
struct snd_pcm_substream *pcm;
|
||||
|
||||
/* Not used. */
|
||||
*syt = 0xffff;
|
||||
|
||||
/* TODO: how to interact control messages between userspace? */
|
||||
|
||||
if (p->midi_ports)
|
||||
write_midi_messages(s, buffer, data_blocks);
|
||||
|
||||
pcm = ACCESS_ONCE(s->pcm);
|
||||
if (pcm)
|
||||
write_pcm_s32(s, pcm->runtime, buffer, data_blocks);
|
||||
else
|
||||
write_pcm_silence(s, buffer, data_blocks);
|
||||
|
||||
write_sph(s, buffer, data_blocks);
|
||||
|
||||
trace_out_data_block_sph(s, data_blocks, buffer);
|
||||
trace_out_data_block_message(s, data_blocks, buffer);
|
||||
|
||||
return data_blocks;
|
||||
}
|
||||
|
||||
int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit,
|
||||
enum amdtp_stream_direction dir,
|
||||
const struct snd_motu_protocol *const protocol)
|
||||
{
|
||||
amdtp_stream_process_data_blocks_t process_data_blocks;
|
||||
int fmt = CIP_FMT_MOTU;
|
||||
int flags = CIP_BLOCKING;
|
||||
int err;
|
||||
|
||||
if (dir == AMDTP_IN_STREAM) {
|
||||
process_data_blocks = process_tx_data_blocks;
|
||||
|
||||
/*
|
||||
* Units of version 3 transmits packets with invalid CIP header
|
||||
* against IEC 61883-1.
|
||||
*/
|
||||
if (protocol == &snd_motu_protocol_v3) {
|
||||
flags |= CIP_WRONG_DBS |
|
||||
CIP_SKIP_DBC_ZERO_CHECK |
|
||||
CIP_HEADER_WITHOUT_EOH;
|
||||
fmt = CIP_FMT_MOTU_TX_V3;
|
||||
}
|
||||
} else {
|
||||
process_data_blocks = process_rx_data_blocks;
|
||||
flags |= CIP_DBC_IS_END_EVENT;
|
||||
}
|
||||
|
||||
err = amdtp_stream_init(s, unit, dir, flags, fmt, process_data_blocks,
|
||||
sizeof(struct amdtp_motu));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
s->sph = 1;
|
||||
s->fdf = MOTU_FDF_AM824;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* motu-hwdep.c - a part of driver for MOTU FireWire series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This codes have five functionalities.
|
||||
*
|
||||
* 1.get information about firewire node
|
||||
* 2.get notification about starting/stopping stream
|
||||
* 3.lock/unlock streaming
|
||||
*
|
||||
*/
|
||||
|
||||
#include "motu.h"
|
||||
|
||||
static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
|
||||
loff_t *offset)
|
||||
{
|
||||
struct snd_motu *motu = hwdep->private_data;
|
||||
DEFINE_WAIT(wait);
|
||||
union snd_firewire_event event;
|
||||
|
||||
spin_lock_irq(&motu->lock);
|
||||
|
||||
while (!motu->dev_lock_changed && motu->msg == 0) {
|
||||
prepare_to_wait(&motu->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
|
||||
spin_unlock_irq(&motu->lock);
|
||||
schedule();
|
||||
finish_wait(&motu->hwdep_wait, &wait);
|
||||
if (signal_pending(current))
|
||||
return -ERESTARTSYS;
|
||||
spin_lock_irq(&motu->lock);
|
||||
}
|
||||
|
||||
memset(&event, 0, sizeof(event));
|
||||
if (motu->dev_lock_changed) {
|
||||
event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
|
||||
event.lock_status.status = (motu->dev_lock_count > 0);
|
||||
motu->dev_lock_changed = false;
|
||||
|
||||
count = min_t(long, count, sizeof(event.lock_status));
|
||||
} else {
|
||||
event.motu_notification.type = SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION;
|
||||
event.motu_notification.message = motu->msg;
|
||||
motu->msg = 0;
|
||||
|
||||
count = min_t(long, count, sizeof(event.motu_notification));
|
||||
}
|
||||
|
||||
spin_unlock_irq(&motu->lock);
|
||||
|
||||
if (copy_to_user(buf, &event, count))
|
||||
return -EFAULT;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
|
||||
poll_table *wait)
|
||||
{
|
||||
struct snd_motu *motu = hwdep->private_data;
|
||||
unsigned int events;
|
||||
|
||||
poll_wait(file, &motu->hwdep_wait, wait);
|
||||
|
||||
spin_lock_irq(&motu->lock);
|
||||
if (motu->dev_lock_changed || motu->msg)
|
||||
events = POLLIN | POLLRDNORM;
|
||||
else
|
||||
events = 0;
|
||||
spin_unlock_irq(&motu->lock);
|
||||
|
||||
return events | POLLOUT;
|
||||
}
|
||||
|
||||
static int hwdep_get_info(struct snd_motu *motu, void __user *arg)
|
||||
{
|
||||
struct fw_device *dev = fw_parent_device(motu->unit);
|
||||
struct snd_firewire_get_info info;
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.type = SNDRV_FIREWIRE_TYPE_MOTU;
|
||||
info.card = dev->card->index;
|
||||
*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
|
||||
*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
|
||||
strlcpy(info.device_name, dev_name(&dev->device),
|
||||
sizeof(info.device_name));
|
||||
|
||||
if (copy_to_user(arg, &info, sizeof(info)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hwdep_lock(struct snd_motu *motu)
|
||||
{
|
||||
int err;
|
||||
|
||||
spin_lock_irq(&motu->lock);
|
||||
|
||||
if (motu->dev_lock_count == 0) {
|
||||
motu->dev_lock_count = -1;
|
||||
err = 0;
|
||||
} else {
|
||||
err = -EBUSY;
|
||||
}
|
||||
|
||||
spin_unlock_irq(&motu->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int hwdep_unlock(struct snd_motu *motu)
|
||||
{
|
||||
int err;
|
||||
|
||||
spin_lock_irq(&motu->lock);
|
||||
|
||||
if (motu->dev_lock_count == -1) {
|
||||
motu->dev_lock_count = 0;
|
||||
err = 0;
|
||||
} else {
|
||||
err = -EBADFD;
|
||||
}
|
||||
|
||||
spin_unlock_irq(&motu->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
|
||||
{
|
||||
struct snd_motu *motu = hwdep->private_data;
|
||||
|
||||
spin_lock_irq(&motu->lock);
|
||||
if (motu->dev_lock_count == -1)
|
||||
motu->dev_lock_count = 0;
|
||||
spin_unlock_irq(&motu->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct snd_motu *motu = hwdep->private_data;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_FIREWIRE_IOCTL_GET_INFO:
|
||||
return hwdep_get_info(motu, (void __user *)arg);
|
||||
case SNDRV_FIREWIRE_IOCTL_LOCK:
|
||||
return hwdep_lock(motu);
|
||||
case SNDRV_FIREWIRE_IOCTL_UNLOCK:
|
||||
return hwdep_unlock(motu);
|
||||
default:
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
return hwdep_ioctl(hwdep, file, cmd,
|
||||
(unsigned long)compat_ptr(arg));
|
||||
}
|
||||
#else
|
||||
#define hwdep_compat_ioctl NULL
|
||||
#endif
|
||||
|
||||
int snd_motu_create_hwdep_device(struct snd_motu *motu)
|
||||
{
|
||||
static const struct snd_hwdep_ops ops = {
|
||||
.read = hwdep_read,
|
||||
.release = hwdep_release,
|
||||
.poll = hwdep_poll,
|
||||
.ioctl = hwdep_ioctl,
|
||||
.ioctl_compat = hwdep_compat_ioctl,
|
||||
};
|
||||
struct snd_hwdep *hwdep;
|
||||
int err;
|
||||
|
||||
err = snd_hwdep_new(motu->card, motu->card->driver, 0, &hwdep);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
strcpy(hwdep->name, "MOTU");
|
||||
hwdep->iface = SNDRV_HWDEP_IFACE_FW_MOTU;
|
||||
hwdep->ops = ops;
|
||||
hwdep->private_data = motu;
|
||||
hwdep->exclusive = true;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* motu-midi.h - a part of driver for MOTU FireWire series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
#include "motu.h"
|
||||
|
||||
static int midi_capture_open(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_motu *motu = substream->rmidi->private_data;
|
||||
int err;
|
||||
|
||||
err = snd_motu_stream_lock_try(motu);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
mutex_lock(&motu->mutex);
|
||||
|
||||
motu->capture_substreams++;
|
||||
err = snd_motu_stream_start_duplex(motu, 0);
|
||||
|
||||
mutex_unlock(&motu->mutex);
|
||||
|
||||
if (err < 0)
|
||||
snd_motu_stream_lock_release(motu);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int midi_playback_open(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_motu *motu = substream->rmidi->private_data;
|
||||
int err;
|
||||
|
||||
err = snd_motu_stream_lock_try(motu);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
mutex_lock(&motu->mutex);
|
||||
|
||||
motu->playback_substreams++;
|
||||
err = snd_motu_stream_start_duplex(motu, 0);
|
||||
|
||||
mutex_unlock(&motu->mutex);
|
||||
|
||||
if (err < 0)
|
||||
snd_motu_stream_lock_release(motu);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int midi_capture_close(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_motu *motu = substream->rmidi->private_data;
|
||||
|
||||
mutex_lock(&motu->mutex);
|
||||
|
||||
motu->capture_substreams--;
|
||||
snd_motu_stream_stop_duplex(motu);
|
||||
|
||||
mutex_unlock(&motu->mutex);
|
||||
|
||||
snd_motu_stream_lock_release(motu);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int midi_playback_close(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_motu *motu = substream->rmidi->private_data;
|
||||
|
||||
mutex_lock(&motu->mutex);
|
||||
|
||||
motu->playback_substreams--;
|
||||
snd_motu_stream_stop_duplex(motu);
|
||||
|
||||
mutex_unlock(&motu->mutex);
|
||||
|
||||
snd_motu_stream_lock_release(motu);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
|
||||
{
|
||||
struct snd_motu *motu = substrm->rmidi->private_data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&motu->lock, flags);
|
||||
|
||||
if (up)
|
||||
amdtp_motu_midi_trigger(&motu->tx_stream, substrm->number,
|
||||
substrm);
|
||||
else
|
||||
amdtp_motu_midi_trigger(&motu->tx_stream, substrm->number,
|
||||
NULL);
|
||||
|
||||
spin_unlock_irqrestore(&motu->lock, flags);
|
||||
}
|
||||
|
||||
static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
|
||||
{
|
||||
struct snd_motu *motu = substrm->rmidi->private_data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&motu->lock, flags);
|
||||
|
||||
if (up)
|
||||
amdtp_motu_midi_trigger(&motu->rx_stream, substrm->number,
|
||||
substrm);
|
||||
else
|
||||
amdtp_motu_midi_trigger(&motu->rx_stream, substrm->number,
|
||||
NULL);
|
||||
|
||||
spin_unlock_irqrestore(&motu->lock, flags);
|
||||
}
|
||||
|
||||
static void set_midi_substream_names(struct snd_motu *motu,
|
||||
struct snd_rawmidi_str *str)
|
||||
{
|
||||
struct snd_rawmidi_substream *subs;
|
||||
|
||||
list_for_each_entry(subs, &str->substreams, list) {
|
||||
snprintf(subs->name, sizeof(subs->name),
|
||||
"%s MIDI %d", motu->card->shortname, subs->number + 1);
|
||||
}
|
||||
}
|
||||
|
||||
int snd_motu_create_midi_devices(struct snd_motu *motu)
|
||||
{
|
||||
static struct snd_rawmidi_ops capture_ops = {
|
||||
.open = midi_capture_open,
|
||||
.close = midi_capture_close,
|
||||
.trigger = midi_capture_trigger,
|
||||
};
|
||||
static struct snd_rawmidi_ops playback_ops = {
|
||||
.open = midi_playback_open,
|
||||
.close = midi_playback_close,
|
||||
.trigger = midi_playback_trigger,
|
||||
};
|
||||
struct snd_rawmidi *rmidi;
|
||||
struct snd_rawmidi_str *str;
|
||||
int err;
|
||||
|
||||
/* create midi ports */
|
||||
err = snd_rawmidi_new(motu->card, motu->card->driver, 0, 1, 1, &rmidi);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
snprintf(rmidi->name, sizeof(rmidi->name),
|
||||
"%s MIDI", motu->card->shortname);
|
||||
rmidi->private_data = motu;
|
||||
|
||||
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT |
|
||||
SNDRV_RAWMIDI_INFO_OUTPUT |
|
||||
SNDRV_RAWMIDI_INFO_DUPLEX;
|
||||
|
||||
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
|
||||
&capture_ops);
|
||||
str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT];
|
||||
set_midi_substream_names(motu, str);
|
||||
|
||||
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
|
||||
&playback_ops);
|
||||
str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
|
||||
set_midi_substream_names(motu, str);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,398 @@
|
|||
/*
|
||||
* motu-pcm.c - a part of driver for MOTU FireWire series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include <sound/pcm_params.h>
|
||||
#include "motu.h"
|
||||
|
||||
static int motu_rate_constraint(struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_rule *rule)
|
||||
{
|
||||
struct snd_motu_packet_format *formats = rule->private;
|
||||
|
||||
const struct snd_interval *c =
|
||||
hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
|
||||
struct snd_interval *r =
|
||||
hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
|
||||
struct snd_interval rates = {
|
||||
.min = UINT_MAX, .max = 0, .integer = 1
|
||||
};
|
||||
unsigned int i, pcm_channels, rate, mode;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) {
|
||||
rate = snd_motu_clock_rates[i];
|
||||
mode = i / 2;
|
||||
|
||||
pcm_channels = formats->fixed_part_pcm_chunks[mode] +
|
||||
formats->differed_part_pcm_chunks[mode];
|
||||
if (!snd_interval_test(c, pcm_channels))
|
||||
continue;
|
||||
|
||||
rates.min = min(rates.min, rate);
|
||||
rates.max = max(rates.max, rate);
|
||||
}
|
||||
|
||||
return snd_interval_refine(r, &rates);
|
||||
}
|
||||
|
||||
static int motu_channels_constraint(struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_rule *rule)
|
||||
{
|
||||
struct snd_motu_packet_format *formats = rule->private;
|
||||
|
||||
const struct snd_interval *r =
|
||||
hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE);
|
||||
struct snd_interval *c =
|
||||
hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
|
||||
struct snd_interval channels = {
|
||||
.min = UINT_MAX, .max = 0, .integer = 1
|
||||
};
|
||||
unsigned int i, pcm_channels, rate, mode;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) {
|
||||
rate = snd_motu_clock_rates[i];
|
||||
mode = i / 2;
|
||||
|
||||
if (!snd_interval_test(r, rate))
|
||||
continue;
|
||||
|
||||
pcm_channels = formats->fixed_part_pcm_chunks[mode] +
|
||||
formats->differed_part_pcm_chunks[mode];
|
||||
channels.min = min(channels.min, pcm_channels);
|
||||
channels.max = max(channels.max, pcm_channels);
|
||||
}
|
||||
|
||||
return snd_interval_refine(c, &channels);
|
||||
}
|
||||
|
||||
static void limit_channels_and_rates(struct snd_motu *motu,
|
||||
struct snd_pcm_runtime *runtime,
|
||||
struct snd_motu_packet_format *formats)
|
||||
{
|
||||
struct snd_pcm_hardware *hw = &runtime->hw;
|
||||
unsigned int i, pcm_channels, rate, mode;
|
||||
|
||||
hw->channels_min = UINT_MAX;
|
||||
hw->channels_max = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) {
|
||||
rate = snd_motu_clock_rates[i];
|
||||
mode = i / 2;
|
||||
|
||||
pcm_channels = formats->fixed_part_pcm_chunks[mode] +
|
||||
formats->differed_part_pcm_chunks[mode];
|
||||
if (pcm_channels == 0)
|
||||
continue;
|
||||
|
||||
hw->rates |= snd_pcm_rate_to_rate_bit(rate);
|
||||
hw->channels_min = min(hw->channels_min, pcm_channels);
|
||||
hw->channels_max = max(hw->channels_max, pcm_channels);
|
||||
}
|
||||
|
||||
snd_pcm_limit_hw_rates(runtime);
|
||||
}
|
||||
|
||||
static void limit_period_and_buffer(struct snd_pcm_hardware *hw)
|
||||
{
|
||||
hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */
|
||||
hw->periods_max = UINT_MAX;
|
||||
|
||||
hw->period_bytes_min = 4 * hw->channels_max; /* byte for a frame */
|
||||
|
||||
/* Just to prevent from allocating much pages. */
|
||||
hw->period_bytes_max = hw->period_bytes_min * 2048;
|
||||
hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min;
|
||||
}
|
||||
|
||||
static int init_hw_info(struct snd_motu *motu,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_pcm_hardware *hw = &runtime->hw;
|
||||
struct amdtp_stream *stream;
|
||||
struct snd_motu_packet_format *formats;
|
||||
int err;
|
||||
|
||||
hw->info = SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_BATCH |
|
||||
SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_JOINT_DUPLEX |
|
||||
SNDRV_PCM_INFO_BLOCK_TRANSFER;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
hw->formats = SNDRV_PCM_FMTBIT_S32;
|
||||
stream = &motu->tx_stream;
|
||||
formats = &motu->tx_packet_formats;
|
||||
} else {
|
||||
hw->formats = SNDRV_PCM_FMTBIT_S32;
|
||||
stream = &motu->rx_stream;
|
||||
formats = &motu->rx_packet_formats;
|
||||
}
|
||||
|
||||
limit_channels_and_rates(motu, runtime, formats);
|
||||
limit_period_and_buffer(hw);
|
||||
|
||||
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
|
||||
motu_rate_constraint, formats,
|
||||
SNDRV_PCM_HW_PARAM_CHANNELS, -1);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
|
||||
motu_channels_constraint, formats,
|
||||
SNDRV_PCM_HW_PARAM_RATE, -1);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return amdtp_motu_add_pcm_hw_constraints(stream, runtime);
|
||||
}
|
||||
|
||||
static int pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_motu *motu = substream->private_data;
|
||||
const struct snd_motu_protocol *const protocol = motu->spec->protocol;
|
||||
enum snd_motu_clock_source src;
|
||||
unsigned int rate;
|
||||
int err;
|
||||
|
||||
err = snd_motu_stream_lock_try(motu);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
mutex_lock(&motu->mutex);
|
||||
|
||||
err = protocol->cache_packet_formats(motu);
|
||||
if (err < 0)
|
||||
goto err_locked;
|
||||
|
||||
err = init_hw_info(motu, substream);
|
||||
if (err < 0)
|
||||
goto err_locked;
|
||||
|
||||
/*
|
||||
* When source of clock is not internal or any PCM streams are running,
|
||||
* available sampling rate is limited at current sampling rate.
|
||||
*/
|
||||
err = protocol->get_clock_source(motu, &src);
|
||||
if (err < 0)
|
||||
goto err_locked;
|
||||
if (src != SND_MOTU_CLOCK_SOURCE_INTERNAL ||
|
||||
amdtp_stream_pcm_running(&motu->tx_stream) ||
|
||||
amdtp_stream_pcm_running(&motu->rx_stream)) {
|
||||
err = protocol->get_clock_rate(motu, &rate);
|
||||
if (err < 0)
|
||||
goto err_locked;
|
||||
substream->runtime->hw.rate_min = rate;
|
||||
substream->runtime->hw.rate_max = rate;
|
||||
}
|
||||
|
||||
snd_pcm_set_sync(substream);
|
||||
|
||||
mutex_unlock(&motu->mutex);
|
||||
|
||||
return err;
|
||||
err_locked:
|
||||
mutex_unlock(&motu->mutex);
|
||||
snd_motu_stream_lock_release(motu);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_motu *motu = substream->private_data;
|
||||
|
||||
snd_motu_stream_lock_release(motu);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int capture_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct snd_motu *motu = substream->private_data;
|
||||
int err;
|
||||
|
||||
err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
||||
mutex_lock(&motu->mutex);
|
||||
motu->capture_substreams++;
|
||||
mutex_unlock(&motu->mutex);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
static int playback_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct snd_motu *motu = substream->private_data;
|
||||
int err;
|
||||
|
||||
err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
||||
mutex_lock(&motu->mutex);
|
||||
motu->playback_substreams++;
|
||||
mutex_unlock(&motu->mutex);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int capture_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_motu *motu = substream->private_data;
|
||||
|
||||
mutex_lock(&motu->mutex);
|
||||
|
||||
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
|
||||
motu->capture_substreams--;
|
||||
|
||||
snd_motu_stream_stop_duplex(motu);
|
||||
|
||||
mutex_unlock(&motu->mutex);
|
||||
|
||||
return snd_pcm_lib_free_vmalloc_buffer(substream);
|
||||
}
|
||||
|
||||
static int playback_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_motu *motu = substream->private_data;
|
||||
|
||||
mutex_lock(&motu->mutex);
|
||||
|
||||
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
|
||||
motu->playback_substreams--;
|
||||
|
||||
snd_motu_stream_stop_duplex(motu);
|
||||
|
||||
mutex_unlock(&motu->mutex);
|
||||
|
||||
return snd_pcm_lib_free_vmalloc_buffer(substream);
|
||||
}
|
||||
|
||||
static int capture_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_motu *motu = substream->private_data;
|
||||
int err;
|
||||
|
||||
mutex_lock(&motu->mutex);
|
||||
err = snd_motu_stream_start_duplex(motu, substream->runtime->rate);
|
||||
mutex_unlock(&motu->mutex);
|
||||
if (err >= 0)
|
||||
amdtp_stream_pcm_prepare(&motu->tx_stream);
|
||||
|
||||
return 0;
|
||||
}
|
||||
static int playback_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_motu *motu = substream->private_data;
|
||||
int err;
|
||||
|
||||
mutex_lock(&motu->mutex);
|
||||
err = snd_motu_stream_start_duplex(motu, substream->runtime->rate);
|
||||
mutex_unlock(&motu->mutex);
|
||||
if (err >= 0)
|
||||
amdtp_stream_pcm_prepare(&motu->rx_stream);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int capture_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_motu *motu = substream->private_data;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
amdtp_stream_pcm_trigger(&motu->tx_stream, substream);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
amdtp_stream_pcm_trigger(&motu->tx_stream, NULL);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
static int playback_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_motu *motu = substream->private_data;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
amdtp_stream_pcm_trigger(&motu->rx_stream, substream);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
amdtp_stream_pcm_trigger(&motu->rx_stream, NULL);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t capture_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_motu *motu = substream->private_data;
|
||||
|
||||
return amdtp_stream_pcm_pointer(&motu->tx_stream);
|
||||
}
|
||||
static snd_pcm_uframes_t playback_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_motu *motu = substream->private_data;
|
||||
|
||||
return amdtp_stream_pcm_pointer(&motu->rx_stream);
|
||||
}
|
||||
|
||||
int snd_motu_create_pcm_devices(struct snd_motu *motu)
|
||||
{
|
||||
static struct snd_pcm_ops capture_ops = {
|
||||
.open = pcm_open,
|
||||
.close = pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = capture_hw_params,
|
||||
.hw_free = capture_hw_free,
|
||||
.prepare = capture_prepare,
|
||||
.trigger = capture_trigger,
|
||||
.pointer = capture_pointer,
|
||||
.page = snd_pcm_lib_get_vmalloc_page,
|
||||
.mmap = snd_pcm_lib_mmap_vmalloc,
|
||||
};
|
||||
static struct snd_pcm_ops playback_ops = {
|
||||
.open = pcm_open,
|
||||
.close = pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = playback_hw_params,
|
||||
.hw_free = playback_hw_free,
|
||||
.prepare = playback_prepare,
|
||||
.trigger = playback_trigger,
|
||||
.pointer = playback_pointer,
|
||||
.page = snd_pcm_lib_get_vmalloc_page,
|
||||
.mmap = snd_pcm_lib_mmap_vmalloc,
|
||||
};
|
||||
struct snd_pcm *pcm;
|
||||
int err;
|
||||
|
||||
err = snd_pcm_new(motu->card, motu->card->driver, 0, 1, 1, &pcm);
|
||||
if (err < 0)
|
||||
return err;
|
||||
pcm->private_data = motu;
|
||||
strcpy(pcm->name, motu->card->shortname);
|
||||
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops);
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* motu-proc.c - a part of driver for MOTU FireWire series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "./motu.h"
|
||||
|
||||
static const char *const clock_names[] = {
|
||||
[SND_MOTU_CLOCK_SOURCE_INTERNAL] = "Internal",
|
||||
[SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB] = "ADAT on Dsub-9pin interface",
|
||||
[SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT] = "ADAT on optical interface",
|
||||
[SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A] = "ADAT on optical interface A",
|
||||
[SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B] = "ADAT on optical interface B",
|
||||
[SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT] = "S/PDIF on optical interface",
|
||||
[SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A] = "S/PDIF on optical interface A",
|
||||
[SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B] = "S/PDIF on optical interface B",
|
||||
[SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX] = "S/PCIF on coaxial interface",
|
||||
[SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR] = "AESEBU on XLR interface",
|
||||
[SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC] = "Word clock on BNC interface",
|
||||
};
|
||||
|
||||
static void proc_read_clock(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
|
||||
struct snd_motu *motu = entry->private_data;
|
||||
const struct snd_motu_protocol *const protocol = motu->spec->protocol;
|
||||
unsigned int rate;
|
||||
enum snd_motu_clock_source source;
|
||||
|
||||
if (protocol->get_clock_rate(motu, &rate) < 0)
|
||||
return;
|
||||
if (protocol->get_clock_source(motu, &source) < 0)
|
||||
return;
|
||||
|
||||
snd_iprintf(buffer, "Rate:\t%d\n", rate);
|
||||
snd_iprintf(buffer, "Source:\t%s\n", clock_names[source]);
|
||||
}
|
||||
|
||||
static void proc_read_format(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct snd_motu *motu = entry->private_data;
|
||||
const struct snd_motu_protocol *const protocol = motu->spec->protocol;
|
||||
unsigned int mode;
|
||||
struct snd_motu_packet_format *formats;
|
||||
int i;
|
||||
|
||||
if (protocol->cache_packet_formats(motu) < 0)
|
||||
return;
|
||||
|
||||
snd_iprintf(buffer, "tx:\tmsg\tfixed\tdiffered\n");
|
||||
for (i = 0; i < SND_MOTU_CLOCK_RATE_COUNT; ++i) {
|
||||
mode = i >> 1;
|
||||
|
||||
formats = &motu->tx_packet_formats;
|
||||
snd_iprintf(buffer,
|
||||
"%u:\t%u\t%u\t%u\n",
|
||||
snd_motu_clock_rates[i],
|
||||
formats->msg_chunks,
|
||||
formats->fixed_part_pcm_chunks[mode],
|
||||
formats->differed_part_pcm_chunks[mode]);
|
||||
}
|
||||
|
||||
snd_iprintf(buffer, "rx:\tmsg\tfixed\tdiffered\n");
|
||||
for (i = 0; i < SND_MOTU_CLOCK_RATE_COUNT; ++i) {
|
||||
mode = i >> 1;
|
||||
|
||||
formats = &motu->rx_packet_formats;
|
||||
snd_iprintf(buffer,
|
||||
"%u:\t%u\t%u\t%u\n",
|
||||
snd_motu_clock_rates[i],
|
||||
formats->msg_chunks,
|
||||
formats->fixed_part_pcm_chunks[mode],
|
||||
formats->differed_part_pcm_chunks[mode]);
|
||||
}
|
||||
}
|
||||
|
||||
static void add_node(struct snd_motu *motu, struct snd_info_entry *root,
|
||||
const char *name,
|
||||
void (*op)(struct snd_info_entry *e,
|
||||
struct snd_info_buffer *b))
|
||||
{
|
||||
struct snd_info_entry *entry;
|
||||
|
||||
entry = snd_info_create_card_entry(motu->card, name, root);
|
||||
if (entry == NULL)
|
||||
return;
|
||||
|
||||
snd_info_set_text_ops(entry, motu, op);
|
||||
if (snd_info_register(entry) < 0)
|
||||
snd_info_free_entry(entry);
|
||||
}
|
||||
|
||||
void snd_motu_proc_init(struct snd_motu *motu)
|
||||
{
|
||||
struct snd_info_entry *root;
|
||||
|
||||
/*
|
||||
* All nodes are automatically removed at snd_card_disconnect(),
|
||||
* by following to link list.
|
||||
*/
|
||||
root = snd_info_create_card_entry(motu->card, "firewire",
|
||||
motu->card->proc_root);
|
||||
if (root == NULL)
|
||||
return;
|
||||
root->mode = S_IFDIR | S_IRUGO | S_IXUGO;
|
||||
if (snd_info_register(root) < 0) {
|
||||
snd_info_free_entry(root);
|
||||
return;
|
||||
}
|
||||
|
||||
add_node(motu, root, "clock", proc_read_clock);
|
||||
add_node(motu, root, "format", proc_read_format);
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* motu-protocol-v2.c - a part of driver for MOTU FireWire series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "motu.h"
|
||||
|
||||
#define V2_CLOCK_STATUS_OFFSET 0x0b14
|
||||
#define V2_CLOCK_RATE_MASK 0x00000038
|
||||
#define V2_CLOCK_RATE_SHIFT 3
|
||||
#define V2_CLOCK_SRC_MASK 0x00000007
|
||||
#define V2_CLOCK_SRC_SHIFT 0
|
||||
|
||||
#define V2_IN_OUT_CONF_OFFSET 0x0c04
|
||||
#define V2_OPT_OUT_IFACE_MASK 0x00000c00
|
||||
#define V2_OPT_OUT_IFACE_SHIFT 10
|
||||
#define V2_OPT_IN_IFACE_MASK 0x00000300
|
||||
#define V2_OPT_IN_IFACE_SHIFT 8
|
||||
#define V2_OPT_IFACE_MODE_NONE 0
|
||||
#define V2_OPT_IFACE_MODE_ADAT 1
|
||||
#define V2_OPT_IFACE_MODE_SPDIF 2
|
||||
|
||||
static int v2_get_clock_rate(struct snd_motu *motu, unsigned int *rate)
|
||||
{
|
||||
__be32 reg;
|
||||
unsigned int index;
|
||||
int err;
|
||||
|
||||
err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®,
|
||||
sizeof(reg));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
index = (be32_to_cpu(reg) & V2_CLOCK_RATE_MASK) >> V2_CLOCK_RATE_SHIFT;
|
||||
if (index >= ARRAY_SIZE(snd_motu_clock_rates))
|
||||
return -EIO;
|
||||
|
||||
*rate = snd_motu_clock_rates[index];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int v2_set_clock_rate(struct snd_motu *motu, unsigned int rate)
|
||||
{
|
||||
__be32 reg;
|
||||
u32 data;
|
||||
int i;
|
||||
int err;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) {
|
||||
if (snd_motu_clock_rates[i] == rate)
|
||||
break;
|
||||
}
|
||||
if (i == ARRAY_SIZE(snd_motu_clock_rates))
|
||||
return -EINVAL;
|
||||
|
||||
err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®,
|
||||
sizeof(reg));
|
||||
if (err < 0)
|
||||
return err;
|
||||
data = be32_to_cpu(reg);
|
||||
|
||||
data &= ~V2_CLOCK_RATE_MASK;
|
||||
data |= i << V2_CLOCK_RATE_SHIFT;
|
||||
|
||||
reg = cpu_to_be32(data);
|
||||
return snd_motu_transaction_write(motu, V2_CLOCK_STATUS_OFFSET, ®,
|
||||
sizeof(reg));
|
||||
}
|
||||
|
||||
static int v2_get_clock_source(struct snd_motu *motu,
|
||||
enum snd_motu_clock_source *src)
|
||||
{
|
||||
__be32 reg;
|
||||
unsigned int index;
|
||||
int err;
|
||||
|
||||
err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®,
|
||||
sizeof(reg));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
index = be32_to_cpu(reg) & V2_CLOCK_SRC_MASK;
|
||||
if (index > 5)
|
||||
return -EIO;
|
||||
|
||||
/* To check the configuration of optical interface. */
|
||||
err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, ®,
|
||||
sizeof(reg));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
switch (index) {
|
||||
case 0:
|
||||
*src = SND_MOTU_CLOCK_SOURCE_INTERNAL;
|
||||
break;
|
||||
case 1:
|
||||
if (be32_to_cpu(reg) & 0x00000200)
|
||||
*src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT;
|
||||
else
|
||||
*src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT;
|
||||
break;
|
||||
case 2:
|
||||
*src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX;
|
||||
break;
|
||||
case 4:
|
||||
*src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC;
|
||||
break;
|
||||
case 5:
|
||||
*src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB;
|
||||
break;
|
||||
default:
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int v2_switch_fetching_mode(struct snd_motu *motu, bool enable)
|
||||
{
|
||||
/* V2 protocol doesn't have this feature. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void calculate_fixed_part(struct snd_motu_packet_format *formats,
|
||||
enum amdtp_stream_direction dir,
|
||||
enum snd_motu_spec_flags flags,
|
||||
unsigned char analog_ports)
|
||||
{
|
||||
unsigned char pcm_chunks[3] = {0, 0, 0};
|
||||
|
||||
formats->msg_chunks = 2;
|
||||
|
||||
pcm_chunks[0] = analog_ports;
|
||||
pcm_chunks[1] = analog_ports;
|
||||
if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4)
|
||||
pcm_chunks[2] = analog_ports;
|
||||
|
||||
if (dir == AMDTP_IN_STREAM) {
|
||||
if (flags & SND_MOTU_SPEC_TX_MICINST_CHUNK) {
|
||||
pcm_chunks[0] += 2;
|
||||
pcm_chunks[1] += 2;
|
||||
}
|
||||
if (flags & SND_MOTU_SPEC_TX_RETURN_CHUNK) {
|
||||
pcm_chunks[0] += 2;
|
||||
pcm_chunks[1] += 2;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Packets to v2 units transfer main-out-1/2 and phone-out-1/2.
|
||||
*/
|
||||
pcm_chunks[0] += 4;
|
||||
pcm_chunks[1] += 4;
|
||||
}
|
||||
|
||||
/*
|
||||
* All of v2 models have a pair of coaxial interfaces for digital in/out
|
||||
* port. At 44.1/48.0/88.2/96.0 kHz, packets includes PCM from these
|
||||
* ports.
|
||||
*/
|
||||
pcm_chunks[0] += 2;
|
||||
pcm_chunks[1] += 2;
|
||||
|
||||
/* This part should be multiples of 4. */
|
||||
formats->fixed_part_pcm_chunks[0] = round_up(2 + pcm_chunks[0], 4) - 2;
|
||||
formats->fixed_part_pcm_chunks[1] = round_up(2 + pcm_chunks[1], 4) - 2;
|
||||
if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4)
|
||||
formats->fixed_part_pcm_chunks[2] =
|
||||
round_up(2 + pcm_chunks[2], 4) - 2;
|
||||
}
|
||||
|
||||
static void calculate_differed_part(struct snd_motu_packet_format *formats,
|
||||
enum snd_motu_spec_flags flags,
|
||||
u32 data, u32 mask, u32 shift)
|
||||
{
|
||||
unsigned char pcm_chunks[3] = {0, 0};
|
||||
|
||||
/*
|
||||
* When optical interfaces are configured for S/PDIF (TOSLINK),
|
||||
* the above PCM frames come from them, instead of coaxial
|
||||
* interfaces.
|
||||
*/
|
||||
data = (data & mask) >> shift;
|
||||
if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_A) &&
|
||||
data == V2_OPT_IFACE_MODE_ADAT) {
|
||||
pcm_chunks[0] += 8;
|
||||
pcm_chunks[1] += 4;
|
||||
}
|
||||
|
||||
/* At mode x4, no data chunks are supported in this part. */
|
||||
formats->differed_part_pcm_chunks[0] = pcm_chunks[0];
|
||||
formats->differed_part_pcm_chunks[1] = pcm_chunks[1];
|
||||
}
|
||||
|
||||
static int v2_cache_packet_formats(struct snd_motu *motu)
|
||||
{
|
||||
__be32 reg;
|
||||
u32 data;
|
||||
int err;
|
||||
|
||||
err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, ®,
|
||||
sizeof(reg));
|
||||
if (err < 0)
|
||||
return err;
|
||||
data = be32_to_cpu(reg);
|
||||
|
||||
calculate_fixed_part(&motu->tx_packet_formats, AMDTP_IN_STREAM,
|
||||
motu->spec->flags, motu->spec->analog_in_ports);
|
||||
calculate_differed_part(&motu->tx_packet_formats, motu->spec->flags,
|
||||
data, V2_OPT_IN_IFACE_MASK, V2_OPT_IN_IFACE_SHIFT);
|
||||
|
||||
calculate_fixed_part(&motu->rx_packet_formats, AMDTP_OUT_STREAM,
|
||||
motu->spec->flags, motu->spec->analog_out_ports);
|
||||
calculate_differed_part(&motu->rx_packet_formats, motu->spec->flags,
|
||||
data, V2_OPT_OUT_IFACE_MASK, V2_OPT_OUT_IFACE_SHIFT);
|
||||
|
||||
motu->tx_packet_formats.midi_flag_offset = 4;
|
||||
motu->tx_packet_formats.midi_byte_offset = 6;
|
||||
motu->tx_packet_formats.pcm_byte_offset = 10;
|
||||
|
||||
motu->rx_packet_formats.midi_flag_offset = 4;
|
||||
motu->rx_packet_formats.midi_byte_offset = 6;
|
||||
motu->rx_packet_formats.pcm_byte_offset = 10;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct snd_motu_protocol snd_motu_protocol_v2 = {
|
||||
.get_clock_rate = v2_get_clock_rate,
|
||||
.set_clock_rate = v2_set_clock_rate,
|
||||
.get_clock_source = v2_get_clock_source,
|
||||
.switch_fetching_mode = v2_switch_fetching_mode,
|
||||
.cache_packet_formats = v2_cache_packet_formats,
|
||||
};
|
|
@ -0,0 +1,311 @@
|
|||
/*
|
||||
* motu-protocol-v3.c - a part of driver for MOTU FireWire series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include "motu.h"
|
||||
|
||||
#define V3_CLOCK_STATUS_OFFSET 0x0b14
|
||||
#define V3_FETCH_PCM_FRAMES 0x02000000
|
||||
#define V3_CLOCK_RATE_MASK 0x0000ff00
|
||||
#define V3_CLOCK_RATE_SHIFT 8
|
||||
#define V3_CLOCK_SOURCE_MASK 0x000000ff
|
||||
|
||||
#define V3_OPT_IFACE_MODE_OFFSET 0x0c94
|
||||
#define V3_ENABLE_OPT_IN_IFACE_A 0x00000001
|
||||
#define V3_ENABLE_OPT_IN_IFACE_B 0x00000002
|
||||
#define V3_ENABLE_OPT_OUT_IFACE_A 0x00000100
|
||||
#define V3_ENABLE_OPT_OUT_IFACE_B 0x00000200
|
||||
#define V3_NO_ADAT_OPT_IN_IFACE_A 0x00010000
|
||||
#define V3_NO_ADAT_OPT_IN_IFACE_B 0x00100000
|
||||
#define V3_NO_ADAT_OPT_OUT_IFACE_A 0x00040000
|
||||
#define V3_NO_ADAT_OPT_OUT_IFACE_B 0x00400000
|
||||
|
||||
static int v3_get_clock_rate(struct snd_motu *motu, unsigned int *rate)
|
||||
{
|
||||
__be32 reg;
|
||||
u32 data;
|
||||
int err;
|
||||
|
||||
err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®,
|
||||
sizeof(reg));
|
||||
if (err < 0)
|
||||
return err;
|
||||
data = be32_to_cpu(reg);
|
||||
|
||||
data = (data & V3_CLOCK_RATE_MASK) >> V3_CLOCK_RATE_SHIFT;
|
||||
if (data >= ARRAY_SIZE(snd_motu_clock_rates))
|
||||
return -EIO;
|
||||
|
||||
*rate = snd_motu_clock_rates[data];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int v3_set_clock_rate(struct snd_motu *motu, unsigned int rate)
|
||||
{
|
||||
__be32 reg;
|
||||
u32 data;
|
||||
bool need_to_wait;
|
||||
int i, err;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) {
|
||||
if (snd_motu_clock_rates[i] == rate)
|
||||
break;
|
||||
}
|
||||
if (i == ARRAY_SIZE(snd_motu_clock_rates))
|
||||
return -EINVAL;
|
||||
|
||||
err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®,
|
||||
sizeof(reg));
|
||||
if (err < 0)
|
||||
return err;
|
||||
data = be32_to_cpu(reg);
|
||||
|
||||
data &= ~(V3_CLOCK_RATE_MASK | V3_FETCH_PCM_FRAMES);
|
||||
data |= i << V3_CLOCK_RATE_SHIFT;
|
||||
|
||||
need_to_wait = data != be32_to_cpu(reg);
|
||||
|
||||
reg = cpu_to_be32(data);
|
||||
err = snd_motu_transaction_write(motu, V3_CLOCK_STATUS_OFFSET, ®,
|
||||
sizeof(reg));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (need_to_wait) {
|
||||
/* Cost expensive. */
|
||||
if (msleep_interruptible(4000) > 0)
|
||||
return -EINTR;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int v3_get_clock_source(struct snd_motu *motu,
|
||||
enum snd_motu_clock_source *src)
|
||||
{
|
||||
__be32 reg;
|
||||
u32 data;
|
||||
unsigned int val;
|
||||
int err;
|
||||
|
||||
err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®,
|
||||
sizeof(reg));
|
||||
if (err < 0)
|
||||
return err;
|
||||
data = be32_to_cpu(reg);
|
||||
|
||||
val = data & V3_CLOCK_SOURCE_MASK;
|
||||
if (val == 0x00) {
|
||||
*src = SND_MOTU_CLOCK_SOURCE_INTERNAL;
|
||||
} else if (val == 0x01) {
|
||||
*src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC;
|
||||
} else if (val == 0x10) {
|
||||
*src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX;
|
||||
} else if (val == 0x18 || val == 0x19) {
|
||||
err = snd_motu_transaction_read(motu, V3_OPT_IFACE_MODE_OFFSET,
|
||||
®, sizeof(reg));
|
||||
if (err < 0)
|
||||
return err;
|
||||
data = be32_to_cpu(reg);
|
||||
|
||||
if (val == 0x18) {
|
||||
if (data & V3_NO_ADAT_OPT_IN_IFACE_A)
|
||||
*src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A;
|
||||
else
|
||||
*src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A;
|
||||
} else {
|
||||
if (data & V3_NO_ADAT_OPT_IN_IFACE_B)
|
||||
*src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B;
|
||||
else
|
||||
*src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B;
|
||||
}
|
||||
} else {
|
||||
*src = SND_MOTU_CLOCK_SOURCE_UNKNOWN;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int v3_switch_fetching_mode(struct snd_motu *motu, bool enable)
|
||||
{
|
||||
__be32 reg;
|
||||
u32 data;
|
||||
int err;
|
||||
|
||||
err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®,
|
||||
sizeof(reg));
|
||||
if (err < 0)
|
||||
return 0;
|
||||
data = be32_to_cpu(reg);
|
||||
|
||||
if (enable)
|
||||
data |= V3_FETCH_PCM_FRAMES;
|
||||
else
|
||||
data &= ~V3_FETCH_PCM_FRAMES;
|
||||
|
||||
reg = cpu_to_be32(data);
|
||||
return snd_motu_transaction_write(motu, V3_CLOCK_STATUS_OFFSET, ®,
|
||||
sizeof(reg));
|
||||
}
|
||||
|
||||
static void calculate_fixed_part(struct snd_motu_packet_format *formats,
|
||||
enum amdtp_stream_direction dir,
|
||||
enum snd_motu_spec_flags flags,
|
||||
unsigned char analog_ports)
|
||||
{
|
||||
unsigned char pcm_chunks[3] = {0, 0, 0};
|
||||
|
||||
formats->msg_chunks = 2;
|
||||
|
||||
pcm_chunks[0] = analog_ports;
|
||||
pcm_chunks[1] = analog_ports;
|
||||
if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4)
|
||||
pcm_chunks[2] = analog_ports;
|
||||
|
||||
if (dir == AMDTP_IN_STREAM) {
|
||||
if (flags & SND_MOTU_SPEC_TX_MICINST_CHUNK) {
|
||||
pcm_chunks[0] += 2;
|
||||
pcm_chunks[1] += 2;
|
||||
if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4)
|
||||
pcm_chunks[2] += 2;
|
||||
}
|
||||
|
||||
if (flags & SND_MOTU_SPEC_TX_RETURN_CHUNK) {
|
||||
pcm_chunks[0] += 2;
|
||||
pcm_chunks[1] += 2;
|
||||
if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4)
|
||||
pcm_chunks[2] += 2;
|
||||
}
|
||||
|
||||
if (flags & SND_MOTU_SPEC_TX_REVERB_CHUNK) {
|
||||
pcm_chunks[0] += 2;
|
||||
pcm_chunks[1] += 2;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Packets to v2 units transfer main-out-1/2 and phone-out-1/2.
|
||||
*/
|
||||
pcm_chunks[0] += 4;
|
||||
pcm_chunks[1] += 4;
|
||||
}
|
||||
|
||||
/*
|
||||
* At least, packets have two data chunks for S/PDIF on coaxial
|
||||
* interface.
|
||||
*/
|
||||
pcm_chunks[0] += 2;
|
||||
pcm_chunks[1] += 2;
|
||||
|
||||
/*
|
||||
* Fixed part consists of PCM chunks multiple of 4, with msg chunks. As
|
||||
* a result, this part can includes empty data chunks.
|
||||
*/
|
||||
formats->fixed_part_pcm_chunks[0] = round_up(2 + pcm_chunks[0], 4) - 2;
|
||||
formats->fixed_part_pcm_chunks[1] = round_up(2 + pcm_chunks[1], 4) - 2;
|
||||
if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4)
|
||||
formats->fixed_part_pcm_chunks[2] =
|
||||
round_up(2 + pcm_chunks[2], 4) - 2;
|
||||
}
|
||||
|
||||
static void calculate_differed_part(struct snd_motu_packet_format *formats,
|
||||
enum snd_motu_spec_flags flags, u32 data,
|
||||
u32 a_enable_mask, u32 a_no_adat_mask,
|
||||
u32 b_enable_mask, u32 b_no_adat_mask)
|
||||
{
|
||||
unsigned char pcm_chunks[3] = {0, 0, 0};
|
||||
int i;
|
||||
|
||||
if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_A) && (data & a_enable_mask)) {
|
||||
if (data & a_no_adat_mask) {
|
||||
/*
|
||||
* Additional two data chunks for S/PDIF on optical
|
||||
* interface A. This includes empty data chunks.
|
||||
*/
|
||||
pcm_chunks[0] += 4;
|
||||
pcm_chunks[1] += 4;
|
||||
} else {
|
||||
/*
|
||||
* Additional data chunks for ADAT on optical interface
|
||||
* A.
|
||||
*/
|
||||
pcm_chunks[0] += 8;
|
||||
pcm_chunks[1] += 4;
|
||||
}
|
||||
}
|
||||
|
||||
if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_B) && (data & b_enable_mask)) {
|
||||
if (data & b_no_adat_mask) {
|
||||
/*
|
||||
* Additional two data chunks for S/PDIF on optical
|
||||
* interface B. This includes empty data chunks.
|
||||
*/
|
||||
pcm_chunks[0] += 4;
|
||||
pcm_chunks[1] += 4;
|
||||
} else {
|
||||
/*
|
||||
* Additional data chunks for ADAT on optical interface
|
||||
* B.
|
||||
*/
|
||||
pcm_chunks[0] += 8;
|
||||
pcm_chunks[1] += 4;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < 3; ++i) {
|
||||
if (pcm_chunks[i] > 0)
|
||||
pcm_chunks[i] = round_up(pcm_chunks[i], 4);
|
||||
|
||||
formats->differed_part_pcm_chunks[i] = pcm_chunks[i];
|
||||
}
|
||||
}
|
||||
|
||||
static int v3_cache_packet_formats(struct snd_motu *motu)
|
||||
{
|
||||
__be32 reg;
|
||||
u32 data;
|
||||
int err;
|
||||
|
||||
err = snd_motu_transaction_read(motu, V3_OPT_IFACE_MODE_OFFSET, ®,
|
||||
sizeof(reg));
|
||||
if (err < 0)
|
||||
return err;
|
||||
data = be32_to_cpu(reg);
|
||||
|
||||
calculate_fixed_part(&motu->tx_packet_formats, AMDTP_IN_STREAM,
|
||||
motu->spec->flags, motu->spec->analog_in_ports);
|
||||
calculate_differed_part(&motu->tx_packet_formats,
|
||||
motu->spec->flags, data,
|
||||
V3_ENABLE_OPT_IN_IFACE_A, V3_NO_ADAT_OPT_IN_IFACE_A,
|
||||
V3_ENABLE_OPT_IN_IFACE_B, V3_NO_ADAT_OPT_IN_IFACE_B);
|
||||
|
||||
calculate_fixed_part(&motu->rx_packet_formats, AMDTP_OUT_STREAM,
|
||||
motu->spec->flags, motu->spec->analog_out_ports);
|
||||
calculate_differed_part(&motu->rx_packet_formats,
|
||||
motu->spec->flags, data,
|
||||
V3_ENABLE_OPT_OUT_IFACE_A, V3_NO_ADAT_OPT_OUT_IFACE_A,
|
||||
V3_ENABLE_OPT_OUT_IFACE_B, V3_NO_ADAT_OPT_OUT_IFACE_B);
|
||||
|
||||
motu->tx_packet_formats.midi_flag_offset = 8;
|
||||
motu->tx_packet_formats.midi_byte_offset = 7;
|
||||
motu->tx_packet_formats.pcm_byte_offset = 10;
|
||||
|
||||
motu->rx_packet_formats.midi_flag_offset = 8;
|
||||
motu->rx_packet_formats.midi_byte_offset = 7;
|
||||
motu->rx_packet_formats.pcm_byte_offset = 10;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct snd_motu_protocol snd_motu_protocol_v3 = {
|
||||
.get_clock_rate = v3_get_clock_rate,
|
||||
.set_clock_rate = v3_set_clock_rate,
|
||||
.get_clock_source = v3_get_clock_source,
|
||||
.switch_fetching_mode = v3_switch_fetching_mode,
|
||||
.cache_packet_formats = v3_cache_packet_formats,
|
||||
};
|
|
@ -0,0 +1,381 @@
|
|||
/*
|
||||
* motu-stream.c - a part of driver for MOTU FireWire series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "motu.h"
|
||||
|
||||
#define CALLBACK_TIMEOUT 200
|
||||
|
||||
#define ISOC_COMM_CONTROL_OFFSET 0x0b00
|
||||
#define ISOC_COMM_CONTROL_MASK 0xffff0000
|
||||
#define CHANGE_RX_ISOC_COMM_STATE 0x80000000
|
||||
#define RX_ISOC_COMM_IS_ACTIVATED 0x40000000
|
||||
#define RX_ISOC_COMM_CHANNEL_MASK 0x3f000000
|
||||
#define RX_ISOC_COMM_CHANNEL_SHIFT 24
|
||||
#define CHANGE_TX_ISOC_COMM_STATE 0x00800000
|
||||
#define TX_ISOC_COMM_IS_ACTIVATED 0x00400000
|
||||
#define TX_ISOC_COMM_CHANNEL_MASK 0x003f0000
|
||||
#define TX_ISOC_COMM_CHANNEL_SHIFT 16
|
||||
|
||||
#define PACKET_FORMAT_OFFSET 0x0b10
|
||||
#define TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS 0x00000080
|
||||
#define RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS 0x00000040
|
||||
#define TX_PACKET_TRANSMISSION_SPEED_MASK 0x0000000f
|
||||
|
||||
static int start_both_streams(struct snd_motu *motu, unsigned int rate)
|
||||
{
|
||||
unsigned int midi_ports = 0;
|
||||
__be32 reg;
|
||||
u32 data;
|
||||
int err;
|
||||
|
||||
if (motu->spec->flags & SND_MOTU_SPEC_HAS_MIDI)
|
||||
midi_ports = 1;
|
||||
|
||||
/* Set packet formation to our packet streaming engine. */
|
||||
err = amdtp_motu_set_parameters(&motu->rx_stream, rate, midi_ports,
|
||||
&motu->rx_packet_formats);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = amdtp_motu_set_parameters(&motu->tx_stream, rate, midi_ports,
|
||||
&motu->tx_packet_formats);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Get isochronous resources on the bus. */
|
||||
err = fw_iso_resources_allocate(&motu->rx_resources,
|
||||
amdtp_stream_get_max_payload(&motu->rx_stream),
|
||||
fw_parent_device(motu->unit)->max_speed);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = fw_iso_resources_allocate(&motu->tx_resources,
|
||||
amdtp_stream_get_max_payload(&motu->tx_stream),
|
||||
fw_parent_device(motu->unit)->max_speed);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Configure the unit to start isochronous communication. */
|
||||
err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, ®,
|
||||
sizeof(reg));
|
||||
if (err < 0)
|
||||
return err;
|
||||
data = be32_to_cpu(reg) & ~ISOC_COMM_CONTROL_MASK;
|
||||
|
||||
data |= CHANGE_RX_ISOC_COMM_STATE | RX_ISOC_COMM_IS_ACTIVATED |
|
||||
(motu->rx_resources.channel << RX_ISOC_COMM_CHANNEL_SHIFT) |
|
||||
CHANGE_TX_ISOC_COMM_STATE | TX_ISOC_COMM_IS_ACTIVATED |
|
||||
(motu->tx_resources.channel << TX_ISOC_COMM_CHANNEL_SHIFT);
|
||||
|
||||
reg = cpu_to_be32(data);
|
||||
return snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, ®,
|
||||
sizeof(reg));
|
||||
}
|
||||
|
||||
static void stop_both_streams(struct snd_motu *motu)
|
||||
{
|
||||
__be32 reg;
|
||||
u32 data;
|
||||
int err;
|
||||
|
||||
err = motu->spec->protocol->switch_fetching_mode(motu, false);
|
||||
if (err < 0)
|
||||
return;
|
||||
|
||||
err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, ®,
|
||||
sizeof(reg));
|
||||
if (err < 0)
|
||||
return;
|
||||
data = be32_to_cpu(reg);
|
||||
|
||||
data &= ~(RX_ISOC_COMM_IS_ACTIVATED | TX_ISOC_COMM_IS_ACTIVATED);
|
||||
data |= CHANGE_RX_ISOC_COMM_STATE | CHANGE_TX_ISOC_COMM_STATE;
|
||||
|
||||
reg = cpu_to_be32(data);
|
||||
snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, ®,
|
||||
sizeof(reg));
|
||||
|
||||
fw_iso_resources_free(&motu->tx_resources);
|
||||
fw_iso_resources_free(&motu->rx_resources);
|
||||
}
|
||||
|
||||
static int start_isoc_ctx(struct snd_motu *motu, struct amdtp_stream *stream)
|
||||
{
|
||||
struct fw_iso_resources *resources;
|
||||
int err;
|
||||
|
||||
if (stream == &motu->rx_stream)
|
||||
resources = &motu->rx_resources;
|
||||
else
|
||||
resources = &motu->tx_resources;
|
||||
|
||||
err = amdtp_stream_start(stream, resources->channel,
|
||||
fw_parent_device(motu->unit)->max_speed);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (!amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT)) {
|
||||
amdtp_stream_stop(stream);
|
||||
fw_iso_resources_free(resources);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stop_isoc_ctx(struct snd_motu *motu, struct amdtp_stream *stream)
|
||||
{
|
||||
struct fw_iso_resources *resources;
|
||||
|
||||
if (stream == &motu->rx_stream)
|
||||
resources = &motu->rx_resources;
|
||||
else
|
||||
resources = &motu->tx_resources;
|
||||
|
||||
amdtp_stream_stop(stream);
|
||||
fw_iso_resources_free(resources);
|
||||
}
|
||||
|
||||
static int ensure_packet_formats(struct snd_motu *motu)
|
||||
{
|
||||
__be32 reg;
|
||||
u32 data;
|
||||
int err;
|
||||
|
||||
err = snd_motu_transaction_read(motu, PACKET_FORMAT_OFFSET, ®,
|
||||
sizeof(reg));
|
||||
if (err < 0)
|
||||
return err;
|
||||
data = be32_to_cpu(reg);
|
||||
|
||||
data &= ~(TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS |
|
||||
RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS|
|
||||
TX_PACKET_TRANSMISSION_SPEED_MASK);
|
||||
if (motu->tx_packet_formats.differed_part_pcm_chunks[0] == 0)
|
||||
data |= TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS;
|
||||
if (motu->rx_packet_formats.differed_part_pcm_chunks[0] == 0)
|
||||
data |= RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS;
|
||||
data |= fw_parent_device(motu->unit)->max_speed;
|
||||
|
||||
reg = cpu_to_be32(data);
|
||||
return snd_motu_transaction_write(motu, PACKET_FORMAT_OFFSET, ®,
|
||||
sizeof(reg));
|
||||
}
|
||||
|
||||
int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate)
|
||||
{
|
||||
const struct snd_motu_protocol *protocol = motu->spec->protocol;
|
||||
unsigned int curr_rate;
|
||||
int err = 0;
|
||||
|
||||
if (motu->capture_substreams == 0 && motu->playback_substreams == 0)
|
||||
return 0;
|
||||
|
||||
/* Some packet queueing errors. */
|
||||
if (amdtp_streaming_error(&motu->rx_stream) ||
|
||||
amdtp_streaming_error(&motu->tx_stream)) {
|
||||
amdtp_stream_stop(&motu->rx_stream);
|
||||
amdtp_stream_stop(&motu->tx_stream);
|
||||
stop_both_streams(motu);
|
||||
}
|
||||
|
||||
err = protocol->cache_packet_formats(motu);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Stop stream if rate is different. */
|
||||
err = protocol->get_clock_rate(motu, &curr_rate);
|
||||
if (err < 0) {
|
||||
dev_err(&motu->unit->device,
|
||||
"fail to get sampling rate: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
if (rate == 0)
|
||||
rate = curr_rate;
|
||||
if (rate != curr_rate) {
|
||||
amdtp_stream_stop(&motu->rx_stream);
|
||||
amdtp_stream_stop(&motu->tx_stream);
|
||||
stop_both_streams(motu);
|
||||
}
|
||||
|
||||
if (!amdtp_stream_running(&motu->rx_stream)) {
|
||||
err = protocol->set_clock_rate(motu, rate);
|
||||
if (err < 0) {
|
||||
dev_err(&motu->unit->device,
|
||||
"fail to set sampling rate: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = ensure_packet_formats(motu);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = start_both_streams(motu, rate);
|
||||
if (err < 0) {
|
||||
dev_err(&motu->unit->device,
|
||||
"fail to start isochronous comm: %d\n", err);
|
||||
stop_both_streams(motu);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = start_isoc_ctx(motu, &motu->rx_stream);
|
||||
if (err < 0) {
|
||||
dev_err(&motu->unit->device,
|
||||
"fail to start IT context: %d\n", err);
|
||||
stop_both_streams(motu);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = protocol->switch_fetching_mode(motu, true);
|
||||
if (err < 0) {
|
||||
dev_err(&motu->unit->device,
|
||||
"fail to enable frame fetching: %d\n", err);
|
||||
stop_both_streams(motu);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!amdtp_stream_running(&motu->tx_stream) &&
|
||||
motu->capture_substreams > 0) {
|
||||
err = start_isoc_ctx(motu, &motu->tx_stream);
|
||||
if (err < 0) {
|
||||
dev_err(&motu->unit->device,
|
||||
"fail to start IR context: %d", err);
|
||||
amdtp_stream_stop(&motu->rx_stream);
|
||||
stop_both_streams(motu);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void snd_motu_stream_stop_duplex(struct snd_motu *motu)
|
||||
{
|
||||
if (motu->capture_substreams == 0) {
|
||||
if (amdtp_stream_running(&motu->tx_stream))
|
||||
stop_isoc_ctx(motu, &motu->tx_stream);
|
||||
|
||||
if (motu->playback_substreams == 0) {
|
||||
if (amdtp_stream_running(&motu->rx_stream))
|
||||
stop_isoc_ctx(motu, &motu->rx_stream);
|
||||
stop_both_streams(motu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int init_stream(struct snd_motu *motu, enum amdtp_stream_direction dir)
|
||||
{
|
||||
int err;
|
||||
struct amdtp_stream *stream;
|
||||
struct fw_iso_resources *resources;
|
||||
|
||||
if (dir == AMDTP_IN_STREAM) {
|
||||
stream = &motu->tx_stream;
|
||||
resources = &motu->tx_resources;
|
||||
} else {
|
||||
stream = &motu->rx_stream;
|
||||
resources = &motu->rx_resources;
|
||||
}
|
||||
|
||||
err = fw_iso_resources_init(resources, motu->unit);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = amdtp_motu_init(stream, motu->unit, dir, motu->spec->protocol);
|
||||
if (err < 0) {
|
||||
amdtp_stream_destroy(stream);
|
||||
fw_iso_resources_destroy(resources);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void destroy_stream(struct snd_motu *motu,
|
||||
enum amdtp_stream_direction dir)
|
||||
{
|
||||
struct amdtp_stream *stream;
|
||||
struct fw_iso_resources *resources;
|
||||
|
||||
if (dir == AMDTP_IN_STREAM) {
|
||||
stream = &motu->tx_stream;
|
||||
resources = &motu->tx_resources;
|
||||
} else {
|
||||
stream = &motu->rx_stream;
|
||||
resources = &motu->rx_resources;
|
||||
}
|
||||
|
||||
amdtp_stream_destroy(stream);
|
||||
fw_iso_resources_free(resources);
|
||||
}
|
||||
|
||||
int snd_motu_stream_init_duplex(struct snd_motu *motu)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = init_stream(motu, AMDTP_IN_STREAM);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = init_stream(motu, AMDTP_OUT_STREAM);
|
||||
if (err < 0)
|
||||
destroy_stream(motu, AMDTP_IN_STREAM);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function should be called before starting streams or after stopping
|
||||
* streams.
|
||||
*/
|
||||
void snd_motu_stream_destroy_duplex(struct snd_motu *motu)
|
||||
{
|
||||
destroy_stream(motu, AMDTP_IN_STREAM);
|
||||
destroy_stream(motu, AMDTP_OUT_STREAM);
|
||||
|
||||
motu->playback_substreams = 0;
|
||||
motu->capture_substreams = 0;
|
||||
}
|
||||
|
||||
static void motu_lock_changed(struct snd_motu *motu)
|
||||
{
|
||||
motu->dev_lock_changed = true;
|
||||
wake_up(&motu->hwdep_wait);
|
||||
}
|
||||
|
||||
int snd_motu_stream_lock_try(struct snd_motu *motu)
|
||||
{
|
||||
int err;
|
||||
|
||||
spin_lock_irq(&motu->lock);
|
||||
|
||||
if (motu->dev_lock_count < 0) {
|
||||
err = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (motu->dev_lock_count++ == 0)
|
||||
motu_lock_changed(motu);
|
||||
err = 0;
|
||||
out:
|
||||
spin_unlock_irq(&motu->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
void snd_motu_stream_lock_release(struct snd_motu *motu)
|
||||
{
|
||||
spin_lock_irq(&motu->lock);
|
||||
|
||||
if (WARN_ON(motu->dev_lock_count <= 0))
|
||||
goto out;
|
||||
|
||||
if (--motu->dev_lock_count == 0)
|
||||
motu_lock_changed(motu);
|
||||
out:
|
||||
spin_unlock_irq(&motu->lock);
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* motu-transaction.c - a part of driver for MOTU FireWire series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
|
||||
#include "motu.h"
|
||||
|
||||
#define SND_MOTU_ADDR_BASE 0xfffff0000000ULL
|
||||
#define ASYNC_ADDR_HI 0x0b04
|
||||
#define ASYNC_ADDR_LO 0x0b08
|
||||
|
||||
int snd_motu_transaction_read(struct snd_motu *motu, u32 offset, __be32 *reg,
|
||||
size_t size)
|
||||
{
|
||||
int tcode;
|
||||
|
||||
if (size % sizeof(__be32) > 0 || size <= 0)
|
||||
return -EINVAL;
|
||||
if (size == sizeof(__be32))
|
||||
tcode = TCODE_READ_QUADLET_REQUEST;
|
||||
else
|
||||
tcode = TCODE_READ_BLOCK_REQUEST;
|
||||
|
||||
return snd_fw_transaction(motu->unit, tcode,
|
||||
SND_MOTU_ADDR_BASE + offset, reg, size, 0);
|
||||
}
|
||||
|
||||
int snd_motu_transaction_write(struct snd_motu *motu, u32 offset, __be32 *reg,
|
||||
size_t size)
|
||||
{
|
||||
int tcode;
|
||||
|
||||
if (size % sizeof(__be32) > 0 || size <= 0)
|
||||
return -EINVAL;
|
||||
if (size == sizeof(__be32))
|
||||
tcode = TCODE_WRITE_QUADLET_REQUEST;
|
||||
else
|
||||
tcode = TCODE_WRITE_BLOCK_REQUEST;
|
||||
|
||||
return snd_fw_transaction(motu->unit, tcode,
|
||||
SND_MOTU_ADDR_BASE + offset, reg, size, 0);
|
||||
}
|
||||
|
||||
static void handle_message(struct fw_card *card, struct fw_request *request,
|
||||
int tcode, int destination, int source,
|
||||
int generation, unsigned long long offset,
|
||||
void *data, size_t length, void *callback_data)
|
||||
{
|
||||
struct snd_motu *motu = callback_data;
|
||||
__be32 *buf = (__be32 *)data;
|
||||
unsigned long flags;
|
||||
|
||||
if (tcode != TCODE_WRITE_QUADLET_REQUEST) {
|
||||
fw_send_response(card, request, RCODE_COMPLETE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (offset != motu->async_handler.offset || length != 4) {
|
||||
fw_send_response(card, request, RCODE_ADDRESS_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&motu->lock, flags);
|
||||
motu->msg = be32_to_cpu(*buf);
|
||||
spin_unlock_irqrestore(&motu->lock, flags);
|
||||
|
||||
fw_send_response(card, request, RCODE_COMPLETE);
|
||||
|
||||
wake_up(&motu->hwdep_wait);
|
||||
}
|
||||
|
||||
int snd_motu_transaction_reregister(struct snd_motu *motu)
|
||||
{
|
||||
struct fw_device *device = fw_parent_device(motu->unit);
|
||||
__be32 data;
|
||||
int err;
|
||||
|
||||
if (motu->async_handler.callback_data == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
/* Register messaging address. Block transaction is not allowed. */
|
||||
data = cpu_to_be32((device->card->node_id << 16) |
|
||||
(motu->async_handler.offset >> 32));
|
||||
err = snd_motu_transaction_write(motu, ASYNC_ADDR_HI, &data,
|
||||
sizeof(data));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
data = cpu_to_be32(motu->async_handler.offset);
|
||||
return snd_motu_transaction_write(motu, ASYNC_ADDR_LO, &data,
|
||||
sizeof(data));
|
||||
}
|
||||
|
||||
int snd_motu_transaction_register(struct snd_motu *motu)
|
||||
{
|
||||
static const struct fw_address_region resp_register_region = {
|
||||
.start = 0xffffe0000000ull,
|
||||
.end = 0xffffe000ffffull,
|
||||
};
|
||||
int err;
|
||||
|
||||
/* Perhaps, 4 byte messages are transferred. */
|
||||
motu->async_handler.length = 4;
|
||||
motu->async_handler.address_callback = handle_message;
|
||||
motu->async_handler.callback_data = motu;
|
||||
|
||||
err = fw_core_add_address_handler(&motu->async_handler,
|
||||
&resp_register_region);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = snd_motu_transaction_reregister(motu);
|
||||
if (err < 0) {
|
||||
fw_core_remove_address_handler(&motu->async_handler);
|
||||
motu->async_handler.address_callback = NULL;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
void snd_motu_transaction_unregister(struct snd_motu *motu)
|
||||
{
|
||||
__be32 data;
|
||||
|
||||
if (motu->async_handler.address_callback != NULL)
|
||||
fw_core_remove_address_handler(&motu->async_handler);
|
||||
motu->async_handler.address_callback = NULL;
|
||||
|
||||
/* Unregister the address. */
|
||||
data = cpu_to_be32(0x00000000);
|
||||
snd_motu_transaction_write(motu, ASYNC_ADDR_HI, &data, sizeof(data));
|
||||
snd_motu_transaction_write(motu, ASYNC_ADDR_LO, &data, sizeof(data));
|
||||
}
|
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
* motu.c - a part of driver for MOTU FireWire series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "motu.h"
|
||||
|
||||
#define OUI_MOTU 0x0001f2
|
||||
|
||||
MODULE_DESCRIPTION("MOTU FireWire driver");
|
||||
MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT] = {
|
||||
/* mode 0 */
|
||||
[0] = 44100,
|
||||
[1] = 48000,
|
||||
/* mode 1 */
|
||||
[2] = 88200,
|
||||
[3] = 96000,
|
||||
/* mode 2 */
|
||||
[4] = 176400,
|
||||
[5] = 192000,
|
||||
};
|
||||
|
||||
static void name_card(struct snd_motu *motu)
|
||||
{
|
||||
struct fw_device *fw_dev = fw_parent_device(motu->unit);
|
||||
struct fw_csr_iterator it;
|
||||
int key, val;
|
||||
u32 version = 0;
|
||||
|
||||
fw_csr_iterator_init(&it, motu->unit->directory);
|
||||
while (fw_csr_iterator_next(&it, &key, &val)) {
|
||||
switch (key) {
|
||||
case CSR_VERSION:
|
||||
version = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
strcpy(motu->card->driver, "FW-MOTU");
|
||||
strcpy(motu->card->shortname, motu->spec->name);
|
||||
strcpy(motu->card->mixername, motu->spec->name);
|
||||
snprintf(motu->card->longname, sizeof(motu->card->longname),
|
||||
"MOTU %s (version:%d), GUID %08x%08x at %s, S%d",
|
||||
motu->spec->name, version,
|
||||
fw_dev->config_rom[3], fw_dev->config_rom[4],
|
||||
dev_name(&motu->unit->device), 100 << fw_dev->max_speed);
|
||||
}
|
||||
|
||||
static void motu_free(struct snd_motu *motu)
|
||||
{
|
||||
snd_motu_transaction_unregister(motu);
|
||||
|
||||
snd_motu_stream_destroy_duplex(motu);
|
||||
fw_unit_put(motu->unit);
|
||||
|
||||
mutex_destroy(&motu->mutex);
|
||||
kfree(motu);
|
||||
}
|
||||
|
||||
/*
|
||||
* This module releases the FireWire unit data after all ALSA character devices
|
||||
* are released by applications. This is for releasing stream data or finishing
|
||||
* transactions safely. Thus at returning from .remove(), this module still keep
|
||||
* references for the unit.
|
||||
*/
|
||||
static void motu_card_free(struct snd_card *card)
|
||||
{
|
||||
motu_free(card->private_data);
|
||||
}
|
||||
|
||||
static void do_registration(struct work_struct *work)
|
||||
{
|
||||
struct snd_motu *motu = container_of(work, struct snd_motu, dwork.work);
|
||||
int err;
|
||||
|
||||
if (motu->registered)
|
||||
return;
|
||||
|
||||
err = snd_card_new(&motu->unit->device, -1, NULL, THIS_MODULE, 0,
|
||||
&motu->card);
|
||||
if (err < 0)
|
||||
return;
|
||||
|
||||
name_card(motu);
|
||||
|
||||
err = snd_motu_transaction_register(motu);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = snd_motu_stream_init_duplex(motu);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
snd_motu_proc_init(motu);
|
||||
|
||||
err = snd_motu_create_pcm_devices(motu);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
if (motu->spec->flags & SND_MOTU_SPEC_HAS_MIDI) {
|
||||
err = snd_motu_create_midi_devices(motu);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
err = snd_motu_create_hwdep_device(motu);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = snd_card_register(motu->card);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
/*
|
||||
* After registered, motu instance can be released corresponding to
|
||||
* releasing the sound card instance.
|
||||
*/
|
||||
motu->card->private_free = motu_card_free;
|
||||
motu->card->private_data = motu;
|
||||
motu->registered = true;
|
||||
|
||||
return;
|
||||
error:
|
||||
snd_motu_transaction_unregister(motu);
|
||||
snd_card_free(motu->card);
|
||||
dev_info(&motu->unit->device,
|
||||
"Sound card registration failed: %d\n", err);
|
||||
}
|
||||
|
||||
static int motu_probe(struct fw_unit *unit,
|
||||
const struct ieee1394_device_id *entry)
|
||||
{
|
||||
struct snd_motu *motu;
|
||||
|
||||
/* Allocate this independently of sound card instance. */
|
||||
motu = kzalloc(sizeof(struct snd_motu), GFP_KERNEL);
|
||||
if (motu == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
motu->spec = (const struct snd_motu_spec *)entry->driver_data;
|
||||
motu->unit = fw_unit_get(unit);
|
||||
dev_set_drvdata(&unit->device, motu);
|
||||
|
||||
mutex_init(&motu->mutex);
|
||||
spin_lock_init(&motu->lock);
|
||||
init_waitqueue_head(&motu->hwdep_wait);
|
||||
|
||||
/* Allocate and register this sound card later. */
|
||||
INIT_DEFERRABLE_WORK(&motu->dwork, do_registration);
|
||||
snd_fw_schedule_registration(unit, &motu->dwork);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void motu_remove(struct fw_unit *unit)
|
||||
{
|
||||
struct snd_motu *motu = dev_get_drvdata(&unit->device);
|
||||
|
||||
/*
|
||||
* Confirm to stop the work for registration before the sound card is
|
||||
* going to be released. The work is not scheduled again because bus
|
||||
* reset handler is not called anymore.
|
||||
*/
|
||||
cancel_delayed_work_sync(&motu->dwork);
|
||||
|
||||
if (motu->registered) {
|
||||
/* No need to wait for releasing card object in this context. */
|
||||
snd_card_free_when_closed(motu->card);
|
||||
} else {
|
||||
/* Don't forget this case. */
|
||||
motu_free(motu);
|
||||
}
|
||||
}
|
||||
|
||||
static void motu_bus_update(struct fw_unit *unit)
|
||||
{
|
||||
struct snd_motu *motu = dev_get_drvdata(&unit->device);
|
||||
|
||||
/* Postpone a workqueue for deferred registration. */
|
||||
if (!motu->registered)
|
||||
snd_fw_schedule_registration(unit, &motu->dwork);
|
||||
|
||||
/* The handler address register becomes initialized. */
|
||||
snd_motu_transaction_reregister(motu);
|
||||
}
|
||||
|
||||
static struct snd_motu_spec motu_828mk2 = {
|
||||
.name = "828mk2",
|
||||
.protocol = &snd_motu_protocol_v2,
|
||||
.flags = SND_MOTU_SPEC_SUPPORT_CLOCK_X2 |
|
||||
SND_MOTU_SPEC_TX_MICINST_CHUNK |
|
||||
SND_MOTU_SPEC_TX_RETURN_CHUNK |
|
||||
SND_MOTU_SPEC_HAS_OPT_IFACE_A |
|
||||
SND_MOTU_SPEC_HAS_MIDI,
|
||||
|
||||
.analog_in_ports = 8,
|
||||
.analog_out_ports = 8,
|
||||
};
|
||||
|
||||
static struct snd_motu_spec motu_828mk3 = {
|
||||
.name = "828mk3",
|
||||
.protocol = &snd_motu_protocol_v3,
|
||||
.flags = SND_MOTU_SPEC_SUPPORT_CLOCK_X2 |
|
||||
SND_MOTU_SPEC_SUPPORT_CLOCK_X4 |
|
||||
SND_MOTU_SPEC_TX_MICINST_CHUNK |
|
||||
SND_MOTU_SPEC_TX_RETURN_CHUNK |
|
||||
SND_MOTU_SPEC_TX_REVERB_CHUNK |
|
||||
SND_MOTU_SPEC_HAS_OPT_IFACE_A |
|
||||
SND_MOTU_SPEC_HAS_OPT_IFACE_B |
|
||||
SND_MOTU_SPEC_HAS_MIDI,
|
||||
|
||||
.analog_in_ports = 8,
|
||||
.analog_out_ports = 8,
|
||||
};
|
||||
|
||||
#define SND_MOTU_DEV_ENTRY(model, data) \
|
||||
{ \
|
||||
.match_flags = IEEE1394_MATCH_VENDOR_ID | \
|
||||
IEEE1394_MATCH_MODEL_ID | \
|
||||
IEEE1394_MATCH_SPECIFIER_ID, \
|
||||
.vendor_id = OUI_MOTU, \
|
||||
.model_id = model, \
|
||||
.specifier_id = OUI_MOTU, \
|
||||
.driver_data = (kernel_ulong_t)data, \
|
||||
}
|
||||
|
||||
static const struct ieee1394_device_id motu_id_table[] = {
|
||||
SND_MOTU_DEV_ENTRY(0x101800, &motu_828mk2),
|
||||
SND_MOTU_DEV_ENTRY(0x106800, &motu_828mk3), /* FireWire only. */
|
||||
SND_MOTU_DEV_ENTRY(0x100800, &motu_828mk3), /* Hybrid. */
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(ieee1394, motu_id_table);
|
||||
|
||||
static struct fw_driver motu_driver = {
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = KBUILD_MODNAME,
|
||||
.bus = &fw_bus_type,
|
||||
},
|
||||
.probe = motu_probe,
|
||||
.update = motu_bus_update,
|
||||
.remove = motu_remove,
|
||||
.id_table = motu_id_table,
|
||||
};
|
||||
|
||||
static int __init alsa_motu_init(void)
|
||||
{
|
||||
return driver_register(&motu_driver.driver);
|
||||
}
|
||||
|
||||
static void __exit alsa_motu_exit(void)
|
||||
{
|
||||
driver_unregister(&motu_driver.driver);
|
||||
}
|
||||
|
||||
module_init(alsa_motu_init);
|
||||
module_exit(alsa_motu_exit);
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* motu.h - a part of driver for MOTU FireWire series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#ifndef SOUND_FIREWIRE_MOTU_H_INCLUDED
|
||||
#define SOUND_FIREWIRE_MOTU_H_INCLUDED
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/firewire.h>
|
||||
#include <linux/firewire-constants.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/compat.h>
|
||||
#include <linux/sched/signal.h>
|
||||
|
||||
#include <sound/control.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/info.h>
|
||||
#include <sound/rawmidi.h>
|
||||
#include <sound/firewire.h>
|
||||
#include <sound/hwdep.h>
|
||||
|
||||
#include "../lib.h"
|
||||
#include "../amdtp-stream.h"
|
||||
#include "../iso-resources.h"
|
||||
|
||||
struct snd_motu_packet_format {
|
||||
unsigned char midi_flag_offset;
|
||||
unsigned char midi_byte_offset;
|
||||
unsigned char pcm_byte_offset;
|
||||
|
||||
unsigned char msg_chunks;
|
||||
unsigned char fixed_part_pcm_chunks[3];
|
||||
unsigned char differed_part_pcm_chunks[3];
|
||||
};
|
||||
|
||||
struct snd_motu {
|
||||
struct snd_card *card;
|
||||
struct fw_unit *unit;
|
||||
struct mutex mutex;
|
||||
spinlock_t lock;
|
||||
|
||||
bool registered;
|
||||
struct delayed_work dwork;
|
||||
|
||||
/* Model dependent information. */
|
||||
const struct snd_motu_spec *spec;
|
||||
|
||||
/* For packet streaming */
|
||||
struct snd_motu_packet_format tx_packet_formats;
|
||||
struct snd_motu_packet_format rx_packet_formats;
|
||||
struct amdtp_stream tx_stream;
|
||||
struct amdtp_stream rx_stream;
|
||||
struct fw_iso_resources tx_resources;
|
||||
struct fw_iso_resources rx_resources;
|
||||
unsigned int capture_substreams;
|
||||
unsigned int playback_substreams;
|
||||
|
||||
/* For notification. */
|
||||
struct fw_address_handler async_handler;
|
||||
u32 msg;
|
||||
|
||||
/* For uapi */
|
||||
int dev_lock_count;
|
||||
bool dev_lock_changed;
|
||||
wait_queue_head_t hwdep_wait;
|
||||
};
|
||||
|
||||
enum snd_motu_spec_flags {
|
||||
SND_MOTU_SPEC_SUPPORT_CLOCK_X2 = 0x0001,
|
||||
SND_MOTU_SPEC_SUPPORT_CLOCK_X4 = 0x0002,
|
||||
SND_MOTU_SPEC_TX_MICINST_CHUNK = 0x0004,
|
||||
SND_MOTU_SPEC_TX_RETURN_CHUNK = 0x0008,
|
||||
SND_MOTU_SPEC_TX_REVERB_CHUNK = 0x0010,
|
||||
SND_MOTU_SPEC_TX_AESEBU_CHUNK = 0x0020,
|
||||
SND_MOTU_SPEC_HAS_OPT_IFACE_A = 0x0040,
|
||||
SND_MOTU_SPEC_HAS_OPT_IFACE_B = 0x0080,
|
||||
SND_MOTU_SPEC_HAS_MIDI = 0x0100,
|
||||
};
|
||||
|
||||
#define SND_MOTU_CLOCK_RATE_COUNT 6
|
||||
extern const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT];
|
||||
|
||||
enum snd_motu_clock_source {
|
||||
SND_MOTU_CLOCK_SOURCE_INTERNAL,
|
||||
SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB,
|
||||
SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT,
|
||||
SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A,
|
||||
SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B,
|
||||
SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT,
|
||||
SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A,
|
||||
SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B,
|
||||
SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX,
|
||||
SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR,
|
||||
SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC,
|
||||
SND_MOTU_CLOCK_SOURCE_UNKNOWN,
|
||||
};
|
||||
|
||||
struct snd_motu_protocol {
|
||||
int (*get_clock_rate)(struct snd_motu *motu, unsigned int *rate);
|
||||
int (*set_clock_rate)(struct snd_motu *motu, unsigned int rate);
|
||||
int (*get_clock_source)(struct snd_motu *motu,
|
||||
enum snd_motu_clock_source *source);
|
||||
int (*switch_fetching_mode)(struct snd_motu *motu, bool enable);
|
||||
int (*cache_packet_formats)(struct snd_motu *motu);
|
||||
};
|
||||
|
||||
struct snd_motu_spec {
|
||||
const char *const name;
|
||||
enum snd_motu_spec_flags flags;
|
||||
|
||||
unsigned char analog_in_ports;
|
||||
unsigned char analog_out_ports;
|
||||
|
||||
const struct snd_motu_protocol *const protocol;
|
||||
};
|
||||
|
||||
extern const struct snd_motu_protocol snd_motu_protocol_v2;
|
||||
extern const struct snd_motu_protocol snd_motu_protocol_v3;
|
||||
|
||||
int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit,
|
||||
enum amdtp_stream_direction dir,
|
||||
const struct snd_motu_protocol *const protocol);
|
||||
int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate,
|
||||
unsigned int midi_ports,
|
||||
struct snd_motu_packet_format *formats);
|
||||
int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s,
|
||||
struct snd_pcm_runtime *runtime);
|
||||
void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port,
|
||||
struct snd_rawmidi_substream *midi);
|
||||
|
||||
int snd_motu_transaction_read(struct snd_motu *motu, u32 offset, __be32 *reg,
|
||||
size_t size);
|
||||
int snd_motu_transaction_write(struct snd_motu *motu, u32 offset, __be32 *reg,
|
||||
size_t size);
|
||||
int snd_motu_transaction_register(struct snd_motu *motu);
|
||||
int snd_motu_transaction_reregister(struct snd_motu *motu);
|
||||
void snd_motu_transaction_unregister(struct snd_motu *motu);
|
||||
|
||||
int snd_motu_stream_init_duplex(struct snd_motu *motu);
|
||||
void snd_motu_stream_destroy_duplex(struct snd_motu *motu);
|
||||
int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate);
|
||||
void snd_motu_stream_stop_duplex(struct snd_motu *motu);
|
||||
int snd_motu_stream_lock_try(struct snd_motu *motu);
|
||||
void snd_motu_stream_lock_release(struct snd_motu *motu);
|
||||
|
||||
void snd_motu_proc_init(struct snd_motu *motu);
|
||||
|
||||
int snd_motu_create_pcm_devices(struct snd_motu *motu);
|
||||
|
||||
int snd_motu_create_midi_devices(struct snd_motu *motu);
|
||||
|
||||
int snd_motu_create_hwdep_device(struct snd_motu *motu);
|
||||
#endif
|
|
@ -34,7 +34,9 @@ int avc_stream_set_format(struct fw_unit *unit, enum avc_general_plug_dir dir,
|
|||
err = fcp_avc_transaction(unit, buf, len + 10, buf, len + 10,
|
||||
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
|
||||
BIT(6) | BIT(7) | BIT(8));
|
||||
if ((err > 0) && (err < len + 10))
|
||||
if (err < 0)
|
||||
;
|
||||
else if (err < len + 10)
|
||||
err = -EIO;
|
||||
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
|
||||
err = -ENOSYS;
|
||||
|
@ -77,7 +79,9 @@ int avc_stream_get_format(struct fw_unit *unit,
|
|||
err = fcp_avc_transaction(unit, buf, 12, buf, *len,
|
||||
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
|
||||
BIT(6) | BIT(7));
|
||||
if ((err > 0) && (err < 10))
|
||||
if (err < 0)
|
||||
;
|
||||
else if (err < 12)
|
||||
err = -EIO;
|
||||
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
|
||||
err = -ENOSYS;
|
||||
|
@ -139,7 +143,9 @@ int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate,
|
|||
/* do transaction and check buf[1-5] are the same against command */
|
||||
err = fcp_avc_transaction(unit, buf, 8, buf, 8,
|
||||
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5));
|
||||
if ((err > 0) && (err < 8))
|
||||
if (err < 0)
|
||||
;
|
||||
else if (err < 8)
|
||||
err = -EIO;
|
||||
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
|
||||
err = -ENOSYS;
|
||||
|
|
|
@ -18,9 +18,8 @@ static int midi_playback_open(struct snd_rawmidi_substream *substream)
|
|||
{
|
||||
struct snd_tscm *tscm = substream->rmidi->private_data;
|
||||
|
||||
/* Initialize internal status. */
|
||||
tscm->running_status[substream->number] = 0;
|
||||
tscm->on_sysex[substream->number] = 0;
|
||||
snd_fw_async_midi_port_init(&tscm->out_ports[substream->number]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -31,12 +30,15 @@ static int midi_capture_close(struct snd_rawmidi_substream *substream)
|
|||
}
|
||||
|
||||
static int midi_playback_close(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void midi_playback_drain(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_tscm *tscm = substream->rmidi->private_data;
|
||||
|
||||
snd_fw_async_midi_port_finish(&tscm->out_ports[substream->number]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
|
||||
|
@ -78,6 +80,7 @@ int snd_tscm_create_midi_devices(struct snd_tscm *tscm)
|
|||
static const struct snd_rawmidi_ops playback_ops = {
|
||||
.open = midi_playback_open,
|
||||
.close = midi_playback_close,
|
||||
.drain = midi_playback_drain,
|
||||
.trigger = midi_playback_trigger,
|
||||
};
|
||||
struct snd_rawmidi *rmidi;
|
||||
|
|
|
@ -58,39 +58,38 @@ static inline int calculate_message_bytes(u8 status)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf)
|
||||
static int fill_message(struct snd_fw_async_midi_port *port,
|
||||
struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_tscm *tscm = substream->rmidi->private_data;
|
||||
unsigned int port = substream->number;
|
||||
int i, len, consume;
|
||||
u8 *label, *msg;
|
||||
u8 status;
|
||||
|
||||
/* The first byte is used for label, the rest for MIDI bytes. */
|
||||
label = buf;
|
||||
msg = buf + 1;
|
||||
label = port->buf;
|
||||
msg = port->buf + 1;
|
||||
|
||||
consume = snd_rawmidi_transmit_peek(substream, msg, 3);
|
||||
if (consume == 0)
|
||||
return 0;
|
||||
|
||||
/* On exclusive message. */
|
||||
if (tscm->on_sysex[port]) {
|
||||
if (port->on_sysex) {
|
||||
/* Seek the end of exclusives. */
|
||||
for (i = 0; i < consume; ++i) {
|
||||
if (msg[i] == 0xf7) {
|
||||
tscm->on_sysex[port] = false;
|
||||
port->on_sysex = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* At the end of exclusive message, use label 0x07. */
|
||||
if (!tscm->on_sysex[port]) {
|
||||
if (!port->on_sysex) {
|
||||
consume = i + 1;
|
||||
*label = (port << 4) | 0x07;
|
||||
*label = (substream->number << 4) | 0x07;
|
||||
/* During exclusive message, use label 0x04. */
|
||||
} else if (consume == 3) {
|
||||
*label = (port << 4) | 0x04;
|
||||
*label = (substream->number << 4) | 0x04;
|
||||
/* We need to fill whole 3 bytes. Go to next change. */
|
||||
} else {
|
||||
return 0;
|
||||
|
@ -101,12 +100,12 @@ static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf)
|
|||
/* The beginning of exclusives. */
|
||||
if (msg[0] == 0xf0) {
|
||||
/* Transfer it in next chance in another condition. */
|
||||
tscm->on_sysex[port] = true;
|
||||
port->on_sysex = true;
|
||||
return 0;
|
||||
} else {
|
||||
/* On running-status. */
|
||||
if ((msg[0] & 0x80) != 0x80)
|
||||
status = tscm->running_status[port];
|
||||
status = port->running_status;
|
||||
else
|
||||
status = msg[0];
|
||||
|
||||
|
@ -124,18 +123,18 @@ static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf)
|
|||
|
||||
msg[2] = msg[1];
|
||||
msg[1] = msg[0];
|
||||
msg[0] = tscm->running_status[port];
|
||||
msg[0] = port->running_status;
|
||||
} else {
|
||||
/* Enough MIDI bytes were not retrieved. */
|
||||
if (consume < len)
|
||||
return 0;
|
||||
consume = len;
|
||||
|
||||
tscm->running_status[port] = msg[0];
|
||||
port->running_status = msg[0];
|
||||
}
|
||||
}
|
||||
|
||||
*label = (port << 4) | (msg[0] >> 4);
|
||||
*label = (substream->number << 4) | (msg[0] >> 4);
|
||||
}
|
||||
|
||||
if (len > 0 && len < 3)
|
||||
|
@ -144,6 +143,106 @@ static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf)
|
|||
return consume;
|
||||
}
|
||||
|
||||
static void async_midi_port_callback(struct fw_card *card, int rcode,
|
||||
void *data, size_t length,
|
||||
void *callback_data)
|
||||
{
|
||||
struct snd_fw_async_midi_port *port = callback_data;
|
||||
struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream);
|
||||
|
||||
/* This port is closed. */
|
||||
if (substream == NULL)
|
||||
return;
|
||||
|
||||
if (rcode == RCODE_COMPLETE)
|
||||
snd_rawmidi_transmit_ack(substream, port->consume_bytes);
|
||||
else if (!rcode_is_permanent_error(rcode))
|
||||
/* To start next transaction immediately for recovery. */
|
||||
port->next_ktime = 0;
|
||||
else
|
||||
/* Don't continue processing. */
|
||||
port->error = true;
|
||||
|
||||
port->idling = true;
|
||||
|
||||
if (!snd_rawmidi_transmit_empty(substream))
|
||||
schedule_work(&port->work);
|
||||
}
|
||||
|
||||
static void midi_port_work(struct work_struct *work)
|
||||
{
|
||||
struct snd_fw_async_midi_port *port =
|
||||
container_of(work, struct snd_fw_async_midi_port, work);
|
||||
struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream);
|
||||
int generation;
|
||||
|
||||
/* Under transacting or error state. */
|
||||
if (!port->idling || port->error)
|
||||
return;
|
||||
|
||||
/* Nothing to do. */
|
||||
if (substream == NULL || snd_rawmidi_transmit_empty(substream))
|
||||
return;
|
||||
|
||||
/* Do it in next chance. */
|
||||
if (ktime_after(port->next_ktime, ktime_get())) {
|
||||
schedule_work(&port->work);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fill the buffer. The callee must use snd_rawmidi_transmit_peek().
|
||||
* Later, snd_rawmidi_transmit_ack() is called.
|
||||
*/
|
||||
memset(port->buf, 0, 4);
|
||||
port->consume_bytes = fill_message(port, substream);
|
||||
if (port->consume_bytes <= 0) {
|
||||
/* Do it in next chance, immediately. */
|
||||
if (port->consume_bytes == 0) {
|
||||
port->next_ktime = 0;
|
||||
schedule_work(&port->work);
|
||||
} else {
|
||||
/* Fatal error. */
|
||||
port->error = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set interval to next transaction. */
|
||||
port->next_ktime = ktime_add_ns(ktime_get(),
|
||||
port->consume_bytes * 8 * NSEC_PER_SEC / 31250);
|
||||
|
||||
/* Start this transaction. */
|
||||
port->idling = false;
|
||||
|
||||
/*
|
||||
* In Linux FireWire core, when generation is updated with memory
|
||||
* barrier, node id has already been updated. In this module, After
|
||||
* this smp_rmb(), load/store instructions to memory are completed.
|
||||
* Thus, both of generation and node id are available with recent
|
||||
* values. This is a light-serialization solution to handle bus reset
|
||||
* events on IEEE 1394 bus.
|
||||
*/
|
||||
generation = port->parent->generation;
|
||||
smp_rmb();
|
||||
|
||||
fw_send_request(port->parent->card, &port->transaction,
|
||||
TCODE_WRITE_QUADLET_REQUEST,
|
||||
port->parent->node_id, generation,
|
||||
port->parent->max_speed,
|
||||
TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_RX_QUAD,
|
||||
port->buf, 4, async_midi_port_callback,
|
||||
port);
|
||||
}
|
||||
|
||||
void snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port)
|
||||
{
|
||||
port->idling = true;
|
||||
port->error = false;
|
||||
port->running_status = 0;
|
||||
port->on_sysex = false;
|
||||
}
|
||||
|
||||
static void handle_midi_tx(struct fw_card *card, struct fw_request *request,
|
||||
int tcode, int destination, int source,
|
||||
int generation, unsigned long long offset,
|
||||
|
@ -219,12 +318,9 @@ int snd_tscm_transaction_register(struct snd_tscm *tscm)
|
|||
goto error;
|
||||
|
||||
for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++) {
|
||||
err = snd_fw_async_midi_port_init(
|
||||
&tscm->out_ports[i], tscm->unit,
|
||||
TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_RX_QUAD,
|
||||
4, fill_message);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
tscm->out_ports[i].parent = fw_parent_device(tscm->unit);
|
||||
tscm->out_ports[i].next_ktime = 0;
|
||||
INIT_WORK(&tscm->out_ports[i].work, midi_port_work);
|
||||
}
|
||||
|
||||
return err;
|
||||
|
@ -275,7 +371,6 @@ int snd_tscm_transaction_reregister(struct snd_tscm *tscm)
|
|||
void snd_tscm_transaction_unregister(struct snd_tscm *tscm)
|
||||
{
|
||||
__be32 reg;
|
||||
unsigned int i;
|
||||
|
||||
if (tscm->async_handler.callback_data == NULL)
|
||||
return;
|
||||
|
@ -302,7 +397,4 @@ void snd_tscm_transaction_unregister(struct snd_tscm *tscm)
|
|||
|
||||
fw_core_remove_address_handler(&tscm->async_handler);
|
||||
tscm->async_handler.callback_data = NULL;
|
||||
|
||||
for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++)
|
||||
snd_fw_async_midi_port_destroy(&tscm->out_ports[i]);
|
||||
}
|
||||
|
|
|
@ -45,6 +45,23 @@ struct snd_tscm_spec {
|
|||
#define TSCM_MIDI_IN_PORT_MAX 4
|
||||
#define TSCM_MIDI_OUT_PORT_MAX 4
|
||||
|
||||
struct snd_fw_async_midi_port {
|
||||
struct fw_device *parent;
|
||||
struct work_struct work;
|
||||
bool idling;
|
||||
ktime_t next_ktime;
|
||||
bool error;
|
||||
|
||||
struct fw_transaction transaction;
|
||||
|
||||
u8 buf[4];
|
||||
u8 running_status;
|
||||
bool on_sysex;
|
||||
|
||||
struct snd_rawmidi_substream *substream;
|
||||
int consume_bytes;
|
||||
};
|
||||
|
||||
struct snd_tscm {
|
||||
struct snd_card *card;
|
||||
struct fw_unit *unit;
|
||||
|
@ -72,8 +89,6 @@ struct snd_tscm {
|
|||
|
||||
/* For MIDI message outgoing transactions. */
|
||||
struct snd_fw_async_midi_port out_ports[TSCM_MIDI_OUT_PORT_MAX];
|
||||
u8 running_status[TSCM_MIDI_OUT_PORT_MAX];
|
||||
bool on_sysex[TSCM_MIDI_OUT_PORT_MAX];
|
||||
};
|
||||
|
||||
#define TSCM_ADDR_BASE 0xffff00000000ull
|
||||
|
@ -131,6 +146,26 @@ void snd_tscm_stream_lock_changed(struct snd_tscm *tscm);
|
|||
int snd_tscm_stream_lock_try(struct snd_tscm *tscm);
|
||||
void snd_tscm_stream_lock_release(struct snd_tscm *tscm);
|
||||
|
||||
void snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port);
|
||||
|
||||
static inline void
|
||||
snd_fw_async_midi_port_run(struct snd_fw_async_midi_port *port,
|
||||
struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
if (!port->error) {
|
||||
port->substream = substream;
|
||||
schedule_work(&port->work);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
snd_fw_async_midi_port_finish(struct snd_fw_async_midi_port *port)
|
||||
{
|
||||
port->substream = NULL;
|
||||
cancel_work_sync(&port->work);
|
||||
port->error = false;
|
||||
}
|
||||
|
||||
int snd_tscm_transaction_register(struct snd_tscm *tscm);
|
||||
int snd_tscm_transaction_reregister(struct snd_tscm *tscm);
|
||||
void snd_tscm_transaction_unregister(struct snd_tscm *tscm);
|
||||
|
|
|
@ -171,7 +171,7 @@ static int check_hdac_link_power_active(struct hdac_ext_link *link, bool enable)
|
|||
{
|
||||
int timeout;
|
||||
u32 val;
|
||||
int mask = (1 << AZX_MLCTL_CPA);
|
||||
int mask = (1 << AZX_MLCTL_CPA_SHIFT);
|
||||
|
||||
udelay(3);
|
||||
timeout = 150;
|
||||
|
@ -179,10 +179,10 @@ static int check_hdac_link_power_active(struct hdac_ext_link *link, bool enable)
|
|||
do {
|
||||
val = readl(link->ml_addr + AZX_REG_ML_LCTL);
|
||||
if (enable) {
|
||||
if (((val & mask) >> AZX_MLCTL_CPA))
|
||||
if (((val & mask) >> AZX_MLCTL_CPA_SHIFT))
|
||||
return 0;
|
||||
} else {
|
||||
if (!((val & mask) >> AZX_MLCTL_CPA))
|
||||
if (!((val & mask) >> AZX_MLCTL_CPA_SHIFT))
|
||||
return 0;
|
||||
}
|
||||
udelay(3);
|
||||
|
|
|
@ -272,7 +272,7 @@ int snd_hdac_bus_parse_capabilities(struct hdac_bus *bus)
|
|||
|
||||
/* Lets walk the linked capabilities list */
|
||||
do {
|
||||
cur_cap = _snd_hdac_chip_read(l, bus, offset);
|
||||
cur_cap = _snd_hdac_chip_readl(bus, offset);
|
||||
|
||||
dev_dbg(bus->dev, "Capability version: 0x%x\n",
|
||||
(cur_cap & AZX_CAP_HDR_VER_MASK) >> AZX_CAP_HDR_VER_OFF);
|
||||
|
|
|
@ -555,12 +555,12 @@ void snd_hdac_stream_sync_trigger(struct hdac_stream *azx_dev, bool set,
|
|||
|
||||
if (!reg)
|
||||
reg = AZX_REG_SSYNC;
|
||||
val = _snd_hdac_chip_read(l, bus, reg);
|
||||
val = _snd_hdac_chip_readl(bus, reg);
|
||||
if (set)
|
||||
val |= streams;
|
||||
else
|
||||
val &= ~streams;
|
||||
_snd_hdac_chip_write(l, bus, reg, val);
|
||||
_snd_hdac_chip_writel(bus, reg, val);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_stream_sync_trigger);
|
||||
|
||||
|
|
|
@ -742,7 +742,7 @@ int snd_es1688_pcm(struct snd_card *card, struct snd_es1688 *chip, int device)
|
|||
|
||||
pcm->private_data = chip;
|
||||
pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
|
||||
sprintf(pcm->name, snd_es1688_chip_id(chip));
|
||||
strcpy(pcm->name, snd_es1688_chip_id(chip));
|
||||
chip->pcm = pcm;
|
||||
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
|
|
|
@ -1339,7 +1339,7 @@ static int snd_ali_prepare(struct snd_pcm_substream *substream)
|
|||
rate = snd_ali_get_spdif_in_rate(codec);
|
||||
if (rate == 0) {
|
||||
dev_warn(codec->card->dev,
|
||||
"ali_capture_preapre: spdif rate detect err!\n");
|
||||
"ali_capture_prepare: spdif rate detect err!\n");
|
||||
rate = 48000;
|
||||
}
|
||||
spin_lock_irq(&codec->reg_lock);
|
||||
|
|
|
@ -846,7 +846,7 @@ snd_vortex_a3d_filter_put(struct snd_kcontrol *kcontrol,
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new vortex_a3d_kcontrol = {
|
||||
static const struct snd_kcontrol_new vortex_a3d_kcontrol = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
.name = "Playback PCM advanced processing",
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||
|
|
|
@ -2279,6 +2279,9 @@ vortex_adb_allocroute(vortex_t *vortex, int dma, int nr_ch, int dir,
|
|||
} else {
|
||||
int src[2], mix[2];
|
||||
|
||||
if (nr_ch < 1)
|
||||
return -EINVAL;
|
||||
|
||||
/* Get SRC and MIXER hardware resources. */
|
||||
for (i = 0; i < nr_ch; i++) {
|
||||
if ((mix[i] =
|
||||
|
|
|
@ -757,7 +757,7 @@ snd_vortex_eqtoggle_put(struct snd_kcontrol *kcontrol,
|
|||
return 1; /* Allways changes */
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new vortex_eqtoggle_kcontrol = {
|
||||
static const struct snd_kcontrol_new vortex_eqtoggle_kcontrol = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "EQ Enable",
|
||||
.index = 0,
|
||||
|
@ -815,7 +815,7 @@ snd_vortex_eq_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucon
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new vortex_eq_kcontrol = {
|
||||
static const struct snd_kcontrol_new vortex_eq_kcontrol = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = " .",
|
||||
.index = 0,
|
||||
|
@ -855,7 +855,7 @@ snd_vortex_peaks_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new vortex_levels_kcontrol = {
|
||||
static const struct snd_kcontrol_new vortex_levels_kcontrol = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "EQ Peaks",
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
|
||||
|
|
|
@ -601,7 +601,7 @@ static int snd_vortex_pcm_vol_put(struct snd_kcontrol *kcontrol,
|
|||
|
||||
static const DECLARE_TLV_DB_MINMAX(vortex_pcm_vol_db_scale, -9600, 2400);
|
||||
|
||||
static struct snd_kcontrol_new snd_vortex_pcm_vol = {
|
||||
static const struct snd_kcontrol_new snd_vortex_pcm_vol = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
.name = "PCM Playback Volume",
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
||||
|
|
|
@ -202,7 +202,7 @@ static const struct snd_pcm_ops snd_aw2_capture_ops = {
|
|||
.pointer = snd_aw2_pcm_pointer_capture,
|
||||
};
|
||||
|
||||
static struct snd_kcontrol_new aw2_control = {
|
||||
static const struct snd_kcontrol_new aw2_control = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "PCM Capture Route",
|
||||
.index = 0,
|
||||
|
|
|
@ -598,7 +598,7 @@ static int snd_bt87x_capture_volume_put(struct snd_kcontrol *kcontrol,
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_bt87x_capture_volume = {
|
||||
static const struct snd_kcontrol_new snd_bt87x_capture_volume = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Capture Volume",
|
||||
.info = snd_bt87x_capture_volume_info,
|
||||
|
@ -634,7 +634,7 @@ static int snd_bt87x_capture_boost_put(struct snd_kcontrol *kcontrol,
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_bt87x_capture_boost = {
|
||||
static const struct snd_kcontrol_new snd_bt87x_capture_boost = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Capture Boost",
|
||||
.info = snd_bt87x_capture_boost_info,
|
||||
|
@ -676,7 +676,7 @@ static int snd_bt87x_capture_source_put(struct snd_kcontrol *kcontrol,
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_bt87x_capture_source = {
|
||||
static const struct snd_kcontrol_new snd_bt87x_capture_source = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Capture Source",
|
||||
.info = snd_bt87x_capture_source_info,
|
||||
|
|
|
@ -301,7 +301,7 @@ static int snd_ca0106_capture_mic_line_in_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ca0106_capture_mic_line_in =
|
||||
static const struct snd_kcontrol_new snd_ca0106_capture_mic_line_in =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Shared Mic/Line in Capture Switch",
|
||||
|
@ -310,7 +310,7 @@ static struct snd_kcontrol_new snd_ca0106_capture_mic_line_in =
|
|||
.put = snd_ca0106_capture_mic_line_in_put
|
||||
};
|
||||
|
||||
static struct snd_kcontrol_new snd_ca0106_capture_line_in_side_out =
|
||||
static const struct snd_kcontrol_new snd_ca0106_capture_line_in_side_out =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Shared Line in/Side out Capture Switch",
|
||||
|
|
|
@ -1045,7 +1045,7 @@ static int snd_cmipci_spdif_default_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_cmipci_spdif_default =
|
||||
static const struct snd_kcontrol_new snd_cmipci_spdif_default =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
|
||||
|
@ -1072,7 +1072,7 @@ static int snd_cmipci_spdif_mask_get(struct snd_kcontrol *kcontrol,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_cmipci_spdif_mask =
|
||||
static const struct snd_kcontrol_new snd_cmipci_spdif_mask =
|
||||
{
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
|
@ -1119,7 +1119,7 @@ static int snd_cmipci_spdif_stream_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_cmipci_spdif_stream =
|
||||
static const struct snd_kcontrol_new snd_cmipci_spdif_stream =
|
||||
{
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
|
|
|
@ -1055,7 +1055,7 @@ static int snd_cs4281_put_volume(struct snd_kcontrol *kcontrol,
|
|||
|
||||
static const DECLARE_TLV_DB_SCALE(db_scale_dsp, -4650, 150, 0);
|
||||
|
||||
static struct snd_kcontrol_new snd_cs4281_fm_vol =
|
||||
static const struct snd_kcontrol_new snd_cs4281_fm_vol =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Synth Playback Volume",
|
||||
|
@ -1066,7 +1066,7 @@ static struct snd_kcontrol_new snd_cs4281_fm_vol =
|
|||
.tlv = { .p = db_scale_dsp },
|
||||
};
|
||||
|
||||
static struct snd_kcontrol_new snd_cs4281_pcm_vol =
|
||||
static const struct snd_kcontrol_new snd_cs4281_pcm_vol =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "PCM Stream Playback Volume",
|
||||
|
|
|
@ -1039,7 +1039,7 @@ static int snd_echo_output_gain_put(struct snd_kcontrol *kcontrol,
|
|||
|
||||
#ifdef ECHOCARD_HAS_LINE_OUT_GAIN
|
||||
/* On the Mia this one controls the line-out volume */
|
||||
static struct snd_kcontrol_new snd_echo_line_output_gain = {
|
||||
static const struct snd_kcontrol_new snd_echo_line_output_gain = {
|
||||
.name = "Line Playback Volume",
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
||||
|
@ -1050,7 +1050,7 @@ static struct snd_kcontrol_new snd_echo_line_output_gain = {
|
|||
.tlv = {.p = db_scale_output_gain},
|
||||
};
|
||||
#else
|
||||
static struct snd_kcontrol_new snd_echo_pcm_output_gain = {
|
||||
static const struct snd_kcontrol_new snd_echo_pcm_output_gain = {
|
||||
.name = "PCM Playback Volume",
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ,
|
||||
|
@ -1120,7 +1120,7 @@ static int snd_echo_input_gain_put(struct snd_kcontrol *kcontrol,
|
|||
|
||||
static const DECLARE_TLV_DB_SCALE(db_scale_input_gain, -2500, 50, 0);
|
||||
|
||||
static struct snd_kcontrol_new snd_echo_line_input_gain = {
|
||||
static const struct snd_kcontrol_new snd_echo_line_input_gain = {
|
||||
.name = "Line Capture Volume",
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ,
|
||||
|
@ -1184,7 +1184,7 @@ static int snd_echo_output_nominal_put(struct snd_kcontrol *kcontrol,
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_echo_output_nominal_level = {
|
||||
static const struct snd_kcontrol_new snd_echo_output_nominal_level = {
|
||||
.name = "Line Playback Switch (-10dBV)",
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.info = snd_echo_output_nominal_info,
|
||||
|
@ -1250,7 +1250,7 @@ static int snd_echo_input_nominal_put(struct snd_kcontrol *kcontrol,
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_echo_intput_nominal_level = {
|
||||
static const struct snd_kcontrol_new snd_echo_intput_nominal_level = {
|
||||
.name = "Line Capture Switch (-10dBV)",
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.info = snd_echo_input_nominal_info,
|
||||
|
@ -1477,7 +1477,7 @@ static int snd_echo_digital_mode_put(struct snd_kcontrol *kcontrol,
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_echo_digital_mode_switch = {
|
||||
static const struct snd_kcontrol_new snd_echo_digital_mode_switch = {
|
||||
.name = "Digital mode Switch",
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
|
||||
.info = snd_echo_digital_mode_info,
|
||||
|
@ -1527,7 +1527,7 @@ static int snd_echo_spdif_mode_put(struct snd_kcontrol *kcontrol,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_echo_spdif_mode_switch = {
|
||||
static const struct snd_kcontrol_new snd_echo_spdif_mode_switch = {
|
||||
.name = "S/PDIF mode Switch",
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
|
||||
.info = snd_echo_spdif_mode_info,
|
||||
|
@ -1600,7 +1600,7 @@ static int snd_echo_clock_source_put(struct snd_kcontrol *kcontrol,
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_echo_clock_source_switch = {
|
||||
static const struct snd_kcontrol_new snd_echo_clock_source_switch = {
|
||||
.name = "Sample Clock Source",
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
.info = snd_echo_clock_source_info,
|
||||
|
@ -1643,7 +1643,7 @@ static int snd_echo_phantom_power_put(struct snd_kcontrol *kcontrol,
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_echo_phantom_power_switch = {
|
||||
static const struct snd_kcontrol_new snd_echo_phantom_power_switch = {
|
||||
.name = "Phantom power Switch",
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
|
||||
.info = snd_echo_phantom_power_info,
|
||||
|
@ -1686,7 +1686,7 @@ static int snd_echo_automute_put(struct snd_kcontrol *kcontrol,
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_echo_automute_switch = {
|
||||
static const struct snd_kcontrol_new snd_echo_automute_switch = {
|
||||
.name = "Digital Capture Switch (automute)",
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
|
||||
.info = snd_echo_automute_info,
|
||||
|
@ -1713,7 +1713,7 @@ static int snd_echo_vumeters_switch_put(struct snd_kcontrol *kcontrol,
|
|||
return 1;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_echo_vumeters_switch = {
|
||||
static const struct snd_kcontrol_new snd_echo_vumeters_switch = {
|
||||
.name = "VU-meters Switch",
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_WRITE,
|
||||
|
@ -1751,7 +1751,7 @@ static int snd_echo_vumeters_get(struct snd_kcontrol *kcontrol,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_echo_vumeters = {
|
||||
static const struct snd_kcontrol_new snd_echo_vumeters = {
|
||||
.name = "VU-meters",
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ |
|
||||
|
@ -1804,7 +1804,7 @@ static int snd_echo_channels_info_get(struct snd_kcontrol *kcontrol,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_echo_channels_info = {
|
||||
static const struct snd_kcontrol_new snd_echo_channels_info = {
|
||||
.name = "Channels info",
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_HWDEP,
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
|
||||
|
|
|
@ -1112,7 +1112,7 @@ static int snd_emu10k1x_shared_spdif_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_emu10k1x_shared_spdif =
|
||||
static const struct snd_kcontrol_new snd_emu10k1x_shared_spdif =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Analog/Digital Output Jack",
|
||||
|
@ -1171,7 +1171,7 @@ static int snd_emu10k1x_spdif_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_emu10k1x_spdif_mask_control =
|
||||
static const struct snd_kcontrol_new snd_emu10k1x_spdif_mask_control =
|
||||
{
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
|
@ -1181,7 +1181,7 @@ static struct snd_kcontrol_new snd_emu10k1x_spdif_mask_control =
|
|||
.get = snd_emu10k1x_spdif_get_mask
|
||||
};
|
||||
|
||||
static struct snd_kcontrol_new snd_emu10k1x_spdif_control =
|
||||
static const struct snd_kcontrol_new snd_emu10k1x_spdif_control =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
|
||||
|
|
|
@ -795,7 +795,7 @@ static int snd_emu1010_internal_clock_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_emu1010_internal_clock =
|
||||
static const struct snd_kcontrol_new snd_emu1010_internal_clock =
|
||||
{
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
|
@ -847,7 +847,7 @@ static int snd_emu1010_optical_out_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_emu1010_optical_out = {
|
||||
static const struct snd_kcontrol_new snd_emu1010_optical_out = {
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Optical Output Mode",
|
||||
|
@ -898,7 +898,7 @@ static int snd_emu1010_optical_in_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_emu1010_optical_in = {
|
||||
static const struct snd_kcontrol_new snd_emu1010_optical_in = {
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Optical Input Mode",
|
||||
|
@ -978,7 +978,7 @@ static int snd_audigy_i2c_capture_source_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_audigy_i2c_capture_source =
|
||||
static const struct snd_kcontrol_new snd_audigy_i2c_capture_source =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Capture Source",
|
||||
|
@ -1177,7 +1177,7 @@ static int snd_emu10k1_spdif_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_emu10k1_spdif_mask_control =
|
||||
static const struct snd_kcontrol_new snd_emu10k1_spdif_mask_control =
|
||||
{
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
|
@ -1187,7 +1187,7 @@ static struct snd_kcontrol_new snd_emu10k1_spdif_mask_control =
|
|||
.get = snd_emu10k1_spdif_get_mask
|
||||
};
|
||||
|
||||
static struct snd_kcontrol_new snd_emu10k1_spdif_control =
|
||||
static const struct snd_kcontrol_new snd_emu10k1_spdif_control =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
|
||||
|
@ -1293,7 +1293,7 @@ static int snd_emu10k1_send_routing_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_emu10k1_send_routing_control =
|
||||
static const struct snd_kcontrol_new snd_emu10k1_send_routing_control =
|
||||
{
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
|
@ -1364,7 +1364,7 @@ static int snd_emu10k1_send_volume_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_emu10k1_send_volume_control =
|
||||
static const struct snd_kcontrol_new snd_emu10k1_send_volume_control =
|
||||
{
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
|
@ -1429,7 +1429,7 @@ static int snd_emu10k1_attn_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_emu10k1_attn_control =
|
||||
static const struct snd_kcontrol_new snd_emu10k1_attn_control =
|
||||
{
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
|
@ -1501,7 +1501,7 @@ static int snd_emu10k1_efx_send_routing_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_emu10k1_efx_send_routing_control =
|
||||
static const struct snd_kcontrol_new snd_emu10k1_efx_send_routing_control =
|
||||
{
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
|
@ -1568,7 +1568,7 @@ static int snd_emu10k1_efx_send_volume_put(struct snd_kcontrol *kcontrol,
|
|||
}
|
||||
|
||||
|
||||
static struct snd_kcontrol_new snd_emu10k1_efx_send_volume_control =
|
||||
static const struct snd_kcontrol_new snd_emu10k1_efx_send_volume_control =
|
||||
{
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
|
@ -1626,7 +1626,7 @@ static int snd_emu10k1_efx_attn_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_emu10k1_efx_attn_control =
|
||||
static const struct snd_kcontrol_new snd_emu10k1_efx_attn_control =
|
||||
{
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
|
@ -1691,7 +1691,7 @@ static int snd_emu10k1_shared_spdif_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_emu10k1_shared_spdif =
|
||||
static const struct snd_kcontrol_new snd_emu10k1_shared_spdif =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "SB Live Analog/Digital Output Jack",
|
||||
|
@ -1700,7 +1700,7 @@ static struct snd_kcontrol_new snd_emu10k1_shared_spdif =
|
|||
.put = snd_emu10k1_shared_spdif_put
|
||||
};
|
||||
|
||||
static struct snd_kcontrol_new snd_audigy_shared_spdif =
|
||||
static const struct snd_kcontrol_new snd_audigy_shared_spdif =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Audigy Analog/Digital Output Jack",
|
||||
|
@ -1738,7 +1738,7 @@ static int snd_audigy_capture_boost_put(struct snd_kcontrol *kcontrol,
|
|||
return snd_ac97_update(emu->ac97, AC97_REC_GAIN, val);
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_audigy_capture_boost =
|
||||
static const struct snd_kcontrol_new snd_audigy_capture_boost =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Mic Extra Boost",
|
||||
|
|
|
@ -1542,7 +1542,7 @@ static int snd_emu10k1_pcm_efx_voices_mask_put(struct snd_kcontrol *kcontrol, st
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_emu10k1_pcm_efx_voices_mask = {
|
||||
static const struct snd_kcontrol_new snd_emu10k1_pcm_efx_voices_mask = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
.name = "Captured FX8010 Outputs",
|
||||
.info = snd_emu10k1_pcm_efx_voices_mask_info,
|
||||
|
|
|
@ -1530,7 +1530,7 @@ static int snd_es1373_rear_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ens1373_rear =
|
||||
static const struct snd_kcontrol_new snd_ens1373_rear =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "AC97 2ch->4ch Copy Switch",
|
||||
|
@ -1575,7 +1575,7 @@ static int snd_es1373_line_put(struct snd_kcontrol *kcontrol,
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ens1373_line =
|
||||
static const struct snd_kcontrol_new snd_ens1373_line =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Line In->Rear Out Switch",
|
||||
|
|
|
@ -580,6 +580,7 @@ const char *hda_get_autocfg_input_label(struct hda_codec *codec,
|
|||
has_multiple_pins = 1;
|
||||
if (has_multiple_pins && type == AUTO_PIN_MIC)
|
||||
has_multiple_pins &= check_mic_location_need(codec, cfg, input);
|
||||
has_multiple_pins |= codec->force_pin_prefix;
|
||||
return hda_get_input_pin_label(codec, &cfg->inputs[input],
|
||||
cfg->inputs[input].pin,
|
||||
has_multiple_pins);
|
||||
|
|
|
@ -1965,7 +1965,7 @@ static int vmaster_mute_mode_put(struct snd_kcontrol *kcontrol,
|
|||
return 1;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new vmaster_mute_mode = {
|
||||
static const struct snd_kcontrol_new vmaster_mute_mode = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Mute-LED Mode",
|
||||
.info = vmaster_mute_mode_info,
|
||||
|
@ -2705,7 +2705,7 @@ static int spdif_share_sw_put(struct snd_kcontrol *kcontrol,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new spdif_share_sw = {
|
||||
static const struct snd_kcontrol_new spdif_share_sw = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "IEC958 Default PCM Playback Switch",
|
||||
.info = snd_ctl_boolean_mono_info,
|
||||
|
|
|
@ -256,6 +256,7 @@ struct hda_codec {
|
|||
unsigned int dump_coef:1; /* dump processing coefs in codec proc file */
|
||||
unsigned int power_save_node:1; /* advanced PM for each widget */
|
||||
unsigned int auto_runtime_pm:1; /* enable automatic codec runtime pm */
|
||||
unsigned int force_pin_prefix:1; /* Add location prefix */
|
||||
#ifdef CONFIG_PM
|
||||
unsigned long power_on_acct;
|
||||
unsigned long power_off_acct;
|
||||
|
|
|
@ -196,6 +196,9 @@ static void parse_user_hints(struct hda_codec *codec)
|
|||
val = snd_hda_get_bool_hint(codec, "hp_mic_detect");
|
||||
if (val >= 0)
|
||||
spec->suppress_hp_mic_detect = !val;
|
||||
val = snd_hda_get_bool_hint(codec, "vmaster");
|
||||
if (val >= 0)
|
||||
spec->suppress_vmaster = !val;
|
||||
|
||||
if (!snd_hda_get_int_hint(codec, "mixer_nid", &val))
|
||||
spec->mixer_nid = val;
|
||||
|
@ -1125,6 +1128,7 @@ static const char *get_line_out_pfx(struct hda_codec *codec, int ch,
|
|||
|
||||
*index = 0;
|
||||
if (cfg->line_outs == 1 && !spec->multi_ios &&
|
||||
!codec->force_pin_prefix &&
|
||||
!cfg->hp_outs && !cfg->speaker_outs)
|
||||
return spec->vmaster_mute.hook ? "PCM" : "Master";
|
||||
|
||||
|
@ -1132,6 +1136,7 @@ static const char *get_line_out_pfx(struct hda_codec *codec, int ch,
|
|||
* use it master (or "PCM" if a vmaster hook is present)
|
||||
*/
|
||||
if (spec->multiout.num_dacs == 1 && !spec->mixer_nid &&
|
||||
!codec->force_pin_prefix &&
|
||||
!spec->multiout.hp_out_nid[0] && !spec->multiout.extra_out_nid[0])
|
||||
return spec->vmaster_mute.hook ? "PCM" : "Master";
|
||||
|
||||
|
@ -5031,7 +5036,7 @@ int snd_hda_gen_build_controls(struct hda_codec *codec)
|
|||
}
|
||||
|
||||
/* if we have no master control, let's create it */
|
||||
if (!spec->no_analog &&
|
||||
if (!spec->no_analog && !spec->suppress_vmaster &&
|
||||
!snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) {
|
||||
err = snd_hda_add_vmaster(codec, "Master Playback Volume",
|
||||
spec->vmaster_tlv, slave_pfxs,
|
||||
|
@ -5039,7 +5044,7 @@ int snd_hda_gen_build_controls(struct hda_codec *codec)
|
|||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
if (!spec->no_analog &&
|
||||
if (!spec->no_analog && !spec->suppress_vmaster &&
|
||||
!snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) {
|
||||
err = __snd_hda_add_vmaster(codec, "Master Playback Switch",
|
||||
NULL, slave_pfxs,
|
||||
|
|
|
@ -229,6 +229,7 @@ struct hda_gen_spec {
|
|||
unsigned int add_jack_modes:1; /* add i/o jack mode enum ctls */
|
||||
unsigned int power_down_unused:1; /* power down unused widgets */
|
||||
unsigned int dac_min_mute:1; /* minimal = mute for DACs */
|
||||
unsigned int suppress_vmaster:1; /* don't create vmaster kctls */
|
||||
|
||||
/* other internal flags */
|
||||
unsigned int no_analog:1; /* digital I/O only */
|
||||
|
|
|
@ -77,6 +77,7 @@ enum {
|
|||
POS_FIX_POSBUF,
|
||||
POS_FIX_VIACOMBO,
|
||||
POS_FIX_COMBO,
|
||||
POS_FIX_SKL,
|
||||
};
|
||||
|
||||
/* Defines for ATI HD Audio support in SB450 south bridge */
|
||||
|
@ -148,7 +149,7 @@ module_param_array(model, charp, NULL, 0444);
|
|||
MODULE_PARM_DESC(model, "Use the given board model.");
|
||||
module_param_array(position_fix, int, NULL, 0444);
|
||||
MODULE_PARM_DESC(position_fix, "DMA pointer read method."
|
||||
"(-1 = system default, 0 = auto, 1 = LPIB, 2 = POSBUF, 3 = VIACOMBO, 4 = COMBO).");
|
||||
"(-1 = system default, 0 = auto, 1 = LPIB, 2 = POSBUF, 3 = VIACOMBO, 4 = COMBO, 5 = SKL+).");
|
||||
module_param_array(bdl_pos_adj, int, NULL, 0644);
|
||||
MODULE_PARM_DESC(bdl_pos_adj, "BDL position adjustment offset.");
|
||||
module_param_array(probe_mask, int, NULL, 0444);
|
||||
|
@ -369,8 +370,10 @@ enum {
|
|||
#define IS_KBL_LP(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x9d71)
|
||||
#define IS_KBL_H(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0xa2f0)
|
||||
#define IS_BXT(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x5a98)
|
||||
#define IS_GLK(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x3198)
|
||||
#define IS_SKL_PLUS(pci) (IS_SKL(pci) || IS_SKL_LP(pci) || IS_BXT(pci)) || \
|
||||
IS_KBL(pci) || IS_KBL_LP(pci) || IS_KBL_H(pci)
|
||||
IS_KBL(pci) || IS_KBL_LP(pci) || IS_KBL_H(pci) || \
|
||||
IS_GLK(pci)
|
||||
|
||||
static char *driver_short_names[] = {
|
||||
[AZX_DRIVER_ICH] = "HDA Intel",
|
||||
|
@ -534,9 +537,101 @@ static void bxt_reduce_dma_latency(struct azx *chip)
|
|||
{
|
||||
u32 val;
|
||||
|
||||
val = azx_readl(chip, SKL_EM4L);
|
||||
val = azx_readl(chip, VS_EM4L);
|
||||
val &= (0x3 << 20);
|
||||
azx_writel(chip, SKL_EM4L, val);
|
||||
azx_writel(chip, VS_EM4L, val);
|
||||
}
|
||||
|
||||
/*
|
||||
* ML_LCAP bits:
|
||||
* bit 0: 6 MHz Supported
|
||||
* bit 1: 12 MHz Supported
|
||||
* bit 2: 24 MHz Supported
|
||||
* bit 3: 48 MHz Supported
|
||||
* bit 4: 96 MHz Supported
|
||||
* bit 5: 192 MHz Supported
|
||||
*/
|
||||
static int intel_get_lctl_scf(struct azx *chip)
|
||||
{
|
||||
struct hdac_bus *bus = azx_bus(chip);
|
||||
static int preferred_bits[] = { 2, 3, 1, 4, 5 };
|
||||
u32 val, t;
|
||||
int i;
|
||||
|
||||
val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCAP);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(preferred_bits); i++) {
|
||||
t = preferred_bits[i];
|
||||
if (val & (1 << t))
|
||||
return t;
|
||||
}
|
||||
|
||||
dev_warn(chip->card->dev, "set audio clock frequency to 6MHz");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int intel_ml_lctl_set_power(struct azx *chip, int state)
|
||||
{
|
||||
struct hdac_bus *bus = azx_bus(chip);
|
||||
u32 val;
|
||||
int timeout;
|
||||
|
||||
/*
|
||||
* the codecs are sharing the first link setting by default
|
||||
* If other links are enabled for stream, they need similar fix
|
||||
*/
|
||||
val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL);
|
||||
val &= ~AZX_MLCTL_SPA;
|
||||
val |= state << AZX_MLCTL_SPA_SHIFT;
|
||||
writel(val, bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL);
|
||||
/* wait for CPA */
|
||||
timeout = 50;
|
||||
while (timeout) {
|
||||
if (((readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL)) &
|
||||
AZX_MLCTL_CPA) == (state << AZX_MLCTL_CPA_SHIFT))
|
||||
return 0;
|
||||
timeout--;
|
||||
udelay(10);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void intel_init_lctl(struct azx *chip)
|
||||
{
|
||||
struct hdac_bus *bus = azx_bus(chip);
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
/* 0. check lctl register value is correct or not */
|
||||
val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL);
|
||||
/* if SCF is already set, let's use it */
|
||||
if ((val & ML_LCTL_SCF_MASK) != 0)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Before operating on SPA, CPA must match SPA.
|
||||
* Any deviation may result in undefined behavior.
|
||||
*/
|
||||
if (((val & AZX_MLCTL_SPA) >> AZX_MLCTL_SPA_SHIFT) !=
|
||||
((val & AZX_MLCTL_CPA) >> AZX_MLCTL_CPA_SHIFT))
|
||||
return;
|
||||
|
||||
/* 1. turn link down: set SPA to 0 and wait CPA to 0 */
|
||||
ret = intel_ml_lctl_set_power(chip, 0);
|
||||
udelay(100);
|
||||
if (ret)
|
||||
goto set_spa;
|
||||
|
||||
/* 2. update SCF to select a properly audio clock*/
|
||||
val &= ~ML_LCTL_SCF_MASK;
|
||||
val |= intel_get_lctl_scf(chip);
|
||||
writel(val, bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL);
|
||||
|
||||
set_spa:
|
||||
/* 4. turn link up: set SPA to 1 and wait CPA to 1 */
|
||||
intel_ml_lctl_set_power(chip, 1);
|
||||
udelay(100);
|
||||
}
|
||||
|
||||
static void hda_intel_init_chip(struct azx *chip, bool full_reset)
|
||||
|
@ -564,6 +659,9 @@ static void hda_intel_init_chip(struct azx *chip, bool full_reset)
|
|||
/* reduce dma latency to avoid noise */
|
||||
if (IS_BXT(pci))
|
||||
bxt_reduce_dma_latency(chip);
|
||||
|
||||
if (bus->mlcap != NULL)
|
||||
intel_init_lctl(chip);
|
||||
}
|
||||
|
||||
/* calculate runtime delay from LPIB */
|
||||
|
@ -815,6 +913,31 @@ static unsigned int azx_via_get_position(struct azx *chip,
|
|||
return bound_pos + mod_dma_pos;
|
||||
}
|
||||
|
||||
static unsigned int azx_skl_get_dpib_pos(struct azx *chip,
|
||||
struct azx_dev *azx_dev)
|
||||
{
|
||||
return _snd_hdac_chip_readl(azx_bus(chip),
|
||||
AZX_REG_VS_SDXDPIB_XBASE +
|
||||
(AZX_REG_VS_SDXDPIB_XINTERVAL *
|
||||
azx_dev->core.index));
|
||||
}
|
||||
|
||||
/* get the current DMA position with correction on SKL+ chips */
|
||||
static unsigned int azx_get_pos_skl(struct azx *chip, struct azx_dev *azx_dev)
|
||||
{
|
||||
/* DPIB register gives a more accurate position for playback */
|
||||
if (azx_dev->core.substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
return azx_skl_get_dpib_pos(chip, azx_dev);
|
||||
|
||||
/* For capture, we need to read posbuf, but it requires a delay
|
||||
* for the possible boundary overlap; the read of DPIB fetches the
|
||||
* actual posbuf
|
||||
*/
|
||||
udelay(20);
|
||||
azx_skl_get_dpib_pos(chip, azx_dev);
|
||||
return azx_get_pos_posbuf(chip, azx_dev);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static DEFINE_MUTEX(card_list_lock);
|
||||
static LIST_HEAD(card_list);
|
||||
|
@ -1351,6 +1474,7 @@ static int check_position_fix(struct azx *chip, int fix)
|
|||
case POS_FIX_POSBUF:
|
||||
case POS_FIX_VIACOMBO:
|
||||
case POS_FIX_COMBO:
|
||||
case POS_FIX_SKL:
|
||||
return fix;
|
||||
}
|
||||
|
||||
|
@ -1371,6 +1495,10 @@ static int check_position_fix(struct azx *chip, int fix)
|
|||
dev_dbg(chip->card->dev, "Using LPIB position fix\n");
|
||||
return POS_FIX_LPIB;
|
||||
}
|
||||
if (IS_SKL_PLUS(chip->pci)) {
|
||||
dev_dbg(chip->card->dev, "Using SKL position fix\n");
|
||||
return POS_FIX_SKL;
|
||||
}
|
||||
return POS_FIX_AUTO;
|
||||
}
|
||||
|
||||
|
@ -1382,6 +1510,7 @@ static void assign_position_fix(struct azx *chip, int fix)
|
|||
[POS_FIX_POSBUF] = azx_get_pos_posbuf,
|
||||
[POS_FIX_VIACOMBO] = azx_via_get_position,
|
||||
[POS_FIX_COMBO] = azx_get_pos_lpib,
|
||||
[POS_FIX_SKL] = azx_get_pos_skl,
|
||||
};
|
||||
|
||||
chip->get_position[0] = chip->get_position[1] = callbacks[fix];
|
||||
|
@ -1390,7 +1519,7 @@ static void assign_position_fix(struct azx *chip, int fix)
|
|||
if (fix == POS_FIX_COMBO)
|
||||
chip->get_position[1] = NULL;
|
||||
|
||||
if (fix == POS_FIX_POSBUF &&
|
||||
if ((fix == POS_FIX_POSBUF || fix == POS_FIX_SKL) &&
|
||||
(chip->driver_caps & AZX_DCAPS_COUNT_LPIB_DELAY)) {
|
||||
chip->get_delay[0] = chip->get_delay[1] =
|
||||
azx_get_delay_from_lpib;
|
||||
|
|
|
@ -857,7 +857,7 @@ static int chipio_write_address(struct hda_codec *codec,
|
|||
chip_addx >> 16);
|
||||
}
|
||||
|
||||
spec->curr_chip_addx = (res < 0) ? ~0UL : chip_addx;
|
||||
spec->curr_chip_addx = (res < 0) ? ~0U : chip_addx;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
@ -882,7 +882,7 @@ static int chipio_write_data(struct hda_codec *codec, unsigned int data)
|
|||
/*If no error encountered, automatically increment the address
|
||||
as per chip behaviour*/
|
||||
spec->curr_chip_addx = (res != -EIO) ?
|
||||
(spec->curr_chip_addx + 4) : ~0UL;
|
||||
(spec->curr_chip_addx + 4) : ~0U;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -933,7 +933,7 @@ static int chipio_read_data(struct hda_codec *codec, unsigned int *data)
|
|||
/*If no error encountered, automatically increment the address
|
||||
as per chip behaviour*/
|
||||
spec->curr_chip_addx = (res != -EIO) ?
|
||||
(spec->curr_chip_addx + 4) : ~0UL;
|
||||
(spec->curr_chip_addx + 4) : ~0U;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -1168,7 +1168,7 @@ static int dspio_write_multiple(struct hda_codec *codec,
|
|||
int status = 0;
|
||||
unsigned int count;
|
||||
|
||||
if ((buffer == NULL))
|
||||
if (buffer == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
count = 0;
|
||||
|
@ -1210,7 +1210,7 @@ static int dspio_read_multiple(struct hda_codec *codec, unsigned int *buffer,
|
|||
unsigned int skip_count;
|
||||
unsigned int dummy;
|
||||
|
||||
if ((buffer == NULL))
|
||||
if (buffer == NULL)
|
||||
return -1;
|
||||
|
||||
count = 0;
|
||||
|
|
|
@ -52,6 +52,12 @@ struct conexant_spec {
|
|||
bool dc_enable;
|
||||
unsigned int dc_input_bias; /* offset into olpc_xo_dc_bias */
|
||||
struct nid_path *dc_mode_path;
|
||||
|
||||
int mute_led_polarity;
|
||||
unsigned int gpio_led;
|
||||
unsigned int gpio_mute_led_mask;
|
||||
unsigned int gpio_mic_led_mask;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -264,6 +270,7 @@ enum {
|
|||
CXT_FIXUP_HP_DOCK,
|
||||
CXT_FIXUP_HP_SPECTRE,
|
||||
CXT_FIXUP_HP_GATE_MIC,
|
||||
CXT_FIXUP_MUTE_LED_GPIO,
|
||||
};
|
||||
|
||||
/* for hda_fixup_thinkpad_acpi() */
|
||||
|
@ -646,6 +653,74 @@ static void cxt_fixup_hp_gate_mic_jack(struct hda_codec *codec,
|
|||
snd_hda_jack_set_gating_jack(codec, 0x19, 0x16);
|
||||
}
|
||||
|
||||
/* update LED status via GPIO */
|
||||
static void cxt_update_gpio_led(struct hda_codec *codec, unsigned int mask,
|
||||
bool enabled)
|
||||
{
|
||||
struct conexant_spec *spec = codec->spec;
|
||||
unsigned int oldval = spec->gpio_led;
|
||||
|
||||
if (spec->mute_led_polarity)
|
||||
enabled = !enabled;
|
||||
|
||||
if (enabled)
|
||||
spec->gpio_led &= ~mask;
|
||||
else
|
||||
spec->gpio_led |= mask;
|
||||
if (spec->gpio_led != oldval)
|
||||
snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
|
||||
spec->gpio_led);
|
||||
}
|
||||
|
||||
/* turn on/off mute LED via GPIO per vmaster hook */
|
||||
static void cxt_fixup_gpio_mute_hook(void *private_data, int enabled)
|
||||
{
|
||||
struct hda_codec *codec = private_data;
|
||||
struct conexant_spec *spec = codec->spec;
|
||||
|
||||
cxt_update_gpio_led(codec, spec->gpio_mute_led_mask, enabled);
|
||||
}
|
||||
|
||||
/* turn on/off mic-mute LED via GPIO per capture hook */
|
||||
static void cxt_fixup_gpio_mic_mute_hook(struct hda_codec *codec,
|
||||
struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct conexant_spec *spec = codec->spec;
|
||||
|
||||
if (ucontrol)
|
||||
cxt_update_gpio_led(codec, spec->gpio_mic_led_mask,
|
||||
ucontrol->value.integer.value[0] ||
|
||||
ucontrol->value.integer.value[1]);
|
||||
}
|
||||
|
||||
|
||||
static void cxt_fixup_mute_led_gpio(struct hda_codec *codec,
|
||||
const struct hda_fixup *fix, int action)
|
||||
{
|
||||
struct conexant_spec *spec = codec->spec;
|
||||
static const struct hda_verb gpio_init[] = {
|
||||
{ 0x01, AC_VERB_SET_GPIO_MASK, 0x03 },
|
||||
{ 0x01, AC_VERB_SET_GPIO_DIRECTION, 0x03 },
|
||||
{}
|
||||
};
|
||||
codec_info(codec, "action: %d gpio_led: %d\n", action, spec->gpio_led);
|
||||
|
||||
if (action == HDA_FIXUP_ACT_PRE_PROBE) {
|
||||
spec->gen.vmaster_mute.hook = cxt_fixup_gpio_mute_hook;
|
||||
spec->gen.cap_sync_hook = cxt_fixup_gpio_mic_mute_hook;
|
||||
spec->gpio_led = 0;
|
||||
spec->mute_led_polarity = 0;
|
||||
spec->gpio_mute_led_mask = 0x01;
|
||||
spec->gpio_mic_led_mask = 0x02;
|
||||
}
|
||||
snd_hda_add_verbs(codec, gpio_init);
|
||||
if (spec->gpio_led)
|
||||
snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
|
||||
spec->gpio_led);
|
||||
}
|
||||
|
||||
|
||||
/* ThinkPad X200 & co with cxt5051 */
|
||||
static const struct hda_pintbl cxt_pincfg_lenovo_x200[] = {
|
||||
{ 0x16, 0x042140ff }, /* HP (seq# overridden) */
|
||||
|
@ -799,6 +874,10 @@ static const struct hda_fixup cxt_fixups[] = {
|
|||
.type = HDA_FIXUP_FUNC,
|
||||
.v.func = cxt_fixup_hp_gate_mic_jack,
|
||||
},
|
||||
[CXT_FIXUP_MUTE_LED_GPIO] = {
|
||||
.type = HDA_FIXUP_FUNC,
|
||||
.v.func = cxt_fixup_mute_led_gpio,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct snd_pci_quirk cxt5045_fixups[] = {
|
||||
|
@ -851,6 +930,7 @@ static const struct snd_pci_quirk cxt5066_fixups[] = {
|
|||
SND_PCI_QUIRK(0x103c, 0x8079, "HP EliteBook 840 G3", CXT_FIXUP_HP_DOCK),
|
||||
SND_PCI_QUIRK(0x103c, 0x8174, "HP Spectre x360", CXT_FIXUP_HP_SPECTRE),
|
||||
SND_PCI_QUIRK(0x103c, 0x8115, "HP Z1 Gen3", CXT_FIXUP_HP_GATE_MIC),
|
||||
SND_PCI_QUIRK(0x103c, 0x814f, "HP ZBook 15u G3", CXT_FIXUP_MUTE_LED_GPIO),
|
||||
SND_PCI_QUIRK(0x1043, 0x138d, "Asus", CXT_FIXUP_HEADPHONE_MIC_PIN),
|
||||
SND_PCI_QUIRK(0x152d, 0x0833, "OLPC XO-1.5", CXT_FIXUP_OLPC_XO),
|
||||
SND_PCI_QUIRK(0x17aa, 0x20f2, "Lenovo T400", CXT_PINCFG_LENOVO_TP410),
|
||||
|
@ -882,6 +962,7 @@ static const struct hda_model_fixup cxt5066_fixup_models[] = {
|
|||
{ .id = CXT_FIXUP_OLPC_XO, .name = "olpc-xo" },
|
||||
{ .id = CXT_FIXUP_MUTE_LED_EAPD, .name = "mute-led-eapd" },
|
||||
{ .id = CXT_FIXUP_HP_DOCK, .name = "hp-dock" },
|
||||
{ .id = CXT_FIXUP_MUTE_LED_GPIO, .name = "mute-led-gpio" },
|
||||
{}
|
||||
};
|
||||
|
||||
|
|
|
@ -177,6 +177,7 @@ struct hdmi_spec {
|
|||
bool i915_bound; /* was i915 bound in this driver? */
|
||||
|
||||
struct hdac_chmap chmap;
|
||||
hda_nid_t vendor_nid;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_SND_HDA_I915
|
||||
|
@ -383,7 +384,7 @@ static int hdmi_eld_ctl_get(struct snd_kcontrol *kcontrol,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new eld_bytes_ctl = {
|
||||
static const struct snd_kcontrol_new eld_bytes_ctl = {
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
.name = "ELD",
|
||||
|
@ -2372,6 +2373,7 @@ static void intel_haswell_fixup_connect_list(struct hda_codec *codec,
|
|||
}
|
||||
|
||||
#define INTEL_VENDOR_NID 0x08
|
||||
#define INTEL_GLK_VENDOR_NID 0x0B
|
||||
#define INTEL_GET_VENDOR_VERB 0xf81
|
||||
#define INTEL_SET_VENDOR_VERB 0x781
|
||||
#define INTEL_EN_DP12 0x02 /* enable DP 1.2 features */
|
||||
|
@ -2381,14 +2383,15 @@ static void intel_haswell_enable_all_pins(struct hda_codec *codec,
|
|||
bool update_tree)
|
||||
{
|
||||
unsigned int vendor_param;
|
||||
struct hdmi_spec *spec = codec->spec;
|
||||
|
||||
vendor_param = snd_hda_codec_read(codec, INTEL_VENDOR_NID, 0,
|
||||
vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0,
|
||||
INTEL_GET_VENDOR_VERB, 0);
|
||||
if (vendor_param == -1 || vendor_param & INTEL_EN_ALL_PIN_CVTS)
|
||||
return;
|
||||
|
||||
vendor_param |= INTEL_EN_ALL_PIN_CVTS;
|
||||
vendor_param = snd_hda_codec_read(codec, INTEL_VENDOR_NID, 0,
|
||||
vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0,
|
||||
INTEL_SET_VENDOR_VERB, vendor_param);
|
||||
if (vendor_param == -1)
|
||||
return;
|
||||
|
@ -2400,8 +2403,9 @@ static void intel_haswell_enable_all_pins(struct hda_codec *codec,
|
|||
static void intel_haswell_fixup_enable_dp12(struct hda_codec *codec)
|
||||
{
|
||||
unsigned int vendor_param;
|
||||
struct hdmi_spec *spec = codec->spec;
|
||||
|
||||
vendor_param = snd_hda_codec_read(codec, INTEL_VENDOR_NID, 0,
|
||||
vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0,
|
||||
INTEL_GET_VENDOR_VERB, 0);
|
||||
if (vendor_param == -1 || vendor_param & INTEL_EN_DP12)
|
||||
return;
|
||||
|
@ -2409,7 +2413,7 @@ static void intel_haswell_fixup_enable_dp12(struct hda_codec *codec)
|
|||
/* enable DP1.2 mode */
|
||||
vendor_param |= INTEL_EN_DP12;
|
||||
snd_hdac_regmap_add_vendor_verb(&codec->core, INTEL_SET_VENDOR_VERB);
|
||||
snd_hda_codec_write_cache(codec, INTEL_VENDOR_NID, 0,
|
||||
snd_hda_codec_write_cache(codec, spec->vendor_nid, 0,
|
||||
INTEL_SET_VENDOR_VERB, vendor_param);
|
||||
}
|
||||
|
||||
|
@ -2503,7 +2507,7 @@ static void i915_pin_cvt_fixup(struct hda_codec *codec,
|
|||
}
|
||||
|
||||
/* Intel Haswell and onwards; audio component with eld notifier */
|
||||
static int patch_i915_hsw_hdmi(struct hda_codec *codec)
|
||||
static int intel_hsw_common_init(struct hda_codec *codec, hda_nid_t vendor_nid)
|
||||
{
|
||||
struct hdmi_spec *spec;
|
||||
int err;
|
||||
|
@ -2520,6 +2524,7 @@ static int patch_i915_hsw_hdmi(struct hda_codec *codec)
|
|||
spec = codec->spec;
|
||||
codec->dp_mst = true;
|
||||
spec->dyn_pcm_assign = true;
|
||||
spec->vendor_nid = vendor_nid;
|
||||
|
||||
intel_haswell_enable_all_pins(codec, true);
|
||||
intel_haswell_fixup_enable_dp12(codec);
|
||||
|
@ -2548,6 +2553,16 @@ static int patch_i915_hsw_hdmi(struct hda_codec *codec)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int patch_i915_hsw_hdmi(struct hda_codec *codec)
|
||||
{
|
||||
return intel_hsw_common_init(codec, INTEL_VENDOR_NID);
|
||||
}
|
||||
|
||||
static int patch_i915_glk_hdmi(struct hda_codec *codec)
|
||||
{
|
||||
return intel_hsw_common_init(codec, INTEL_GLK_VENDOR_NID);
|
||||
}
|
||||
|
||||
/* Intel Baytrail and Braswell; with eld notifier */
|
||||
static int patch_i915_byt_hdmi(struct hda_codec *codec)
|
||||
{
|
||||
|
@ -3800,7 +3815,7 @@ HDA_CODEC_ENTRY(0x80862808, "Broadwell HDMI", patch_i915_hsw_hdmi),
|
|||
HDA_CODEC_ENTRY(0x80862809, "Skylake HDMI", patch_i915_hsw_hdmi),
|
||||
HDA_CODEC_ENTRY(0x8086280a, "Broxton HDMI", patch_i915_hsw_hdmi),
|
||||
HDA_CODEC_ENTRY(0x8086280b, "Kabylake HDMI", patch_i915_hsw_hdmi),
|
||||
HDA_CODEC_ENTRY(0x8086280d, "Geminilake HDMI", patch_i915_hsw_hdmi),
|
||||
HDA_CODEC_ENTRY(0x8086280d, "Geminilake HDMI", patch_i915_glk_hdmi),
|
||||
HDA_CODEC_ENTRY(0x80862880, "CedarTrail HDMI", patch_generic_hdmi),
|
||||
HDA_CODEC_ENTRY(0x80862882, "Valleyview2 HDMI", patch_i915_byt_hdmi),
|
||||
HDA_CODEC_ENTRY(0x80862883, "Braswell HDMI", patch_i915_byt_hdmi),
|
||||
|
|
|
@ -1800,6 +1800,7 @@ enum {
|
|||
ALC882_FIXUP_NO_PRIMARY_HP,
|
||||
ALC887_FIXUP_ASUS_BASS,
|
||||
ALC887_FIXUP_BASS_CHMAP,
|
||||
ALC1220_FIXUP_GB_DUAL_CODECS,
|
||||
};
|
||||
|
||||
static void alc889_fixup_coef(struct hda_codec *codec,
|
||||
|
@ -1962,6 +1963,61 @@ static void alc882_fixup_no_primary_hp(struct hda_codec *codec,
|
|||
static void alc_fixup_bass_chmap(struct hda_codec *codec,
|
||||
const struct hda_fixup *fix, int action);
|
||||
|
||||
/* For dual-codec configuration, we need to disable some features to avoid
|
||||
* conflicts of kctls and PCM streams
|
||||
*/
|
||||
static void alc_fixup_dual_codecs(struct hda_codec *codec,
|
||||
const struct hda_fixup *fix, int action)
|
||||
{
|
||||
struct alc_spec *spec = codec->spec;
|
||||
|
||||
if (action != HDA_FIXUP_ACT_PRE_PROBE)
|
||||
return;
|
||||
/* disable vmaster */
|
||||
spec->gen.suppress_vmaster = 1;
|
||||
/* auto-mute and auto-mic switch don't work with multiple codecs */
|
||||
spec->gen.suppress_auto_mute = 1;
|
||||
spec->gen.suppress_auto_mic = 1;
|
||||
/* disable aamix as well */
|
||||
spec->gen.mixer_nid = 0;
|
||||
/* add location prefix to avoid conflicts */
|
||||
codec->force_pin_prefix = 1;
|
||||
}
|
||||
|
||||
static void rename_ctl(struct hda_codec *codec, const char *oldname,
|
||||
const char *newname)
|
||||
{
|
||||
struct snd_kcontrol *kctl;
|
||||
|
||||
kctl = snd_hda_find_mixer_ctl(codec, oldname);
|
||||
if (kctl)
|
||||
strcpy(kctl->id.name, newname);
|
||||
}
|
||||
|
||||
static void alc1220_fixup_gb_dual_codecs(struct hda_codec *codec,
|
||||
const struct hda_fixup *fix,
|
||||
int action)
|
||||
{
|
||||
alc_fixup_dual_codecs(codec, fix, action);
|
||||
switch (action) {
|
||||
case HDA_FIXUP_ACT_PRE_PROBE:
|
||||
/* override card longname to provide a unique UCM profile */
|
||||
strcpy(codec->card->longname, "HDAudio-Gigabyte-ALC1220DualCodecs");
|
||||
break;
|
||||
case HDA_FIXUP_ACT_BUILD:
|
||||
/* rename Capture controls depending on the codec */
|
||||
rename_ctl(codec, "Capture Volume",
|
||||
codec->addr == 0 ?
|
||||
"Rear-Panel Capture Volume" :
|
||||
"Front-Panel Capture Volume");
|
||||
rename_ctl(codec, "Capture Switch",
|
||||
codec->addr == 0 ?
|
||||
"Rear-Panel Capture Switch" :
|
||||
"Front-Panel Capture Switch");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct hda_fixup alc882_fixups[] = {
|
||||
[ALC882_FIXUP_ABIT_AW9D_MAX] = {
|
||||
.type = HDA_FIXUP_PINS,
|
||||
|
@ -2198,6 +2254,10 @@ static const struct hda_fixup alc882_fixups[] = {
|
|||
.type = HDA_FIXUP_FUNC,
|
||||
.v.func = alc_fixup_bass_chmap,
|
||||
},
|
||||
[ALC1220_FIXUP_GB_DUAL_CODECS] = {
|
||||
.type = HDA_FIXUP_FUNC,
|
||||
.v.func = alc1220_fixup_gb_dual_codecs,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct snd_pci_quirk alc882_fixup_tbl[] = {
|
||||
|
@ -2267,6 +2327,7 @@ static const struct snd_pci_quirk alc882_fixup_tbl[] = {
|
|||
SND_PCI_QUIRK(0x1462, 0x7350, "MSI-7350", ALC889_FIXUP_CD),
|
||||
SND_PCI_QUIRK_VENDOR(0x1462, "MSI", ALC882_FIXUP_GPIO3),
|
||||
SND_PCI_QUIRK(0x1458, 0xa002, "Gigabyte EP45-DS3/Z87X-UD3H", ALC889_FIXUP_FRONT_HP_NO_PRESENCE),
|
||||
SND_PCI_QUIRK(0x1458, 0xa0b8, "Gigabyte AZ370-Gaming", ALC1220_FIXUP_GB_DUAL_CODECS),
|
||||
SND_PCI_QUIRK(0x147b, 0x107a, "Abit AW9D-MAX", ALC882_FIXUP_ABIT_AW9D_MAX),
|
||||
SND_PCI_QUIRK_VENDOR(0x1558, "Clevo laptop", ALC882_FIXUP_EAPD),
|
||||
SND_PCI_QUIRK(0x161f, 0x2054, "Medion laptop", ALC883_FIXUP_EAPD),
|
||||
|
@ -4663,7 +4724,6 @@ static void alc282_fixup_asus_tx300(struct hda_codec *codec,
|
|||
{ 0x1b, 0x21114000 }, /* dock speaker pin */
|
||||
{}
|
||||
};
|
||||
struct snd_kcontrol *kctl;
|
||||
|
||||
switch (action) {
|
||||
case HDA_FIXUP_ACT_PRE_PROBE:
|
||||
|
@ -4678,12 +4738,10 @@ static void alc282_fixup_asus_tx300(struct hda_codec *codec,
|
|||
/* this is a bit tricky; give more sane names for the main
|
||||
* (tablet) speaker and the dock speaker, respectively
|
||||
*/
|
||||
kctl = snd_hda_find_mixer_ctl(codec, "Speaker Playback Switch");
|
||||
if (kctl)
|
||||
strcpy(kctl->id.name, "Dock Speaker Playback Switch");
|
||||
kctl = snd_hda_find_mixer_ctl(codec, "Bass Speaker Playback Switch");
|
||||
if (kctl)
|
||||
strcpy(kctl->id.name, "Speaker Playback Switch");
|
||||
rename_ctl(codec, "Speaker Playback Switch",
|
||||
"Dock Speaker Playback Switch");
|
||||
rename_ctl(codec, "Bass Speaker Playback Switch",
|
||||
"Speaker Playback Switch");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -4766,6 +4824,30 @@ static void alc280_fixup_hp_9480m(struct hda_codec *codec,
|
|||
}
|
||||
}
|
||||
|
||||
static void alc233_alc662_fixup_lenovo_dual_codecs(struct hda_codec *codec,
|
||||
const struct hda_fixup *fix,
|
||||
int action)
|
||||
{
|
||||
alc_fixup_dual_codecs(codec, fix, action);
|
||||
switch (action) {
|
||||
case HDA_FIXUP_ACT_PRE_PROBE:
|
||||
/* override card longname to provide a unique UCM profile */
|
||||
strcpy(codec->card->longname, "HDAudio-Lenovo-DualCodecs");
|
||||
break;
|
||||
case HDA_FIXUP_ACT_BUILD:
|
||||
/* rename Capture controls depending on the codec */
|
||||
rename_ctl(codec, "Capture Volume",
|
||||
codec->addr == 0 ?
|
||||
"Rear-Panel Capture Volume" :
|
||||
"Front-Panel Capture Volume");
|
||||
rename_ctl(codec, "Capture Switch",
|
||||
codec->addr == 0 ?
|
||||
"Rear-Panel Capture Switch" :
|
||||
"Front-Panel Capture Switch");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* for hda_fixup_thinkpad_acpi() */
|
||||
#include "thinkpad_helper.c"
|
||||
|
||||
|
@ -4833,6 +4915,8 @@ enum {
|
|||
ALC290_FIXUP_SUBWOOFER_HSJACK,
|
||||
ALC269_FIXUP_THINKPAD_ACPI,
|
||||
ALC269_FIXUP_DMIC_THINKPAD_ACPI,
|
||||
ALC255_FIXUP_ACER_MIC_NO_PRESENCE,
|
||||
ALC255_FIXUP_ASUS_MIC_NO_PRESENCE,
|
||||
ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
|
||||
ALC255_FIXUP_DELL2_MIC_NO_PRESENCE,
|
||||
ALC255_FIXUP_HEADSET_MODE,
|
||||
|
@ -4872,6 +4956,12 @@ enum {
|
|||
ALC256_FIXUP_DELL_INSPIRON_7559_SUBWOOFER,
|
||||
ALC269_FIXUP_ATIV_BOOK_8,
|
||||
ALC221_FIXUP_HP_MIC_NO_PRESENCE,
|
||||
ALC256_FIXUP_ASUS_HEADSET_MODE,
|
||||
ALC256_FIXUP_ASUS_MIC,
|
||||
ALC256_FIXUP_ASUS_AIO_GPIO2,
|
||||
ALC233_FIXUP_ASUS_MIC_NO_PRESENCE,
|
||||
ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE,
|
||||
ALC233_FIXUP_LENOVO_MULTI_CODECS,
|
||||
};
|
||||
|
||||
static const struct hda_fixup alc269_fixups[] = {
|
||||
|
@ -5289,6 +5379,24 @@ static const struct hda_fixup alc269_fixups[] = {
|
|||
.chained = true,
|
||||
.chain_id = ALC269_FIXUP_THINKPAD_ACPI,
|
||||
},
|
||||
[ALC255_FIXUP_ACER_MIC_NO_PRESENCE] = {
|
||||
.type = HDA_FIXUP_PINS,
|
||||
.v.pins = (const struct hda_pintbl[]) {
|
||||
{ 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */
|
||||
{ }
|
||||
},
|
||||
.chained = true,
|
||||
.chain_id = ALC255_FIXUP_HEADSET_MODE
|
||||
},
|
||||
[ALC255_FIXUP_ASUS_MIC_NO_PRESENCE] = {
|
||||
.type = HDA_FIXUP_PINS,
|
||||
.v.pins = (const struct hda_pintbl[]) {
|
||||
{ 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */
|
||||
{ }
|
||||
},
|
||||
.chained = true,
|
||||
.chain_id = ALC255_FIXUP_HEADSET_MODE
|
||||
},
|
||||
[ALC255_FIXUP_DELL1_MIC_NO_PRESENCE] = {
|
||||
.type = HDA_FIXUP_PINS,
|
||||
.v.pins = (const struct hda_pintbl[]) {
|
||||
|
@ -5579,6 +5687,54 @@ static const struct hda_fixup alc269_fixups[] = {
|
|||
.chained = true,
|
||||
.chain_id = ALC269_FIXUP_HEADSET_MODE
|
||||
},
|
||||
[ALC256_FIXUP_ASUS_HEADSET_MODE] = {
|
||||
.type = HDA_FIXUP_FUNC,
|
||||
.v.func = alc_fixup_headset_mode,
|
||||
},
|
||||
[ALC256_FIXUP_ASUS_MIC] = {
|
||||
.type = HDA_FIXUP_PINS,
|
||||
.v.pins = (const struct hda_pintbl[]) {
|
||||
{ 0x13, 0x90a60160 }, /* use as internal mic */
|
||||
{ 0x19, 0x04a11120 }, /* use as headset mic, without its own jack detect */
|
||||
{ }
|
||||
},
|
||||
.chained = true,
|
||||
.chain_id = ALC256_FIXUP_ASUS_HEADSET_MODE
|
||||
},
|
||||
[ALC256_FIXUP_ASUS_AIO_GPIO2] = {
|
||||
.type = HDA_FIXUP_VERBS,
|
||||
.v.verbs = (const struct hda_verb[]) {
|
||||
/* Set up GPIO2 for the speaker amp */
|
||||
{ 0x01, AC_VERB_SET_GPIO_MASK, 0x04 },
|
||||
{ 0x01, AC_VERB_SET_GPIO_DIRECTION, 0x04 },
|
||||
{ 0x01, AC_VERB_SET_GPIO_DATA, 0x04 },
|
||||
{}
|
||||
},
|
||||
},
|
||||
[ALC233_FIXUP_ASUS_MIC_NO_PRESENCE] = {
|
||||
.type = HDA_FIXUP_PINS,
|
||||
.v.pins = (const struct hda_pintbl[]) {
|
||||
{ 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */
|
||||
{ }
|
||||
},
|
||||
.chained = true,
|
||||
.chain_id = ALC269_FIXUP_HEADSET_MIC
|
||||
},
|
||||
[ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE] = {
|
||||
.type = HDA_FIXUP_VERBS,
|
||||
.v.verbs = (const struct hda_verb[]) {
|
||||
/* Enables internal speaker */
|
||||
{0x20, AC_VERB_SET_COEF_INDEX, 0x40},
|
||||
{0x20, AC_VERB_SET_PROC_COEF, 0x8800},
|
||||
{}
|
||||
},
|
||||
.chained = true,
|
||||
.chain_id = ALC233_FIXUP_ASUS_MIC_NO_PRESENCE
|
||||
},
|
||||
[ALC233_FIXUP_LENOVO_MULTI_CODECS] = {
|
||||
.type = HDA_FIXUP_FUNC,
|
||||
.v.func = alc233_alc662_fixup_lenovo_dual_codecs,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct snd_pci_quirk alc269_fixup_tbl[] = {
|
||||
|
@ -5692,15 +5848,27 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
|
|||
SND_PCI_QUIRK(0x103c, 0x8256, "HP", ALC221_FIXUP_HP_FRONT_MIC),
|
||||
SND_PCI_QUIRK(0x103c, 0x82bf, "HP", ALC221_FIXUP_HP_MIC_NO_PRESENCE),
|
||||
SND_PCI_QUIRK(0x103c, 0x82c0, "HP", ALC221_FIXUP_HP_MIC_NO_PRESENCE),
|
||||
SND_PCI_QUIRK(0x1043, 0x103e, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC),
|
||||
SND_PCI_QUIRK(0x1043, 0x103f, "ASUS TX300", ALC282_FIXUP_ASUS_TX300),
|
||||
SND_PCI_QUIRK(0x1043, 0x106d, "Asus K53BE", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
|
||||
SND_PCI_QUIRK(0x1043, 0x10c0, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC),
|
||||
SND_PCI_QUIRK(0x1043, 0x115d, "Asus 1015E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
|
||||
SND_PCI_QUIRK(0x1043, 0x12f0, "ASUS X541UV", ALC256_FIXUP_ASUS_MIC),
|
||||
SND_PCI_QUIRK(0x1043, 0x12e0, "ASUS X541SA", ALC256_FIXUP_ASUS_MIC),
|
||||
SND_PCI_QUIRK(0x1043, 0x13b0, "ASUS Z550SA", ALC256_FIXUP_ASUS_MIC),
|
||||
SND_PCI_QUIRK(0x1043, 0x1427, "Asus Zenbook UX31E", ALC269VB_FIXUP_ASUS_ZENBOOK),
|
||||
SND_PCI_QUIRK(0x1043, 0x1517, "Asus Zenbook UX31A", ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A),
|
||||
SND_PCI_QUIRK(0x1043, 0x16e3, "ASUS UX50", ALC269_FIXUP_STEREO_DMIC),
|
||||
SND_PCI_QUIRK(0x1043, 0x1a13, "Asus G73Jw", ALC269_FIXUP_ASUS_G73JW),
|
||||
SND_PCI_QUIRK(0x1043, 0x1b13, "Asus U41SV", ALC269_FIXUP_INV_DMIC),
|
||||
SND_PCI_QUIRK(0x1043, 0x1c23, "Asus X55U", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
|
||||
SND_PCI_QUIRK(0x1043, 0x1bbd, "ASUS Z550MA", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE),
|
||||
SND_PCI_QUIRK(0x1043, 0x10d0, "ASUS X540LA/X540LJ", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE),
|
||||
SND_PCI_QUIRK(0x1043, 0x11c0, "ASUS X556UR", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE),
|
||||
SND_PCI_QUIRK(0x1043, 0x1290, "ASUS X441SA", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE),
|
||||
SND_PCI_QUIRK(0x1043, 0x12a0, "ASUS X441UV", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE),
|
||||
SND_PCI_QUIRK(0x1043, 0x1ccd, "ASUS X555UB", ALC256_FIXUP_ASUS_MIC),
|
||||
SND_PCI_QUIRK(0x1043, 0x3030, "ASUS ZN270IE", ALC256_FIXUP_ASUS_AIO_GPIO2),
|
||||
SND_PCI_QUIRK(0x1043, 0x831a, "ASUS P901", ALC269_FIXUP_STEREO_DMIC),
|
||||
SND_PCI_QUIRK(0x1043, 0x834a, "ASUS S101", ALC269_FIXUP_STEREO_DMIC),
|
||||
SND_PCI_QUIRK(0x1043, 0x8398, "ASUS P1005", ALC269_FIXUP_STEREO_DMIC),
|
||||
|
@ -5721,6 +5889,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
|
|||
SND_PCI_QUIRK(0x144d, 0xc740, "Samsung Ativ book 8 (NP870Z5G)", ALC269_FIXUP_ATIV_BOOK_8),
|
||||
SND_PCI_QUIRK(0x1458, 0xfa53, "Gigabyte BXBT-2807", ALC283_FIXUP_HEADSET_MIC),
|
||||
SND_PCI_QUIRK(0x1462, 0xb120, "MSI Cubi MS-B120", ALC283_FIXUP_HEADSET_MIC),
|
||||
SND_PCI_QUIRK(0x17aa, 0x1036, "Lenovo P520", ALC233_FIXUP_LENOVO_MULTI_CODECS),
|
||||
SND_PCI_QUIRK(0x17aa, 0x20f2, "Thinkpad SL410/510", ALC269_FIXUP_SKU_IGNORE),
|
||||
SND_PCI_QUIRK(0x17aa, 0x215e, "Thinkpad L512", ALC269_FIXUP_SKU_IGNORE),
|
||||
SND_PCI_QUIRK(0x17aa, 0x21b8, "Thinkpad Edge 14", ALC269_FIXUP_SKU_IGNORE),
|
||||
|
@ -5875,6 +6044,18 @@ static const struct hda_model_fixup alc269_fixup_models[] = {
|
|||
{0x21, 0x03211020}
|
||||
|
||||
static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = {
|
||||
SND_HDA_PIN_QUIRK(0x10ec0255, 0x1025, "Acer", ALC255_FIXUP_ACER_MIC_NO_PRESENCE,
|
||||
{0x12, 0x90a601c0},
|
||||
{0x14, 0x90171120},
|
||||
{0x21, 0x02211030}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0255, 0x1043, "ASUS", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE,
|
||||
{0x14, 0x90170110},
|
||||
{0x1b, 0x90a70130},
|
||||
{0x21, 0x03211020}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0255, 0x1043, "ASUS", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE,
|
||||
{0x1a, 0x90a70130},
|
||||
{0x1b, 0x90170110},
|
||||
{0x21, 0x03211020}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0225, 0x1028, "Dell", ALC225_FIXUP_DELL1_MIC_NO_PRESENCE,
|
||||
ALC225_STANDARD_PINS,
|
||||
{0x12, 0xb7a60130},
|
||||
|
@ -5995,6 +6176,14 @@ static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = {
|
|||
{0x21, 0x02211020}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
|
||||
ALC256_STANDARD_PINS),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC,
|
||||
{0x14, 0x90170110},
|
||||
{0x1b, 0x90a70130},
|
||||
{0x21, 0x04211020}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC,
|
||||
{0x14, 0x90170110},
|
||||
{0x1b, 0x90a70130},
|
||||
{0x21, 0x03211020}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0280, 0x103c, "HP", ALC280_FIXUP_HP_GPIO4,
|
||||
{0x12, 0x90a60130},
|
||||
{0x14, 0x90170110},
|
||||
|
@ -6714,6 +6903,7 @@ enum {
|
|||
ALC668_FIXUP_DELL_DISABLE_AAMIX,
|
||||
ALC668_FIXUP_DELL_XPS13,
|
||||
ALC662_FIXUP_ASUS_Nx50,
|
||||
ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE,
|
||||
ALC668_FIXUP_ASUS_Nx51,
|
||||
ALC891_FIXUP_HEADSET_MODE,
|
||||
ALC891_FIXUP_DELL_MIC_NO_PRESENCE,
|
||||
|
@ -6721,6 +6911,7 @@ enum {
|
|||
ALC892_FIXUP_ASROCK_MOBO,
|
||||
ALC662_FIXUP_USI_FUNC,
|
||||
ALC662_FIXUP_USI_HEADSET_MODE,
|
||||
ALC662_FIXUP_LENOVO_MULTI_CODECS,
|
||||
};
|
||||
|
||||
static const struct hda_fixup alc662_fixups[] = {
|
||||
|
@ -6967,14 +7158,21 @@ static const struct hda_fixup alc662_fixups[] = {
|
|||
.chained = true,
|
||||
.chain_id = ALC662_FIXUP_BASS_1A
|
||||
},
|
||||
[ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE] = {
|
||||
.type = HDA_FIXUP_FUNC,
|
||||
.v.func = alc_fixup_headset_mode_alc668,
|
||||
.chain_id = ALC662_FIXUP_BASS_CHMAP
|
||||
},
|
||||
[ALC668_FIXUP_ASUS_Nx51] = {
|
||||
.type = HDA_FIXUP_PINS,
|
||||
.v.pins = (const struct hda_pintbl[]) {
|
||||
{0x1a, 0x90170151}, /* bass speaker */
|
||||
{ 0x19, 0x03a1913d }, /* use as headphone mic, without its own jack detect */
|
||||
{ 0x1a, 0x90170151 }, /* bass speaker */
|
||||
{ 0x1b, 0x03a1113c }, /* use as headset mic, without its own jack detect */
|
||||
{}
|
||||
},
|
||||
.chained = true,
|
||||
.chain_id = ALC662_FIXUP_BASS_CHMAP,
|
||||
.chain_id = ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE,
|
||||
},
|
||||
[ALC891_FIXUP_HEADSET_MODE] = {
|
||||
.type = HDA_FIXUP_FUNC,
|
||||
|
@ -7019,6 +7217,10 @@ static const struct hda_fixup alc662_fixups[] = {
|
|||
.chained = true,
|
||||
.chain_id = ALC662_FIXUP_USI_FUNC
|
||||
},
|
||||
[ALC662_FIXUP_LENOVO_MULTI_CODECS] = {
|
||||
.type = HDA_FIXUP_FUNC,
|
||||
.v.func = alc233_alc662_fixup_lenovo_dual_codecs,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct snd_pci_quirk alc662_fixup_tbl[] = {
|
||||
|
@ -7056,6 +7258,7 @@ static const struct snd_pci_quirk alc662_fixup_tbl[] = {
|
|||
SND_PCI_QUIRK(0x105b, 0x0cd6, "Foxconn", ALC662_FIXUP_ASUS_MODE2),
|
||||
SND_PCI_QUIRK(0x144d, 0xc051, "Samsung R720", ALC662_FIXUP_IDEAPAD),
|
||||
SND_PCI_QUIRK(0x14cd, 0x5003, "USI", ALC662_FIXUP_USI_HEADSET_MODE),
|
||||
SND_PCI_QUIRK(0x17aa, 0x1036, "Lenovo P520", ALC662_FIXUP_LENOVO_MULTI_CODECS),
|
||||
SND_PCI_QUIRK(0x17aa, 0x38af, "Lenovo Ideapad Y550P", ALC662_FIXUP_IDEAPAD),
|
||||
SND_PCI_QUIRK(0x17aa, 0x3a0d, "Lenovo Ideapad Y550", ALC662_FIXUP_IDEAPAD),
|
||||
SND_PCI_QUIRK(0x1849, 0x5892, "ASRock B150M", ALC892_FIXUP_ASROCK_MOBO),
|
||||
|
|
|
@ -432,7 +432,7 @@ static int snd_ice1712_delta1010lt_wordclock_status_get(struct snd_kcontrol *kco
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ice1712_delta1010lt_wordclock_status =
|
||||
static const struct snd_kcontrol_new snd_ice1712_delta1010lt_wordclock_status =
|
||||
{
|
||||
.access = (SNDRV_CTL_ELEM_ACCESS_READ),
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
|
|
|
@ -719,7 +719,7 @@ static int snd_ice1712_ews88mt_input_sense_put(struct snd_kcontrol *kcontrol, st
|
|||
return ndata != data;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ice1712_ews88mt_input_sense = {
|
||||
static const struct snd_kcontrol_new snd_ice1712_ews88mt_input_sense = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Input Sensitivity Switch",
|
||||
.info = snd_ice1712_ewx_io_sense_info,
|
||||
|
@ -728,7 +728,7 @@ static struct snd_kcontrol_new snd_ice1712_ews88mt_input_sense = {
|
|||
.count = 8,
|
||||
};
|
||||
|
||||
static struct snd_kcontrol_new snd_ice1712_ews88mt_output_sense = {
|
||||
static const struct snd_kcontrol_new snd_ice1712_ews88mt_output_sense = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Output Sensitivity Switch",
|
||||
.info = snd_ice1712_ewx_io_sense_info,
|
||||
|
|
|
@ -279,7 +279,7 @@ static int snd_ice1712_digmix_route_ac97_put(struct snd_kcontrol *kcontrol, stru
|
|||
return val != nval;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ice1712_mixer_digmix_route_ac97 = {
|
||||
static const struct snd_kcontrol_new snd_ice1712_mixer_digmix_route_ac97 = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Digital Mixer To AC97",
|
||||
.info = snd_ice1712_digmix_route_ac97_info,
|
||||
|
@ -1410,7 +1410,7 @@ static struct snd_kcontrol_new snd_ice1712_multi_capture_analog_switch = {
|
|||
.private_value = 10,
|
||||
};
|
||||
|
||||
static struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_switch = {
|
||||
static const struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_switch = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = SNDRV_CTL_NAME_IEC958("Multi ", CAPTURE, SWITCH),
|
||||
.info = snd_ice1712_pro_mixer_switch_info,
|
||||
|
@ -1432,7 +1432,7 @@ static struct snd_kcontrol_new snd_ice1712_multi_capture_analog_volume = {
|
|||
.tlv = { .p = db_scale_playback }
|
||||
};
|
||||
|
||||
static struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_volume = {
|
||||
static const struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_volume = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = SNDRV_CTL_NAME_IEC958("Multi ", CAPTURE, VOLUME),
|
||||
.info = snd_ice1712_pro_mixer_volume_info,
|
||||
|
@ -1630,7 +1630,7 @@ static int snd_ice1712_eeprom_get(struct snd_kcontrol *kcontrol,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ice1712_eeprom = {
|
||||
static const struct snd_kcontrol_new snd_ice1712_eeprom = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
|
||||
.name = "ICE1712 EEPROM",
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
||||
|
@ -1666,7 +1666,7 @@ static int snd_ice1712_spdif_default_put(struct snd_kcontrol *kcontrol,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ice1712_spdif_default =
|
||||
static const struct snd_kcontrol_new snd_ice1712_spdif_default =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
|
||||
|
@ -1717,7 +1717,7 @@ static int snd_ice1712_spdif_maskp_get(struct snd_kcontrol *kcontrol,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ice1712_spdif_maskc =
|
||||
static const struct snd_kcontrol_new snd_ice1712_spdif_maskc =
|
||||
{
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
|
@ -1726,7 +1726,7 @@ static struct snd_kcontrol_new snd_ice1712_spdif_maskc =
|
|||
.get = snd_ice1712_spdif_maskc_get,
|
||||
};
|
||||
|
||||
static struct snd_kcontrol_new snd_ice1712_spdif_maskp =
|
||||
static const struct snd_kcontrol_new snd_ice1712_spdif_maskp =
|
||||
{
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
|
@ -1753,7 +1753,7 @@ static int snd_ice1712_spdif_stream_put(struct snd_kcontrol *kcontrol,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ice1712_spdif_stream =
|
||||
static const struct snd_kcontrol_new snd_ice1712_spdif_stream =
|
||||
{
|
||||
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
||||
SNDRV_CTL_ELEM_ACCESS_INACTIVE),
|
||||
|
@ -1878,7 +1878,7 @@ static int snd_ice1712_pro_internal_clock_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ice1712_pro_internal_clock = {
|
||||
static const struct snd_kcontrol_new snd_ice1712_pro_internal_clock = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Multi Track Internal Clock",
|
||||
.info = snd_ice1712_pro_internal_clock_info,
|
||||
|
@ -1943,7 +1943,7 @@ static int snd_ice1712_pro_internal_clock_default_put(struct snd_kcontrol *kcont
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ice1712_pro_internal_clock_default = {
|
||||
static const struct snd_kcontrol_new snd_ice1712_pro_internal_clock_default = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Multi Track Internal Clock Default",
|
||||
.info = snd_ice1712_pro_internal_clock_default_info,
|
||||
|
@ -1974,7 +1974,7 @@ static int snd_ice1712_pro_rate_locking_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ice1712_pro_rate_locking = {
|
||||
static const struct snd_kcontrol_new snd_ice1712_pro_rate_locking = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Multi Track Rate Locking",
|
||||
.info = snd_ice1712_pro_rate_locking_info,
|
||||
|
@ -2005,7 +2005,7 @@ static int snd_ice1712_pro_rate_reset_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ice1712_pro_rate_reset = {
|
||||
static const struct snd_kcontrol_new snd_ice1712_pro_rate_reset = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Multi Track Rate Reset",
|
||||
.info = snd_ice1712_pro_rate_reset_info,
|
||||
|
@ -2173,7 +2173,7 @@ static struct snd_kcontrol_new snd_ice1712_mixer_pro_analog_route = {
|
|||
.put = snd_ice1712_pro_route_analog_put,
|
||||
};
|
||||
|
||||
static struct snd_kcontrol_new snd_ice1712_mixer_pro_spdif_route = {
|
||||
static const struct snd_kcontrol_new snd_ice1712_mixer_pro_spdif_route = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, NONE) "Route",
|
||||
.info = snd_ice1712_pro_route_info,
|
||||
|
@ -2215,7 +2215,7 @@ static int snd_ice1712_pro_volume_rate_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ice1712_mixer_pro_volume_rate = {
|
||||
static const struct snd_kcontrol_new snd_ice1712_mixer_pro_volume_rate = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Multi Track Volume Rate",
|
||||
.info = snd_ice1712_pro_volume_rate_info,
|
||||
|
@ -2248,7 +2248,7 @@ static int snd_ice1712_pro_peak_get(struct snd_kcontrol *kcontrol,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ice1712_mixer_pro_peak = {
|
||||
static const struct snd_kcontrol_new snd_ice1712_mixer_pro_peak = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
.name = "Multi Track Peak",
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
|
||||
|
|
|
@ -1598,7 +1598,7 @@ static int snd_vt1724_eeprom_get(struct snd_kcontrol *kcontrol,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_vt1724_eeprom = {
|
||||
static const struct snd_kcontrol_new snd_vt1724_eeprom = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
|
||||
.name = "ICE1724 EEPROM",
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
||||
|
@ -1711,7 +1711,7 @@ static int snd_vt1724_spdif_default_put(struct snd_kcontrol *kcontrol,
|
|||
return val != old;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_vt1724_spdif_default =
|
||||
static const struct snd_kcontrol_new snd_vt1724_spdif_default =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
|
||||
|
@ -1743,7 +1743,7 @@ static int snd_vt1724_spdif_maskp_get(struct snd_kcontrol *kcontrol,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_vt1724_spdif_maskc =
|
||||
static const struct snd_kcontrol_new snd_vt1724_spdif_maskc =
|
||||
{
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
|
@ -1752,7 +1752,7 @@ static struct snd_kcontrol_new snd_vt1724_spdif_maskc =
|
|||
.get = snd_vt1724_spdif_maskc_get,
|
||||
};
|
||||
|
||||
static struct snd_kcontrol_new snd_vt1724_spdif_maskp =
|
||||
static const struct snd_kcontrol_new snd_vt1724_spdif_maskp =
|
||||
{
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
|
@ -1789,7 +1789,7 @@ static int snd_vt1724_spdif_sw_put(struct snd_kcontrol *kcontrol,
|
|||
return old != val;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_vt1724_spdif_switch =
|
||||
static const struct snd_kcontrol_new snd_vt1724_spdif_switch =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
/* FIXME: the following conflict with IEC958 Playback Route */
|
||||
|
@ -1964,7 +1964,7 @@ static int snd_vt1724_pro_internal_clock_put(struct snd_kcontrol *kcontrol,
|
|||
return old_rate != new_rate;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_vt1724_pro_internal_clock = {
|
||||
static const struct snd_kcontrol_new snd_vt1724_pro_internal_clock = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Multi Track Internal Clock",
|
||||
.info = snd_vt1724_pro_internal_clock_info,
|
||||
|
@ -1995,7 +1995,7 @@ static int snd_vt1724_pro_rate_locking_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_vt1724_pro_rate_locking = {
|
||||
static const struct snd_kcontrol_new snd_vt1724_pro_rate_locking = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Multi Track Rate Locking",
|
||||
.info = snd_vt1724_pro_rate_locking_info,
|
||||
|
@ -2026,7 +2026,7 @@ static int snd_vt1724_pro_rate_reset_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_vt1724_pro_rate_reset = {
|
||||
static const struct snd_kcontrol_new snd_vt1724_pro_rate_reset = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Multi Track Rate Reset",
|
||||
.info = snd_vt1724_pro_rate_reset_info,
|
||||
|
@ -2151,7 +2151,7 @@ static struct snd_kcontrol_new snd_vt1724_mixer_pro_analog_route =
|
|||
.put = snd_vt1724_pro_route_analog_put,
|
||||
};
|
||||
|
||||
static struct snd_kcontrol_new snd_vt1724_mixer_pro_spdif_route = {
|
||||
static const struct snd_kcontrol_new snd_vt1724_mixer_pro_spdif_route = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, NONE) "Route",
|
||||
.info = snd_vt1724_pro_route_info,
|
||||
|
@ -2187,7 +2187,7 @@ static int snd_vt1724_pro_peak_get(struct snd_kcontrol *kcontrol,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_vt1724_mixer_pro_peak = {
|
||||
static const struct snd_kcontrol_new snd_vt1724_mixer_pro_peak = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
.name = "Multi Track Peak",
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
|
||||
|
|
|
@ -645,7 +645,7 @@ static int lola_input_src_put(struct snd_kcontrol *kcontrol,
|
|||
return lola_set_src_config(chip, mask, true);
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new lola_input_src_mixer = {
|
||||
static const struct snd_kcontrol_new lola_input_src_mixer = {
|
||||
.name = "Digital SRC Capture Switch",
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.info = lola_input_src_info,
|
||||
|
|
|
@ -899,7 +899,7 @@ static int lx_control_playback_put(struct snd_kcontrol *kcontrol,
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new lx_control_playback_switch = {
|
||||
static const struct snd_kcontrol_new lx_control_playback_switch = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "PCM Playback Switch",
|
||||
.index = 0,
|
||||
|
|
|
@ -448,7 +448,7 @@ static int mixart_audio_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_ele
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new mixart_control_output_switch = {
|
||||
static const struct snd_kcontrol_new mixart_control_output_switch = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Master Playback Switch",
|
||||
.info = mixart_sw_info, /* shared */
|
||||
|
@ -1024,7 +1024,7 @@ static int mixart_monitor_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new mixart_control_monitor_vol = {
|
||||
static const struct snd_kcontrol_new mixart_control_monitor_vol = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
||||
SNDRV_CTL_ELEM_ACCESS_TLV_READ),
|
||||
|
@ -1091,7 +1091,7 @@ static int mixart_monitor_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_e
|
|||
return (changed != 0);
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new mixart_control_monitor_sw = {
|
||||
static const struct snd_kcontrol_new mixart_control_monitor_sw = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Monitoring Switch",
|
||||
.info = mixart_sw_info, /* shared */
|
||||
|
|
|
@ -767,6 +767,8 @@ static int get_oxygen_model(struct oxygen *chip,
|
|||
[MODEL_FANTASIA] = "TempoTec HiFier Fantasia",
|
||||
[MODEL_SERENADE] = "TempoTec HiFier Serenade",
|
||||
[MODEL_HG2PCI] = "CMI8787-HG2PCI",
|
||||
[MODEL_XONAR_DG] = "Xonar DG",
|
||||
[MODEL_XONAR_DGX] = "Xonar DGX",
|
||||
};
|
||||
|
||||
chip->model = model_generic;
|
||||
|
@ -829,12 +831,8 @@ static int get_oxygen_model(struct oxygen *chip,
|
|||
chip->model.dac_channels_mixer = 2;
|
||||
break;
|
||||
case MODEL_XONAR_DG:
|
||||
chip->model = model_xonar_dg;
|
||||
chip->model.shortname = "Xonar DG";
|
||||
break;
|
||||
case MODEL_XONAR_DGX:
|
||||
chip->model = model_xonar_dg;
|
||||
chip->model.shortname = "Xonar DGX";
|
||||
break;
|
||||
}
|
||||
if (id->driver_data == MODEL_MERIDIAN ||
|
||||
|
|
|
@ -744,7 +744,7 @@ static int hr222_mic_vol_put(struct snd_kcontrol *kcontrol,
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new hr222_control_mic_level = {
|
||||
static const struct snd_kcontrol_new hr222_control_mic_level = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
||||
SNDRV_CTL_ELEM_ACCESS_TLV_READ),
|
||||
|
@ -794,7 +794,7 @@ static int hr222_mic_boost_put(struct snd_kcontrol *kcontrol,
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new hr222_control_mic_boost = {
|
||||
static const struct snd_kcontrol_new hr222_control_mic_boost = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
||||
SNDRV_CTL_ELEM_ACCESS_TLV_READ),
|
||||
|
@ -836,7 +836,7 @@ static int hr222_phantom_power_put(struct snd_kcontrol *kcontrol,
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new hr222_phantom_power_switch = {
|
||||
static const struct snd_kcontrol_new hr222_phantom_power_switch = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Phantom Power Switch",
|
||||
.info = hr222_phantom_power_info,
|
||||
|
|
|
@ -235,7 +235,7 @@ static int pcxhr_audio_sw_put(struct snd_kcontrol *kcontrol,
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new pcxhr_control_output_switch = {
|
||||
static const struct snd_kcontrol_new pcxhr_control_output_switch = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Master Playback Switch",
|
||||
.info = pcxhr_sw_info, /* shared */
|
||||
|
@ -460,7 +460,7 @@ static int pcxhr_pcm_sw_put(struct snd_kcontrol *kcontrol,
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new pcxhr_control_pcm_switch = {
|
||||
static const struct snd_kcontrol_new pcxhr_control_pcm_switch = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "PCM Playback Switch",
|
||||
.count = PCXHR_PLAYBACK_STREAMS,
|
||||
|
@ -509,7 +509,7 @@ static int pcxhr_monitor_vol_put(struct snd_kcontrol *kcontrol,
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new pcxhr_control_monitor_vol = {
|
||||
static const struct snd_kcontrol_new pcxhr_control_monitor_vol = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
||||
SNDRV_CTL_ELEM_ACCESS_TLV_READ),
|
||||
|
@ -562,7 +562,7 @@ static int pcxhr_monitor_sw_put(struct snd_kcontrol *kcontrol,
|
|||
return (changed != 0);
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new pcxhr_control_monitor_sw = {
|
||||
static const struct snd_kcontrol_new pcxhr_control_monitor_sw = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Monitoring Playback Switch",
|
||||
.info = pcxhr_sw_info, /* shared */
|
||||
|
@ -697,7 +697,7 @@ static int pcxhr_audio_src_put(struct snd_kcontrol *kcontrol,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new pcxhr_control_audio_src = {
|
||||
static const struct snd_kcontrol_new pcxhr_control_audio_src = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Capture Source",
|
||||
.info = pcxhr_audio_src_info,
|
||||
|
@ -798,7 +798,7 @@ static int pcxhr_clock_type_put(struct snd_kcontrol *kcontrol,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new pcxhr_control_clock_type = {
|
||||
static const struct snd_kcontrol_new pcxhr_control_clock_type = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Clock Mode",
|
||||
.info = pcxhr_clock_type_info,
|
||||
|
@ -842,7 +842,7 @@ static int pcxhr_clock_rate_get(struct snd_kcontrol *kcontrol,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new pcxhr_control_clock_rate = {
|
||||
static const struct snd_kcontrol_new pcxhr_control_clock_rate = {
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
|
||||
.name = "Clock Rates",
|
||||
|
@ -1017,14 +1017,14 @@ static int pcxhr_iec958_put(struct snd_kcontrol *kcontrol,
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new pcxhr_control_playback_iec958_mask = {
|
||||
static const struct snd_kcontrol_new pcxhr_control_playback_iec958_mask = {
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK),
|
||||
.info = pcxhr_iec958_info,
|
||||
.get = pcxhr_iec958_mask_get
|
||||
};
|
||||
static struct snd_kcontrol_new pcxhr_control_playback_iec958 = {
|
||||
static const struct snd_kcontrol_new pcxhr_control_playback_iec958 = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
|
||||
.info = pcxhr_iec958_info,
|
||||
|
@ -1033,14 +1033,14 @@ static struct snd_kcontrol_new pcxhr_control_playback_iec958 = {
|
|||
.private_value = 0 /* playback */
|
||||
};
|
||||
|
||||
static struct snd_kcontrol_new pcxhr_control_capture_iec958_mask = {
|
||||
static const struct snd_kcontrol_new pcxhr_control_capture_iec958_mask = {
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
.name = SNDRV_CTL_NAME_IEC958("",CAPTURE,MASK),
|
||||
.info = pcxhr_iec958_info,
|
||||
.get = pcxhr_iec958_mask_get
|
||||
};
|
||||
static struct snd_kcontrol_new pcxhr_control_capture_iec958 = {
|
||||
static const struct snd_kcontrol_new pcxhr_control_capture_iec958 = {
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
.name = SNDRV_CTL_NAME_IEC958("",CAPTURE,DEFAULT),
|
||||
|
|
|
@ -2356,7 +2356,7 @@ static int snd_trident_spdif_control_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_trident_spdif_control =
|
||||
static const struct snd_kcontrol_new snd_trident_spdif_control =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH),
|
||||
|
@ -2419,7 +2419,7 @@ static int snd_trident_spdif_default_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_trident_spdif_default =
|
||||
static const struct snd_kcontrol_new snd_trident_spdif_default =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
|
||||
|
@ -2452,7 +2452,7 @@ static int snd_trident_spdif_mask_get(struct snd_kcontrol *kcontrol,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_trident_spdif_mask =
|
||||
static const struct snd_kcontrol_new snd_trident_spdif_mask =
|
||||
{
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
|
@ -2514,7 +2514,7 @@ static int snd_trident_spdif_stream_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_trident_spdif_stream =
|
||||
static const struct snd_kcontrol_new snd_trident_spdif_stream =
|
||||
{
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
|
@ -2564,7 +2564,7 @@ static int snd_trident_ac97_control_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_trident_ac97_rear_control =
|
||||
static const struct snd_kcontrol_new snd_trident_ac97_rear_control =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Rear Path",
|
||||
|
@ -2622,7 +2622,7 @@ static int snd_trident_vol_control_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_trident_vol_music_control =
|
||||
static const struct snd_kcontrol_new snd_trident_vol_music_control =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Music Playback Volume",
|
||||
|
@ -2633,7 +2633,7 @@ static struct snd_kcontrol_new snd_trident_vol_music_control =
|
|||
.tlv = { .p = db_scale_gvol },
|
||||
};
|
||||
|
||||
static struct snd_kcontrol_new snd_trident_vol_wave_control =
|
||||
static const struct snd_kcontrol_new snd_trident_vol_wave_control =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Wave Playback Volume",
|
||||
|
@ -2700,7 +2700,7 @@ static int snd_trident_pcm_vol_control_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_trident_pcm_vol_control =
|
||||
static const struct snd_kcontrol_new snd_trident_pcm_vol_control =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "PCM Front Playback Volume",
|
||||
|
@ -2764,7 +2764,7 @@ static int snd_trident_pcm_pan_control_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_trident_pcm_pan_control =
|
||||
static const struct snd_kcontrol_new snd_trident_pcm_pan_control =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "PCM Pan Playback Control",
|
||||
|
@ -2821,7 +2821,7 @@ static int snd_trident_pcm_rvol_control_put(struct snd_kcontrol *kcontrol,
|
|||
|
||||
static const DECLARE_TLV_DB_SCALE(db_scale_crvol, -3175, 25, 1);
|
||||
|
||||
static struct snd_kcontrol_new snd_trident_pcm_rvol_control =
|
||||
static const struct snd_kcontrol_new snd_trident_pcm_rvol_control =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "PCM Reverb Playback Volume",
|
||||
|
@ -2877,7 +2877,7 @@ static int snd_trident_pcm_cvol_control_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_trident_pcm_cvol_control =
|
||||
static const struct snd_kcontrol_new snd_trident_pcm_cvol_control =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "PCM Chorus Playback Volume",
|
||||
|
|
|
@ -1683,7 +1683,7 @@ static int snd_via8233_dxs3_spdif_put(struct snd_kcontrol *kcontrol,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_via8233_dxs3_spdif_control = {
|
||||
static const struct snd_kcontrol_new snd_via8233_dxs3_spdif_control = {
|
||||
.name = SNDRV_CTL_NAME_IEC958("Output ",NONE,SWITCH),
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.info = snd_via8233_dxs3_spdif_info,
|
||||
|
@ -1772,7 +1772,7 @@ static int snd_via8233_pcmdxs_volume_put(struct snd_kcontrol *kcontrol,
|
|||
|
||||
static const DECLARE_TLV_DB_SCALE(db_scale_dxs, -4650, 150, 1);
|
||||
|
||||
static struct snd_kcontrol_new snd_via8233_pcmdxs_volume_control = {
|
||||
static const struct snd_kcontrol_new snd_via8233_pcmdxs_volume_control = {
|
||||
.name = "PCM Playback Volume",
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
||||
|
@ -1783,7 +1783,7 @@ static struct snd_kcontrol_new snd_via8233_pcmdxs_volume_control = {
|
|||
.tlv = { .p = db_scale_dxs }
|
||||
};
|
||||
|
||||
static struct snd_kcontrol_new snd_via8233_dxs_volume_control = {
|
||||
static const struct snd_kcontrol_new snd_via8233_dxs_volume_control = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
.device = 0,
|
||||
/* .subdevice set later */
|
||||
|
|
|
@ -945,7 +945,7 @@ static int vx_mic_level_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new vx_control_input_level = {
|
||||
static const struct snd_kcontrol_new vx_control_input_level = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
||||
SNDRV_CTL_ELEM_ACCESS_TLV_READ),
|
||||
|
@ -956,7 +956,7 @@ static struct snd_kcontrol_new vx_control_input_level = {
|
|||
.tlv = { .p = db_scale_mic },
|
||||
};
|
||||
|
||||
static struct snd_kcontrol_new vx_control_mic_level = {
|
||||
static const struct snd_kcontrol_new vx_control_mic_level = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
||||
SNDRV_CTL_ELEM_ACCESS_TLV_READ),
|
||||
|
|
|
@ -1316,7 +1316,7 @@ static int snd_ymfpci_spdif_default_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ymfpci_spdif_default =
|
||||
static const struct snd_kcontrol_new snd_ymfpci_spdif_default =
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
|
||||
|
@ -1344,7 +1344,7 @@ static int snd_ymfpci_spdif_mask_get(struct snd_kcontrol *kcontrol,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ymfpci_spdif_mask =
|
||||
static const struct snd_kcontrol_new snd_ymfpci_spdif_mask =
|
||||
{
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
|
@ -1391,7 +1391,7 @@ static int snd_ymfpci_spdif_stream_put(struct snd_kcontrol *kcontrol,
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ymfpci_spdif_stream =
|
||||
static const struct snd_kcontrol_new snd_ymfpci_spdif_stream =
|
||||
{
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
|
@ -1439,7 +1439,7 @@ static int snd_ymfpci_drec_source_put(struct snd_kcontrol *kcontrol, struct snd_
|
|||
return reg != old_reg;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ymfpci_drec_source = {
|
||||
static const struct snd_kcontrol_new snd_ymfpci_drec_source = {
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Direct Recording Source",
|
||||
|
@ -1609,7 +1609,7 @@ static int snd_ymfpci_put_dup4ch(struct snd_kcontrol *kcontrol, struct snd_ctl_e
|
|||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ymfpci_dup4ch = {
|
||||
static const struct snd_kcontrol_new snd_ymfpci_dup4ch = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "4ch Duplication",
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||
|
@ -1712,7 +1712,7 @@ static int snd_ymfpci_gpio_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ymfpci_rear_shared = {
|
||||
static const struct snd_kcontrol_new snd_ymfpci_rear_shared = {
|
||||
.name = "Shared Rear/Line-In Switch",
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.info = snd_ymfpci_gpio_sw_info,
|
||||
|
@ -1776,7 +1776,7 @@ static int snd_ymfpci_pcm_vol_put(struct snd_kcontrol *kcontrol,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_ymfpci_pcm_volume = {
|
||||
static const struct snd_kcontrol_new snd_ymfpci_pcm_volume = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||
.name = "PCM Playback Volume",
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
||||
|
|
|
@ -27,27 +27,6 @@
|
|||
|
||||
#define SKL_SUSPEND_DELAY 2000
|
||||
|
||||
/* Vendor Specific Registers */
|
||||
#define AZX_REG_VS_EM1 0x1000
|
||||
#define AZX_REG_VS_INRC 0x1004
|
||||
#define AZX_REG_VS_OUTRC 0x1008
|
||||
#define AZX_REG_VS_FIFOTRK 0x100C
|
||||
#define AZX_REG_VS_FIFOTRK2 0x1010
|
||||
#define AZX_REG_VS_EM2 0x1030
|
||||
#define AZX_REG_VS_EM3L 0x1038
|
||||
#define AZX_REG_VS_EM3U 0x103C
|
||||
#define AZX_REG_VS_EM4L 0x1040
|
||||
#define AZX_REG_VS_EM4U 0x1044
|
||||
#define AZX_REG_VS_LTRC 0x1048
|
||||
#define AZX_REG_VS_D0I3C 0x104A
|
||||
#define AZX_REG_VS_PCE 0x104B
|
||||
#define AZX_REG_VS_L2MAGC 0x1050
|
||||
#define AZX_REG_VS_L2LAHPT 0x1054
|
||||
#define AZX_REG_VS_SDXDPIB_XBASE 0x1084
|
||||
#define AZX_REG_VS_SDXDPIB_XINTERVAL 0x20
|
||||
#define AZX_REG_VS_SDXEFIFOS_XBASE 0x1094
|
||||
#define AZX_REG_VS_SDXEFIFOS_XINTERVAL 0x20
|
||||
|
||||
#define AZX_PCIREG_PGCTL 0x44
|
||||
#define AZX_PGCTL_LSRMD_MASK (1 << 4)
|
||||
#define AZX_PCIREG_CGCTL 0x48
|
||||
|
|
|
@ -225,9 +225,9 @@ snd_emux_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format,
|
|||
else if (format == SNDRV_OSS_SOUNDFONT_PATCH) {
|
||||
struct soundfont_patch_info patch;
|
||||
if (count < (int)sizeof(patch))
|
||||
rc = -EINVAL;
|
||||
return -EINVAL;
|
||||
if (copy_from_user(&patch, buf, sizeof(patch)))
|
||||
rc = -EFAULT;
|
||||
return -EFAULT;
|
||||
if (patch.type >= SNDRV_SFNT_LOAD_INFO &&
|
||||
patch.type <= SNDRV_SFNT_PROBE_DATA)
|
||||
rc = snd_soundfont_load(emu->sflist, buf, count, SF_CLIENT_NO(p->chset.port));
|
||||
|
|
|
@ -332,6 +332,7 @@ static int snd_usb_audio_dev_free(struct snd_device *device)
|
|||
static int snd_usb_audio_create(struct usb_interface *intf,
|
||||
struct usb_device *dev, int idx,
|
||||
const struct snd_usb_audio_quirk *quirk,
|
||||
unsigned int usb_id,
|
||||
struct snd_usb_audio **rchip)
|
||||
{
|
||||
struct snd_card *card;
|
||||
|
@ -381,8 +382,7 @@ static int snd_usb_audio_create(struct usb_interface *intf,
|
|||
atomic_set(&chip->usage_count, 0);
|
||||
atomic_set(&chip->shutdown, 0);
|
||||
|
||||
chip->usb_id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
|
||||
le16_to_cpu(dev->descriptor.idProduct));
|
||||
chip->usb_id = usb_id;
|
||||
INIT_LIST_HEAD(&chip->pcm_list);
|
||||
INIT_LIST_HEAD(&chip->ep_list);
|
||||
INIT_LIST_HEAD(&chip->midi_list);
|
||||
|
@ -569,7 +569,7 @@ static int usb_audio_probe(struct usb_interface *intf,
|
|||
(vid[i] == -1 || vid[i] == USB_ID_VENDOR(id)) &&
|
||||
(pid[i] == -1 || pid[i] == USB_ID_PRODUCT(id))) {
|
||||
err = snd_usb_audio_create(intf, dev, i, quirk,
|
||||
&chip);
|
||||
id, &chip);
|
||||
if (err < 0)
|
||||
goto __error;
|
||||
chip->pm_intf = intf;
|
||||
|
|
|
@ -430,7 +430,7 @@ static int snd_line6_control_playback_put(struct snd_kcontrol *kcontrol,
|
|||
}
|
||||
|
||||
/* control definition */
|
||||
static struct snd_kcontrol_new line6_controls[] = {
|
||||
static const struct snd_kcontrol_new line6_controls[] = {
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "PCM Playback Volume",
|
||||
|
|
|
@ -380,7 +380,7 @@ static int snd_pod_control_monitor_put(struct snd_kcontrol *kcontrol,
|
|||
}
|
||||
|
||||
/* control definition */
|
||||
static struct snd_kcontrol_new pod_control_monitor = {
|
||||
static const struct snd_kcontrol_new pod_control_monitor = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Monitor Playback Volume",
|
||||
.index = 0,
|
||||
|
|
|
@ -250,7 +250,7 @@ static void toneport_start_pcm(unsigned long arg)
|
|||
}
|
||||
|
||||
/* control definition */
|
||||
static struct snd_kcontrol_new toneport_control_monitor = {
|
||||
static const struct snd_kcontrol_new toneport_control_monitor = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Monitor Playback Volume",
|
||||
.index = 0,
|
||||
|
@ -261,7 +261,7 @@ static struct snd_kcontrol_new toneport_control_monitor = {
|
|||
};
|
||||
|
||||
/* source selector definition */
|
||||
static struct snd_kcontrol_new toneport_control_source = {
|
||||
static const struct snd_kcontrol_new toneport_control_source = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "PCM Capture Source",
|
||||
.index = 0,
|
||||
|
|
|
@ -1922,7 +1922,7 @@ static int roland_load_put(struct snd_kcontrol *kcontrol,
|
|||
return changed;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new roland_load_ctl = {
|
||||
static const struct snd_kcontrol_new roland_load_ctl = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "MIDI Input Mode",
|
||||
.info = roland_load_info,
|
||||
|
|
|
@ -1172,7 +1172,7 @@ static struct snd_kcontrol_new usb_feature_unit_ctl = {
|
|||
};
|
||||
|
||||
/* the read-only variant */
|
||||
static struct snd_kcontrol_new usb_feature_unit_ctl_ro = {
|
||||
static const struct snd_kcontrol_new usb_feature_unit_ctl_ro = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "", /* will be filled later manually */
|
||||
.info = mixer_ctl_feature_info,
|
||||
|
@ -1745,7 +1745,7 @@ static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol,
|
|||
}
|
||||
|
||||
/* alsa control interface for processing/extension unit */
|
||||
static struct snd_kcontrol_new mixer_procunit_ctl = {
|
||||
static const struct snd_kcontrol_new mixer_procunit_ctl = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "", /* will be filled later */
|
||||
.info = mixer_ctl_feature_info,
|
||||
|
@ -2033,7 +2033,7 @@ static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol,
|
|||
}
|
||||
|
||||
/* alsa control interface for selector unit */
|
||||
static struct snd_kcontrol_new mixer_selectunit_ctl = {
|
||||
static const struct snd_kcontrol_new mixer_selectunit_ctl = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "", /* will be filled later */
|
||||
.info = mixer_ctl_selector_info,
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче