Commit d7dc450d authored by Takashi Iwai's avatar Takashi Iwai

Merge branch 'for-next' into for-linus

For 4.12 merge.
parents d4a2fbce 0997e378
......@@ -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
......
This diff is collapsed.
......@@ -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;
list_for_each_entry(subs, &str->substreams, list) {
if (!is_ctl)
snprintf(subs->name, sizeof(subs->name),
"%s MIDI %d",
dg00x->card->shortname, subs->number + 1);
else
/* This port is for asynchronous transaction. */
snprintf(subs->name, sizeof(subs->name),
"%s control",
dg00x->card->shortname);
struct snd_rawmidi_str *str;
int i;
for (i = 0; i < 2; ++i) {
str = &rmidi->streams[i];
list_for_each_entry(subs, &str->substreams, list) {
if (!is_console) {
snprintf(subs->name, sizeof(subs->name),
"%s MIDI %d",
dg00x->card->shortname,
subs->number + 1);
} else {
snprintf(subs->name, sizeof(subs->name),
"%s control",
dg00x->card->shortname);
}
}
}
}
int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x)
static int add_substream_pair(struct snd_dg00x *dg00x, unsigned int out_ports,
unsigned int in_ports, bool is_console)
{
static const struct snd_rawmidi_ops phys_capture_ops = {
.open = midi_phys_open,
.close = midi_phys_close,
.trigger = midi_phys_capture_trigger,
};
static const struct snd_rawmidi_ops phys_playback_ops = {
.open = midi_phys_open,
.close = midi_phys_close,
.trigger = midi_phys_playback_trigger,
static const struct snd_rawmidi_ops capture_ops = {
.open = midi_open,
.close = midi_close,
.trigger = midi_capture_trigger,
};
static const struct snd_rawmidi_ops ctl_capture_ops = {
.open = midi_ctl_open,
.close = midi_ctl_capture_close,
.trigger = midi_ctl_capture_trigger,
static const struct snd_rawmidi_ops playback_ops = {
.open = midi_open,
.close = midi_close,
.trigger = midi_playback_trigger,
};
static const struct snd_rawmidi_ops ctl_playback_ops = {
.open = midi_ctl_open,
.close = midi_ctl_playback_close,
.trigger = midi_ctl_playback_trigger,
};
struct snd_rawmidi *rmidi[2];
struct snd_rawmidi_str *str;
unsigned int i;
const char *label;
struct snd_rawmidi *rmidi;
int err;
/* Add physical midi ports. */
err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 0,
DOT_MIDI_OUT_PORTS, DOT_MIDI_IN_PORTS, &rmidi[0]);
err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, is_console,
out_ports, in_ports, &rmidi);
if (err < 0)
return err;
rmidi->private_data = dg00x;
snprintf(rmidi[0]->name, sizeof(rmidi[0]->name),
"%s MIDI", dg00x->card->shortname);
snd_rawmidi_set_ops(rmidi[0], SNDRV_RAWMIDI_STREAM_INPUT,
&phys_capture_ops);
snd_rawmidi_set_ops(rmidi[0], SNDRV_RAWMIDI_STREAM_OUTPUT,
&phys_playback_ops);
if (!is_console)
label = "%s control";
else
label = "%s MIDI";
snprintf(rmidi->name, sizeof(rmidi->name), label,
dg00x->card->shortname);
/* Add a pair of control midi ports. */
err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 1,
1, 1, &rmidi[1]);
if (err < 0)
return err;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &playback_ops);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &capture_ops);
snprintf(rmidi[1]->name, sizeof(rmidi[1]->name),
"%s control", dg00x->card->shortname);
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT |
SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_DUPLEX;
snd_rawmidi_set_ops(rmidi[1], SNDRV_RAWMIDI_STREAM_INPUT,
&ctl_capture_ops);
snd_rawmidi_set_ops(rmidi[1], SNDRV_RAWMIDI_STREAM_OUTPUT,
&ctl_playback_ops);
set_substream_names(dg00x, rmidi, is_console);
for (i = 0; i < ARRAY_SIZE(rmidi); i++) {
rmidi[i]->private_data = dg00x;
return 0;
}
rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
str = &rmidi[i]->streams[SNDRV_RAWMIDI_STREAM_INPUT];
set_midi_substream_names(dg00x, str, i);
int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x)
{
int err;
rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
str = &rmidi[i]->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
set_midi_substream_names(dg00x, str, i);
/* Add physical midi ports. */
err = add_substream_pair(dg00x, DOT_MIDI_OUT_PORTS, DOT_MIDI_IN_PORTS,
false);
if (err < 0)
return err;
rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
}
if (dg00x->is_console)
err = add_substream_pair(dg00x, 1, 1, true);
return 0;
return err;
}
......@@ -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;
......
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
/*
* 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));
}
/*
* 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;
}
/*
* 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;
}
This diff is collapsed.
/*
* 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);
}
/*
* 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, &reg, 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, &reg, 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, &reg, 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, &reg, 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, &reg, 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, &reg, 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, &reg, 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, &reg, 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,
};
/*
* 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);
}
/*
* 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,
&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,
&reg, sizeof(reg), 0);
fw_core_remove_address_handler(&ff->async_handler);
}
/*
* 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);
/*
* 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
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
/*
* 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>
This diff is collapsed.
/*
* 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;
}
/*
* 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;
}
This diff is collapsed.
/*
* 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);
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/*
* 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));
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -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);
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment