sound updates for 4.12-rc1

It was a relatively calm development cycle, and no scaring changes are
 seen in both core and driver sides.  Here are some highlights:
 
 ASoC:
 - A new API for hooking up jacks more generically and easily
 - Card longname is set based on DMI for a unique UCM profile
 - Lots of Intel driver fixes: Atom, Broxton, Skylake and newer chips
 - New drivers for Cirrus CS35L35, DIO DIO2125, Everest ES7132,
   HiSilicon hi6210, Maxim MAX98927, MT2701 systems with WM8960, Nuvoton
   NAU8824, Odroid systems, ST STM32 SAI controllers and x86 systems with
   DA7213
 
 HD-audio:
 - Many new quirks to support headset for various devices (mostly ASUS
   ones) as usual
 - Support for dual codecs on some Gigabyte mobos and Lenovo laptop
 - Improvement on PCM position reporting for Skylake and newer
 
 FireWire:
 - New drivers for MOTU and RME Fireface series
 - Updates for Digidesign Digi00x and TASCAM series
 - Support for tracepoints
 
 Others:
 - USB-audio: improved support for quirk_alias option
 - Cleanups, constification allover the places
 -----BEGIN PGP SIGNATURE-----
 
 iQJCBAABCAAsFiEECxfAB4MH3rD5mfB6bDGAVD0pKaQFAlkIphEOHHRpd2FpQHN1
 c2UuZGUACgkQbDGAVD0pKaQwjg//axvFHHVIJGkwL628pfWSVJN7+gTlKkeBPBcn
 NtaOOC7aM5IhkvYGxrq5e55cRFDMt4tvVloGLXu593gzDoN0JYUSCVYcctqklKa8
 nbHeasZgnVwmQHf/44oajiT++UElxH/i4q/kz91ZuYmNVUgh6syH3o04T9UBZA6k
 rxv4MbMctUf1SYwbZVMzPLWXsSCwmaWsUimhi9WiDRzSE1bGI46nJtPbDF5jXhlR
 83Dsp1lp5tQXXAeYksjx+yUtQMRpY85zsQj1NJ/izrD1fjWnXquaRlDZwcIOWCio
 3Vz87liQyIEldY7FHL64igo8SIMeypPhRUFfxugSn9iTqeuWaXFyJNQwo0aENDZH
 RMUmqAutiik2MEXMN0fAAgj3GcxbSVgYK/EfmzHNlrDAtdbgBm+ArIhS67Ue9vPi
 emb6+/STUI7rmH8+RFBvQ/Xz3mpa791z+jVuidTKoEgYJ5/M1Ql8Ucoja74UXj4m
 QjNe+CBO6GXcAOlBNeMZ7PMpQrR14Hl386fusLG2JXRLR8p0SmO4Klt8PrBy/Obh
 4bE0/EWK/e5XbXVX+8QyDOtt9cFsAYZJqbpDr9Enft3LJcid6gmiJZGuZ1i+Iv1d
 L2lYkFQkLI/bjf5xGsamK2pB9xQzOSa7u6Q+q4iBXuVYjDpoKz59l6siwRuuJLMX
 c+QfQgY=
 =8RhW
 -----END PGP SIGNATURE-----

Merge tag 'sound-4.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound

Pull sound updates from Takashi Iwai:
 "It was a relatively calm development cycle, and no scaring changes are
  seen in both core and driver sides. Here are some highlights:

  ASoC:
   - A new API for hooking up jacks more generically and easily

   - Card longname is set based on DMI for a unique UCM profile

   - Lots of Intel driver fixes: Atom, Broxton, Skylake and newer chips

   - New drivers for Cirrus CS35L35, DIO DIO2125, Everest ES7132,
     HiSilicon hi6210, Maxim MAX98927, MT2701 systems with WM8960,
     Nuvoton NAU8824, Odroid systems, ST STM32 SAI controllers and x86
     systems with DA7213

  HD-audio:
   - Many new quirks to support headset for various devices (mostly ASUS
     ones) as usual

   - Support for dual codecs on some Gigabyte mobos and Lenovo laptop

   - Improvement on PCM position reporting for Skylake and newer

  FireWire:
   - New drivers for MOTU and RME Fireface series

   - Updates for Digidesign Digi00x and TASCAM series

   - Support for tracepoints

  Others:
   - USB-audio: improved support for quirk_alias option

   - Cleanups, constification allover the places"

* tag 'sound-4.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: (299 commits)
  ASoC: codec: wm8960: Relax bit clock computation when using PLL
  ASoC: codec: wm9860: avoid maybe-uninitialized warning
  ASoC: nau8824: leave Class D gain at chip default
  ASoC: nau8824: rename controls to match DAPM controls
  ASoC: Intel: Skylake: Return negative error code
  ASoC: Intel: Skylake: Fix unused variable warning
  ASoC: Intel: Skylake: fix uninitialized pointer use
  ASoC: sti: Fix error handling if of_clk_get() fails
  ASoC: cs4271: configure reset GPIO as output
  ASoC: dwc: Disallow building designware_pcm as a module
  ALSA: ali5451: fix spelling mistake in "ali_capture_preapre"
  ASoC: stm32: add SAI driver
  ASoC: stm32: add bindings for SAI
  ASoC: Intel: Skylake: Add loadable module support on KBL platform
  ASoC: Intel: Skylake: Modify load_lib_ipc arguments for a nowait version
  ASoC: Intel: Skylake: Register dsp_fw_ops for kabylake
  ASoC: Intel: Skylake: Modify arguments to reuse module transfer function
  ASoC: Intel: Skylake: Commonize library load
  ASoC: Intel: Skylake: Move sst common initialization to a helper function
  ASoC: nau8824: new driver
  ...
This commit is contained in:
Linus Torvalds 2017-05-03 11:58:59 -07:00
commit 221656e7c4
306 changed files with 19287 additions and 2691 deletions

View file

@ -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) {

View file

@ -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;

View file

@ -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

View file

@ -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/

View file

@ -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

View file

@ -27,6 +27,7 @@
/* isochronous header parameters */
#define ISO_DATA_LENGTH_SHIFT 16
#define TAG_NO_CIP_HEADER 0
#define TAG_CIP 1
/* common isochronous packet header parameters */
@ -37,6 +38,8 @@
#define CIP_SID_MASK 0x3f000000
#define CIP_DBS_MASK 0x00ff0000
#define CIP_DBS_SHIFT 16
#define CIP_SPH_MASK 0x00000400
#define CIP_SPH_SHIFT 10
#define CIP_DBC_MASK 0x000000ff
#define CIP_FMT_SHIFT 24
#define CIP_FMT_MASK 0x3f000000
@ -232,11 +235,15 @@ EXPORT_SYMBOL(amdtp_stream_set_parameters);
unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s)
{
unsigned int multiplier = 1;
unsigned int header_size = 0;
if (s->flags & CIP_JUMBO_PAYLOAD)
multiplier = 5;
if (!(s->flags & CIP_NO_HEADER))
header_size = 8;
return 8 + s->syt_interval * s->data_block_quadlets * 4 * multiplier;
return header_size +
s->syt_interval * s->data_block_quadlets * 4 * multiplier;
}
EXPORT_SYMBOL(amdtp_stream_get_max_payload);
@ -378,7 +385,7 @@ static int queue_packet(struct amdtp_stream *s, unsigned int header_length,
goto end;
p.interrupt = IS_ALIGNED(s->packet_index + 1, INTERRUPT_INTERVAL);
p.tag = TAG_CIP;
p.tag = s->tag;
p.header_length = header_length;
if (payload_length > 0)
p.payload_length = payload_length;
@ -405,17 +412,16 @@ static inline int queue_out_packet(struct amdtp_stream *s,
static inline int queue_in_packet(struct amdtp_stream *s)
{
return queue_packet(s, IN_PACKET_HEADER_SIZE,
amdtp_stream_get_max_payload(s));
return queue_packet(s, IN_PACKET_HEADER_SIZE, s->max_payload_length);
}
static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle,
static int handle_out_packet(struct amdtp_stream *s,
unsigned int payload_length, unsigned int cycle,
unsigned int index)
{
__be32 *buffer;
unsigned int syt;
unsigned int data_blocks;
unsigned int payload_length;
unsigned int pcm_frames;
struct snd_pcm_substream *pcm;
@ -424,15 +430,22 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle,
data_blocks = calculate_data_blocks(s, syt);
pcm_frames = s->process_data_blocks(s, buffer + 2, data_blocks, &syt);
if (s->flags & CIP_DBC_IS_END_EVENT)
s->data_block_counter =
(s->data_block_counter + data_blocks) & 0xff;
buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) |
(s->data_block_quadlets << CIP_DBS_SHIFT) |
((s->sph << CIP_SPH_SHIFT) & CIP_SPH_MASK) |
s->data_block_counter);
buffer[1] = cpu_to_be32(CIP_EOH |
((s->fmt << CIP_FMT_SHIFT) & CIP_FMT_MASK) |
((s->fdf << CIP_FDF_SHIFT) & CIP_FDF_MASK) |
(syt & CIP_SYT_MASK));
s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff;
if (!(s->flags & CIP_DBC_IS_END_EVENT))
s->data_block_counter =
(s->data_block_counter + data_blocks) & 0xff;
payload_length = 8 + data_blocks * 4 * s->data_block_quadlets;
trace_out_packet(s, cycle, buffer, payload_length, index);
@ -448,13 +461,45 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle,
return 0;
}
static int handle_out_packet_without_header(struct amdtp_stream *s,
unsigned int payload_length, unsigned int cycle,
unsigned int index)
{
__be32 *buffer;
unsigned int syt;
unsigned int data_blocks;
unsigned int pcm_frames;
struct snd_pcm_substream *pcm;
buffer = s->buffer.packets[s->packet_index].buffer;
syt = calculate_syt(s, cycle);
data_blocks = calculate_data_blocks(s, syt);
pcm_frames = s->process_data_blocks(s, buffer, data_blocks, &syt);
s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff;
payload_length = data_blocks * 4 * s->data_block_quadlets;
trace_out_packet_without_header(s, cycle, payload_length, data_blocks,
index);
if (queue_out_packet(s, payload_length) < 0)
return -EIO;
pcm = ACCESS_ONCE(s->pcm);
if (pcm && pcm_frames > 0)
update_pcm_pointers(s, pcm, pcm_frames);
/* No need to return the number of handled data blocks. */
return 0;
}
static int handle_in_packet(struct amdtp_stream *s,
unsigned int payload_quadlets, unsigned int cycle,
unsigned int payload_length, unsigned int cycle,
unsigned int index)
{
__be32 *buffer;
u32 cip_header[2];
unsigned int fmt, fdf, syt;
unsigned int sph, fmt, fdf, syt;
unsigned int data_block_quadlets, data_block_counter, dbc_interval;
unsigned int data_blocks;
struct snd_pcm_substream *pcm;
@ -465,14 +510,15 @@ static int handle_in_packet(struct amdtp_stream *s,
cip_header[0] = be32_to_cpu(buffer[0]);
cip_header[1] = be32_to_cpu(buffer[1]);
trace_in_packet(s, cycle, cip_header, payload_quadlets, index);
trace_in_packet(s, cycle, cip_header, payload_length, index);
/*
* This module supports 'Two-quadlet CIP header with SYT field'.
* For convenience, also check FMT field is AM824 or not.
*/
if (((cip_header[0] & CIP_EOH_MASK) == CIP_EOH) ||
((cip_header[1] & CIP_EOH_MASK) != CIP_EOH)) {
if ((((cip_header[0] & CIP_EOH_MASK) == CIP_EOH) ||
((cip_header[1] & CIP_EOH_MASK) != CIP_EOH)) &&
(!(s->flags & CIP_HEADER_WITHOUT_EOH))) {
dev_info_ratelimited(&s->unit->device,
"Invalid CIP header for AMDTP: %08X:%08X\n",
cip_header[0], cip_header[1]);
@ -482,8 +528,9 @@ static int handle_in_packet(struct amdtp_stream *s,
}
/* Check valid protocol or not. */
sph = (cip_header[0] & CIP_SPH_MASK) >> CIP_SPH_SHIFT;
fmt = (cip_header[1] & CIP_FMT_MASK) >> CIP_FMT_SHIFT;
if (fmt != s->fmt) {
if (sph != s->sph || fmt != s->fmt) {
dev_info_ratelimited(&s->unit->device,
"Detect unexpected protocol: %08x %08x\n",
cip_header[0], cip_header[1]);
@ -494,7 +541,7 @@ static int handle_in_packet(struct amdtp_stream *s,
/* Calculate data blocks */
fdf = (cip_header[1] & CIP_FDF_MASK) >> CIP_FDF_SHIFT;
if (payload_quadlets < 3 ||
if (payload_length < 12 ||
(fmt == CIP_FMT_AM && fdf == AMDTP_FDF_NO_DATA)) {
data_blocks = 0;
} else {
@ -510,7 +557,8 @@ static int handle_in_packet(struct amdtp_stream *s,
if (s->flags & CIP_WRONG_DBS)
data_block_quadlets = s->data_block_quadlets;
data_blocks = (payload_quadlets - 2) / data_block_quadlets;
data_blocks = (payload_length / 4 - 2) /
data_block_quadlets;
}
/* Check data block counter continuity */
@ -561,6 +609,34 @@ end:
return 0;
}
static int handle_in_packet_without_header(struct amdtp_stream *s,
unsigned int payload_quadlets, unsigned int cycle,
unsigned int index)
{
__be32 *buffer;
unsigned int data_blocks;
struct snd_pcm_substream *pcm;
unsigned int pcm_frames;
buffer = s->buffer.packets[s->packet_index].buffer;
data_blocks = payload_quadlets / s->data_block_quadlets;
trace_in_packet_without_header(s, cycle, payload_quadlets, data_blocks,
index);
pcm_frames = s->process_data_blocks(s, buffer, data_blocks, NULL);
s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff;
if (queue_in_packet(s) < 0)
return -EIO;
pcm = ACCESS_ONCE(s->pcm);
if (pcm && pcm_frames > 0)
update_pcm_pointers(s, pcm, pcm_frames);
return 0;
}
/*
* In CYCLE_TIMER register of IEEE 1394, 7 bits are used to represent second. On
* the other hand, in DMA descriptors of 1394 OHCI, 3 bits are used to represent
@ -604,7 +680,7 @@ static void out_stream_callback(struct fw_iso_context *context, u32 tstamp,
for (i = 0; i < packets; ++i) {
cycle = increment_cycle_count(cycle, 1);
if (handle_out_packet(s, cycle, i) < 0) {
if (s->handle_packet(s, 0, cycle, i) < 0) {
s->packet_index = -1;
amdtp_stream_pcm_abort(s);
return;
@ -620,7 +696,7 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp,
{
struct amdtp_stream *s = private_data;
unsigned int i, packets;
unsigned int payload_quadlets, max_payload_quadlets;
unsigned int payload_length, max_payload_length;
__be32 *headers = header;
u32 cycle;
@ -636,22 +712,22 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp,
cycle = decrement_cycle_count(cycle, packets);
/* For buffer-over-run prevention. */
max_payload_quadlets = amdtp_stream_get_max_payload(s) / 4;
max_payload_length = s->max_payload_length;
for (i = 0; i < packets; i++) {
cycle = increment_cycle_count(cycle, 1);
/* The number of quadlets in this packet */
payload_quadlets =
(be32_to_cpu(headers[i]) >> ISO_DATA_LENGTH_SHIFT) / 4;
if (payload_quadlets > max_payload_quadlets) {
/* The number of bytes in this packet */
payload_length =
(be32_to_cpu(headers[i]) >> ISO_DATA_LENGTH_SHIFT);
if (payload_length > max_payload_length) {
dev_err(&s->unit->device,
"Detect jumbo payload: %02x %02x\n",
payload_quadlets, max_payload_quadlets);
"Detect jumbo payload: %04x %04x\n",
payload_length, max_payload_length);
break;
}
if (handle_in_packet(s, payload_quadlets, cycle, i) < 0)
if (s->handle_packet(s, payload_length, cycle, i) < 0)
break;
}
@ -671,6 +747,10 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context,
void *header, void *private_data)
{
struct amdtp_stream *s = private_data;
u32 cycle;
unsigned int packets;
s->max_payload_length = amdtp_stream_get_max_payload(s);
/*
* For in-stream, first packet has come.
@ -679,10 +759,27 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context,
s->callbacked = true;
wake_up(&s->callback_wait);
if (s->direction == AMDTP_IN_STREAM)
cycle = compute_cycle_count(tstamp);
if (s->direction == AMDTP_IN_STREAM) {
packets = header_length / IN_PACKET_HEADER_SIZE;
cycle = decrement_cycle_count(cycle, packets);
context->callback.sc = in_stream_callback;
else
if (s->flags & CIP_NO_HEADER)
s->handle_packet = handle_in_packet_without_header;
else
s->handle_packet = handle_in_packet;
} else {
packets = header_length / 4;
cycle = increment_cycle_count(cycle, QUEUE_LENGTH - packets);
context->callback.sc = out_stream_callback;
if (s->flags & CIP_NO_HEADER)
s->handle_packet = handle_out_packet_without_header;
else
s->handle_packet = handle_out_packet;
}
s->start_cycle = cycle;
context->callback.sc(context, tstamp, header_length, header, s);
}
@ -759,6 +856,11 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
amdtp_stream_update(s);
if (s->flags & CIP_NO_HEADER)
s->tag = TAG_NO_CIP_HEADER;
else
s->tag = TAG_CIP;
s->packet_index = 0;
do {
if (s->direction == AMDTP_IN_STREAM)
@ -771,7 +873,7 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
/* NOTE: TAG1 matches CIP. This just affects in stream. */
tag = FW_ISO_CONTEXT_MATCH_TAG1;
if (s->flags & CIP_EMPTY_WITH_TAG0)
if ((s->flags & CIP_EMPTY_WITH_TAG0) || (s->flags & CIP_NO_HEADER))
tag |= FW_ISO_CONTEXT_MATCH_TAG0;
s->callbacked = false;

View file

@ -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;

View file

@ -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;

View file

@ -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;
}

View file

@ -8,7 +8,7 @@
#include "digi00x.h"
static int midi_phys_open(struct snd_rawmidi_substream *substream)
static int midi_open(struct snd_rawmidi_substream *substream)
{
struct snd_dg00x *dg00x = substream->rmidi->private_data;
int err;
@ -27,7 +27,7 @@ static int midi_phys_open(struct snd_rawmidi_substream *substream)
return err;
}
static int midi_phys_close(struct snd_rawmidi_substream *substream)
static int midi_close(struct snd_rawmidi_substream *substream)
{
struct snd_dg00x *dg00x = substream->rmidi->private_data;
@ -40,180 +40,130 @@ static int midi_phys_close(struct snd_rawmidi_substream *substream)
return 0;
}
static void midi_phys_capture_trigger(struct snd_rawmidi_substream *substream,
int up)
static void midi_capture_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct snd_dg00x *dg00x = substream->rmidi->private_data;
unsigned int port;
unsigned long flags;
spin_lock_irqsave(&dg00x->lock, flags);
if (up)
amdtp_dot_midi_trigger(&dg00x->tx_stream, substream->number,
substream);
if (substream->rmidi->device == 0)
port = substream->number;
else
amdtp_dot_midi_trigger(&dg00x->tx_stream, substream->number,
NULL);
spin_unlock_irqrestore(&dg00x->lock, flags);
}
static void midi_phys_playback_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct snd_dg00x *dg00x = substream->rmidi->private_data;
unsigned long flags;
port = 2;
spin_lock_irqsave(&dg00x->lock, flags);
if (up)
amdtp_dot_midi_trigger(&dg00x->rx_stream, substream->number,
substream);
amdtp_dot_midi_trigger(&dg00x->tx_stream, port, substream);
else
amdtp_dot_midi_trigger(&dg00x->rx_stream, substream->number,
NULL);
amdtp_dot_midi_trigger(&dg00x->tx_stream, port, NULL);
spin_unlock_irqrestore(&dg00x->lock, flags);
}
static int midi_ctl_open(struct snd_rawmidi_substream *substream)
{
/* Do nothing. */
return 0;
}
static int midi_ctl_capture_close(struct snd_rawmidi_substream *substream)
{
/* Do nothing. */
return 0;
}
static int midi_ctl_playback_close(struct snd_rawmidi_substream *substream)
{
struct snd_dg00x *dg00x = substream->rmidi->private_data;
snd_fw_async_midi_port_finish(&dg00x->out_control);
return 0;
}
static void midi_ctl_capture_trigger(struct snd_rawmidi_substream *substream,
int up)
static void midi_playback_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct snd_dg00x *dg00x = substream->rmidi->private_data;
unsigned int port;
unsigned long flags;
spin_lock_irqsave(&dg00x->lock, flags);
if (up)
dg00x->in_control = substream;
if (substream->rmidi->device == 0)
port = substream->number;
else
dg00x->in_control = NULL;
spin_unlock_irqrestore(&dg00x->lock, flags);
}
static void midi_ctl_playback_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct snd_dg00x *dg00x = substream->rmidi->private_data;
unsigned long flags;
port = 2;
spin_lock_irqsave(&dg00x->lock, flags);
if (up)
snd_fw_async_midi_port_run(&dg00x->out_control, substream);
amdtp_dot_midi_trigger(&dg00x->rx_stream, port, substream);
else
amdtp_dot_midi_trigger(&dg00x->rx_stream, port, NULL);
spin_unlock_irqrestore(&dg00x->lock, flags);
}
static void set_midi_substream_names(struct snd_dg00x *dg00x,
struct snd_rawmidi_str *str,
bool is_ctl)
static void set_substream_names(struct snd_dg00x *dg00x,
struct snd_rawmidi *rmidi, bool is_console)
{
struct snd_rawmidi_substream *subs;
struct snd_rawmidi_str *str;
int i;
list_for_each_entry(subs, &str->substreams, list) {
if (!is_ctl)
snprintf(subs->name, sizeof(subs->name),
"%s MIDI %d",
dg00x->card->shortname, subs->number + 1);
else
/* This port is for asynchronous transaction. */
snprintf(subs->name, sizeof(subs->name),
"%s control",
dg00x->card->shortname);
for (i = 0; i < 2; ++i) {
str = &rmidi->streams[i];
list_for_each_entry(subs, &str->substreams, list) {
if (!is_console) {
snprintf(subs->name, sizeof(subs->name),
"%s MIDI %d",
dg00x->card->shortname,
subs->number + 1);
} else {
snprintf(subs->name, sizeof(subs->name),
"%s control",
dg00x->card->shortname);
}
}
}
}
static int add_substream_pair(struct snd_dg00x *dg00x, unsigned int out_ports,
unsigned int in_ports, bool is_console)
{
static const struct snd_rawmidi_ops capture_ops = {
.open = midi_open,
.close = midi_close,
.trigger = midi_capture_trigger,
};
static const struct snd_rawmidi_ops playback_ops = {
.open = midi_open,
.close = midi_close,
.trigger = midi_playback_trigger,
};
const char *label;
struct snd_rawmidi *rmidi;
int err;
/* Add physical midi ports. */
err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, is_console,
out_ports, in_ports, &rmidi);
if (err < 0)
return err;
rmidi->private_data = dg00x;
if (!is_console)
label = "%s control";
else
label = "%s MIDI";
snprintf(rmidi->name, sizeof(rmidi->name), label,
dg00x->card->shortname);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &playback_ops);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &capture_ops);
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT |
SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_DUPLEX;
set_substream_names(dg00x, rmidi, is_console);
return 0;
}
int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x)
{
static const struct snd_rawmidi_ops phys_capture_ops = {
.open = midi_phys_open,
.close = midi_phys_close,
.trigger = midi_phys_capture_trigger,
};
static const struct snd_rawmidi_ops phys_playback_ops = {
.open = midi_phys_open,
.close = midi_phys_close,
.trigger = midi_phys_playback_trigger,
};
static const struct snd_rawmidi_ops ctl_capture_ops = {
.open = midi_ctl_open,
.close = midi_ctl_capture_close,
.trigger = midi_ctl_capture_trigger,
};
static const struct snd_rawmidi_ops ctl_playback_ops = {
.open = midi_ctl_open,
.close = midi_ctl_playback_close,
.trigger = midi_ctl_playback_trigger,
};
struct snd_rawmidi *rmidi[2];
struct snd_rawmidi_str *str;
unsigned int i;
int err;
/* Add physical midi ports. */
err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 0,
DOT_MIDI_OUT_PORTS, DOT_MIDI_IN_PORTS, &rmidi[0]);
err = add_substream_pair(dg00x, DOT_MIDI_OUT_PORTS, DOT_MIDI_IN_PORTS,
false);
if (err < 0)
return err;
snprintf(rmidi[0]->name, sizeof(rmidi[0]->name),
"%s MIDI", dg00x->card->shortname);
if (dg00x->is_console)
err = add_substream_pair(dg00x, 1, 1, true);
snd_rawmidi_set_ops(rmidi[0], SNDRV_RAWMIDI_STREAM_INPUT,
&phys_capture_ops);
snd_rawmidi_set_ops(rmidi[0], SNDRV_RAWMIDI_STREAM_OUTPUT,
&phys_playback_ops);
/* Add a pair of control midi ports. */
err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 1,
1, 1, &rmidi[1]);
if (err < 0)
return err;
snprintf(rmidi[1]->name, sizeof(rmidi[1]->name),
"%s control", dg00x->card->shortname);
snd_rawmidi_set_ops(rmidi[1], SNDRV_RAWMIDI_STREAM_INPUT,
&ctl_capture_ops);
snd_rawmidi_set_ops(rmidi[1], SNDRV_RAWMIDI_STREAM_OUTPUT,
&ctl_playback_ops);
for (i = 0; i < ARRAY_SIZE(rmidi); i++) {
rmidi[i]->private_data = dg00x;
rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
str = &rmidi[i]->streams[SNDRV_RAWMIDI_STREAM_INPUT];
set_midi_substream_names(dg00x, str, i);
rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
str = &rmidi[i]->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
set_midi_substream_names(dg00x, str, i);
rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
}
return 0;
return err;
}

View file

@ -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;
}

View file

@ -13,7 +13,8 @@ MODULE_AUTHOR("Takashi Sakamoto <[email protected]>");
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,
},
{}
};

View file

@ -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

View file

@ -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;

View file

@ -0,0 +1,3 @@
snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \
ff-stream.o ff-pcm.o ff-hwdep.o ff-protocol-ff400.o
obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o

View file

@ -0,0 +1,155 @@
/*
* amdtp-ff.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include <sound/pcm.h>
#include "ff.h"
struct amdtp_ff {
unsigned int pcm_channels;
};
int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate,
unsigned int pcm_channels)
{
struct amdtp_ff *p = s->protocol;
unsigned int data_channels;
if (amdtp_stream_running(s))
return -EBUSY;
p->pcm_channels = pcm_channels;
data_channels = pcm_channels;
return amdtp_stream_set_parameters(s, rate, data_channels);
}
static void write_pcm_s32(struct amdtp_stream *s,
struct snd_pcm_substream *pcm,
__le32 *buffer, unsigned int frames)
{
struct amdtp_ff *p = s->protocol;
struct snd_pcm_runtime *runtime = pcm->runtime;
unsigned int channels, remaining_frames, i, c;
const u32 *src;
channels = p->pcm_channels;
src = (void *)runtime->dma_area +
frames_to_bytes(runtime, s->pcm_buffer_pointer);
remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
for (i = 0; i < frames; ++i) {
for (c = 0; c < channels; ++c) {
buffer[c] = cpu_to_le32(*src);
src++;
}
buffer += s->data_block_quadlets;
if (--remaining_frames == 0)
src = (void *)runtime->dma_area;
}
}
static void read_pcm_s32(struct amdtp_stream *s,
struct snd_pcm_substream *pcm,
__le32 *buffer, unsigned int frames)
{
struct amdtp_ff *p = s->protocol;
struct snd_pcm_runtime *runtime = pcm->runtime;
unsigned int channels, remaining_frames, i, c;
u32 *dst;
channels = p->pcm_channels;
dst = (void *)runtime->dma_area +
frames_to_bytes(runtime, s->pcm_buffer_pointer);
remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
for (i = 0; i < frames; ++i) {
for (c = 0; c < channels; ++c) {
*dst = le32_to_cpu(buffer[c]) & 0xffffff00;
dst++;
}
buffer += s->data_block_quadlets;
if (--remaining_frames == 0)
dst = (void *)runtime->dma_area;
}
}
static void write_pcm_silence(struct amdtp_stream *s,
__le32 *buffer, unsigned int frames)
{
struct amdtp_ff *p = s->protocol;
unsigned int i, c, channels = p->pcm_channels;
for (i = 0; i < frames; ++i) {
for (c = 0; c < channels; ++c)
buffer[c] = cpu_to_le32(0x00000000);
buffer += s->data_block_quadlets;
}
}
int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s,
struct snd_pcm_runtime *runtime)
{
int err;
err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
if (err < 0)
return err;
return amdtp_stream_add_pcm_hw_constraints(s, runtime);
}
static unsigned int process_rx_data_blocks(struct amdtp_stream *s,
__be32 *buffer,
unsigned int data_blocks,
unsigned int *syt)
{
struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm);
unsigned int pcm_frames;
if (pcm) {
write_pcm_s32(s, pcm, (__le32 *)buffer, data_blocks);
pcm_frames = data_blocks;
} else {
write_pcm_silence(s, (__le32 *)buffer, data_blocks);
pcm_frames = 0;
}
return pcm_frames;
}
static unsigned int process_tx_data_blocks(struct amdtp_stream *s,
__be32 *buffer,
unsigned int data_blocks,
unsigned int *syt)
{
struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm);
unsigned int pcm_frames;
if (pcm) {
read_pcm_s32(s, pcm, (__le32 *)buffer, data_blocks);
pcm_frames = data_blocks;
} else {
pcm_frames = 0;
}
return pcm_frames;
}
int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit,
enum amdtp_stream_direction dir)
{
amdtp_stream_process_data_blocks_t process_data_blocks;
if (dir == AMDTP_IN_STREAM)
process_data_blocks = process_tx_data_blocks;
else
process_data_blocks = process_rx_data_blocks;
return amdtp_stream_init(s, unit, dir, CIP_NO_HEADER, 0,
process_data_blocks, sizeof(struct amdtp_ff));
}

View file

@ -0,0 +1,191 @@
/*
* ff-hwdep.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
/*
* This codes give three functionality.
*
* 1.get firewire node information
* 2.get notification about starting/stopping stream
* 3.lock/unlock stream
*/
#include "ff.h"
static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
loff_t *offset)
{
struct snd_ff *ff = hwdep->private_data;
DEFINE_WAIT(wait);
union snd_firewire_event event;
spin_lock_irq(&ff->lock);
while (!ff->dev_lock_changed) {
prepare_to_wait(&ff->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
spin_unlock_irq(&ff->lock);
schedule();
finish_wait(&ff->hwdep_wait, &wait);
if (signal_pending(current))
return -ERESTARTSYS;
spin_lock_irq(&ff->lock);
}
memset(&event, 0, sizeof(event));
if (ff->dev_lock_changed) {
event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
event.lock_status.status = (ff->dev_lock_count > 0);
ff->dev_lock_changed = false;
count = min_t(long, count, sizeof(event.lock_status));
}
spin_unlock_irq(&ff->lock);
if (copy_to_user(buf, &event, count))
return -EFAULT;
return count;
}
static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
poll_table *wait)
{
struct snd_ff *ff = hwdep->private_data;
unsigned int events;
poll_wait(file, &ff->hwdep_wait, wait);
spin_lock_irq(&ff->lock);
if (ff->dev_lock_changed)
events = POLLIN | POLLRDNORM;
else
events = 0;
spin_unlock_irq(&ff->lock);
return events;
}
static int hwdep_get_info(struct snd_ff *ff, void __user *arg)
{
struct fw_device *dev = fw_parent_device(ff->unit);
struct snd_firewire_get_info info;
memset(&info, 0, sizeof(info));
info.type = SNDRV_FIREWIRE_TYPE_FIREFACE;
info.card = dev->card->index;
*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
strlcpy(info.device_name, dev_name(&dev->device),
sizeof(info.device_name));
if (copy_to_user(arg, &info, sizeof(info)))
return -EFAULT;
return 0;
}
static int hwdep_lock(struct snd_ff *ff)
{
int err;
spin_lock_irq(&ff->lock);
if (ff->dev_lock_count == 0) {
ff->dev_lock_count = -1;
err = 0;
} else {
err = -EBUSY;
}
spin_unlock_irq(&ff->lock);
return err;
}
static int hwdep_unlock(struct snd_ff *ff)
{
int err;
spin_lock_irq(&ff->lock);
if (ff->dev_lock_count == -1) {
ff->dev_lock_count = 0;
err = 0;
} else {
err = -EBADFD;
}
spin_unlock_irq(&ff->lock);
return err;
}
static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
{
struct snd_ff *ff = hwdep->private_data;
spin_lock_irq(&ff->lock);
if (ff->dev_lock_count == -1)
ff->dev_lock_count = 0;
spin_unlock_irq(&ff->lock);
return 0;
}
static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct snd_ff *ff = hwdep->private_data;
switch (cmd) {
case SNDRV_FIREWIRE_IOCTL_GET_INFO:
return hwdep_get_info(ff, (void __user *)arg);
case SNDRV_FIREWIRE_IOCTL_LOCK:
return hwdep_lock(ff);
case SNDRV_FIREWIRE_IOCTL_UNLOCK:
return hwdep_unlock(ff);
default:
return -ENOIOCTLCMD;
}
}
#ifdef CONFIG_COMPAT
static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
unsigned int cmd, unsigned long arg)
{
return hwdep_ioctl(hwdep, file, cmd,
(unsigned long)compat_ptr(arg));
}
#else
#define hwdep_compat_ioctl NULL
#endif
int snd_ff_create_hwdep_devices(struct snd_ff *ff)
{
static const struct snd_hwdep_ops hwdep_ops = {
.read = hwdep_read,
.release = hwdep_release,
.poll = hwdep_poll,
.ioctl = hwdep_ioctl,
.ioctl_compat = hwdep_compat_ioctl,
};
struct snd_hwdep *hwdep;
int err;
err = snd_hwdep_new(ff->card, ff->card->driver, 0, &hwdep);
if (err < 0)
return err;
strcpy(hwdep->name, ff->card->driver);
hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREFACE;
hwdep->ops = hwdep_ops;
hwdep->private_data = ff;
hwdep->exclusive = true;
return 0;
}

View file

@ -0,0 +1,131 @@
/*
* ff-midi.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "ff.h"
static int midi_capture_open(struct snd_rawmidi_substream *substream)
{
/* Do nothing. */
return 0;
}
static int midi_playback_open(struct snd_rawmidi_substream *substream)
{
struct snd_ff *ff = substream->rmidi->private_data;
/* Initialize internal status. */
ff->running_status[substream->number] = 0;
ff->rx_midi_error[substream->number] = false;
ACCESS_ONCE(ff->rx_midi_substreams[substream->number]) = substream;
return 0;
}
static int midi_capture_close(struct snd_rawmidi_substream *substream)
{
/* Do nothing. */
return 0;
}
static int midi_playback_close(struct snd_rawmidi_substream *substream)
{
struct snd_ff *ff = substream->rmidi->private_data;
cancel_work_sync(&ff->rx_midi_work[substream->number]);
ACCESS_ONCE(ff->rx_midi_substreams[substream->number]) = NULL;
return 0;
}
static void midi_capture_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct snd_ff *ff = substream->rmidi->private_data;
unsigned long flags;
spin_lock_irqsave(&ff->lock, flags);
if (up)
ACCESS_ONCE(ff->tx_midi_substreams[substream->number]) =
substream;
else
ACCESS_ONCE(ff->tx_midi_substreams[substream->number]) = NULL;
spin_unlock_irqrestore(&ff->lock, flags);
}
static void midi_playback_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct snd_ff *ff = substream->rmidi->private_data;
unsigned long flags;
spin_lock_irqsave(&ff->lock, flags);
if (up || !ff->rx_midi_error[substream->number])
schedule_work(&ff->rx_midi_work[substream->number]);
spin_unlock_irqrestore(&ff->lock, flags);
}
static struct snd_rawmidi_ops midi_capture_ops = {
.open = midi_capture_open,
.close = midi_capture_close,
.trigger = midi_capture_trigger,
};
static struct snd_rawmidi_ops midi_playback_ops = {
.open = midi_playback_open,
.close = midi_playback_close,
.trigger = midi_playback_trigger,
};
static void set_midi_substream_names(struct snd_rawmidi_str *stream,
const char *const name)
{
struct snd_rawmidi_substream *substream;
list_for_each_entry(substream, &stream->substreams, list) {
snprintf(substream->name, sizeof(substream->name),
"%s MIDI %d", name, substream->number + 1);
}
}
int snd_ff_create_midi_devices(struct snd_ff *ff)
{
struct snd_rawmidi *rmidi;
struct snd_rawmidi_str *stream;
int err;
err = snd_rawmidi_new(ff->card, ff->card->driver, 0,
ff->spec->midi_out_ports, ff->spec->midi_in_ports,
&rmidi);
if (err < 0)
return err;
snprintf(rmidi->name, sizeof(rmidi->name),
"%s MIDI", ff->card->shortname);
rmidi->private_data = ff;
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
&midi_capture_ops);
stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT];
set_midi_substream_names(stream, ff->card->shortname);
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
&midi_playback_ops);
stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
set_midi_substream_names(stream, ff->card->shortname);
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
return 0;
}

View file

@ -0,0 +1,409 @@
/*
* ff-pcm.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "ff.h"
static inline unsigned int get_multiplier_mode_with_index(unsigned int index)
{
return ((int)index - 1) / 2;
}
static int hw_rule_rate(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
const unsigned int *pcm_channels = rule->private;
struct snd_interval *r =
hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
const struct snd_interval *c =
hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
struct snd_interval t = {
.min = UINT_MAX, .max = 0, .integer = 1
};
unsigned int i, mode;
for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) {
mode = get_multiplier_mode_with_index(i);
if (!snd_interval_test(c, pcm_channels[mode]))
continue;
t.min = min(t.min, amdtp_rate_table[i]);
t.max = max(t.max, amdtp_rate_table[i]);
}
return snd_interval_refine(r, &t);
}
static int hw_rule_channels(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
const unsigned int *pcm_channels = rule->private;
struct snd_interval *c =
hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
const struct snd_interval *r =
hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE);
struct snd_interval t = {
.min = UINT_MAX, .max = 0, .integer = 1
};
unsigned int i, mode;
for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) {
mode = get_multiplier_mode_with_index(i);
if (!snd_interval_test(r, amdtp_rate_table[i]))
continue;
t.min = min(t.min, pcm_channels[mode]);
t.max = max(t.max, pcm_channels[mode]);
}
return snd_interval_refine(c, &t);
}
static void limit_channels_and_rates(struct snd_pcm_hardware *hw,
const unsigned int *pcm_channels)
{
unsigned int mode;
unsigned int rate, channels;
int i;
hw->channels_min = UINT_MAX;
hw->channels_max = 0;
hw->rate_min = UINT_MAX;
hw->rate_max = 0;
for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) {
mode = get_multiplier_mode_with_index(i);
channels = pcm_channels[mode];
if (pcm_channels[mode] == 0)
continue;
hw->channels_min = min(hw->channels_min, channels);
hw->channels_max = max(hw->channels_max, channels);
rate = amdtp_rate_table[i];
hw->rates |= snd_pcm_rate_to_rate_bit(rate);
hw->rate_min = min(hw->rate_min, rate);
hw->rate_max = max(hw->rate_max, rate);
}
}
static void limit_period_and_buffer(struct snd_pcm_hardware *hw)
{
hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */
hw->periods_max = UINT_MAX;
hw->period_bytes_min = 4 * hw->channels_max; /* bytes for a frame */
/* Just to prevent from allocating much pages. */
hw->period_bytes_max = hw->period_bytes_min * 2048;
hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min;
}
static int pcm_init_hw_params(struct snd_ff *ff,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct amdtp_stream *s;
const unsigned int *pcm_channels;
int err;
runtime->hw.info = SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_JOINT_DUPLEX |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
runtime->hw.formats = SNDRV_PCM_FMTBIT_S32;
s = &ff->tx_stream;
pcm_channels = ff->spec->pcm_capture_channels;
} else {
runtime->hw.formats = SNDRV_PCM_FMTBIT_S32;
s = &ff->rx_stream;
pcm_channels = ff->spec->pcm_playback_channels;
}
/* limit rates */
limit_channels_and_rates(&runtime->hw, pcm_channels);
limit_period_and_buffer(&runtime->hw);
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
hw_rule_channels, (void *)pcm_channels,
SNDRV_PCM_HW_PARAM_RATE, -1);
if (err < 0)
return err;
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
hw_rule_rate, (void *)pcm_channels,
SNDRV_PCM_HW_PARAM_CHANNELS, -1);
if (err < 0)
return err;
return amdtp_ff_add_pcm_hw_constraints(s, runtime);
}
static int pcm_open(struct snd_pcm_substream *substream)
{
struct snd_ff *ff = substream->private_data;
unsigned int rate;
enum snd_ff_clock_src src;
int i, err;
err = snd_ff_stream_lock_try(ff);
if (err < 0)
return err;
err = pcm_init_hw_params(ff, substream);
if (err < 0) {
snd_ff_stream_lock_release(ff);
return err;
}
err = ff->spec->protocol->get_clock(ff, &rate, &src);
if (err < 0) {
snd_ff_stream_lock_release(ff);
return err;
}
if (src != SND_FF_CLOCK_SRC_INTERNAL) {
for (i = 0; i < CIP_SFC_COUNT; ++i) {
if (amdtp_rate_table[i] == rate)
break;
}
/*
* The unit is configured at sampling frequency which packet
* streaming engine can't support.
*/
if (i >= CIP_SFC_COUNT) {
snd_ff_stream_lock_release(ff);
return -EIO;
}
substream->runtime->hw.rate_min = rate;
substream->runtime->hw.rate_max = rate;
} else {
if (amdtp_stream_pcm_running(&ff->rx_stream) ||
amdtp_stream_pcm_running(&ff->tx_stream)) {
rate = amdtp_rate_table[ff->rx_stream.sfc];
substream->runtime->hw.rate_min = rate;
substream->runtime->hw.rate_max = rate;
}
}
snd_pcm_set_sync(substream);
return 0;
}
static int pcm_close(struct snd_pcm_substream *substream)
{
struct snd_ff *ff = substream->private_data;
snd_ff_stream_lock_release(ff);
return 0;
}
static int pcm_capture_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_ff *ff = substream->private_data;
int err;
err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
params_buffer_bytes(hw_params));
if (err < 0)
return err;
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
mutex_lock(&ff->mutex);
ff->substreams_counter++;
mutex_unlock(&ff->mutex);
}
return 0;
}
static int pcm_playback_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_ff *ff = substream->private_data;
int err;
err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
params_buffer_bytes(hw_params));
if (err < 0)
return err;
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
mutex_lock(&ff->mutex);
ff->substreams_counter++;
mutex_unlock(&ff->mutex);
}
return 0;
}
static int pcm_capture_hw_free(struct snd_pcm_substream *substream)
{
struct snd_ff *ff = substream->private_data;
mutex_lock(&ff->mutex);
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
ff->substreams_counter--;
snd_ff_stream_stop_duplex(ff);
mutex_unlock(&ff->mutex);
return snd_pcm_lib_free_vmalloc_buffer(substream);
}
static int pcm_playback_hw_free(struct snd_pcm_substream *substream)
{
struct snd_ff *ff = substream->private_data;
mutex_lock(&ff->mutex);
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
ff->substreams_counter--;
snd_ff_stream_stop_duplex(ff);
mutex_unlock(&ff->mutex);
return snd_pcm_lib_free_vmalloc_buffer(substream);
}
static int pcm_capture_prepare(struct snd_pcm_substream *substream)
{
struct snd_ff *ff = substream->private_data;
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
mutex_lock(&ff->mutex);
err = snd_ff_stream_start_duplex(ff, runtime->rate);
if (err >= 0)
amdtp_stream_pcm_prepare(&ff->tx_stream);
mutex_unlock(&ff->mutex);
return err;
}
static int pcm_playback_prepare(struct snd_pcm_substream *substream)
{
struct snd_ff *ff = substream->private_data;
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
mutex_lock(&ff->mutex);
err = snd_ff_stream_start_duplex(ff, runtime->rate);
if (err >= 0)
amdtp_stream_pcm_prepare(&ff->rx_stream);
mutex_unlock(&ff->mutex);
return err;
}
static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_ff *ff = substream->private_data;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
amdtp_stream_pcm_trigger(&ff->tx_stream, substream);
break;
case SNDRV_PCM_TRIGGER_STOP:
amdtp_stream_pcm_trigger(&ff->tx_stream, NULL);
break;
default:
return -EINVAL;
}
return 0;
}
static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_ff *ff = substream->private_data;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
amdtp_stream_pcm_trigger(&ff->rx_stream, substream);
break;
case SNDRV_PCM_TRIGGER_STOP:
amdtp_stream_pcm_trigger(&ff->rx_stream, NULL);
break;
default:
return -EINVAL;
}
return 0;
}
static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
{
struct snd_ff *ff = sbstrm->private_data;
return amdtp_stream_pcm_pointer(&ff->tx_stream);
}
static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
{
struct snd_ff *ff = sbstrm->private_data;
return amdtp_stream_pcm_pointer(&ff->rx_stream);
}
static struct snd_pcm_ops pcm_capture_ops = {
.open = pcm_open,
.close = pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = pcm_capture_hw_params,
.hw_free = pcm_capture_hw_free,
.prepare = pcm_capture_prepare,
.trigger = pcm_capture_trigger,
.pointer = pcm_capture_pointer,
.page = snd_pcm_lib_get_vmalloc_page,
};
static struct snd_pcm_ops pcm_playback_ops = {
.open = pcm_open,
.close = pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = pcm_playback_hw_params,
.hw_free = pcm_playback_hw_free,
.prepare = pcm_playback_prepare,
.trigger = pcm_playback_trigger,
.pointer = pcm_playback_pointer,
.page = snd_pcm_lib_get_vmalloc_page,
.mmap = snd_pcm_lib_mmap_vmalloc,
};
int snd_ff_create_pcm_devices(struct snd_ff *ff)
{
struct snd_pcm *pcm;
int err;
err = snd_pcm_new(ff->card, ff->card->driver, 0, 1, 1, &pcm);
if (err < 0)
return err;
pcm->private_data = ff;
snprintf(pcm->name, sizeof(pcm->name),
"%s PCM", ff->card->shortname);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops);
return 0;
}

View file

@ -0,0 +1,63 @@
/*
* ff-proc.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "./ff.h"
static void proc_dump_clock_config(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_ff *ff = entry->private_data;
ff->spec->protocol->dump_clock_config(ff, buffer);
}
static void proc_dump_sync_status(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_ff *ff = entry->private_data;
ff->spec->protocol->dump_sync_status(ff, buffer);
}
static void add_node(struct snd_ff *ff, struct snd_info_entry *root,
const char *name,
void (*op)(struct snd_info_entry *e,
struct snd_info_buffer *b))
{
struct snd_info_entry *entry;
entry = snd_info_create_card_entry(ff->card, name, root);
if (entry == NULL)
return;
snd_info_set_text_ops(entry, ff, op);
if (snd_info_register(entry) < 0)
snd_info_free_entry(entry);
}
void snd_ff_proc_init(struct snd_ff *ff)
{
struct snd_info_entry *root;
/*
* All nodes are automatically removed at snd_card_disconnect(),
* by following to link list.
*/
root = snd_info_create_card_entry(ff->card, "firewire",
ff->card->proc_root);
if (root == NULL)
return;
root->mode = S_IFDIR | S_IRUGO | S_IXUGO;
if (snd_info_register(root) < 0) {
snd_info_free_entry(root);
return;
}
add_node(ff, root, "clock-config", proc_dump_clock_config);
add_node(ff, root, "sync-status", proc_dump_sync_status);
}

View file

@ -0,0 +1,371 @@
/*
* ff-protocol-ff400.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include <linux/delay.h>
#include "ff.h"
#define FF400_STF 0x000080100500ull
#define FF400_RX_PACKET_FORMAT 0x000080100504ull
#define FF400_ISOC_COMM_START 0x000080100508ull
#define FF400_TX_PACKET_FORMAT 0x00008010050cull
#define FF400_ISOC_COMM_STOP 0x000080100510ull
#define FF400_SYNC_STATUS 0x0000801c0000ull
#define FF400_FETCH_PCM_FRAMES 0x0000801c0000ull /* For block request. */
#define FF400_CLOCK_CONFIG 0x0000801c0004ull
#define FF400_MIDI_HIGH_ADDR 0x0000801003f4ull
#define FF400_MIDI_RX_PORT_0 0x000080180000ull
#define FF400_MIDI_RX_PORT_1 0x000080190000ull
static int ff400_get_clock(struct snd_ff *ff, unsigned int *rate,
enum snd_ff_clock_src *src)
{
__le32 reg;
u32 data;
int err;
err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
FF400_SYNC_STATUS, &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,
};

View file

@ -0,0 +1,282 @@
/*
* ff-stream.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "ff.h"
#define CALLBACK_TIMEOUT_MS 200
static int get_rate_mode(unsigned int rate, unsigned int *mode)
{
int i;
for (i = 0; i < CIP_SFC_COUNT; i++) {
if (amdtp_rate_table[i] == rate)
break;
}
if (i == CIP_SFC_COUNT)
return -EINVAL;
*mode = ((int)i - 1) / 2;
return 0;
}
/*
* Fireface 400 manages isochronous channel number in 3 bit field. Therefore,
* we can allocate between 0 and 7 channel.
*/
static int keep_resources(struct snd_ff *ff, unsigned int rate)
{
int mode;
int err;
err = get_rate_mode(rate, &mode);
if (err < 0)
return err;
/* Keep resources for in-stream. */
err = amdtp_ff_set_parameters(&ff->tx_stream, rate,
ff->spec->pcm_capture_channels[mode]);
if (err < 0)
return err;
ff->tx_resources.channels_mask = 0x00000000000000ffuLL;
err = fw_iso_resources_allocate(&ff->tx_resources,
amdtp_stream_get_max_payload(&ff->tx_stream),
fw_parent_device(ff->unit)->max_speed);
if (err < 0)
return err;
/* Keep resources for out-stream. */
err = amdtp_ff_set_parameters(&ff->rx_stream, rate,
ff->spec->pcm_playback_channels[mode]);
if (err < 0)
return err;
ff->rx_resources.channels_mask = 0x00000000000000ffuLL;
err = fw_iso_resources_allocate(&ff->rx_resources,
amdtp_stream_get_max_payload(&ff->rx_stream),
fw_parent_device(ff->unit)->max_speed);
if (err < 0)
fw_iso_resources_free(&ff->tx_resources);
return err;
}
static void release_resources(struct snd_ff *ff)
{
fw_iso_resources_free(&ff->tx_resources);
fw_iso_resources_free(&ff->rx_resources);
}
static inline void finish_session(struct snd_ff *ff)
{
ff->spec->protocol->finish_session(ff);
ff->spec->protocol->switch_fetching_mode(ff, false);
}
static int init_stream(struct snd_ff *ff, enum amdtp_stream_direction dir)
{
int err;
struct fw_iso_resources *resources;
struct amdtp_stream *stream;
if (dir == AMDTP_IN_STREAM) {
resources = &ff->tx_resources;
stream = &ff->tx_stream;
} else {
resources = &ff->rx_resources;
stream = &ff->rx_stream;
}
err = fw_iso_resources_init(resources, ff->unit);
if (err < 0)
return err;
err = amdtp_ff_init(stream, ff->unit, dir);
if (err < 0)
fw_iso_resources_destroy(resources);
return err;
}
static void destroy_stream(struct snd_ff *ff, enum amdtp_stream_direction dir)
{
if (dir == AMDTP_IN_STREAM) {
amdtp_stream_destroy(&ff->tx_stream);
fw_iso_resources_destroy(&ff->tx_resources);
} else {
amdtp_stream_destroy(&ff->rx_stream);
fw_iso_resources_destroy(&ff->rx_resources);
}
}
int snd_ff_stream_init_duplex(struct snd_ff *ff)
{
int err;
err = init_stream(ff, AMDTP_OUT_STREAM);
if (err < 0)
goto end;
err = init_stream(ff, AMDTP_IN_STREAM);
if (err < 0)
destroy_stream(ff, AMDTP_OUT_STREAM);
end:
return err;
}
/*
* This function should be called before starting streams or after stopping
* streams.
*/
void snd_ff_stream_destroy_duplex(struct snd_ff *ff)
{
destroy_stream(ff, AMDTP_IN_STREAM);
destroy_stream(ff, AMDTP_OUT_STREAM);
}
int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate)
{
unsigned int curr_rate;
enum snd_ff_clock_src src;
int err;
if (ff->substreams_counter == 0)
return 0;
err = ff->spec->protocol->get_clock(ff, &curr_rate, &src);
if (err < 0)
return err;
if (curr_rate != rate ||
amdtp_streaming_error(&ff->tx_stream) ||
amdtp_streaming_error(&ff->rx_stream)) {
finish_session(ff);
amdtp_stream_stop(&ff->tx_stream);
amdtp_stream_stop(&ff->rx_stream);
release_resources(ff);
}
/*
* Regardless of current source of clock signal, drivers transfer some
* packets. Then, the device transfers packets.
*/
if (!amdtp_stream_running(&ff->rx_stream)) {
err = keep_resources(ff, rate);
if (err < 0)
goto error;
err = ff->spec->protocol->begin_session(ff, rate);
if (err < 0)
goto error;
err = amdtp_stream_start(&ff->rx_stream,
ff->rx_resources.channel,
fw_parent_device(ff->unit)->max_speed);
if (err < 0)
goto error;
if (!amdtp_stream_wait_callback(&ff->rx_stream,
CALLBACK_TIMEOUT_MS)) {
err = -ETIMEDOUT;
goto error;
}
err = ff->spec->protocol->switch_fetching_mode(ff, true);
if (err < 0)
goto error;
}
if (!amdtp_stream_running(&ff->tx_stream)) {
err = amdtp_stream_start(&ff->tx_stream,
ff->tx_resources.channel,
fw_parent_device(ff->unit)->max_speed);
if (err < 0)
goto error;
if (!amdtp_stream_wait_callback(&ff->tx_stream,
CALLBACK_TIMEOUT_MS)) {
err = -ETIMEDOUT;
goto error;
}
}
return 0;
error:
amdtp_stream_stop(&ff->tx_stream);
amdtp_stream_stop(&ff->rx_stream);
finish_session(ff);
release_resources(ff);
return err;
}
void snd_ff_stream_stop_duplex(struct snd_ff *ff)
{
if (ff->substreams_counter > 0)
return;
amdtp_stream_stop(&ff->tx_stream);
amdtp_stream_stop(&ff->rx_stream);
finish_session(ff);
release_resources(ff);
}
void snd_ff_stream_update_duplex(struct snd_ff *ff)
{
/* The device discontinue to transfer packets. */
amdtp_stream_pcm_abort(&ff->tx_stream);
amdtp_stream_stop(&ff->tx_stream);
amdtp_stream_pcm_abort(&ff->rx_stream);
amdtp_stream_stop(&ff->rx_stream);
fw_iso_resources_update(&ff->tx_resources);
fw_iso_resources_update(&ff->rx_resources);
}
void snd_ff_stream_lock_changed(struct snd_ff *ff)
{
ff->dev_lock_changed = true;
wake_up(&ff->hwdep_wait);
}
int snd_ff_stream_lock_try(struct snd_ff *ff)
{
int err;
spin_lock_irq(&ff->lock);
/* user land lock this */
if (ff->dev_lock_count < 0) {
err = -EBUSY;
goto end;
}
/* this is the first time */
if (ff->dev_lock_count++ == 0)
snd_ff_stream_lock_changed(ff);
err = 0;
end:
spin_unlock_irq(&ff->lock);
return err;
}
void snd_ff_stream_lock_release(struct snd_ff *ff)
{
spin_lock_irq(&ff->lock);
if (WARN_ON(ff->dev_lock_count <= 0))
goto end;
if (--ff->dev_lock_count == 0)
snd_ff_stream_lock_changed(ff);
end:
spin_unlock_irq(&ff->lock);
}

View file

@ -0,0 +1,295 @@
/*
* ff-transaction.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "ff.h"
static void finish_transmit_midi_msg(struct snd_ff *ff, unsigned int port,
int rcode)
{
struct snd_rawmidi_substream *substream =
ACCESS_ONCE(ff->rx_midi_substreams[port]);
if (rcode_is_permanent_error(rcode)) {
ff->rx_midi_error[port] = true;
return;
}
if (rcode != RCODE_COMPLETE) {
/* Transfer the message again, immediately. */
ff->next_ktime[port] = 0;
schedule_work(&ff->rx_midi_work[port]);
return;
}
snd_rawmidi_transmit_ack(substream, ff->rx_bytes[port]);
ff->rx_bytes[port] = 0;
if (!snd_rawmidi_transmit_empty(substream))
schedule_work(&ff->rx_midi_work[port]);
}
static void finish_transmit_midi0_msg(struct fw_card *card, int rcode,
void *data, size_t length,
void *callback_data)
{
struct snd_ff *ff =
container_of(callback_data, struct snd_ff, transactions[0]);
finish_transmit_midi_msg(ff, 0, rcode);
}
static void finish_transmit_midi1_msg(struct fw_card *card, int rcode,
void *data, size_t length,
void *callback_data)
{
struct snd_ff *ff =
container_of(callback_data, struct snd_ff, transactions[1]);
finish_transmit_midi_msg(ff, 1, rcode);
}
static inline void fill_midi_buf(struct snd_ff *ff, unsigned int port,
unsigned int index, u8 byte)
{
ff->msg_buf[port][index] = cpu_to_le32(byte);
}
static void transmit_midi_msg(struct snd_ff *ff, unsigned int port)
{
struct snd_rawmidi_substream *substream =
ACCESS_ONCE(ff->rx_midi_substreams[port]);
u8 *buf = (u8 *)ff->msg_buf[port];
int i, len;
struct fw_device *fw_dev = fw_parent_device(ff->unit);
unsigned long long addr;
int generation;
fw_transaction_callback_t callback;
if (substream == NULL || snd_rawmidi_transmit_empty(substream))
return;
if (ff->rx_bytes[port] > 0 || ff->rx_midi_error[port])
return;
/* Do it in next chance. */
if (ktime_after(ff->next_ktime[port], ktime_get())) {
schedule_work(&ff->rx_midi_work[port]);
return;
}
len = snd_rawmidi_transmit_peek(substream, buf,
SND_FF_MAXIMIM_MIDI_QUADS);
if (len <= 0)
return;
for (i = len - 1; i >= 0; i--)
fill_midi_buf(ff, port, i, buf[i]);
if (port == 0) {
addr = ff->spec->protocol->midi_rx_port_0_reg;
callback = finish_transmit_midi0_msg;
} else {
addr = ff->spec->protocol->midi_rx_port_1_reg;
callback = finish_transmit_midi1_msg;
}
/* Set interval to next transaction. */
ff->next_ktime[port] = ktime_add_ns(ktime_get(),
len * 8 * NSEC_PER_SEC / 31250);
ff->rx_bytes[port] = len;
/*
* In Linux FireWire core, when generation is updated with memory
* barrier, node id has already been updated. In this module, After
* this smp_rmb(), load/store instructions to memory are completed.
* Thus, both of generation and node id are available with recent
* values. This is a light-serialization solution to handle bus reset
* events on IEEE 1394 bus.
*/
generation = fw_dev->generation;
smp_rmb();
fw_send_request(fw_dev->card, &ff->transactions[port],
TCODE_WRITE_BLOCK_REQUEST,
fw_dev->node_id, generation, fw_dev->max_speed,
addr, &ff->msg_buf[port], len * 4,
callback, &ff->transactions[port]);
}
static void transmit_midi0_msg(struct work_struct *work)
{
struct snd_ff *ff = container_of(work, struct snd_ff, rx_midi_work[0]);
transmit_midi_msg(ff, 0);
}
static void transmit_midi1_msg(struct work_struct *work)
{
struct snd_ff *ff = container_of(work, struct snd_ff, rx_midi_work[1]);
transmit_midi_msg(ff, 1);
}
static void handle_midi_msg(struct fw_card *card, struct fw_request *request,
int tcode, int destination, int source,
int generation, unsigned long long offset,
void *data, size_t length, void *callback_data)
{
struct snd_ff *ff = callback_data;
__le32 *buf = data;
u32 quad;
u8 byte;
unsigned int index;
struct snd_rawmidi_substream *substream;
int i;
fw_send_response(card, request, RCODE_COMPLETE);
for (i = 0; i < length / 4; i++) {
quad = le32_to_cpu(buf[i]);
/* Message in first port. */
/*
* This value may represent the index of this unit when the same
* units are on the same IEEE 1394 bus. This driver doesn't use
* it.
*/
index = (quad >> 8) & 0xff;
if (index > 0) {
substream = ACCESS_ONCE(ff->tx_midi_substreams[0]);
if (substream != NULL) {
byte = quad & 0xff;
snd_rawmidi_receive(substream, &byte, 1);
}
}
/* Message in second port. */
index = (quad >> 24) & 0xff;
if (index > 0) {
substream = ACCESS_ONCE(ff->tx_midi_substreams[1]);
if (substream != NULL) {
byte = (quad >> 16) & 0xff;
snd_rawmidi_receive(substream, &byte, 1);
}
}
}
}
static int allocate_own_address(struct snd_ff *ff, int i)
{
struct fw_address_region midi_msg_region;
int err;
ff->async_handler.length = SND_FF_MAXIMIM_MIDI_QUADS * 4;
ff->async_handler.address_callback = handle_midi_msg;
ff->async_handler.callback_data = ff;
midi_msg_region.start = 0x000100000000ull * i;
midi_msg_region.end = midi_msg_region.start + ff->async_handler.length;
err = fw_core_add_address_handler(&ff->async_handler, &midi_msg_region);
if (err >= 0) {
/* Controllers are allowed to register this region. */
if (ff->async_handler.offset & 0x0000ffffffff) {
fw_core_remove_address_handler(&ff->async_handler);
err = -EAGAIN;
}
}
return err;
}
/*
* The configuration to start asynchronous transactions for MIDI messages is in
* 0x'0000'8010'051c. This register includes the other options, thus this driver
* doesn't touch it and leaves the decision to userspace. The userspace MUST add
* 0x04000000 to write transactions to the register to receive any MIDI
* messages.
*
* Here, I just describe MIDI-related offsets of the register, in little-endian
* order.
*
* Controllers are allowed to register higher 4 bytes of address to receive
* the transactions. The register is 0x'0000'8010'03f4. On the other hand, the
* controllers are not allowed to register lower 4 bytes of the address. They
* are forced to select from 4 options by writing corresponding bits to
* 0x'0000'8010'051c.
*
* The 3rd-6th bits in MSB of this register are used to indicate lower 4 bytes
* of address to which the device transferrs the transactions.
* - 6th: 0x'....'....'0000'0180
* - 5th: 0x'....'....'0000'0100
* - 4th: 0x'....'....'0000'0080
* - 3rd: 0x'....'....'0000'0000
*
* This driver configure 0x'....'....'0000'0000 for units to receive MIDI
* messages. 3rd bit of the register should be configured, however this driver
* deligates this task to user space applications due to a restriction that
* this register is write-only and the other bits have own effects.
*
* The 1st and 2nd bits in LSB of this register are used to cancel transferring
* asynchronous transactions. These two bits have the same effect.
* - 1st/2nd: cancel transferring
*/
int snd_ff_transaction_reregister(struct snd_ff *ff)
{
struct fw_card *fw_card = fw_parent_device(ff->unit)->card;
u32 addr;
__le32 reg;
/*
* Controllers are allowed to register its node ID and upper 2 byte of
* local address to listen asynchronous transactions.
*/
addr = (fw_card->node_id << 16) | (ff->async_handler.offset >> 32);
reg = cpu_to_le32(addr);
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
ff->spec->protocol->midi_high_addr_reg,
&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);
}

View file

@ -0,0 +1,209 @@
/*
* ff.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "ff.h"
#define OUI_RME 0x000a35
MODULE_DESCRIPTION("RME Fireface series Driver");
MODULE_AUTHOR("Takashi Sakamoto <[email protected]>");
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);

View file

@ -0,0 +1,146 @@
/*
* ff.h - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#ifndef SOUND_FIREFACE_H_INCLUDED
#define SOUND_FIREFACE_H_INCLUDED
#include <linux/device.h>
#include <linux/firewire.h>
#include <linux/firewire-constants.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/sched/signal.h>
#include <sound/core.h>
#include <sound/info.h>
#include <sound/rawmidi.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/hwdep.h>
#include <sound/firewire.h>
#include "../lib.h"
#include "../amdtp-stream.h"
#include "../iso-resources.h"
#define SND_FF_STREAM_MODES 3
#define SND_FF_MAXIMIM_MIDI_QUADS 9
#define SND_FF_IN_MIDI_PORTS 2
#define SND_FF_OUT_MIDI_PORTS 2
struct snd_ff_protocol;
struct snd_ff_spec {
const char *const name;
const unsigned int pcm_capture_channels[SND_FF_STREAM_MODES];
const unsigned int pcm_playback_channels[SND_FF_STREAM_MODES];
unsigned int midi_in_ports;
unsigned int midi_out_ports;
struct snd_ff_protocol *protocol;
};
struct snd_ff {
struct snd_card *card;
struct fw_unit *unit;
struct mutex mutex;
spinlock_t lock;
bool registered;
struct delayed_work dwork;
const struct snd_ff_spec *spec;
/* To handle MIDI tx. */
struct snd_rawmidi_substream *tx_midi_substreams[SND_FF_IN_MIDI_PORTS];
struct fw_address_handler async_handler;
/* TO handle MIDI rx. */
struct snd_rawmidi_substream *rx_midi_substreams[SND_FF_OUT_MIDI_PORTS];
u8 running_status[SND_FF_OUT_MIDI_PORTS];
__le32 msg_buf[SND_FF_OUT_MIDI_PORTS][SND_FF_MAXIMIM_MIDI_QUADS];
struct work_struct rx_midi_work[SND_FF_OUT_MIDI_PORTS];
struct fw_transaction transactions[SND_FF_OUT_MIDI_PORTS];
ktime_t next_ktime[SND_FF_OUT_MIDI_PORTS];
bool rx_midi_error[SND_FF_OUT_MIDI_PORTS];
unsigned int rx_bytes[SND_FF_OUT_MIDI_PORTS];
unsigned int substreams_counter;
struct amdtp_stream tx_stream;
struct amdtp_stream rx_stream;
struct fw_iso_resources tx_resources;
struct fw_iso_resources rx_resources;
int dev_lock_count;
bool dev_lock_changed;
wait_queue_head_t hwdep_wait;
};
enum snd_ff_clock_src {
SND_FF_CLOCK_SRC_INTERNAL,
SND_FF_CLOCK_SRC_SPDIF,
SND_FF_CLOCK_SRC_ADAT,
SND_FF_CLOCK_SRC_WORD,
SND_FF_CLOCK_SRC_LTC,
/* TODO: perhaps ADAT2 and TCO exists. */
};
struct snd_ff_protocol {
int (*get_clock)(struct snd_ff *ff, unsigned int *rate,
enum snd_ff_clock_src *src);
int (*begin_session)(struct snd_ff *ff, unsigned int rate);
void (*finish_session)(struct snd_ff *ff);
int (*switch_fetching_mode)(struct snd_ff *ff, bool enable);
void (*dump_sync_status)(struct snd_ff *ff,
struct snd_info_buffer *buffer);
void (*dump_clock_config)(struct snd_ff *ff,
struct snd_info_buffer *buffer);
u64 midi_high_addr_reg;
u64 midi_rx_port_0_reg;
u64 midi_rx_port_1_reg;
};
extern struct snd_ff_protocol snd_ff_protocol_ff400;
int snd_ff_transaction_register(struct snd_ff *ff);
int snd_ff_transaction_reregister(struct snd_ff *ff);
void snd_ff_transaction_unregister(struct snd_ff *ff);
int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate,
unsigned int pcm_channels);
int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s,
struct snd_pcm_runtime *runtime);
int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit,
enum amdtp_stream_direction dir);
int snd_ff_stream_init_duplex(struct snd_ff *ff);
void snd_ff_stream_destroy_duplex(struct snd_ff *ff);
int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate);
void snd_ff_stream_stop_duplex(struct snd_ff *ff);
void snd_ff_stream_update_duplex(struct snd_ff *ff);
void snd_ff_stream_lock_changed(struct snd_ff *ff);
int snd_ff_stream_lock_try(struct snd_ff *ff);
void snd_ff_stream_lock_release(struct snd_ff *ff);
void snd_ff_proc_init(struct snd_ff *ff);
int snd_ff_create_midi_devices(struct snd_ff *ff);
int snd_ff_create_pcm_devices(struct snd_ff *ff);
int snd_ff_create_hwdep_devices(struct snd_ff *ff);
#endif

View file

@ -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 <[email protected]>");
MODULE_LICENSE("GPL v2");

View file

@ -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

View file

@ -0,0 +1,6 @@
CFLAGS_amdtp-motu.o := -I$(src)
snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \
motu-proc.o motu-pcm.o motu-midi.o motu-hwdep.o \
motu-protocol-v2.o motu-protocol-v3.o
obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o

View file

@ -0,0 +1,123 @@
/*
* amdtp-motu-trace.h - tracepoint definitions to dump a part of packet data
*
* Copyright (c) 2017 Takashi Sakamoto
* Licensed under the terms of the GNU General Public License, version 2.
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM snd_firewire_motu
#if !defined(_SND_FIREWIRE_MOTU_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
#define _SND_FIREWIRE_MOTU_TRACE_H
#include <linux/tracepoint.h>
static void copy_sph(u32 *frame, __be32 *buffer, unsigned int data_blocks,
unsigned int data_block_quadlets);
static void copy_message(u64 *frames, __be32 *buffer, unsigned int data_blocks,
unsigned int data_block_quadlets);
TRACE_EVENT(in_data_block_sph,
TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer),
TP_ARGS(s, data_blocks, buffer),
TP_STRUCT__entry(
__field(int, src)
__field(int, dst)
__field(unsigned int, data_blocks)
__dynamic_array(u32, tstamps, data_blocks)
),
TP_fast_assign(
__entry->src = fw_parent_device(s->unit)->node_id;
__entry->dst = fw_parent_device(s->unit)->card->node_id;
__entry->data_blocks = data_blocks;
copy_sph(__get_dynamic_array(tstamps), buffer, data_blocks, s->data_block_quadlets);
),
TP_printk(
"%04x %04x %u %s",
__entry->src,
__entry->dst,
__entry->data_blocks,
__print_array(__get_dynamic_array(tstamps), __entry->data_blocks, 4)
)
);
TRACE_EVENT(out_data_block_sph,
TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer),
TP_ARGS(s, data_blocks, buffer),
TP_STRUCT__entry(
__field(int, src)
__field(int, dst)
__field(unsigned int, data_blocks)
__dynamic_array(u32, tstamps, data_blocks)
),
TP_fast_assign(
__entry->src = fw_parent_device(s->unit)->card->node_id;
__entry->dst = fw_parent_device(s->unit)->node_id;
__entry->data_blocks = data_blocks;
copy_sph(__get_dynamic_array(tstamps), buffer, data_blocks, s->data_block_quadlets);
),
TP_printk(
"%04x %04x %u %s",
__entry->src,
__entry->dst,
__entry->data_blocks,
__print_array(__get_dynamic_array(tstamps), __entry->data_blocks, 4)
)
);
TRACE_EVENT(in_data_block_message,
TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer),
TP_ARGS(s, data_blocks, buffer),
TP_STRUCT__entry(
__field(int, src)
__field(int, dst)
__field(unsigned int, data_blocks)
__dynamic_array(u64, messages, data_blocks)
),
TP_fast_assign(
__entry->src = fw_parent_device(s->unit)->node_id;
__entry->dst = fw_parent_device(s->unit)->card->node_id;
__entry->data_blocks = data_blocks;
copy_message(__get_dynamic_array(messages), buffer, data_blocks, s->data_block_quadlets);
),
TP_printk(
"%04x %04x %u %s",
__entry->src,
__entry->dst,
__entry->data_blocks,
__print_array(__get_dynamic_array(messages), __entry->data_blocks, 8)
)
);
TRACE_EVENT(out_data_block_message,
TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer),
TP_ARGS(s, data_blocks, buffer),
TP_STRUCT__entry(
__field(int, src)
__field(int, dst)
__field(unsigned int, data_blocks)
__dynamic_array(u64, messages, data_blocks)
),
TP_fast_assign(
__entry->src = fw_parent_device(s->unit)->card->node_id;
__entry->dst = fw_parent_device(s->unit)->node_id;
__entry->data_blocks = data_blocks;
copy_message(__get_dynamic_array(messages), buffer, data_blocks, s->data_block_quadlets);
),
TP_printk(
"%04x %04x %u %s",
__entry->src,
__entry->dst,
__entry->data_blocks,
__print_array(__get_dynamic_array(messages), __entry->data_blocks, 8)
)
);
#endif
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE amdtp-motu-trace
#include <trace/define_trace.h>

View file

@ -0,0 +1,427 @@
/*
* amdtp-motu.c - a part of driver for MOTU FireWire series
*
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include <linux/slab.h>
#include <sound/pcm.h>
#include "motu.h"
#define CREATE_TRACE_POINTS
#include "amdtp-motu-trace.h"
#define CIP_FMT_MOTU 0x02
#define CIP_FMT_MOTU_TX_V3 0x22
#define MOTU_FDF_AM824 0x22
/*
* Nominally 3125 bytes/second, but the MIDI port's clock might be
* 1% too slow, and the bus clock 100 ppm too fast.
*/
#define MIDI_BYTES_PER_SECOND 3093
struct amdtp_motu {
/* For timestamp processing. */
unsigned int quotient_ticks_per_event;
unsigned int remainder_ticks_per_event;
unsigned int next_ticks;
unsigned int next_accumulated;
unsigned int next_cycles;
unsigned int next_seconds;
unsigned int pcm_chunks;
unsigned int pcm_byte_offset;
struct snd_rawmidi_substream *midi;
unsigned int midi_ports;
unsigned int midi_flag_offset;
unsigned int midi_byte_offset;
int midi_db_count;
unsigned int midi_db_interval;
};
int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate,
unsigned int midi_ports,
struct snd_motu_packet_format *formats)
{
static const struct {
unsigned int quotient_ticks_per_event;
unsigned int remainder_ticks_per_event;
} params[] = {
[CIP_SFC_44100] = { 557, 123 },
[CIP_SFC_48000] = { 512, 0 },
[CIP_SFC_88200] = { 278, 282 },
[CIP_SFC_96000] = { 256, 0 },
[CIP_SFC_176400] = { 139, 141 },
[CIP_SFC_192000] = { 128, 0 },
};
struct amdtp_motu *p = s->protocol;
unsigned int pcm_chunks, data_chunks, data_block_quadlets;
unsigned int delay;
unsigned int mode;
int i, err;
if (amdtp_stream_running(s))
return -EBUSY;
for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) {
if (snd_motu_clock_rates[i] == rate) {
mode = i >> 1;
break;
}
}
if (i == ARRAY_SIZE(snd_motu_clock_rates))
return -EINVAL;
pcm_chunks = formats->fixed_part_pcm_chunks[mode] +
formats->differed_part_pcm_chunks[mode];
data_chunks = formats->msg_chunks + pcm_chunks;
/*
* Each data block includes SPH in its head. Data chunks follow with
* 3 byte alignment. Padding follows with zero to conform to quadlet
* alignment.
*/
data_block_quadlets = 1 + DIV_ROUND_UP(data_chunks * 3, 4);
err = amdtp_stream_set_parameters(s, rate, data_block_quadlets);
if (err < 0)
return err;
p->pcm_chunks = pcm_chunks;
p->pcm_byte_offset = formats->pcm_byte_offset;
p->midi_ports = midi_ports;
p->midi_flag_offset = formats->midi_flag_offset;
p->midi_byte_offset = formats->midi_byte_offset;
p->midi_db_count = 0;
p->midi_db_interval = rate / MIDI_BYTES_PER_SECOND;
/* IEEE 1394 bus requires. */
delay = 0x2e00;
/* For no-data or empty packets to adjust PCM sampling frequency. */
delay += 8000 * 3072 * s->syt_interval / rate;
p->next_seconds = 0;
p->next_cycles = delay / 3072;
p->quotient_ticks_per_event = params[s->sfc].quotient_ticks_per_event;
p->remainder_ticks_per_event = params[s->sfc].remainder_ticks_per_event;
p->next_ticks = delay % 3072;
p->next_accumulated = 0;
return 0;
}
static void read_pcm_s32(struct amdtp_stream *s,
struct snd_pcm_runtime *runtime,
__be32 *buffer, unsigned int data_blocks)
{
struct amdtp_motu *p = s->protocol;
unsigned int channels, remaining_frames, i, c;
u8 *byte;
u32 *dst;
channels = p->pcm_chunks;
dst = (void *)runtime->dma_area +
frames_to_bytes(runtime, s->pcm_buffer_pointer);
remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
for (i = 0; i < data_blocks; ++i) {
byte = (u8 *)buffer + p->pcm_byte_offset;
for (c = 0; c < channels; ++c) {
*dst = (byte[0] << 24) | (byte[1] << 16) | byte[2];
byte += 3;
dst++;
}
buffer += s->data_block_quadlets;
if (--remaining_frames == 0)
dst = (void *)runtime->dma_area;
}
}
static void write_pcm_s32(struct amdtp_stream *s,
struct snd_pcm_runtime *runtime,
__be32 *buffer, unsigned int data_blocks)
{
struct amdtp_motu *p = s->protocol;
unsigned int channels, remaining_frames, i, c;
u8 *byte;
const u32 *src;
channels = p->pcm_chunks;
src = (void *)runtime->dma_area +
frames_to_bytes(runtime, s->pcm_buffer_pointer);
remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
for (i = 0; i < data_blocks; ++i) {
byte = (u8 *)buffer + p->pcm_byte_offset;
for (c = 0; c < channels; ++c) {
byte[0] = (*src >> 24) & 0xff;
byte[1] = (*src >> 16) & 0xff;
byte[2] = (*src >> 8) & 0xff;
byte += 3;
src++;
}
buffer += s->data_block_quadlets;
if (--remaining_frames == 0)
src = (void *)runtime->dma_area;
}
}
static void write_pcm_silence(struct amdtp_stream *s, __be32 *buffer,
unsigned int data_blocks)
{
struct amdtp_motu *p = s->protocol;
unsigned int channels, i, c;
u8 *byte;
channels = p->pcm_chunks;
for (i = 0; i < data_blocks; ++i) {
byte = (u8 *)buffer + p->pcm_byte_offset;
for (c = 0; c < channels; ++c) {
byte[0] = 0;
byte[1] = 0;
byte[2] = 0;
byte += 3;
}
buffer += s->data_block_quadlets;
}
}
int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s,
struct snd_pcm_runtime *runtime)
{
int err;
/* TODO: how to set an constraint for exactly 24bit PCM sample? */
err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
if (err < 0)
return err;
return amdtp_stream_add_pcm_hw_constraints(s, runtime);
}
void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port,
struct snd_rawmidi_substream *midi)
{
struct amdtp_motu *p = s->protocol;
if (port < p->midi_ports)
WRITE_ONCE(p->midi, midi);
}
static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer,
unsigned int data_blocks)
{
struct amdtp_motu *p = s->protocol;
struct snd_rawmidi_substream *midi = READ_ONCE(p->midi);
u8 *b;
int i;
for (i = 0; i < data_blocks; i++) {
b = (u8 *)buffer;
if (midi && p->midi_db_count == 0 &&
snd_rawmidi_transmit(midi, b + p->midi_byte_offset, 1) == 1) {
b[p->midi_flag_offset] = 0x01;
} else {
b[p->midi_byte_offset] = 0x00;
b[p->midi_flag_offset] = 0x00;
}
buffer += s->data_block_quadlets;
if (--p->midi_db_count < 0)
p->midi_db_count = p->midi_db_interval;
}
}
static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer,
unsigned int data_blocks)
{
struct amdtp_motu *p = s->protocol;
struct snd_rawmidi_substream *midi;
u8 *b;
int i;
for (i = 0; i < data_blocks; i++) {
b = (u8 *)buffer;
midi = READ_ONCE(p->midi);
if (midi && (b[p->midi_flag_offset] & 0x01))
snd_rawmidi_receive(midi, b + p->midi_byte_offset, 1);
buffer += s->data_block_quadlets;
}
}
/* For tracepoints. */
static void __maybe_unused copy_sph(u32 *frames, __be32 *buffer,
unsigned int data_blocks,
unsigned int data_block_quadlets)
{
unsigned int i;
for (i = 0; i < data_blocks; ++i) {
*frames = be32_to_cpu(*buffer);
buffer += data_block_quadlets;
frames++;
}
}
/* For tracepoints. */
static void __maybe_unused copy_message(u64 *frames, __be32 *buffer,
unsigned int data_blocks,
unsigned int data_block_quadlets)
{
unsigned int i;
/* This is just for v2/v3 protocol. */
for (i = 0; i < data_blocks; ++i) {
*frames = (be32_to_cpu(buffer[1]) << 16) |
(be32_to_cpu(buffer[2]) >> 16);
buffer += data_block_quadlets;
frames++;
}
}
static unsigned int process_tx_data_blocks(struct amdtp_stream *s,
__be32 *buffer, unsigned int data_blocks,
unsigned int *syt)
{
struct amdtp_motu *p = s->protocol;
struct snd_pcm_substream *pcm;
trace_in_data_block_sph(s, data_blocks, buffer);
trace_in_data_block_message(s, data_blocks, buffer);
if (p->midi_ports)
read_midi_messages(s, buffer, data_blocks);
pcm = ACCESS_ONCE(s->pcm);
if (data_blocks > 0 && pcm)
read_pcm_s32(s, pcm->runtime, buffer, data_blocks);
return data_blocks;
}
static inline void compute_next_elapse_from_start(struct amdtp_motu *p)
{
p->next_accumulated += p->remainder_ticks_per_event;
if (p->next_accumulated >= 441) {
p->next_accumulated -= 441;
p->next_ticks++;
}
p->next_ticks += p->quotient_ticks_per_event;
if (p->next_ticks >= 3072) {
p->next_ticks -= 3072;
p->next_cycles++;
}
if (p->next_cycles >= 8000) {
p->next_cycles -= 8000;
p->next_seconds++;
}
if (p->next_seconds >= 128)
p->next_seconds -= 128;
}
static void write_sph(struct amdtp_stream *s, __be32 *buffer,
unsigned int data_blocks)
{
struct amdtp_motu *p = s->protocol;
unsigned int next_cycles;
unsigned int i;
u32 sph;
for (i = 0; i < data_blocks; i++) {
next_cycles = (s->start_cycle + p->next_cycles) % 8000;
sph = ((next_cycles << 12) | p->next_ticks) & 0x01ffffff;
*buffer = cpu_to_be32(sph);
compute_next_elapse_from_start(p);
buffer += s->data_block_quadlets;
}
}
static unsigned int process_rx_data_blocks(struct amdtp_stream *s,
__be32 *buffer, unsigned int data_blocks,
unsigned int *syt)
{
struct amdtp_motu *p = (struct amdtp_motu *)s->protocol;
struct snd_pcm_substream *pcm;
/* Not used. */
*syt = 0xffff;
/* TODO: how to interact control messages between userspace? */
if (p->midi_ports)
write_midi_messages(s, buffer, data_blocks);
pcm = ACCESS_ONCE(s->pcm);
if (pcm)
write_pcm_s32(s, pcm->runtime, buffer, data_blocks);
else
write_pcm_silence(s, buffer, data_blocks);
write_sph(s, buffer, data_blocks);
trace_out_data_block_sph(s, data_blocks, buffer);
trace_out_data_block_message(s, data_blocks, buffer);
return data_blocks;
}
int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit,
enum amdtp_stream_direction dir,
const struct snd_motu_protocol *const protocol)
{
amdtp_stream_process_data_blocks_t process_data_blocks;
int fmt = CIP_FMT_MOTU;
int flags = CIP_BLOCKING;
int err;
if (dir == AMDTP_IN_STREAM) {
process_data_blocks = process_tx_data_blocks;
/*
* Units of version 3 transmits packets with invalid CIP header
* against IEC 61883-1.
*/
if (protocol == &snd_motu_protocol_v3) {
flags |= CIP_WRONG_DBS |
CIP_SKIP_DBC_ZERO_CHECK |
CIP_HEADER_WITHOUT_EOH;
fmt = CIP_FMT_MOTU_TX_V3;
}
} else {
process_data_blocks = process_rx_data_blocks;
flags |= CIP_DBC_IS_END_EVENT;
}
err = amdtp_stream_init(s, unit, dir, flags, fmt, process_data_blocks,
sizeof(struct amdtp_motu));
if (err < 0)
return err;
s->sph = 1;
s->fdf = MOTU_FDF_AM824;
return 0;
}

View file

@ -0,0 +1,198 @@
/*
* motu-hwdep.c - a part of driver for MOTU FireWire series
*
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
/*
* This codes have five functionalities.
*
* 1.get information about firewire node
* 2.get notification about starting/stopping stream
* 3.lock/unlock streaming
*
*/
#include "motu.h"
static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
loff_t *offset)
{
struct snd_motu *motu = hwdep->private_data;
DEFINE_WAIT(wait);
union snd_firewire_event event;
spin_lock_irq(&motu->lock);
while (!motu->dev_lock_changed && motu->msg == 0) {
prepare_to_wait(&motu->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
spin_unlock_irq(&motu->lock);
schedule();
finish_wait(&motu->hwdep_wait, &wait);
if (signal_pending(current))
return -ERESTARTSYS;
spin_lock_irq(&motu->lock);
}
memset(&event, 0, sizeof(event));
if (motu->dev_lock_changed) {
event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
event.lock_status.status = (motu->dev_lock_count > 0);
motu->dev_lock_changed = false;
count = min_t(long, count, sizeof(event.lock_status));
} else {
event.motu_notification.type = SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION;
event.motu_notification.message = motu->msg;
motu->msg = 0;
count = min_t(long, count, sizeof(event.motu_notification));
}
spin_unlock_irq(&motu->lock);
if (copy_to_user(buf, &event, count))
return -EFAULT;
return count;
}
static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
poll_table *wait)
{
struct snd_motu *motu = hwdep->private_data;
unsigned int events;
poll_wait(file, &motu->hwdep_wait, wait);
spin_lock_irq(&motu->lock);
if (motu->dev_lock_changed || motu->msg)
events = POLLIN | POLLRDNORM;
else
events = 0;
spin_unlock_irq(&motu->lock);
return events | POLLOUT;
}
static int hwdep_get_info(struct snd_motu *motu, void __user *arg)
{
struct fw_device *dev = fw_parent_device(motu->unit);
struct snd_firewire_get_info info;
memset(&info, 0, sizeof(info));
info.type = SNDRV_FIREWIRE_TYPE_MOTU;
info.card = dev->card->index;
*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
strlcpy(info.device_name, dev_name(&dev->device),
sizeof(info.device_name));
if (copy_to_user(arg, &info, sizeof(info)))
return -EFAULT;
return 0;
}
static int hwdep_lock(struct snd_motu *motu)
{
int err;
spin_lock_irq(&motu->lock);
if (motu->dev_lock_count == 0) {
motu->dev_lock_count = -1;
err = 0;
} else {
err = -EBUSY;
}
spin_unlock_irq(&motu->lock);
return err;
}
static int hwdep_unlock(struct snd_motu *motu)
{
int err;
spin_lock_irq(&motu->lock);
if (motu->dev_lock_count == -1) {
motu->dev_lock_count = 0;
err = 0;
} else {
err = -EBADFD;
}
spin_unlock_irq(&motu->lock);
return err;
}
static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
{
struct snd_motu *motu = hwdep->private_data;
spin_lock_irq(&motu->lock);
if (motu->dev_lock_count == -1)
motu->dev_lock_count = 0;
spin_unlock_irq(&motu->lock);
return 0;
}
static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct snd_motu *motu = hwdep->private_data;
switch (cmd) {
case SNDRV_FIREWIRE_IOCTL_GET_INFO:
return hwdep_get_info(motu, (void __user *)arg);
case SNDRV_FIREWIRE_IOCTL_LOCK:
return hwdep_lock(motu);
case SNDRV_FIREWIRE_IOCTL_UNLOCK:
return hwdep_unlock(motu);
default:
return -ENOIOCTLCMD;
}
}
#ifdef CONFIG_COMPAT
static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
unsigned int cmd, unsigned long arg)
{
return hwdep_ioctl(hwdep, file, cmd,
(unsigned long)compat_ptr(arg));
}
#else
#define hwdep_compat_ioctl NULL
#endif
int snd_motu_create_hwdep_device(struct snd_motu *motu)
{
static const struct snd_hwdep_ops ops = {
.read = hwdep_read,
.release = hwdep_release,
.poll = hwdep_poll,
.ioctl = hwdep_ioctl,
.ioctl_compat = hwdep_compat_ioctl,
};
struct snd_hwdep *hwdep;
int err;
err = snd_hwdep_new(motu->card, motu->card->driver, 0, &hwdep);
if (err < 0)
return err;
strcpy(hwdep->name, "MOTU");
hwdep->iface = SNDRV_HWDEP_IFACE_FW_MOTU;
hwdep->ops = ops;
hwdep->private_data = motu;
hwdep->exclusive = true;
return 0;
}

View file

@ -0,0 +1,169 @@
/*
* motu-midi.h - a part of driver for MOTU FireWire series
*
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "motu.h"
static int midi_capture_open(struct snd_rawmidi_substream *substream)
{
struct snd_motu *motu = substream->rmidi->private_data;
int err;
err = snd_motu_stream_lock_try(motu);
if (err < 0)
return err;
mutex_lock(&motu->mutex);
motu->capture_substreams++;
err = snd_motu_stream_start_duplex(motu, 0);
mutex_unlock(&motu->mutex);
if (err < 0)
snd_motu_stream_lock_release(motu);
return err;
}
static int midi_playback_open(struct snd_rawmidi_substream *substream)
{
struct snd_motu *motu = substream->rmidi->private_data;
int err;
err = snd_motu_stream_lock_try(motu);
if (err < 0)
return err;
mutex_lock(&motu->mutex);
motu->playback_substreams++;
err = snd_motu_stream_start_duplex(motu, 0);
mutex_unlock(&motu->mutex);
if (err < 0)
snd_motu_stream_lock_release(motu);
return err;
}
static int midi_capture_close(struct snd_rawmidi_substream *substream)
{
struct snd_motu *motu = substream->rmidi->private_data;
mutex_lock(&motu->mutex);
motu->capture_substreams--;
snd_motu_stream_stop_duplex(motu);
mutex_unlock(&motu->mutex);
snd_motu_stream_lock_release(motu);
return 0;
}
static int midi_playback_close(struct snd_rawmidi_substream *substream)
{
struct snd_motu *motu = substream->rmidi->private_data;
mutex_lock(&motu->mutex);
motu->playback_substreams--;
snd_motu_stream_stop_duplex(motu);
mutex_unlock(&motu->mutex);
snd_motu_stream_lock_release(motu);
return 0;
}
static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
{
struct snd_motu *motu = substrm->rmidi->private_data;
unsigned long flags;
spin_lock_irqsave(&motu->lock, flags);
if (up)
amdtp_motu_midi_trigger(&motu->tx_stream, substrm->number,
substrm);
else
amdtp_motu_midi_trigger(&motu->tx_stream, substrm->number,
NULL);
spin_unlock_irqrestore(&motu->lock, flags);
}
static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
{
struct snd_motu *motu = substrm->rmidi->private_data;
unsigned long flags;
spin_lock_irqsave(&motu->lock, flags);
if (up)
amdtp_motu_midi_trigger(&motu->rx_stream, substrm->number,
substrm);
else
amdtp_motu_midi_trigger(&motu->rx_stream, substrm->number,
NULL);
spin_unlock_irqrestore(&motu->lock, flags);
}
static void set_midi_substream_names(struct snd_motu *motu,
struct snd_rawmidi_str *str)
{
struct snd_rawmidi_substream *subs;
list_for_each_entry(subs, &str->substreams, list) {
snprintf(subs->name, sizeof(subs->name),
"%s MIDI %d", motu->card->shortname, subs->number + 1);
}
}
int snd_motu_create_midi_devices(struct snd_motu *motu)
{
static struct snd_rawmidi_ops capture_ops = {
.open = midi_capture_open,
.close = midi_capture_close,
.trigger = midi_capture_trigger,
};
static struct snd_rawmidi_ops playback_ops = {
.open = midi_playback_open,
.close = midi_playback_close,
.trigger = midi_playback_trigger,
};
struct snd_rawmidi *rmidi;
struct snd_rawmidi_str *str;
int err;
/* create midi ports */
err = snd_rawmidi_new(motu->card, motu->card->driver, 0, 1, 1, &rmidi);
if (err < 0)
return err;
snprintf(rmidi->name, sizeof(rmidi->name),
"%s MIDI", motu->card->shortname);
rmidi->private_data = motu;
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT |
SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_DUPLEX;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
&capture_ops);
str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT];
set_midi_substream_names(motu, str);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
&playback_ops);
str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
set_midi_substream_names(motu, str);
return 0;
}

View file

@ -0,0 +1,398 @@
/*
* motu-pcm.c - a part of driver for MOTU FireWire series
*
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include <sound/pcm_params.h>
#include "motu.h"
static int motu_rate_constraint(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
struct snd_motu_packet_format *formats = rule->private;
const struct snd_interval *c =
hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
struct snd_interval *r =
hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
struct snd_interval rates = {
.min = UINT_MAX, .max = 0, .integer = 1
};
unsigned int i, pcm_channels, rate, mode;
for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) {
rate = snd_motu_clock_rates[i];
mode = i / 2;
pcm_channels = formats->fixed_part_pcm_chunks[mode] +
formats->differed_part_pcm_chunks[mode];
if (!snd_interval_test(c, pcm_channels))
continue;
rates.min = min(rates.min, rate);
rates.max = max(rates.max, rate);
}
return snd_interval_refine(r, &rates);
}
static int motu_channels_constraint(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
struct snd_motu_packet_format *formats = rule->private;
const struct snd_interval *r =
hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE);
struct snd_interval *c =
hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
struct snd_interval channels = {
.min = UINT_MAX, .max = 0, .integer = 1
};
unsigned int i, pcm_channels, rate, mode;
for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) {
rate = snd_motu_clock_rates[i];
mode = i / 2;
if (!snd_interval_test(r, rate))
continue;
pcm_channels = formats->fixed_part_pcm_chunks[mode] +
formats->differed_part_pcm_chunks[mode];
channels.min = min(channels.min, pcm_channels);
channels.max = max(channels.max, pcm_channels);
}
return snd_interval_refine(c, &channels);
}
static void limit_channels_and_rates(struct snd_motu *motu,
struct snd_pcm_runtime *runtime,
struct snd_motu_packet_format *formats)
{
struct snd_pcm_hardware *hw = &runtime->hw;
unsigned int i, pcm_channels, rate, mode;
hw->channels_min = UINT_MAX;
hw->channels_max = 0;
for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) {
rate = snd_motu_clock_rates[i];
mode = i / 2;
pcm_channels = formats->fixed_part_pcm_chunks[mode] +
formats->differed_part_pcm_chunks[mode];
if (pcm_channels == 0)
continue;
hw->rates |= snd_pcm_rate_to_rate_bit(rate);
hw->channels_min = min(hw->channels_min, pcm_channels);
hw->channels_max = max(hw->channels_max, pcm_channels);
}
snd_pcm_limit_hw_rates(runtime);
}
static void limit_period_and_buffer(struct snd_pcm_hardware *hw)
{
hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */
hw->periods_max = UINT_MAX;
hw->period_bytes_min = 4 * hw->channels_max; /* byte for a frame */
/* Just to prevent from allocating much pages. */
hw->period_bytes_max = hw->period_bytes_min * 2048;
hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min;
}
static int init_hw_info(struct snd_motu *motu,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_pcm_hardware *hw = &runtime->hw;
struct amdtp_stream *stream;
struct snd_motu_packet_format *formats;
int err;
hw->info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_JOINT_DUPLEX |
SNDRV_PCM_INFO_BLOCK_TRANSFER;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
hw->formats = SNDRV_PCM_FMTBIT_S32;
stream = &motu->tx_stream;
formats = &motu->tx_packet_formats;
} else {
hw->formats = SNDRV_PCM_FMTBIT_S32;
stream = &motu->rx_stream;
formats = &motu->rx_packet_formats;
}
limit_channels_and_rates(motu, runtime, formats);
limit_period_and_buffer(hw);
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
motu_rate_constraint, formats,
SNDRV_PCM_HW_PARAM_CHANNELS, -1);
if (err < 0)
return err;
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
motu_channels_constraint, formats,
SNDRV_PCM_HW_PARAM_RATE, -1);
if (err < 0)
return err;
return amdtp_motu_add_pcm_hw_constraints(stream, runtime);
}
static int pcm_open(struct snd_pcm_substream *substream)
{
struct snd_motu *motu = substream->private_data;
const struct snd_motu_protocol *const protocol = motu->spec->protocol;
enum snd_motu_clock_source src;
unsigned int rate;
int err;
err = snd_motu_stream_lock_try(motu);
if (err < 0)
return err;
mutex_lock(&motu->mutex);
err = protocol->cache_packet_formats(motu);
if (err < 0)
goto err_locked;
err = init_hw_info(motu, substream);
if (err < 0)
goto err_locked;
/*
* When source of clock is not internal or any PCM streams are running,
* available sampling rate is limited at current sampling rate.
*/
err = protocol->get_clock_source(motu, &src);
if (err < 0)
goto err_locked;
if (src != SND_MOTU_CLOCK_SOURCE_INTERNAL ||
amdtp_stream_pcm_running(&motu->tx_stream) ||
amdtp_stream_pcm_running(&motu->rx_stream)) {
err = protocol->get_clock_rate(motu, &rate);
if (err < 0)
goto err_locked;
substream->runtime->hw.rate_min = rate;
substream->runtime->hw.rate_max = rate;
}
snd_pcm_set_sync(substream);
mutex_unlock(&motu->mutex);
return err;
err_locked:
mutex_unlock(&motu->mutex);
snd_motu_stream_lock_release(motu);
return err;
}
static int pcm_close(struct snd_pcm_substream *substream)
{
struct snd_motu *motu = substream->private_data;
snd_motu_stream_lock_release(motu);
return 0;
}
static int capture_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_motu *motu = substream->private_data;
int err;
err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
params_buffer_bytes(hw_params));
if (err < 0)
return err;
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
mutex_lock(&motu->mutex);
motu->capture_substreams++;
mutex_unlock(&motu->mutex);
}
return 0;
}
static int playback_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_motu *motu = substream->private_data;
int err;
err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
params_buffer_bytes(hw_params));
if (err < 0)
return err;
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
mutex_lock(&motu->mutex);
motu->playback_substreams++;
mutex_unlock(&motu->mutex);
}
return 0;
}
static int capture_hw_free(struct snd_pcm_substream *substream)
{
struct snd_motu *motu = substream->private_data;
mutex_lock(&motu->mutex);
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
motu->capture_substreams--;
snd_motu_stream_stop_duplex(motu);
mutex_unlock(&motu->mutex);
return snd_pcm_lib_free_vmalloc_buffer(substream);
}
static int playback_hw_free(struct snd_pcm_substream *substream)
{
struct snd_motu *motu = substream->private_data;
mutex_lock(&motu->mutex);
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
motu->playback_substreams--;
snd_motu_stream_stop_duplex(motu);
mutex_unlock(&motu->mutex);
return snd_pcm_lib_free_vmalloc_buffer(substream);
}
static int capture_prepare(struct snd_pcm_substream *substream)
{
struct snd_motu *motu = substream->private_data;
int err;
mutex_lock(&motu->mutex);
err = snd_motu_stream_start_duplex(motu, substream->runtime->rate);
mutex_unlock(&motu->mutex);
if (err >= 0)
amdtp_stream_pcm_prepare(&motu->tx_stream);
return 0;
}
static int playback_prepare(struct snd_pcm_substream *substream)
{
struct snd_motu *motu = substream->private_data;
int err;
mutex_lock(&motu->mutex);
err = snd_motu_stream_start_duplex(motu, substream->runtime->rate);
mutex_unlock(&motu->mutex);
if (err >= 0)
amdtp_stream_pcm_prepare(&motu->rx_stream);
return err;
}
static int capture_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_motu *motu = substream->private_data;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
amdtp_stream_pcm_trigger(&motu->tx_stream, substream);
break;
case SNDRV_PCM_TRIGGER_STOP:
amdtp_stream_pcm_trigger(&motu->tx_stream, NULL);
break;
default:
return -EINVAL;
}
return 0;
}
static int playback_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_motu *motu = substream->private_data;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
amdtp_stream_pcm_trigger(&motu->rx_stream, substream);
break;
case SNDRV_PCM_TRIGGER_STOP:
amdtp_stream_pcm_trigger(&motu->rx_stream, NULL);
break;
default:
return -EINVAL;
}
return 0;
}
static snd_pcm_uframes_t capture_pointer(struct snd_pcm_substream *substream)
{
struct snd_motu *motu = substream->private_data;
return amdtp_stream_pcm_pointer(&motu->tx_stream);
}
static snd_pcm_uframes_t playback_pointer(struct snd_pcm_substream *substream)
{
struct snd_motu *motu = substream->private_data;
return amdtp_stream_pcm_pointer(&motu->rx_stream);
}
int snd_motu_create_pcm_devices(struct snd_motu *motu)
{
static struct snd_pcm_ops capture_ops = {
.open = pcm_open,
.close = pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = capture_hw_params,
.hw_free = capture_hw_free,
.prepare = capture_prepare,
.trigger = capture_trigger,
.pointer = capture_pointer,
.page = snd_pcm_lib_get_vmalloc_page,
.mmap = snd_pcm_lib_mmap_vmalloc,
};
static struct snd_pcm_ops playback_ops = {
.open = pcm_open,
.close = pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = playback_hw_params,
.hw_free = playback_hw_free,
.prepare = playback_prepare,
.trigger = playback_trigger,
.pointer = playback_pointer,
.page = snd_pcm_lib_get_vmalloc_page,
.mmap = snd_pcm_lib_mmap_vmalloc,
};
struct snd_pcm *pcm;
int err;
err = snd_pcm_new(motu->card, motu->card->driver, 0, 1, 1, &pcm);
if (err < 0)
return err;
pcm->private_data = motu;
strcpy(pcm->name, motu->card->shortname);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
return 0;
}

View file

@ -0,0 +1,118 @@
/*
* motu-proc.c - a part of driver for MOTU FireWire series
*
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "./motu.h"
static const char *const clock_names[] = {
[SND_MOTU_CLOCK_SOURCE_INTERNAL] = "Internal",
[SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB] = "ADAT on Dsub-9pin interface",
[SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT] = "ADAT on optical interface",
[SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A] = "ADAT on optical interface A",
[SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B] = "ADAT on optical interface B",
[SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT] = "S/PDIF on optical interface",
[SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A] = "S/PDIF on optical interface A",
[SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B] = "S/PDIF on optical interface B",
[SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX] = "S/PCIF on coaxial interface",
[SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR] = "AESEBU on XLR interface",
[SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC] = "Word clock on BNC interface",
};
static void proc_read_clock(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_motu *motu = entry->private_data;
const struct snd_motu_protocol *const protocol = motu->spec->protocol;
unsigned int rate;
enum snd_motu_clock_source source;
if (protocol->get_clock_rate(motu, &rate) < 0)
return;
if (protocol->get_clock_source(motu, &source) < 0)
return;
snd_iprintf(buffer, "Rate:\t%d\n", rate);
snd_iprintf(buffer, "Source:\t%s\n", clock_names[source]);
}
static void proc_read_format(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_motu *motu = entry->private_data;
const struct snd_motu_protocol *const protocol = motu->spec->protocol;
unsigned int mode;
struct snd_motu_packet_format *formats;
int i;
if (protocol->cache_packet_formats(motu) < 0)
return;
snd_iprintf(buffer, "tx:\tmsg\tfixed\tdiffered\n");
for (i = 0; i < SND_MOTU_CLOCK_RATE_COUNT; ++i) {
mode = i >> 1;
formats = &motu->tx_packet_formats;
snd_iprintf(buffer,
"%u:\t%u\t%u\t%u\n",
snd_motu_clock_rates[i],
formats->msg_chunks,
formats->fixed_part_pcm_chunks[mode],
formats->differed_part_pcm_chunks[mode]);
}
snd_iprintf(buffer, "rx:\tmsg\tfixed\tdiffered\n");
for (i = 0; i < SND_MOTU_CLOCK_RATE_COUNT; ++i) {
mode = i >> 1;
formats = &motu->rx_packet_formats;
snd_iprintf(buffer,
"%u:\t%u\t%u\t%u\n",
snd_motu_clock_rates[i],
formats->msg_chunks,
formats->fixed_part_pcm_chunks[mode],
formats->differed_part_pcm_chunks[mode]);
}
}
static void add_node(struct snd_motu *motu, struct snd_info_entry *root,
const char *name,
void (*op)(struct snd_info_entry *e,
struct snd_info_buffer *b))
{
struct snd_info_entry *entry;
entry = snd_info_create_card_entry(motu->card, name, root);
if (entry == NULL)
return;
snd_info_set_text_ops(entry, motu, op);
if (snd_info_register(entry) < 0)
snd_info_free_entry(entry);
}
void snd_motu_proc_init(struct snd_motu *motu)
{
struct snd_info_entry *root;
/*
* All nodes are automatically removed at snd_card_disconnect(),
* by following to link list.
*/
root = snd_info_create_card_entry(motu->card, "firewire",
motu->card->proc_root);
if (root == NULL)
return;
root->mode = S_IFDIR | S_IRUGO | S_IXUGO;
if (snd_info_register(root) < 0) {
snd_info_free_entry(root);
return;
}
add_node(motu, root, "clock", proc_read_clock);
add_node(motu, root, "format", proc_read_format);
}

View file

@ -0,0 +1,237 @@
/*
* motu-protocol-v2.c - a part of driver for MOTU FireWire series
*
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "motu.h"
#define V2_CLOCK_STATUS_OFFSET 0x0b14
#define V2_CLOCK_RATE_MASK 0x00000038
#define V2_CLOCK_RATE_SHIFT 3
#define V2_CLOCK_SRC_MASK 0x00000007
#define V2_CLOCK_SRC_SHIFT 0
#define V2_IN_OUT_CONF_OFFSET 0x0c04
#define V2_OPT_OUT_IFACE_MASK 0x00000c00
#define V2_OPT_OUT_IFACE_SHIFT 10
#define V2_OPT_IN_IFACE_MASK 0x00000300
#define V2_OPT_IN_IFACE_SHIFT 8
#define V2_OPT_IFACE_MODE_NONE 0
#define V2_OPT_IFACE_MODE_ADAT 1
#define V2_OPT_IFACE_MODE_SPDIF 2
static int v2_get_clock_rate(struct snd_motu *motu, unsigned int *rate)
{
__be32 reg;
unsigned int index;
int err;
err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, &reg,
sizeof(reg));
if (err < 0)
return err;
index = (be32_to_cpu(reg) & V2_CLOCK_RATE_MASK) >> V2_CLOCK_RATE_SHIFT;
if (index >= ARRAY_SIZE(snd_motu_clock_rates))
return -EIO;
*rate = snd_motu_clock_rates[index];
return 0;
}
static int v2_set_clock_rate(struct snd_motu *motu, unsigned int rate)
{
__be32 reg;
u32 data;
int i;
int err;
for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) {
if (snd_motu_clock_rates[i] == rate)
break;
}
if (i == ARRAY_SIZE(snd_motu_clock_rates))
return -EINVAL;
err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, &reg,
sizeof(reg));
if (err < 0)
return err;
data = be32_to_cpu(reg);
data &= ~V2_CLOCK_RATE_MASK;
data |= i << V2_CLOCK_RATE_SHIFT;
reg = cpu_to_be32(data);
return snd_motu_transaction_write(motu, V2_CLOCK_STATUS_OFFSET, &reg,
sizeof(reg));
}
static int v2_get_clock_source(struct snd_motu *motu,
enum snd_motu_clock_source *src)
{
__be32 reg;
unsigned int index;
int err;
err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, &reg,
sizeof(reg));
if (err < 0)
return err;
index = be32_to_cpu(reg) & V2_CLOCK_SRC_MASK;
if (index > 5)
return -EIO;
/* To check the configuration of optical interface. */
err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, &reg,
sizeof(reg));
if (err < 0)
return err;
switch (index) {
case 0:
*src = SND_MOTU_CLOCK_SOURCE_INTERNAL;
break;
case 1:
if (be32_to_cpu(reg) & 0x00000200)
*src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT;
else
*src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT;
break;
case 2:
*src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX;
break;
case 4:
*src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC;
break;
case 5:
*src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB;
break;
default:
return -EIO;
}
return 0;
}
static int v2_switch_fetching_mode(struct snd_motu *motu, bool enable)
{
/* V2 protocol doesn't have this feature. */
return 0;
}
static void calculate_fixed_part(struct snd_motu_packet_format *formats,
enum amdtp_stream_direction dir,
enum snd_motu_spec_flags flags,
unsigned char analog_ports)
{
unsigned char pcm_chunks[3] = {0, 0, 0};
formats->msg_chunks = 2;
pcm_chunks[0] = analog_ports;
pcm_chunks[1] = analog_ports;
if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4)
pcm_chunks[2] = analog_ports;
if (dir == AMDTP_IN_STREAM) {
if (flags & SND_MOTU_SPEC_TX_MICINST_CHUNK) {
pcm_chunks[0] += 2;
pcm_chunks[1] += 2;
}
if (flags & SND_MOTU_SPEC_TX_RETURN_CHUNK) {
pcm_chunks[0] += 2;
pcm_chunks[1] += 2;
}
} else {
/*
* Packets to v2 units transfer main-out-1/2 and phone-out-1/2.
*/
pcm_chunks[0] += 4;
pcm_chunks[1] += 4;
}
/*
* All of v2 models have a pair of coaxial interfaces for digital in/out
* port. At 44.1/48.0/88.2/96.0 kHz, packets includes PCM from these
* ports.
*/
pcm_chunks[0] += 2;
pcm_chunks[1] += 2;
/* This part should be multiples of 4. */
formats->fixed_part_pcm_chunks[0] = round_up(2 + pcm_chunks[0], 4) - 2;
formats->fixed_part_pcm_chunks[1] = round_up(2 + pcm_chunks[1], 4) - 2;
if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4)
formats->fixed_part_pcm_chunks[2] =
round_up(2 + pcm_chunks[2], 4) - 2;
}
static void calculate_differed_part(struct snd_motu_packet_format *formats,
enum snd_motu_spec_flags flags,
u32 data, u32 mask, u32 shift)
{
unsigned char pcm_chunks[3] = {0, 0};
/*
* When optical interfaces are configured for S/PDIF (TOSLINK),
* the above PCM frames come from them, instead of coaxial
* interfaces.
*/
data = (data & mask) >> shift;
if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_A) &&
data == V2_OPT_IFACE_MODE_ADAT) {
pcm_chunks[0] += 8;
pcm_chunks[1] += 4;
}
/* At mode x4, no data chunks are supported in this part. */
formats->differed_part_pcm_chunks[0] = pcm_chunks[0];
formats->differed_part_pcm_chunks[1] = pcm_chunks[1];
}
static int v2_cache_packet_formats(struct snd_motu *motu)
{
__be32 reg;
u32 data;
int err;
err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, &reg,
sizeof(reg));
if (err < 0)
return err;
data = be32_to_cpu(reg);
calculate_fixed_part(&motu->tx_packet_formats, AMDTP_IN_STREAM,
motu->spec->flags, motu->spec->analog_in_ports);
calculate_differed_part(&motu->tx_packet_formats, motu->spec->flags,
data, V2_OPT_IN_IFACE_MASK, V2_OPT_IN_IFACE_SHIFT);
calculate_fixed_part(&motu->rx_packet_formats, AMDTP_OUT_STREAM,
motu->spec->flags, motu->spec->analog_out_ports);
calculate_differed_part(&motu->rx_packet_formats, motu->spec->flags,
data, V2_OPT_OUT_IFACE_MASK, V2_OPT_OUT_IFACE_SHIFT);
motu->tx_packet_formats.midi_flag_offset = 4;
motu->tx_packet_formats.midi_byte_offset = 6;
motu->tx_packet_formats.pcm_byte_offset = 10;
motu->rx_packet_formats.midi_flag_offset = 4;
motu->rx_packet_formats.midi_byte_offset = 6;
motu->rx_packet_formats.pcm_byte_offset = 10;
return 0;
}
const struct snd_motu_protocol snd_motu_protocol_v2 = {
.get_clock_rate = v2_get_clock_rate,
.set_clock_rate = v2_set_clock_rate,
.get_clock_source = v2_get_clock_source,
.switch_fetching_mode = v2_switch_fetching_mode,
.cache_packet_formats = v2_cache_packet_formats,
};

View file

@ -0,0 +1,311 @@
/*
* motu-protocol-v3.c - a part of driver for MOTU FireWire series
*
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include <linux/delay.h>
#include "motu.h"
#define V3_CLOCK_STATUS_OFFSET 0x0b14
#define V3_FETCH_PCM_FRAMES 0x02000000
#define V3_CLOCK_RATE_MASK 0x0000ff00
#define V3_CLOCK_RATE_SHIFT 8
#define V3_CLOCK_SOURCE_MASK 0x000000ff
#define V3_OPT_IFACE_MODE_OFFSET 0x0c94
#define V3_ENABLE_OPT_IN_IFACE_A 0x00000001
#define V3_ENABLE_OPT_IN_IFACE_B 0x00000002
#define V3_ENABLE_OPT_OUT_IFACE_A 0x00000100
#define V3_ENABLE_OPT_OUT_IFACE_B 0x00000200
#define V3_NO_ADAT_OPT_IN_IFACE_A 0x00010000
#define V3_NO_ADAT_OPT_IN_IFACE_B 0x00100000
#define V3_NO_ADAT_OPT_OUT_IFACE_A 0x00040000
#define V3_NO_ADAT_OPT_OUT_IFACE_B 0x00400000
static int v3_get_clock_rate(struct snd_motu *motu, unsigned int *rate)
{
__be32 reg;
u32 data;
int err;
err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, &reg,
sizeof(reg));
if (err < 0)
return err;
data = be32_to_cpu(reg);
data = (data & V3_CLOCK_RATE_MASK) >> V3_CLOCK_RATE_SHIFT;
if (data >= ARRAY_SIZE(snd_motu_clock_rates))
return -EIO;
*rate = snd_motu_clock_rates[data];
return 0;
}
static int v3_set_clock_rate(struct snd_motu *motu, unsigned int rate)
{
__be32 reg;
u32 data;
bool need_to_wait;
int i, err;
for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) {
if (snd_motu_clock_rates[i] == rate)
break;
}
if (i == ARRAY_SIZE(snd_motu_clock_rates))
return -EINVAL;
err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, &reg,
sizeof(reg));
if (err < 0)
return err;
data = be32_to_cpu(reg);
data &= ~(V3_CLOCK_RATE_MASK | V3_FETCH_PCM_FRAMES);
data |= i << V3_CLOCK_RATE_SHIFT;
need_to_wait = data != be32_to_cpu(reg);
reg = cpu_to_be32(data);
err = snd_motu_transaction_write(motu, V3_CLOCK_STATUS_OFFSET, &reg,
sizeof(reg));
if (err < 0)
return err;
if (need_to_wait) {
/* Cost expensive. */
if (msleep_interruptible(4000) > 0)
return -EINTR;
}
return 0;
}
static int v3_get_clock_source(struct snd_motu *motu,
enum snd_motu_clock_source *src)
{
__be32 reg;
u32 data;
unsigned int val;
int err;
err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, &reg,
sizeof(reg));
if (err < 0)
return err;
data = be32_to_cpu(reg);
val = data & V3_CLOCK_SOURCE_MASK;
if (val == 0x00) {
*src = SND_MOTU_CLOCK_SOURCE_INTERNAL;
} else if (val == 0x01) {
*src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC;
} else if (val == 0x10) {
*src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX;
} else if (val == 0x18 || val == 0x19) {
err = snd_motu_transaction_read(motu, V3_OPT_IFACE_MODE_OFFSET,
&reg, sizeof(reg));
if (err < 0)
return err;
data = be32_to_cpu(reg);
if (val == 0x18) {
if (data & V3_NO_ADAT_OPT_IN_IFACE_A)
*src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A;
else
*src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A;
} else {
if (data & V3_NO_ADAT_OPT_IN_IFACE_B)
*src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B;
else
*src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B;
}
} else {
*src = SND_MOTU_CLOCK_SOURCE_UNKNOWN;
}
return 0;
}
static int v3_switch_fetching_mode(struct snd_motu *motu, bool enable)
{
__be32 reg;
u32 data;
int err;
err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, &reg,
sizeof(reg));
if (err < 0)
return 0;
data = be32_to_cpu(reg);
if (enable)
data |= V3_FETCH_PCM_FRAMES;
else
data &= ~V3_FETCH_PCM_FRAMES;
reg = cpu_to_be32(data);
return snd_motu_transaction_write(motu, V3_CLOCK_STATUS_OFFSET, &reg,
sizeof(reg));
}
static void calculate_fixed_part(struct snd_motu_packet_format *formats,
enum amdtp_stream_direction dir,
enum snd_motu_spec_flags flags,
unsigned char analog_ports)
{
unsigned char pcm_chunks[3] = {0, 0, 0};
formats->msg_chunks = 2;
pcm_chunks[0] = analog_ports;
pcm_chunks[1] = analog_ports;
if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4)
pcm_chunks[2] = analog_ports;
if (dir == AMDTP_IN_STREAM) {
if (flags & SND_MOTU_SPEC_TX_MICINST_CHUNK) {
pcm_chunks[0] += 2;
pcm_chunks[1] += 2;
if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4)
pcm_chunks[2] += 2;
}
if (flags & SND_MOTU_SPEC_TX_RETURN_CHUNK) {
pcm_chunks[0] += 2;
pcm_chunks[1] += 2;
if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4)
pcm_chunks[2] += 2;
}
if (flags & SND_MOTU_SPEC_TX_REVERB_CHUNK) {
pcm_chunks[0] += 2;
pcm_chunks[1] += 2;
}
} else {
/*
* Packets to v2 units transfer main-out-1/2 and phone-out-1/2.
*/
pcm_chunks[0] += 4;
pcm_chunks[1] += 4;
}
/*
* At least, packets have two data chunks for S/PDIF on coaxial
* interface.
*/
pcm_chunks[0] += 2;
pcm_chunks[1] += 2;
/*
* Fixed part consists of PCM chunks multiple of 4, with msg chunks. As
* a result, this part can includes empty data chunks.
*/
formats->fixed_part_pcm_chunks[0] = round_up(2 + pcm_chunks[0], 4) - 2;
formats->fixed_part_pcm_chunks[1] = round_up(2 + pcm_chunks[1], 4) - 2;
if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4)
formats->fixed_part_pcm_chunks[2] =
round_up(2 + pcm_chunks[2], 4) - 2;
}
static void calculate_differed_part(struct snd_motu_packet_format *formats,
enum snd_motu_spec_flags flags, u32 data,
u32 a_enable_mask, u32 a_no_adat_mask,
u32 b_enable_mask, u32 b_no_adat_mask)
{
unsigned char pcm_chunks[3] = {0, 0, 0};
int i;
if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_A) && (data & a_enable_mask)) {
if (data & a_no_adat_mask) {
/*
* Additional two data chunks for S/PDIF on optical
* interface A. This includes empty data chunks.
*/
pcm_chunks[0] += 4;
pcm_chunks[1] += 4;
} else {
/*
* Additional data chunks for ADAT on optical interface
* A.
*/
pcm_chunks[0] += 8;
pcm_chunks[1] += 4;
}
}
if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_B) && (data & b_enable_mask)) {
if (data & b_no_adat_mask) {
/*
* Additional two data chunks for S/PDIF on optical
* interface B. This includes empty data chunks.
*/
pcm_chunks[0] += 4;
pcm_chunks[1] += 4;
} else {
/*
* Additional data chunks for ADAT on optical interface
* B.
*/
pcm_chunks[0] += 8;
pcm_chunks[1] += 4;
}
}
for (i = 0; i < 3; ++i) {
if (pcm_chunks[i] > 0)
pcm_chunks[i] = round_up(pcm_chunks[i], 4);
formats->differed_part_pcm_chunks[i] = pcm_chunks[i];
}
}
static int v3_cache_packet_formats(struct snd_motu *motu)
{
__be32 reg;
u32 data;
int err;
err = snd_motu_transaction_read(motu, V3_OPT_IFACE_MODE_OFFSET, &reg,
sizeof(reg));
if (err < 0)
return err;
data = be32_to_cpu(reg);
calculate_fixed_part(&motu->tx_packet_formats, AMDTP_IN_STREAM,
motu->spec->flags, motu->spec->analog_in_ports);
calculate_differed_part(&motu->tx_packet_formats,
motu->spec->flags, data,
V3_ENABLE_OPT_IN_IFACE_A, V3_NO_ADAT_OPT_IN_IFACE_A,
V3_ENABLE_OPT_IN_IFACE_B, V3_NO_ADAT_OPT_IN_IFACE_B);
calculate_fixed_part(&motu->rx_packet_formats, AMDTP_OUT_STREAM,
motu->spec->flags, motu->spec->analog_out_ports);
calculate_differed_part(&motu->rx_packet_formats,
motu->spec->flags, data,
V3_ENABLE_OPT_OUT_IFACE_A, V3_NO_ADAT_OPT_OUT_IFACE_A,
V3_ENABLE_OPT_OUT_IFACE_B, V3_NO_ADAT_OPT_OUT_IFACE_B);
motu->tx_packet_formats.midi_flag_offset = 8;
motu->tx_packet_formats.midi_byte_offset = 7;
motu->tx_packet_formats.pcm_byte_offset = 10;
motu->rx_packet_formats.midi_flag_offset = 8;
motu->rx_packet_formats.midi_byte_offset = 7;
motu->rx_packet_formats.pcm_byte_offset = 10;
return 0;
}
const struct snd_motu_protocol snd_motu_protocol_v3 = {
.get_clock_rate = v3_get_clock_rate,
.set_clock_rate = v3_set_clock_rate,
.get_clock_source = v3_get_clock_source,
.switch_fetching_mode = v3_switch_fetching_mode,
.cache_packet_formats = v3_cache_packet_formats,
};

View file

@ -0,0 +1,381 @@
/*
* motu-stream.c - a part of driver for MOTU FireWire series
*
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "motu.h"
#define CALLBACK_TIMEOUT 200
#define ISOC_COMM_CONTROL_OFFSET 0x0b00
#define ISOC_COMM_CONTROL_MASK 0xffff0000
#define CHANGE_RX_ISOC_COMM_STATE 0x80000000
#define RX_ISOC_COMM_IS_ACTIVATED 0x40000000
#define RX_ISOC_COMM_CHANNEL_MASK 0x3f000000
#define RX_ISOC_COMM_CHANNEL_SHIFT 24
#define CHANGE_TX_ISOC_COMM_STATE 0x00800000
#define TX_ISOC_COMM_IS_ACTIVATED 0x00400000
#define TX_ISOC_COMM_CHANNEL_MASK 0x003f0000
#define TX_ISOC_COMM_CHANNEL_SHIFT 16
#define PACKET_FORMAT_OFFSET 0x0b10
#define TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS 0x00000080
#define RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS 0x00000040
#define TX_PACKET_TRANSMISSION_SPEED_MASK 0x0000000f
static int start_both_streams(struct snd_motu *motu, unsigned int rate)
{
unsigned int midi_ports = 0;
__be32 reg;
u32 data;
int err;
if (motu->spec->flags & SND_MOTU_SPEC_HAS_MIDI)
midi_ports = 1;
/* Set packet formation to our packet streaming engine. */
err = amdtp_motu_set_parameters(&motu->rx_stream, rate, midi_ports,
&motu->rx_packet_formats);
if (err < 0)
return err;
err = amdtp_motu_set_parameters(&motu->tx_stream, rate, midi_ports,
&motu->tx_packet_formats);
if (err < 0)
return err;
/* Get isochronous resources on the bus. */
err = fw_iso_resources_allocate(&motu->rx_resources,
amdtp_stream_get_max_payload(&motu->rx_stream),
fw_parent_device(motu->unit)->max_speed);
if (err < 0)
return err;
err = fw_iso_resources_allocate(&motu->tx_resources,
amdtp_stream_get_max_payload(&motu->tx_stream),
fw_parent_device(motu->unit)->max_speed);
if (err < 0)
return err;
/* Configure the unit to start isochronous communication. */
err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, &reg,
sizeof(reg));
if (err < 0)
return err;
data = be32_to_cpu(reg) & ~ISOC_COMM_CONTROL_MASK;
data |= CHANGE_RX_ISOC_COMM_STATE | RX_ISOC_COMM_IS_ACTIVATED |
(motu->rx_resources.channel << RX_ISOC_COMM_CHANNEL_SHIFT) |
CHANGE_TX_ISOC_COMM_STATE | TX_ISOC_COMM_IS_ACTIVATED |
(motu->tx_resources.channel << TX_ISOC_COMM_CHANNEL_SHIFT);
reg = cpu_to_be32(data);
return snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, &reg,
sizeof(reg));
}
static void stop_both_streams(struct snd_motu *motu)
{
__be32 reg;
u32 data;
int err;
err = motu->spec->protocol->switch_fetching_mode(motu, false);
if (err < 0)
return;
err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, &reg,
sizeof(reg));
if (err < 0)
return;
data = be32_to_cpu(reg);
data &= ~(RX_ISOC_COMM_IS_ACTIVATED | TX_ISOC_COMM_IS_ACTIVATED);
data |= CHANGE_RX_ISOC_COMM_STATE | CHANGE_TX_ISOC_COMM_STATE;
reg = cpu_to_be32(data);
snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, &reg,
sizeof(reg));
fw_iso_resources_free(&motu->tx_resources);
fw_iso_resources_free(&motu->rx_resources);
}
static int start_isoc_ctx(struct snd_motu *motu, struct amdtp_stream *stream)
{
struct fw_iso_resources *resources;
int err;
if (stream == &motu->rx_stream)
resources = &motu->rx_resources;
else
resources = &motu->tx_resources;
err = amdtp_stream_start(stream, resources->channel,
fw_parent_device(motu->unit)->max_speed);
if (err < 0)
return err;
if (!amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT)) {
amdtp_stream_stop(stream);
fw_iso_resources_free(resources);
return -ETIMEDOUT;
}
return 0;
}
static void stop_isoc_ctx(struct snd_motu *motu, struct amdtp_stream *stream)
{
struct fw_iso_resources *resources;
if (stream == &motu->rx_stream)
resources = &motu->rx_resources;
else
resources = &motu->tx_resources;
amdtp_stream_stop(stream);
fw_iso_resources_free(resources);
}
static int ensure_packet_formats(struct snd_motu *motu)
{
__be32 reg;
u32 data;
int err;
err = snd_motu_transaction_read(motu, PACKET_FORMAT_OFFSET, &reg,
sizeof(reg));
if (err < 0)
return err;
data = be32_to_cpu(reg);
data &= ~(TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS |
RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS|
TX_PACKET_TRANSMISSION_SPEED_MASK);
if (motu->tx_packet_formats.differed_part_pcm_chunks[0] == 0)
data |= TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS;
if (motu->rx_packet_formats.differed_part_pcm_chunks[0] == 0)
data |= RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS;
data |= fw_parent_device(motu->unit)->max_speed;
reg = cpu_to_be32(data);
return snd_motu_transaction_write(motu, PACKET_FORMAT_OFFSET, &reg,
sizeof(reg));
}
int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate)
{
const struct snd_motu_protocol *protocol = motu->spec->protocol;
unsigned int curr_rate;
int err = 0;
if (motu->capture_substreams == 0 && motu->playback_substreams == 0)
return 0;
/* Some packet queueing errors. */
if (amdtp_streaming_error(&motu->rx_stream) ||
amdtp_streaming_error(&motu->tx_stream)) {
amdtp_stream_stop(&motu->rx_stream);
amdtp_stream_stop(&motu->tx_stream);
stop_both_streams(motu);
}
err = protocol->cache_packet_formats(motu);
if (err < 0)
return err;
/* Stop stream if rate is different. */
err = protocol->get_clock_rate(motu, &curr_rate);
if (err < 0) {
dev_err(&motu->unit->device,
"fail to get sampling rate: %d\n", err);
return err;
}
if (rate == 0)
rate = curr_rate;
if (rate != curr_rate) {
amdtp_stream_stop(&motu->rx_stream);
amdtp_stream_stop(&motu->tx_stream);
stop_both_streams(motu);
}
if (!amdtp_stream_running(&motu->rx_stream)) {
err = protocol->set_clock_rate(motu, rate);
if (err < 0) {
dev_err(&motu->unit->device,
"fail to set sampling rate: %d\n", err);
return err;
}
err = ensure_packet_formats(motu);
if (err < 0)
return err;
err = start_both_streams(motu, rate);
if (err < 0) {
dev_err(&motu->unit->device,
"fail to start isochronous comm: %d\n", err);
stop_both_streams(motu);
return err;
}
err = start_isoc_ctx(motu, &motu->rx_stream);
if (err < 0) {
dev_err(&motu->unit->device,
"fail to start IT context: %d\n", err);
stop_both_streams(motu);
return err;
}
err = protocol->switch_fetching_mode(motu, true);
if (err < 0) {
dev_err(&motu->unit->device,
"fail to enable frame fetching: %d\n", err);
stop_both_streams(motu);
return err;
}
}
if (!amdtp_stream_running(&motu->tx_stream) &&
motu->capture_substreams > 0) {
err = start_isoc_ctx(motu, &motu->tx_stream);
if (err < 0) {
dev_err(&motu->unit->device,
"fail to start IR context: %d", err);
amdtp_stream_stop(&motu->rx_stream);
stop_both_streams(motu);
return err;
}
}
return 0;
}
void snd_motu_stream_stop_duplex(struct snd_motu *motu)
{
if (motu->capture_substreams == 0) {
if (amdtp_stream_running(&motu->tx_stream))
stop_isoc_ctx(motu, &motu->tx_stream);
if (motu->playback_substreams == 0) {
if (amdtp_stream_running(&motu->rx_stream))
stop_isoc_ctx(motu, &motu->rx_stream);
stop_both_streams(motu);
}
}
}
static int init_stream(struct snd_motu *motu, enum amdtp_stream_direction dir)
{
int err;
struct amdtp_stream *stream;
struct fw_iso_resources *resources;
if (dir == AMDTP_IN_STREAM) {
stream = &motu->tx_stream;
resources = &motu->tx_resources;
} else {
stream = &motu->rx_stream;
resources = &motu->rx_resources;
}
err = fw_iso_resources_init(resources, motu->unit);
if (err < 0)
return err;
err = amdtp_motu_init(stream, motu->unit, dir, motu->spec->protocol);
if (err < 0) {
amdtp_stream_destroy(stream);
fw_iso_resources_destroy(resources);
}
return err;
}
static void destroy_stream(struct snd_motu *motu,
enum amdtp_stream_direction dir)
{
struct amdtp_stream *stream;
struct fw_iso_resources *resources;
if (dir == AMDTP_IN_STREAM) {
stream = &motu->tx_stream;
resources = &motu->tx_resources;
} else {
stream = &motu->rx_stream;
resources = &motu->rx_resources;
}
amdtp_stream_destroy(stream);
fw_iso_resources_free(resources);
}
int snd_motu_stream_init_duplex(struct snd_motu *motu)
{
int err;
err = init_stream(motu, AMDTP_IN_STREAM);
if (err < 0)
return err;
err = init_stream(motu, AMDTP_OUT_STREAM);
if (err < 0)
destroy_stream(motu, AMDTP_IN_STREAM);
return err;
}
/*
* This function should be called before starting streams or after stopping
* streams.
*/
void snd_motu_stream_destroy_duplex(struct snd_motu *motu)
{
destroy_stream(motu, AMDTP_IN_STREAM);
destroy_stream(motu, AMDTP_OUT_STREAM);
motu->playback_substreams = 0;
motu->capture_substreams = 0;
}
static void motu_lock_changed(struct snd_motu *motu)
{
motu->dev_lock_changed = true;
wake_up(&motu->hwdep_wait);
}
int snd_motu_stream_lock_try(struct snd_motu *motu)
{
int err;
spin_lock_irq(&motu->lock);
if (motu->dev_lock_count < 0) {
err = -EBUSY;
goto out;
}
if (motu->dev_lock_count++ == 0)
motu_lock_changed(motu);
err = 0;
out:
spin_unlock_irq(&motu->lock);
return err;
}
void snd_motu_stream_lock_release(struct snd_motu *motu)
{
spin_lock_irq(&motu->lock);
if (WARN_ON(motu->dev_lock_count <= 0))
goto out;
if (--motu->dev_lock_count == 0)
motu_lock_changed(motu);
out:
spin_unlock_irq(&motu->lock);
}

View file

@ -0,0 +1,137 @@
/*
* motu-transaction.c - a part of driver for MOTU FireWire series
*
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "motu.h"
#define SND_MOTU_ADDR_BASE 0xfffff0000000ULL
#define ASYNC_ADDR_HI 0x0b04
#define ASYNC_ADDR_LO 0x0b08
int snd_motu_transaction_read(struct snd_motu *motu, u32 offset, __be32 *reg,
size_t size)
{
int tcode;
if (size % sizeof(__be32) > 0 || size <= 0)
return -EINVAL;
if (size == sizeof(__be32))
tcode = TCODE_READ_QUADLET_REQUEST;
else
tcode = TCODE_READ_BLOCK_REQUEST;
return snd_fw_transaction(motu->unit, tcode,
SND_MOTU_ADDR_BASE + offset, reg, size, 0);
}
int snd_motu_transaction_write(struct snd_motu *motu, u32 offset, __be32 *reg,
size_t size)
{
int tcode;
if (size % sizeof(__be32) > 0 || size <= 0)
return -EINVAL;
if (size == sizeof(__be32))
tcode = TCODE_WRITE_QUADLET_REQUEST;
else
tcode = TCODE_WRITE_BLOCK_REQUEST;
return snd_fw_transaction(motu->unit, tcode,
SND_MOTU_ADDR_BASE + offset, reg, size, 0);
}
static void handle_message(struct fw_card *card, struct fw_request *request,
int tcode, int destination, int source,
int generation, unsigned long long offset,
void *data, size_t length, void *callback_data)
{
struct snd_motu *motu = callback_data;
__be32 *buf = (__be32 *)data;
unsigned long flags;
if (tcode != TCODE_WRITE_QUADLET_REQUEST) {
fw_send_response(card, request, RCODE_COMPLETE);
return;
}
if (offset != motu->async_handler.offset || length != 4) {
fw_send_response(card, request, RCODE_ADDRESS_ERROR);
return;
}
spin_lock_irqsave(&motu->lock, flags);
motu->msg = be32_to_cpu(*buf);
spin_unlock_irqrestore(&motu->lock, flags);
fw_send_response(card, request, RCODE_COMPLETE);
wake_up(&motu->hwdep_wait);
}
int snd_motu_transaction_reregister(struct snd_motu *motu)
{
struct fw_device *device = fw_parent_device(motu->unit);
__be32 data;
int err;
if (motu->async_handler.callback_data == NULL)
return -EINVAL;
/* Register messaging address. Block transaction is not allowed. */
data = cpu_to_be32((device->card->node_id << 16) |
(motu->async_handler.offset >> 32));
err = snd_motu_transaction_write(motu, ASYNC_ADDR_HI, &data,
sizeof(data));
if (err < 0)
return err;
data = cpu_to_be32(motu->async_handler.offset);
return snd_motu_transaction_write(motu, ASYNC_ADDR_LO, &data,
sizeof(data));
}
int snd_motu_transaction_register(struct snd_motu *motu)
{
static const struct fw_address_region resp_register_region = {
.start = 0xffffe0000000ull,
.end = 0xffffe000ffffull,
};
int err;
/* Perhaps, 4 byte messages are transferred. */
motu->async_handler.length = 4;
motu->async_handler.address_callback = handle_message;
motu->async_handler.callback_data = motu;
err = fw_core_add_address_handler(&motu->async_handler,
&resp_register_region);
if (err < 0)
return err;
err = snd_motu_transaction_reregister(motu);
if (err < 0) {
fw_core_remove_address_handler(&motu->async_handler);
motu->async_handler.address_callback = NULL;
}
return err;
}
void snd_motu_transaction_unregister(struct snd_motu *motu)
{
__be32 data;
if (motu->async_handler.address_callback != NULL)
fw_core_remove_address_handler(&motu->async_handler);
motu->async_handler.address_callback = NULL;
/* Unregister the address. */
data = cpu_to_be32(0x00000000);
snd_motu_transaction_write(motu, ASYNC_ADDR_HI, &data, sizeof(data));
snd_motu_transaction_write(motu, ASYNC_ADDR_LO, &data, sizeof(data));
}

264
sound/firewire/motu/motu.c Normal file
View file

@ -0,0 +1,264 @@
/*
* motu.c - a part of driver for MOTU FireWire series
*
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "motu.h"
#define OUI_MOTU 0x0001f2
MODULE_DESCRIPTION("MOTU FireWire driver");
MODULE_AUTHOR("Takashi Sakamoto <[email protected]>");
MODULE_LICENSE("GPL v2");
const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT] = {
/* mode 0 */
[0] = 44100,
[1] = 48000,
/* mode 1 */
[2] = 88200,
[3] = 96000,
/* mode 2 */
[4] = 176400,
[5] = 192000,
};
static void name_card(struct snd_motu *motu)
{
struct fw_device *fw_dev = fw_parent_device(motu->unit);
struct fw_csr_iterator it;
int key, val;
u32 version = 0;
fw_csr_iterator_init(&it, motu->unit->directory);
while (fw_csr_iterator_next(&it, &key, &val)) {
switch (key) {
case CSR_VERSION:
version = val;
break;
}
}
strcpy(motu->card->driver, "FW-MOTU");
strcpy(motu->card->shortname, motu->spec->name);
strcpy(motu->card->mixername, motu->spec->name);
snprintf(motu->card->longname, sizeof(motu->card->longname),
"MOTU %s (version:%d), GUID %08x%08x at %s, S%d",
motu->spec->name, version,
fw_dev->config_rom[3], fw_dev->config_rom[4],
dev_name(&motu->unit->device), 100 << fw_dev->max_speed);
}
static void motu_free(struct snd_motu *motu)
{
snd_motu_transaction_unregister(motu);
snd_motu_stream_destroy_duplex(motu);
fw_unit_put(motu->unit);
mutex_destroy(&motu->mutex);
kfree(motu);
}
/*
* This module releases the FireWire unit data after all ALSA character devices
* are released by applications. This is for releasing stream data or finishing
* transactions safely. Thus at returning from .remove(), this module still keep
* references for the unit.
*/
static void motu_card_free(struct snd_card *card)
{
motu_free(card->private_data);
}
static void do_registration(struct work_struct *work)
{
struct snd_motu *motu = container_of(work, struct snd_motu, dwork.work);
int err;
if (motu->registered)
return;
err = snd_card_new(&motu->unit->device, -1, NULL, THIS_MODULE, 0,
&motu->card);
if (err < 0)
return;
name_card(motu);
err = snd_motu_transaction_register(motu);
if (err < 0)
goto error;
err = snd_motu_stream_init_duplex(motu);
if (err < 0)
goto error;
snd_motu_proc_init(motu);
err = snd_motu_create_pcm_devices(motu);
if (err < 0)
goto error;
if (motu->spec->flags & SND_MOTU_SPEC_HAS_MIDI) {
err = snd_motu_create_midi_devices(motu);
if (err < 0)
goto error;
}
err = snd_motu_create_hwdep_device(motu);
if (err < 0)
goto error;
err = snd_card_register(motu->card);
if (err < 0)
goto error;
/*
* After registered, motu instance can be released corresponding to
* releasing the sound card instance.
*/
motu->card->private_free = motu_card_free;
motu->card->private_data = motu;
motu->registered = true;
return;
error:
snd_motu_transaction_unregister(motu);
snd_card_free(motu->card);
dev_info(&motu->unit->device,
"Sound card registration failed: %d\n", err);
}
static int motu_probe(struct fw_unit *unit,
const struct ieee1394_device_id *entry)
{
struct snd_motu *motu;
/* Allocate this independently of sound card instance. */
motu = kzalloc(sizeof(struct snd_motu), GFP_KERNEL);
if (motu == NULL)
return -ENOMEM;
motu->spec = (const struct snd_motu_spec *)entry->driver_data;
motu->unit = fw_unit_get(unit);
dev_set_drvdata(&unit->device, motu);
mutex_init(&motu->mutex);
spin_lock_init(&motu->lock);
init_waitqueue_head(&motu->hwdep_wait);
/* Allocate and register this sound card later. */
INIT_DEFERRABLE_WORK(&motu->dwork, do_registration);
snd_fw_schedule_registration(unit, &motu->dwork);
return 0;
}
static void motu_remove(struct fw_unit *unit)
{
struct snd_motu *motu = dev_get_drvdata(&unit->device);
/*
* Confirm to stop the work for registration before the sound card is
* going to be released. The work is not scheduled again because bus
* reset handler is not called anymore.
*/
cancel_delayed_work_sync(&motu->dwork);
if (motu->registered) {
/* No need to wait for releasing card object in this context. */
snd_card_free_when_closed(motu->card);
} else {
/* Don't forget this case. */
motu_free(motu);
}
}
static void motu_bus_update(struct fw_unit *unit)
{
struct snd_motu *motu = dev_get_drvdata(&unit->device);
/* Postpone a workqueue for deferred registration. */
if (!motu->registered)
snd_fw_schedule_registration(unit, &motu->dwork);
/* The handler address register becomes initialized. */
snd_motu_transaction_reregister(motu);
}
static struct snd_motu_spec motu_828mk2 = {
.name = "828mk2",
.protocol = &snd_motu_protocol_v2,
.flags = SND_MOTU_SPEC_SUPPORT_CLOCK_X2 |
SND_MOTU_SPEC_TX_MICINST_CHUNK |
SND_MOTU_SPEC_TX_RETURN_CHUNK |
SND_MOTU_SPEC_HAS_OPT_IFACE_A |
SND_MOTU_SPEC_HAS_MIDI,
.analog_in_ports = 8,
.analog_out_ports = 8,
};
static struct snd_motu_spec motu_828mk3 = {
.name = "828mk3",
.protocol = &snd_motu_protocol_v3,
.flags = SND_MOTU_SPEC_SUPPORT_CLOCK_X2 |
SND_MOTU_SPEC_SUPPORT_CLOCK_X4 |
SND_MOTU_SPEC_TX_MICINST_CHUNK |
SND_MOTU_SPEC_TX_RETURN_CHUNK |
SND_MOTU_SPEC_TX_REVERB_CHUNK |
SND_MOTU_SPEC_HAS_OPT_IFACE_A |
SND_MOTU_SPEC_HAS_OPT_IFACE_B |
SND_MOTU_SPEC_HAS_MIDI,
.analog_in_ports = 8,
.analog_out_ports = 8,
};
#define SND_MOTU_DEV_ENTRY(model, data) \
{ \
.match_flags = IEEE1394_MATCH_VENDOR_ID | \
IEEE1394_MATCH_MODEL_ID | \
IEEE1394_MATCH_SPECIFIER_ID, \
.vendor_id = OUI_MOTU, \
.model_id = model, \
.specifier_id = OUI_MOTU, \
.driver_data = (kernel_ulong_t)data, \
}
static const struct ieee1394_device_id motu_id_table[] = {
SND_MOTU_DEV_ENTRY(0x101800, &motu_828mk2),
SND_MOTU_DEV_ENTRY(0x106800, &motu_828mk3), /* FireWire only. */
SND_MOTU_DEV_ENTRY(0x100800, &motu_828mk3), /* Hybrid. */
{ }
};
MODULE_DEVICE_TABLE(ieee1394, motu_id_table);
static struct fw_driver motu_driver = {
.driver = {
.owner = THIS_MODULE,
.name = KBUILD_MODNAME,
.bus = &fw_bus_type,
},
.probe = motu_probe,
.update = motu_bus_update,
.remove = motu_remove,
.id_table = motu_id_table,
};
static int __init alsa_motu_init(void)
{
return driver_register(&motu_driver.driver);
}
static void __exit alsa_motu_exit(void)
{
driver_unregister(&motu_driver.driver);
}
module_init(alsa_motu_init);
module_exit(alsa_motu_exit);

161
sound/firewire/motu/motu.h Normal file
View file

@ -0,0 +1,161 @@
/*
* motu.h - a part of driver for MOTU FireWire series
*
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#ifndef SOUND_FIREWIRE_MOTU_H_INCLUDED
#define SOUND_FIREWIRE_MOTU_H_INCLUDED
#include <linux/device.h>
#include <linux/firewire.h>
#include <linux/firewire-constants.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/sched/signal.h>
#include <sound/control.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/info.h>
#include <sound/rawmidi.h>
#include <sound/firewire.h>
#include <sound/hwdep.h>
#include "../lib.h"
#include "../amdtp-stream.h"
#include "../iso-resources.h"
struct snd_motu_packet_format {
unsigned char midi_flag_offset;
unsigned char midi_byte_offset;
unsigned char pcm_byte_offset;
unsigned char msg_chunks;
unsigned char fixed_part_pcm_chunks[3];
unsigned char differed_part_pcm_chunks[3];
};
struct snd_motu {
struct snd_card *card;
struct fw_unit *unit;
struct mutex mutex;
spinlock_t lock;
bool registered;
struct delayed_work dwork;
/* Model dependent information. */
const struct snd_motu_spec *spec;
/* For packet streaming */
struct snd_motu_packet_format tx_packet_formats;
struct snd_motu_packet_format rx_packet_formats;
struct amdtp_stream tx_stream;
struct amdtp_stream rx_stream;
struct fw_iso_resources tx_resources;
struct fw_iso_resources rx_resources;
unsigned int capture_substreams;
unsigned int playback_substreams;
/* For notification. */
struct fw_address_handler async_handler;
u32 msg;
/* For uapi */
int dev_lock_count;
bool dev_lock_changed;
wait_queue_head_t hwdep_wait;
};
enum snd_motu_spec_flags {
SND_MOTU_SPEC_SUPPORT_CLOCK_X2 = 0x0001,
SND_MOTU_SPEC_SUPPORT_CLOCK_X4 = 0x0002,
SND_MOTU_SPEC_TX_MICINST_CHUNK = 0x0004,
SND_MOTU_SPEC_TX_RETURN_CHUNK = 0x0008,
SND_MOTU_SPEC_TX_REVERB_CHUNK = 0x0010,
SND_MOTU_SPEC_TX_AESEBU_CHUNK = 0x0020,
SND_MOTU_SPEC_HAS_OPT_IFACE_A = 0x0040,
SND_MOTU_SPEC_HAS_OPT_IFACE_B = 0x0080,
SND_MOTU_SPEC_HAS_MIDI = 0x0100,
};
#define SND_MOTU_CLOCK_RATE_COUNT 6
extern const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT];
enum snd_motu_clock_source {
SND_MOTU_CLOCK_SOURCE_INTERNAL,
SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB,
SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT,
SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A,
SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B,
SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT,
SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A,
SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B,
SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX,
SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR,
SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC,
SND_MOTU_CLOCK_SOURCE_UNKNOWN,
};
struct snd_motu_protocol {
int (*get_clock_rate)(struct snd_motu *motu, unsigned int *rate);
int (*set_clock_rate)(struct snd_motu *motu, unsigned int rate);
int (*get_clock_source)(struct snd_motu *motu,
enum snd_motu_clock_source *source);
int (*switch_fetching_mode)(struct snd_motu *motu, bool enable);
int (*cache_packet_formats)(struct snd_motu *motu);
};
struct snd_motu_spec {
const char *const name;
enum snd_motu_spec_flags flags;
unsigned char analog_in_ports;
unsigned char analog_out_ports;
const struct snd_motu_protocol *const protocol;
};
extern const struct snd_motu_protocol snd_motu_protocol_v2;
extern const struct snd_motu_protocol snd_motu_protocol_v3;
int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit,
enum amdtp_stream_direction dir,
const struct snd_motu_protocol *const protocol);
int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate,
unsigned int midi_ports,
struct snd_motu_packet_format *formats);
int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s,
struct snd_pcm_runtime *runtime);
void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port,
struct snd_rawmidi_substream *midi);
int snd_motu_transaction_read(struct snd_motu *motu, u32 offset, __be32 *reg,
size_t size);
int snd_motu_transaction_write(struct snd_motu *motu, u32 offset, __be32 *reg,
size_t size);
int snd_motu_transaction_register(struct snd_motu *motu);
int snd_motu_transaction_reregister(struct snd_motu *motu);
void snd_motu_transaction_unregister(struct snd_motu *motu);
int snd_motu_stream_init_duplex(struct snd_motu *motu);
void snd_motu_stream_destroy_duplex(struct snd_motu *motu);
int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate);
void snd_motu_stream_stop_duplex(struct snd_motu *motu);
int snd_motu_stream_lock_try(struct snd_motu *motu);
void snd_motu_stream_lock_release(struct snd_motu *motu);
void snd_motu_proc_init(struct snd_motu *motu);
int snd_motu_create_pcm_devices(struct snd_motu *motu);
int snd_motu_create_midi_devices(struct snd_motu *motu);
int snd_motu_create_hwdep_device(struct snd_motu *motu);
#endif

View file

@ -34,7 +34,9 @@ int avc_stream_set_format(struct fw_unit *unit, enum avc_general_plug_dir dir,
err = fcp_avc_transaction(unit, buf, len + 10, buf, len + 10,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
BIT(6) | BIT(7) | BIT(8));
if ((err > 0) && (err < len + 10))
if (err < 0)
;
else if (err < len + 10)
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
@ -77,7 +79,9 @@ int avc_stream_get_format(struct fw_unit *unit,
err = fcp_avc_transaction(unit, buf, 12, buf, *len,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
BIT(6) | BIT(7));
if ((err > 0) && (err < 10))
if (err < 0)
;
else if (err < 12)
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
@ -139,7 +143,9 @@ int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate,
/* do transaction and check buf[1-5] are the same against command */
err = fcp_avc_transaction(unit, buf, 8, buf, 8,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5));
if ((err > 0) && (err < 8))
if (err < 0)
;
else if (err < 8)
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;

View file

@ -18,9 +18,8 @@ static int midi_playback_open(struct snd_rawmidi_substream *substream)
{
struct snd_tscm *tscm = substream->rmidi->private_data;
/* Initialize internal status. */
tscm->running_status[substream->number] = 0;
tscm->on_sysex[substream->number] = 0;
snd_fw_async_midi_port_init(&tscm->out_ports[substream->number]);
return 0;
}
@ -31,12 +30,15 @@ static int midi_capture_close(struct snd_rawmidi_substream *substream)
}
static int midi_playback_close(struct snd_rawmidi_substream *substream)
{
return 0;
}
static void midi_playback_drain(struct snd_rawmidi_substream *substream)
{
struct snd_tscm *tscm = substream->rmidi->private_data;
snd_fw_async_midi_port_finish(&tscm->out_ports[substream->number]);
return 0;
}
static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
@ -78,6 +80,7 @@ int snd_tscm_create_midi_devices(struct snd_tscm *tscm)
static const struct snd_rawmidi_ops playback_ops = {
.open = midi_playback_open,
.close = midi_playback_close,
.drain = midi_playback_drain,
.trigger = midi_playback_trigger,
};
struct snd_rawmidi *rmidi;

View file

@ -58,39 +58,38 @@ static inline int calculate_message_bytes(u8 status)
return -EINVAL;
}
static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf)
static int fill_message(struct snd_fw_async_midi_port *port,
struct snd_rawmidi_substream *substream)
{
struct snd_tscm *tscm = substream->rmidi->private_data;
unsigned int port = substream->number;
int i, len, consume;
u8 *label, *msg;
u8 status;
/* The first byte is used for label, the rest for MIDI bytes. */
label = buf;
msg = buf + 1;
label = port->buf;
msg = port->buf + 1;
consume = snd_rawmidi_transmit_peek(substream, msg, 3);
if (consume == 0)
return 0;
/* On exclusive message. */
if (tscm->on_sysex[port]) {
if (port->on_sysex) {
/* Seek the end of exclusives. */
for (i = 0; i < consume; ++i) {
if (msg[i] == 0xf7) {
tscm->on_sysex[port] = false;
port->on_sysex = false;
break;
}
}
/* At the end of exclusive message, use label 0x07. */
if (!tscm->on_sysex[port]) {
if (!port->on_sysex) {
consume = i + 1;
*label = (port << 4) | 0x07;
*label = (substream->number << 4) | 0x07;
/* During exclusive message, use label 0x04. */
} else if (consume == 3) {
*label = (port << 4) | 0x04;
*label = (substream->number << 4) | 0x04;
/* We need to fill whole 3 bytes. Go to next change. */
} else {
return 0;
@ -101,12 +100,12 @@ static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf)
/* The beginning of exclusives. */
if (msg[0] == 0xf0) {
/* Transfer it in next chance in another condition. */
tscm->on_sysex[port] = true;
port->on_sysex = true;
return 0;
} else {
/* On running-status. */
if ((msg[0] & 0x80) != 0x80)
status = tscm->running_status[port];
status = port->running_status;
else
status = msg[0];
@ -124,18 +123,18 @@ static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf)
msg[2] = msg[1];
msg[1] = msg[0];
msg[0] = tscm->running_status[port];
msg[0] = port->running_status;
} else {
/* Enough MIDI bytes were not retrieved. */
if (consume < len)
return 0;
consume = len;
tscm->running_status[port] = msg[0];
port->running_status = msg[0];
}
}
*label = (port << 4) | (msg[0] >> 4);
*label = (substream->number << 4) | (msg[0] >> 4);
}
if (len > 0 && len < 3)
@ -144,6 +143,106 @@ static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf)
return consume;
}
static void async_midi_port_callback(struct fw_card *card, int rcode,
void *data, size_t length,
void *callback_data)
{
struct snd_fw_async_midi_port *port = callback_data;
struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream);
/* This port is closed. */
if (substream == NULL)
return;
if (rcode == RCODE_COMPLETE)
snd_rawmidi_transmit_ack(substream, port->consume_bytes);
else if (!rcode_is_permanent_error(rcode))
/* To start next transaction immediately for recovery. */
port->next_ktime = 0;
else
/* Don't continue processing. */
port->error = true;
port->idling = true;
if (!snd_rawmidi_transmit_empty(substream))
schedule_work(&port->work);
}
static void midi_port_work(struct work_struct *work)
{
struct snd_fw_async_midi_port *port =
container_of(work, struct snd_fw_async_midi_port, work);
struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream);
int generation;
/* Under transacting or error state. */
if (!port->idling || port->error)
return;
/* Nothing to do. */
if (substream == NULL || snd_rawmidi_transmit_empty(substream))
return;
/* Do it in next chance. */
if (ktime_after(port->next_ktime, ktime_get())) {
schedule_work(&port->work);
return;
}
/*
* Fill the buffer. The callee must use snd_rawmidi_transmit_peek().
* Later, snd_rawmidi_transmit_ack() is called.
*/
memset(port->buf, 0, 4);
port->consume_bytes = fill_message(port, substream);
if (port->consume_bytes <= 0) {
/* Do it in next chance, immediately. */
if (port->consume_bytes == 0) {
port->next_ktime = 0;
schedule_work(&port->work);
} else {
/* Fatal error. */
port->error = true;
}
return;
}
/* Set interval to next transaction. */
port->next_ktime = ktime_add_ns(ktime_get(),
port->consume_bytes * 8 * NSEC_PER_SEC / 31250);
/* Start this transaction. */
port->idling = false;
/*
* In Linux FireWire core, when generation is updated with memory
* barrier, node id has already been updated. In this module, After
* this smp_rmb(), load/store instructions to memory are completed.
* Thus, both of generation and node id are available with recent
* values. This is a light-serialization solution to handle bus reset
* events on IEEE 1394 bus.
*/
generation = port->parent->generation;
smp_rmb();
fw_send_request(port->parent->card, &port->transaction,
TCODE_WRITE_QUADLET_REQUEST,
port->parent->node_id, generation,
port->parent->max_speed,
TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_RX_QUAD,
port->buf, 4, async_midi_port_callback,
port);
}
void snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port)
{
port->idling = true;
port->error = false;
port->running_status = 0;
port->on_sysex = false;
}
static void handle_midi_tx(struct fw_card *card, struct fw_request *request,
int tcode, int destination, int source,
int generation, unsigned long long offset,
@ -219,12 +318,9 @@ int snd_tscm_transaction_register(struct snd_tscm *tscm)
goto error;
for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++) {
err = snd_fw_async_midi_port_init(
&tscm->out_ports[i], tscm->unit,
TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_RX_QUAD,
4, fill_message);
if (err < 0)
goto error;
tscm->out_ports[i].parent = fw_parent_device(tscm->unit);
tscm->out_ports[i].next_ktime = 0;
INIT_WORK(&tscm->out_ports[i].work, midi_port_work);
}
return err;
@ -275,7 +371,6 @@ int snd_tscm_transaction_reregister(struct snd_tscm *tscm)
void snd_tscm_transaction_unregister(struct snd_tscm *tscm)
{
__be32 reg;
unsigned int i;
if (tscm->async_handler.callback_data == NULL)
return;
@ -302,7 +397,4 @@ void snd_tscm_transaction_unregister(struct snd_tscm *tscm)
fw_core_remove_address_handler(&tscm->async_handler);
tscm->async_handler.callback_data = NULL;
for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++)
snd_fw_async_midi_port_destroy(&tscm->out_ports[i]);
}

View file

@ -45,6 +45,23 @@ struct snd_tscm_spec {
#define TSCM_MIDI_IN_PORT_MAX 4
#define TSCM_MIDI_OUT_PORT_MAX 4
struct snd_fw_async_midi_port {
struct fw_device *parent;
struct work_struct work;
bool idling;
ktime_t next_ktime;
bool error;
struct fw_transaction transaction;
u8 buf[4];
u8 running_status;
bool on_sysex;
struct snd_rawmidi_substream *substream;
int consume_bytes;
};
struct snd_tscm {
struct snd_card *card;
struct fw_unit *unit;
@ -72,8 +89,6 @@ struct snd_tscm {
/* For MIDI message outgoing transactions. */
struct snd_fw_async_midi_port out_ports[TSCM_MIDI_OUT_PORT_MAX];
u8 running_status[TSCM_MIDI_OUT_PORT_MAX];
bool on_sysex[TSCM_MIDI_OUT_PORT_MAX];
};
#define TSCM_ADDR_BASE 0xffff00000000ull
@ -131,6 +146,26 @@ void snd_tscm_stream_lock_changed(struct snd_tscm *tscm);
int snd_tscm_stream_lock_try(struct snd_tscm *tscm);
void snd_tscm_stream_lock_release(struct snd_tscm *tscm);
void snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port);
static inline void
snd_fw_async_midi_port_run(struct snd_fw_async_midi_port *port,
struct snd_rawmidi_substream *substream)
{
if (!port->error) {
port->substream = substream;
schedule_work(&port->work);
}
}
static inline void
snd_fw_async_midi_port_finish(struct snd_fw_async_midi_port *port)
{
port->substream = NULL;
cancel_work_sync(&port->work);
port->error = false;
}
int snd_tscm_transaction_register(struct snd_tscm *tscm);
int snd_tscm_transaction_reregister(struct snd_tscm *tscm);
void snd_tscm_transaction_unregister(struct snd_tscm *tscm);

View file

@ -171,7 +171,7 @@ static int check_hdac_link_power_active(struct hdac_ext_link *link, bool enable)
{
int timeout;
u32 val;
int mask = (1 << AZX_MLCTL_CPA);
int mask = (1 << AZX_MLCTL_CPA_SHIFT);
udelay(3);
timeout = 150;
@ -179,10 +179,10 @@ static int check_hdac_link_power_active(struct hdac_ext_link *link, bool enable)
do {
val = readl(link->ml_addr + AZX_REG_ML_LCTL);
if (enable) {
if (((val & mask) >> AZX_MLCTL_CPA))
if (((val & mask) >> AZX_MLCTL_CPA_SHIFT))
return 0;
} else {
if (!((val & mask) >> AZX_MLCTL_CPA))
if (!((val & mask) >> AZX_MLCTL_CPA_SHIFT))
return 0;
}
udelay(3);

View file

@ -268,11 +268,11 @@ int snd_hdac_bus_parse_capabilities(struct hdac_bus *bus)
unsigned int offset;
unsigned int counter = 0;
offset = snd_hdac_chip_readl(bus, LLCH);
offset = snd_hdac_chip_readw(bus, LLCH);
/* 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);

View file

@ -555,12 +555,12 @@ void snd_hdac_stream_sync_trigger(struct hdac_stream *azx_dev, bool set,
if (!reg)
reg = AZX_REG_SSYNC;
val = _snd_hdac_chip_read(l, bus, reg);
val = _snd_hdac_chip_readl(bus, reg);
if (set)
val |= streams;
else
val &= ~streams;
_snd_hdac_chip_write(l, bus, reg, val);
_snd_hdac_chip_writel(bus, reg, val);
}
EXPORT_SYMBOL_GPL(snd_hdac_stream_sync_trigger);

View file

@ -742,7 +742,7 @@ int snd_es1688_pcm(struct snd_card *card, struct snd_es1688 *chip, int device)
pcm->private_data = chip;
pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
sprintf(pcm->name, snd_es1688_chip_id(chip));
strcpy(pcm->name, snd_es1688_chip_id(chip));
chip->pcm = pcm;
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,

View file

@ -1339,7 +1339,7 @@ static int snd_ali_prepare(struct snd_pcm_substream *substream)
rate = snd_ali_get_spdif_in_rate(codec);
if (rate == 0) {
dev_warn(codec->card->dev,
"ali_capture_preapre: spdif rate detect err!\n");
"ali_capture_prepare: spdif rate detect err!\n");
rate = 48000;
}
spin_lock_irq(&codec->reg_lock);

View file

@ -846,7 +846,7 @@ snd_vortex_a3d_filter_put(struct snd_kcontrol *kcontrol,
return changed;
}
static struct snd_kcontrol_new vortex_a3d_kcontrol = {
static const struct snd_kcontrol_new vortex_a3d_kcontrol = {
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "Playback PCM advanced processing",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,

View file

@ -2279,6 +2279,9 @@ vortex_adb_allocroute(vortex_t *vortex, int dma, int nr_ch, int dir,
} else {
int src[2], mix[2];
if (nr_ch < 1)
return -EINVAL;
/* Get SRC and MIXER hardware resources. */
for (i = 0; i < nr_ch; i++) {
if ((mix[i] =

View file

@ -757,7 +757,7 @@ snd_vortex_eqtoggle_put(struct snd_kcontrol *kcontrol,
return 1; /* Allways changes */
}
static struct snd_kcontrol_new vortex_eqtoggle_kcontrol = {
static const struct snd_kcontrol_new vortex_eqtoggle_kcontrol = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "EQ Enable",
.index = 0,
@ -815,7 +815,7 @@ snd_vortex_eq_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucon
return changed;
}
static struct snd_kcontrol_new vortex_eq_kcontrol = {
static const struct snd_kcontrol_new vortex_eq_kcontrol = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = " .",
.index = 0,
@ -855,7 +855,7 @@ snd_vortex_peaks_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u
return 0;
}
static struct snd_kcontrol_new vortex_levels_kcontrol = {
static const struct snd_kcontrol_new vortex_levels_kcontrol = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "EQ Peaks",
.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,

View file

@ -601,7 +601,7 @@ static int snd_vortex_pcm_vol_put(struct snd_kcontrol *kcontrol,
static const DECLARE_TLV_DB_MINMAX(vortex_pcm_vol_db_scale, -9600, 2400);
static struct snd_kcontrol_new snd_vortex_pcm_vol = {
static const struct snd_kcontrol_new snd_vortex_pcm_vol = {
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "PCM Playback Volume",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |

View file

@ -202,7 +202,7 @@ static const struct snd_pcm_ops snd_aw2_capture_ops = {
.pointer = snd_aw2_pcm_pointer_capture,
};
static struct snd_kcontrol_new aw2_control = {
static const struct snd_kcontrol_new aw2_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Capture Route",
.index = 0,

View file

@ -598,7 +598,7 @@ static int snd_bt87x_capture_volume_put(struct snd_kcontrol *kcontrol,
return changed;
}
static struct snd_kcontrol_new snd_bt87x_capture_volume = {
static const struct snd_kcontrol_new snd_bt87x_capture_volume = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Capture Volume",
.info = snd_bt87x_capture_volume_info,
@ -634,7 +634,7 @@ static int snd_bt87x_capture_boost_put(struct snd_kcontrol *kcontrol,
return changed;
}
static struct snd_kcontrol_new snd_bt87x_capture_boost = {
static const struct snd_kcontrol_new snd_bt87x_capture_boost = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Capture Boost",
.info = snd_bt87x_capture_boost_info,
@ -676,7 +676,7 @@ static int snd_bt87x_capture_source_put(struct snd_kcontrol *kcontrol,
return changed;
}
static struct snd_kcontrol_new snd_bt87x_capture_source = {
static const struct snd_kcontrol_new snd_bt87x_capture_source = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Capture Source",
.info = snd_bt87x_capture_source_info,

View file

@ -301,7 +301,7 @@ static int snd_ca0106_capture_mic_line_in_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_ca0106_capture_mic_line_in =
static const struct snd_kcontrol_new snd_ca0106_capture_mic_line_in =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Shared Mic/Line in Capture Switch",
@ -310,7 +310,7 @@ static struct snd_kcontrol_new snd_ca0106_capture_mic_line_in =
.put = snd_ca0106_capture_mic_line_in_put
};
static struct snd_kcontrol_new snd_ca0106_capture_line_in_side_out =
static const struct snd_kcontrol_new snd_ca0106_capture_line_in_side_out =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Shared Line in/Side out Capture Switch",

View file

@ -1045,7 +1045,7 @@ static int snd_cmipci_spdif_default_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_cmipci_spdif_default =
static const struct snd_kcontrol_new snd_cmipci_spdif_default =
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
@ -1072,7 +1072,7 @@ static int snd_cmipci_spdif_mask_get(struct snd_kcontrol *kcontrol,
return 0;
}
static struct snd_kcontrol_new snd_cmipci_spdif_mask =
static const struct snd_kcontrol_new snd_cmipci_spdif_mask =
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
@ -1119,7 +1119,7 @@ static int snd_cmipci_spdif_stream_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_cmipci_spdif_stream =
static const struct snd_kcontrol_new snd_cmipci_spdif_stream =
{
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,

View file

@ -1055,7 +1055,7 @@ static int snd_cs4281_put_volume(struct snd_kcontrol *kcontrol,
static const DECLARE_TLV_DB_SCALE(db_scale_dsp, -4650, 150, 0);
static struct snd_kcontrol_new snd_cs4281_fm_vol =
static const struct snd_kcontrol_new snd_cs4281_fm_vol =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Synth Playback Volume",
@ -1066,7 +1066,7 @@ static struct snd_kcontrol_new snd_cs4281_fm_vol =
.tlv = { .p = db_scale_dsp },
};
static struct snd_kcontrol_new snd_cs4281_pcm_vol =
static const struct snd_kcontrol_new snd_cs4281_pcm_vol =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Stream Playback Volume",

View file

@ -1039,7 +1039,7 @@ static int snd_echo_output_gain_put(struct snd_kcontrol *kcontrol,
#ifdef ECHOCARD_HAS_LINE_OUT_GAIN
/* On the Mia this one controls the line-out volume */
static struct snd_kcontrol_new snd_echo_line_output_gain = {
static const struct snd_kcontrol_new snd_echo_line_output_gain = {
.name = "Line Playback Volume",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
@ -1050,7 +1050,7 @@ static struct snd_kcontrol_new snd_echo_line_output_gain = {
.tlv = {.p = db_scale_output_gain},
};
#else
static struct snd_kcontrol_new snd_echo_pcm_output_gain = {
static const struct snd_kcontrol_new snd_echo_pcm_output_gain = {
.name = "PCM Playback Volume",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ,
@ -1120,7 +1120,7 @@ static int snd_echo_input_gain_put(struct snd_kcontrol *kcontrol,
static const DECLARE_TLV_DB_SCALE(db_scale_input_gain, -2500, 50, 0);
static struct snd_kcontrol_new snd_echo_line_input_gain = {
static const struct snd_kcontrol_new snd_echo_line_input_gain = {
.name = "Line Capture Volume",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ,
@ -1184,7 +1184,7 @@ static int snd_echo_output_nominal_put(struct snd_kcontrol *kcontrol,
return changed;
}
static struct snd_kcontrol_new snd_echo_output_nominal_level = {
static const struct snd_kcontrol_new snd_echo_output_nominal_level = {
.name = "Line Playback Switch (-10dBV)",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.info = snd_echo_output_nominal_info,
@ -1250,7 +1250,7 @@ static int snd_echo_input_nominal_put(struct snd_kcontrol *kcontrol,
return changed;
}
static struct snd_kcontrol_new snd_echo_intput_nominal_level = {
static const struct snd_kcontrol_new snd_echo_intput_nominal_level = {
.name = "Line Capture Switch (-10dBV)",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.info = snd_echo_input_nominal_info,
@ -1477,7 +1477,7 @@ static int snd_echo_digital_mode_put(struct snd_kcontrol *kcontrol,
return changed;
}
static struct snd_kcontrol_new snd_echo_digital_mode_switch = {
static const struct snd_kcontrol_new snd_echo_digital_mode_switch = {
.name = "Digital mode Switch",
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
.info = snd_echo_digital_mode_info,
@ -1527,7 +1527,7 @@ static int snd_echo_spdif_mode_put(struct snd_kcontrol *kcontrol,
return 0;
}
static struct snd_kcontrol_new snd_echo_spdif_mode_switch = {
static const struct snd_kcontrol_new snd_echo_spdif_mode_switch = {
.name = "S/PDIF mode Switch",
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
.info = snd_echo_spdif_mode_info,
@ -1600,7 +1600,7 @@ static int snd_echo_clock_source_put(struct snd_kcontrol *kcontrol,
return changed;
}
static struct snd_kcontrol_new snd_echo_clock_source_switch = {
static const struct snd_kcontrol_new snd_echo_clock_source_switch = {
.name = "Sample Clock Source",
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.info = snd_echo_clock_source_info,
@ -1643,7 +1643,7 @@ static int snd_echo_phantom_power_put(struct snd_kcontrol *kcontrol,
return changed;
}
static struct snd_kcontrol_new snd_echo_phantom_power_switch = {
static const struct snd_kcontrol_new snd_echo_phantom_power_switch = {
.name = "Phantom power Switch",
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
.info = snd_echo_phantom_power_info,
@ -1686,7 +1686,7 @@ static int snd_echo_automute_put(struct snd_kcontrol *kcontrol,
return changed;
}
static struct snd_kcontrol_new snd_echo_automute_switch = {
static const struct snd_kcontrol_new snd_echo_automute_switch = {
.name = "Digital Capture Switch (automute)",
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
.info = snd_echo_automute_info,
@ -1713,7 +1713,7 @@ static int snd_echo_vumeters_switch_put(struct snd_kcontrol *kcontrol,
return 1;
}
static struct snd_kcontrol_new snd_echo_vumeters_switch = {
static const struct snd_kcontrol_new snd_echo_vumeters_switch = {
.name = "VU-meters Switch",
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
.access = SNDRV_CTL_ELEM_ACCESS_WRITE,
@ -1751,7 +1751,7 @@ static int snd_echo_vumeters_get(struct snd_kcontrol *kcontrol,
return 0;
}
static struct snd_kcontrol_new snd_echo_vumeters = {
static const struct snd_kcontrol_new snd_echo_vumeters = {
.name = "VU-meters",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = SNDRV_CTL_ELEM_ACCESS_READ |
@ -1804,7 +1804,7 @@ static int snd_echo_channels_info_get(struct snd_kcontrol *kcontrol,
return 0;
}
static struct snd_kcontrol_new snd_echo_channels_info = {
static const struct snd_kcontrol_new snd_echo_channels_info = {
.name = "Channels info",
.iface = SNDRV_CTL_ELEM_IFACE_HWDEP,
.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,

View file

@ -1112,7 +1112,7 @@ static int snd_emu10k1x_shared_spdif_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_emu10k1x_shared_spdif =
static const struct snd_kcontrol_new snd_emu10k1x_shared_spdif =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Analog/Digital Output Jack",
@ -1171,7 +1171,7 @@ static int snd_emu10k1x_spdif_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_emu10k1x_spdif_mask_control =
static const struct snd_kcontrol_new snd_emu10k1x_spdif_mask_control =
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
@ -1181,7 +1181,7 @@ static struct snd_kcontrol_new snd_emu10k1x_spdif_mask_control =
.get = snd_emu10k1x_spdif_get_mask
};
static struct snd_kcontrol_new snd_emu10k1x_spdif_control =
static const struct snd_kcontrol_new snd_emu10k1x_spdif_control =
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),

View file

@ -795,7 +795,7 @@ static int snd_emu1010_internal_clock_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_emu1010_internal_clock =
static const struct snd_kcontrol_new snd_emu1010_internal_clock =
{
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
@ -847,7 +847,7 @@ static int snd_emu1010_optical_out_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_emu1010_optical_out = {
static const struct snd_kcontrol_new snd_emu1010_optical_out = {
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Optical Output Mode",
@ -898,7 +898,7 @@ static int snd_emu1010_optical_in_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_emu1010_optical_in = {
static const struct snd_kcontrol_new snd_emu1010_optical_in = {
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Optical Input Mode",
@ -978,7 +978,7 @@ static int snd_audigy_i2c_capture_source_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_audigy_i2c_capture_source =
static const struct snd_kcontrol_new snd_audigy_i2c_capture_source =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Capture Source",
@ -1177,7 +1177,7 @@ static int snd_emu10k1_spdif_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_emu10k1_spdif_mask_control =
static const struct snd_kcontrol_new snd_emu10k1_spdif_mask_control =
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
@ -1187,7 +1187,7 @@ static struct snd_kcontrol_new snd_emu10k1_spdif_mask_control =
.get = snd_emu10k1_spdif_get_mask
};
static struct snd_kcontrol_new snd_emu10k1_spdif_control =
static const struct snd_kcontrol_new snd_emu10k1_spdif_control =
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
@ -1293,7 +1293,7 @@ static int snd_emu10k1_send_routing_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_emu10k1_send_routing_control =
static const struct snd_kcontrol_new snd_emu10k1_send_routing_control =
{
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
@ -1364,7 +1364,7 @@ static int snd_emu10k1_send_volume_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_emu10k1_send_volume_control =
static const struct snd_kcontrol_new snd_emu10k1_send_volume_control =
{
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
@ -1429,7 +1429,7 @@ static int snd_emu10k1_attn_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_emu10k1_attn_control =
static const struct snd_kcontrol_new snd_emu10k1_attn_control =
{
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
@ -1501,7 +1501,7 @@ static int snd_emu10k1_efx_send_routing_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_emu10k1_efx_send_routing_control =
static const struct snd_kcontrol_new snd_emu10k1_efx_send_routing_control =
{
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
@ -1568,7 +1568,7 @@ static int snd_emu10k1_efx_send_volume_put(struct snd_kcontrol *kcontrol,
}
static struct snd_kcontrol_new snd_emu10k1_efx_send_volume_control =
static const struct snd_kcontrol_new snd_emu10k1_efx_send_volume_control =
{
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
@ -1626,7 +1626,7 @@ static int snd_emu10k1_efx_attn_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_emu10k1_efx_attn_control =
static const struct snd_kcontrol_new snd_emu10k1_efx_attn_control =
{
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
@ -1691,7 +1691,7 @@ static int snd_emu10k1_shared_spdif_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_emu10k1_shared_spdif =
static const struct snd_kcontrol_new snd_emu10k1_shared_spdif =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "SB Live Analog/Digital Output Jack",
@ -1700,7 +1700,7 @@ static struct snd_kcontrol_new snd_emu10k1_shared_spdif =
.put = snd_emu10k1_shared_spdif_put
};
static struct snd_kcontrol_new snd_audigy_shared_spdif =
static const struct snd_kcontrol_new snd_audigy_shared_spdif =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Audigy Analog/Digital Output Jack",
@ -1738,7 +1738,7 @@ static int snd_audigy_capture_boost_put(struct snd_kcontrol *kcontrol,
return snd_ac97_update(emu->ac97, AC97_REC_GAIN, val);
}
static struct snd_kcontrol_new snd_audigy_capture_boost =
static const struct snd_kcontrol_new snd_audigy_capture_boost =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Mic Extra Boost",

View file

@ -1542,7 +1542,7 @@ static int snd_emu10k1_pcm_efx_voices_mask_put(struct snd_kcontrol *kcontrol, st
return change;
}
static struct snd_kcontrol_new snd_emu10k1_pcm_efx_voices_mask = {
static const struct snd_kcontrol_new snd_emu10k1_pcm_efx_voices_mask = {
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "Captured FX8010 Outputs",
.info = snd_emu10k1_pcm_efx_voices_mask_info,

View file

@ -1530,7 +1530,7 @@ static int snd_es1373_rear_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_ens1373_rear =
static const struct snd_kcontrol_new snd_ens1373_rear =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "AC97 2ch->4ch Copy Switch",
@ -1575,7 +1575,7 @@ static int snd_es1373_line_put(struct snd_kcontrol *kcontrol,
return changed;
}
static struct snd_kcontrol_new snd_ens1373_line =
static const struct snd_kcontrol_new snd_ens1373_line =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Line In->Rear Out Switch",

View file

@ -580,6 +580,7 @@ const char *hda_get_autocfg_input_label(struct hda_codec *codec,
has_multiple_pins = 1;
if (has_multiple_pins && type == AUTO_PIN_MIC)
has_multiple_pins &= check_mic_location_need(codec, cfg, input);
has_multiple_pins |= codec->force_pin_prefix;
return hda_get_input_pin_label(codec, &cfg->inputs[input],
cfg->inputs[input].pin,
has_multiple_pins);

View file

@ -1965,7 +1965,7 @@ static int vmaster_mute_mode_put(struct snd_kcontrol *kcontrol,
return 1;
}
static struct snd_kcontrol_new vmaster_mute_mode = {
static const struct snd_kcontrol_new vmaster_mute_mode = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Mute-LED Mode",
.info = vmaster_mute_mode_info,
@ -2705,7 +2705,7 @@ static int spdif_share_sw_put(struct snd_kcontrol *kcontrol,
return 0;
}
static struct snd_kcontrol_new spdif_share_sw = {
static const struct snd_kcontrol_new spdif_share_sw = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "IEC958 Default PCM Playback Switch",
.info = snd_ctl_boolean_mono_info,

View file

@ -256,6 +256,7 @@ struct hda_codec {
unsigned int dump_coef:1; /* dump processing coefs in codec proc file */
unsigned int power_save_node:1; /* advanced PM for each widget */
unsigned int auto_runtime_pm:1; /* enable automatic codec runtime pm */
unsigned int force_pin_prefix:1; /* Add location prefix */
#ifdef CONFIG_PM
unsigned long power_on_acct;
unsigned long power_off_acct;

View file

@ -196,6 +196,9 @@ static void parse_user_hints(struct hda_codec *codec)
val = snd_hda_get_bool_hint(codec, "hp_mic_detect");
if (val >= 0)
spec->suppress_hp_mic_detect = !val;
val = snd_hda_get_bool_hint(codec, "vmaster");
if (val >= 0)
spec->suppress_vmaster = !val;
if (!snd_hda_get_int_hint(codec, "mixer_nid", &val))
spec->mixer_nid = val;
@ -1125,6 +1128,7 @@ static const char *get_line_out_pfx(struct hda_codec *codec, int ch,
*index = 0;
if (cfg->line_outs == 1 && !spec->multi_ios &&
!codec->force_pin_prefix &&
!cfg->hp_outs && !cfg->speaker_outs)
return spec->vmaster_mute.hook ? "PCM" : "Master";
@ -1132,6 +1136,7 @@ static const char *get_line_out_pfx(struct hda_codec *codec, int ch,
* use it master (or "PCM" if a vmaster hook is present)
*/
if (spec->multiout.num_dacs == 1 && !spec->mixer_nid &&
!codec->force_pin_prefix &&
!spec->multiout.hp_out_nid[0] && !spec->multiout.extra_out_nid[0])
return spec->vmaster_mute.hook ? "PCM" : "Master";
@ -5031,7 +5036,7 @@ int snd_hda_gen_build_controls(struct hda_codec *codec)
}
/* if we have no master control, let's create it */
if (!spec->no_analog &&
if (!spec->no_analog && !spec->suppress_vmaster &&
!snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) {
err = snd_hda_add_vmaster(codec, "Master Playback Volume",
spec->vmaster_tlv, slave_pfxs,
@ -5039,7 +5044,7 @@ int snd_hda_gen_build_controls(struct hda_codec *codec)
if (err < 0)
return err;
}
if (!spec->no_analog &&
if (!spec->no_analog && !spec->suppress_vmaster &&
!snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) {
err = __snd_hda_add_vmaster(codec, "Master Playback Switch",
NULL, slave_pfxs,

View file

@ -229,6 +229,7 @@ struct hda_gen_spec {
unsigned int add_jack_modes:1; /* add i/o jack mode enum ctls */
unsigned int power_down_unused:1; /* power down unused widgets */
unsigned int dac_min_mute:1; /* minimal = mute for DACs */
unsigned int suppress_vmaster:1; /* don't create vmaster kctls */
/* other internal flags */
unsigned int no_analog:1; /* digital I/O only */

View file

@ -77,6 +77,7 @@ enum {
POS_FIX_POSBUF,
POS_FIX_VIACOMBO,
POS_FIX_COMBO,
POS_FIX_SKL,
};
/* Defines for ATI HD Audio support in SB450 south bridge */
@ -148,7 +149,7 @@ module_param_array(model, charp, NULL, 0444);
MODULE_PARM_DESC(model, "Use the given board model.");
module_param_array(position_fix, int, NULL, 0444);
MODULE_PARM_DESC(position_fix, "DMA pointer read method."
"(-1 = system default, 0 = auto, 1 = LPIB, 2 = POSBUF, 3 = VIACOMBO, 4 = COMBO).");
"(-1 = system default, 0 = auto, 1 = LPIB, 2 = POSBUF, 3 = VIACOMBO, 4 = COMBO, 5 = SKL+).");
module_param_array(bdl_pos_adj, int, NULL, 0644);
MODULE_PARM_DESC(bdl_pos_adj, "BDL position adjustment offset.");
module_param_array(probe_mask, int, NULL, 0444);
@ -369,8 +370,10 @@ enum {
#define IS_KBL_LP(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x9d71)
#define IS_KBL_H(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0xa2f0)
#define IS_BXT(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x5a98)
#define IS_GLK(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x3198)
#define IS_SKL_PLUS(pci) (IS_SKL(pci) || IS_SKL_LP(pci) || IS_BXT(pci)) || \
IS_KBL(pci) || IS_KBL_LP(pci) || IS_KBL_H(pci)
IS_KBL(pci) || IS_KBL_LP(pci) || IS_KBL_H(pci) || \
IS_GLK(pci)
static char *driver_short_names[] = {
[AZX_DRIVER_ICH] = "HDA Intel",
@ -534,9 +537,101 @@ static void bxt_reduce_dma_latency(struct azx *chip)
{
u32 val;
val = azx_readl(chip, SKL_EM4L);
val = azx_readl(chip, VS_EM4L);
val &= (0x3 << 20);
azx_writel(chip, SKL_EM4L, val);
azx_writel(chip, VS_EM4L, val);
}
/*
* ML_LCAP bits:
* bit 0: 6 MHz Supported
* bit 1: 12 MHz Supported
* bit 2: 24 MHz Supported
* bit 3: 48 MHz Supported
* bit 4: 96 MHz Supported
* bit 5: 192 MHz Supported
*/
static int intel_get_lctl_scf(struct azx *chip)
{
struct hdac_bus *bus = azx_bus(chip);
static int preferred_bits[] = { 2, 3, 1, 4, 5 };
u32 val, t;
int i;
val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCAP);
for (i = 0; i < ARRAY_SIZE(preferred_bits); i++) {
t = preferred_bits[i];
if (val & (1 << t))
return t;
}
dev_warn(chip->card->dev, "set audio clock frequency to 6MHz");
return 0;
}
static int intel_ml_lctl_set_power(struct azx *chip, int state)
{
struct hdac_bus *bus = azx_bus(chip);
u32 val;
int timeout;
/*
* the codecs are sharing the first link setting by default
* If other links are enabled for stream, they need similar fix
*/
val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL);
val &= ~AZX_MLCTL_SPA;
val |= state << AZX_MLCTL_SPA_SHIFT;
writel(val, bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL);
/* wait for CPA */
timeout = 50;
while (timeout) {
if (((readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL)) &
AZX_MLCTL_CPA) == (state << AZX_MLCTL_CPA_SHIFT))
return 0;
timeout--;
udelay(10);
}
return -1;
}
static void intel_init_lctl(struct azx *chip)
{
struct hdac_bus *bus = azx_bus(chip);
u32 val;
int ret;
/* 0. check lctl register value is correct or not */
val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL);
/* if SCF is already set, let's use it */
if ((val & ML_LCTL_SCF_MASK) != 0)
return;
/*
* Before operating on SPA, CPA must match SPA.
* Any deviation may result in undefined behavior.
*/
if (((val & AZX_MLCTL_SPA) >> AZX_MLCTL_SPA_SHIFT) !=
((val & AZX_MLCTL_CPA) >> AZX_MLCTL_CPA_SHIFT))
return;
/* 1. turn link down: set SPA to 0 and wait CPA to 0 */
ret = intel_ml_lctl_set_power(chip, 0);
udelay(100);
if (ret)
goto set_spa;
/* 2. update SCF to select a properly audio clock*/
val &= ~ML_LCTL_SCF_MASK;
val |= intel_get_lctl_scf(chip);
writel(val, bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL);
set_spa:
/* 4. turn link up: set SPA to 1 and wait CPA to 1 */
intel_ml_lctl_set_power(chip, 1);
udelay(100);
}
static void hda_intel_init_chip(struct azx *chip, bool full_reset)
@ -564,6 +659,9 @@ static void hda_intel_init_chip(struct azx *chip, bool full_reset)
/* reduce dma latency to avoid noise */
if (IS_BXT(pci))
bxt_reduce_dma_latency(chip);
if (bus->mlcap != NULL)
intel_init_lctl(chip);
}
/* calculate runtime delay from LPIB */
@ -815,6 +913,31 @@ static unsigned int azx_via_get_position(struct azx *chip,
return bound_pos + mod_dma_pos;
}
static unsigned int azx_skl_get_dpib_pos(struct azx *chip,
struct azx_dev *azx_dev)
{
return _snd_hdac_chip_readl(azx_bus(chip),
AZX_REG_VS_SDXDPIB_XBASE +
(AZX_REG_VS_SDXDPIB_XINTERVAL *
azx_dev->core.index));
}
/* get the current DMA position with correction on SKL+ chips */
static unsigned int azx_get_pos_skl(struct azx *chip, struct azx_dev *azx_dev)
{
/* DPIB register gives a more accurate position for playback */
if (azx_dev->core.substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
return azx_skl_get_dpib_pos(chip, azx_dev);
/* For capture, we need to read posbuf, but it requires a delay
* for the possible boundary overlap; the read of DPIB fetches the
* actual posbuf
*/
udelay(20);
azx_skl_get_dpib_pos(chip, azx_dev);
return azx_get_pos_posbuf(chip, azx_dev);
}
#ifdef CONFIG_PM
static DEFINE_MUTEX(card_list_lock);
static LIST_HEAD(card_list);
@ -1351,6 +1474,7 @@ static int check_position_fix(struct azx *chip, int fix)
case POS_FIX_POSBUF:
case POS_FIX_VIACOMBO:
case POS_FIX_COMBO:
case POS_FIX_SKL:
return fix;
}
@ -1371,6 +1495,10 @@ static int check_position_fix(struct azx *chip, int fix)
dev_dbg(chip->card->dev, "Using LPIB position fix\n");
return POS_FIX_LPIB;
}
if (IS_SKL_PLUS(chip->pci)) {
dev_dbg(chip->card->dev, "Using SKL position fix\n");
return POS_FIX_SKL;
}
return POS_FIX_AUTO;
}
@ -1382,6 +1510,7 @@ static void assign_position_fix(struct azx *chip, int fix)
[POS_FIX_POSBUF] = azx_get_pos_posbuf,
[POS_FIX_VIACOMBO] = azx_via_get_position,
[POS_FIX_COMBO] = azx_get_pos_lpib,
[POS_FIX_SKL] = azx_get_pos_skl,
};
chip->get_position[0] = chip->get_position[1] = callbacks[fix];
@ -1390,7 +1519,7 @@ static void assign_position_fix(struct azx *chip, int fix)
if (fix == POS_FIX_COMBO)
chip->get_position[1] = NULL;
if (fix == POS_FIX_POSBUF &&
if ((fix == POS_FIX_POSBUF || fix == POS_FIX_SKL) &&
(chip->driver_caps & AZX_DCAPS_COUNT_LPIB_DELAY)) {
chip->get_delay[0] = chip->get_delay[1] =
azx_get_delay_from_lpib;

View file

@ -857,7 +857,7 @@ static int chipio_write_address(struct hda_codec *codec,
chip_addx >> 16);
}
spec->curr_chip_addx = (res < 0) ? ~0UL : chip_addx;
spec->curr_chip_addx = (res < 0) ? ~0U : chip_addx;
return res;
}
@ -882,7 +882,7 @@ static int chipio_write_data(struct hda_codec *codec, unsigned int data)
/*If no error encountered, automatically increment the address
as per chip behaviour*/
spec->curr_chip_addx = (res != -EIO) ?
(spec->curr_chip_addx + 4) : ~0UL;
(spec->curr_chip_addx + 4) : ~0U;
return res;
}
@ -933,7 +933,7 @@ static int chipio_read_data(struct hda_codec *codec, unsigned int *data)
/*If no error encountered, automatically increment the address
as per chip behaviour*/
spec->curr_chip_addx = (res != -EIO) ?
(spec->curr_chip_addx + 4) : ~0UL;
(spec->curr_chip_addx + 4) : ~0U;
return res;
}
@ -1168,7 +1168,7 @@ static int dspio_write_multiple(struct hda_codec *codec,
int status = 0;
unsigned int count;
if ((buffer == NULL))
if (buffer == NULL)
return -EINVAL;
count = 0;
@ -1210,7 +1210,7 @@ static int dspio_read_multiple(struct hda_codec *codec, unsigned int *buffer,
unsigned int skip_count;
unsigned int dummy;
if ((buffer == NULL))
if (buffer == NULL)
return -1;
count = 0;

View file

@ -52,6 +52,12 @@ struct conexant_spec {
bool dc_enable;
unsigned int dc_input_bias; /* offset into olpc_xo_dc_bias */
struct nid_path *dc_mode_path;
int mute_led_polarity;
unsigned int gpio_led;
unsigned int gpio_mute_led_mask;
unsigned int gpio_mic_led_mask;
};
@ -264,6 +270,7 @@ enum {
CXT_FIXUP_HP_DOCK,
CXT_FIXUP_HP_SPECTRE,
CXT_FIXUP_HP_GATE_MIC,
CXT_FIXUP_MUTE_LED_GPIO,
};
/* for hda_fixup_thinkpad_acpi() */
@ -646,6 +653,74 @@ static void cxt_fixup_hp_gate_mic_jack(struct hda_codec *codec,
snd_hda_jack_set_gating_jack(codec, 0x19, 0x16);
}
/* update LED status via GPIO */
static void cxt_update_gpio_led(struct hda_codec *codec, unsigned int mask,
bool enabled)
{
struct conexant_spec *spec = codec->spec;
unsigned int oldval = spec->gpio_led;
if (spec->mute_led_polarity)
enabled = !enabled;
if (enabled)
spec->gpio_led &= ~mask;
else
spec->gpio_led |= mask;
if (spec->gpio_led != oldval)
snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
spec->gpio_led);
}
/* turn on/off mute LED via GPIO per vmaster hook */
static void cxt_fixup_gpio_mute_hook(void *private_data, int enabled)
{
struct hda_codec *codec = private_data;
struct conexant_spec *spec = codec->spec;
cxt_update_gpio_led(codec, spec->gpio_mute_led_mask, enabled);
}
/* turn on/off mic-mute LED via GPIO per capture hook */
static void cxt_fixup_gpio_mic_mute_hook(struct hda_codec *codec,
struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct conexant_spec *spec = codec->spec;
if (ucontrol)
cxt_update_gpio_led(codec, spec->gpio_mic_led_mask,
ucontrol->value.integer.value[0] ||
ucontrol->value.integer.value[1]);
}
static void cxt_fixup_mute_led_gpio(struct hda_codec *codec,
const struct hda_fixup *fix, int action)
{
struct conexant_spec *spec = codec->spec;
static const struct hda_verb gpio_init[] = {
{ 0x01, AC_VERB_SET_GPIO_MASK, 0x03 },
{ 0x01, AC_VERB_SET_GPIO_DIRECTION, 0x03 },
{}
};
codec_info(codec, "action: %d gpio_led: %d\n", action, spec->gpio_led);
if (action == HDA_FIXUP_ACT_PRE_PROBE) {
spec->gen.vmaster_mute.hook = cxt_fixup_gpio_mute_hook;
spec->gen.cap_sync_hook = cxt_fixup_gpio_mic_mute_hook;
spec->gpio_led = 0;
spec->mute_led_polarity = 0;
spec->gpio_mute_led_mask = 0x01;
spec->gpio_mic_led_mask = 0x02;
}
snd_hda_add_verbs(codec, gpio_init);
if (spec->gpio_led)
snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
spec->gpio_led);
}
/* ThinkPad X200 & co with cxt5051 */
static const struct hda_pintbl cxt_pincfg_lenovo_x200[] = {
{ 0x16, 0x042140ff }, /* HP (seq# overridden) */
@ -799,6 +874,10 @@ static const struct hda_fixup cxt_fixups[] = {
.type = HDA_FIXUP_FUNC,
.v.func = cxt_fixup_hp_gate_mic_jack,
},
[CXT_FIXUP_MUTE_LED_GPIO] = {
.type = HDA_FIXUP_FUNC,
.v.func = cxt_fixup_mute_led_gpio,
},
};
static const struct snd_pci_quirk cxt5045_fixups[] = {
@ -851,6 +930,7 @@ static const struct snd_pci_quirk cxt5066_fixups[] = {
SND_PCI_QUIRK(0x103c, 0x8079, "HP EliteBook 840 G3", CXT_FIXUP_HP_DOCK),
SND_PCI_QUIRK(0x103c, 0x8174, "HP Spectre x360", CXT_FIXUP_HP_SPECTRE),
SND_PCI_QUIRK(0x103c, 0x8115, "HP Z1 Gen3", CXT_FIXUP_HP_GATE_MIC),
SND_PCI_QUIRK(0x103c, 0x814f, "HP ZBook 15u G3", CXT_FIXUP_MUTE_LED_GPIO),
SND_PCI_QUIRK(0x1043, 0x138d, "Asus", CXT_FIXUP_HEADPHONE_MIC_PIN),
SND_PCI_QUIRK(0x152d, 0x0833, "OLPC XO-1.5", CXT_FIXUP_OLPC_XO),
SND_PCI_QUIRK(0x17aa, 0x20f2, "Lenovo T400", CXT_PINCFG_LENOVO_TP410),
@ -882,6 +962,7 @@ static const struct hda_model_fixup cxt5066_fixup_models[] = {
{ .id = CXT_FIXUP_OLPC_XO, .name = "olpc-xo" },
{ .id = CXT_FIXUP_MUTE_LED_EAPD, .name = "mute-led-eapd" },
{ .id = CXT_FIXUP_HP_DOCK, .name = "hp-dock" },
{ .id = CXT_FIXUP_MUTE_LED_GPIO, .name = "mute-led-gpio" },
{}
};

View file

@ -177,6 +177,7 @@ struct hdmi_spec {
bool i915_bound; /* was i915 bound in this driver? */
struct hdac_chmap chmap;
hda_nid_t vendor_nid;
};
#ifdef CONFIG_SND_HDA_I915
@ -383,7 +384,7 @@ static int hdmi_eld_ctl_get(struct snd_kcontrol *kcontrol,
return 0;
}
static struct snd_kcontrol_new eld_bytes_ctl = {
static const struct snd_kcontrol_new eld_bytes_ctl = {
.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "ELD",
@ -2372,6 +2373,7 @@ static void intel_haswell_fixup_connect_list(struct hda_codec *codec,
}
#define INTEL_VENDOR_NID 0x08
#define INTEL_GLK_VENDOR_NID 0x0B
#define INTEL_GET_VENDOR_VERB 0xf81
#define INTEL_SET_VENDOR_VERB 0x781
#define INTEL_EN_DP12 0x02 /* enable DP 1.2 features */
@ -2381,14 +2383,15 @@ static void intel_haswell_enable_all_pins(struct hda_codec *codec,
bool update_tree)
{
unsigned int vendor_param;
struct hdmi_spec *spec = codec->spec;
vendor_param = snd_hda_codec_read(codec, INTEL_VENDOR_NID, 0,
vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0,
INTEL_GET_VENDOR_VERB, 0);
if (vendor_param == -1 || vendor_param & INTEL_EN_ALL_PIN_CVTS)
return;
vendor_param |= INTEL_EN_ALL_PIN_CVTS;
vendor_param = snd_hda_codec_read(codec, INTEL_VENDOR_NID, 0,
vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0,
INTEL_SET_VENDOR_VERB, vendor_param);
if (vendor_param == -1)
return;
@ -2400,8 +2403,9 @@ static void intel_haswell_enable_all_pins(struct hda_codec *codec,
static void intel_haswell_fixup_enable_dp12(struct hda_codec *codec)
{
unsigned int vendor_param;
struct hdmi_spec *spec = codec->spec;
vendor_param = snd_hda_codec_read(codec, INTEL_VENDOR_NID, 0,
vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0,
INTEL_GET_VENDOR_VERB, 0);
if (vendor_param == -1 || vendor_param & INTEL_EN_DP12)
return;
@ -2409,7 +2413,7 @@ static void intel_haswell_fixup_enable_dp12(struct hda_codec *codec)
/* enable DP1.2 mode */
vendor_param |= INTEL_EN_DP12;
snd_hdac_regmap_add_vendor_verb(&codec->core, INTEL_SET_VENDOR_VERB);
snd_hda_codec_write_cache(codec, INTEL_VENDOR_NID, 0,
snd_hda_codec_write_cache(codec, spec->vendor_nid, 0,
INTEL_SET_VENDOR_VERB, vendor_param);
}
@ -2503,7 +2507,7 @@ static void i915_pin_cvt_fixup(struct hda_codec *codec,
}
/* Intel Haswell and onwards; audio component with eld notifier */
static int patch_i915_hsw_hdmi(struct hda_codec *codec)
static int intel_hsw_common_init(struct hda_codec *codec, hda_nid_t vendor_nid)
{
struct hdmi_spec *spec;
int err;
@ -2520,6 +2524,7 @@ static int patch_i915_hsw_hdmi(struct hda_codec *codec)
spec = codec->spec;
codec->dp_mst = true;
spec->dyn_pcm_assign = true;
spec->vendor_nid = vendor_nid;
intel_haswell_enable_all_pins(codec, true);
intel_haswell_fixup_enable_dp12(codec);
@ -2548,6 +2553,16 @@ static int patch_i915_hsw_hdmi(struct hda_codec *codec)
return 0;
}
static int patch_i915_hsw_hdmi(struct hda_codec *codec)
{
return intel_hsw_common_init(codec, INTEL_VENDOR_NID);
}
static int patch_i915_glk_hdmi(struct hda_codec *codec)
{
return intel_hsw_common_init(codec, INTEL_GLK_VENDOR_NID);
}
/* Intel Baytrail and Braswell; with eld notifier */
static int patch_i915_byt_hdmi(struct hda_codec *codec)
{
@ -3800,7 +3815,7 @@ HDA_CODEC_ENTRY(0x80862808, "Broadwell HDMI", patch_i915_hsw_hdmi),
HDA_CODEC_ENTRY(0x80862809, "Skylake HDMI", patch_i915_hsw_hdmi),
HDA_CODEC_ENTRY(0x8086280a, "Broxton HDMI", patch_i915_hsw_hdmi),
HDA_CODEC_ENTRY(0x8086280b, "Kabylake HDMI", patch_i915_hsw_hdmi),
HDA_CODEC_ENTRY(0x8086280d, "Geminilake HDMI", patch_i915_hsw_hdmi),
HDA_CODEC_ENTRY(0x8086280d, "Geminilake HDMI", patch_i915_glk_hdmi),
HDA_CODEC_ENTRY(0x80862880, "CedarTrail HDMI", patch_generic_hdmi),
HDA_CODEC_ENTRY(0x80862882, "Valleyview2 HDMI", patch_i915_byt_hdmi),
HDA_CODEC_ENTRY(0x80862883, "Braswell HDMI", patch_i915_byt_hdmi),

View file

@ -1800,6 +1800,7 @@ enum {
ALC882_FIXUP_NO_PRIMARY_HP,
ALC887_FIXUP_ASUS_BASS,
ALC887_FIXUP_BASS_CHMAP,
ALC1220_FIXUP_GB_DUAL_CODECS,
};
static void alc889_fixup_coef(struct hda_codec *codec,
@ -1962,6 +1963,61 @@ static void alc882_fixup_no_primary_hp(struct hda_codec *codec,
static void alc_fixup_bass_chmap(struct hda_codec *codec,
const struct hda_fixup *fix, int action);
/* For dual-codec configuration, we need to disable some features to avoid
* conflicts of kctls and PCM streams
*/
static void alc_fixup_dual_codecs(struct hda_codec *codec,
const struct hda_fixup *fix, int action)
{
struct alc_spec *spec = codec->spec;
if (action != HDA_FIXUP_ACT_PRE_PROBE)
return;
/* disable vmaster */
spec->gen.suppress_vmaster = 1;
/* auto-mute and auto-mic switch don't work with multiple codecs */
spec->gen.suppress_auto_mute = 1;
spec->gen.suppress_auto_mic = 1;
/* disable aamix as well */
spec->gen.mixer_nid = 0;
/* add location prefix to avoid conflicts */
codec->force_pin_prefix = 1;
}
static void rename_ctl(struct hda_codec *codec, const char *oldname,
const char *newname)
{
struct snd_kcontrol *kctl;
kctl = snd_hda_find_mixer_ctl(codec, oldname);
if (kctl)
strcpy(kctl->id.name, newname);
}
static void alc1220_fixup_gb_dual_codecs(struct hda_codec *codec,
const struct hda_fixup *fix,
int action)
{
alc_fixup_dual_codecs(codec, fix, action);
switch (action) {
case HDA_FIXUP_ACT_PRE_PROBE:
/* override card longname to provide a unique UCM profile */
strcpy(codec->card->longname, "HDAudio-Gigabyte-ALC1220DualCodecs");
break;
case HDA_FIXUP_ACT_BUILD:
/* rename Capture controls depending on the codec */
rename_ctl(codec, "Capture Volume",
codec->addr == 0 ?
"Rear-Panel Capture Volume" :
"Front-Panel Capture Volume");
rename_ctl(codec, "Capture Switch",
codec->addr == 0 ?
"Rear-Panel Capture Switch" :
"Front-Panel Capture Switch");
break;
}
}
static const struct hda_fixup alc882_fixups[] = {
[ALC882_FIXUP_ABIT_AW9D_MAX] = {
.type = HDA_FIXUP_PINS,
@ -2198,6 +2254,10 @@ static const struct hda_fixup alc882_fixups[] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc_fixup_bass_chmap,
},
[ALC1220_FIXUP_GB_DUAL_CODECS] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc1220_fixup_gb_dual_codecs,
},
};
static const struct snd_pci_quirk alc882_fixup_tbl[] = {
@ -2267,6 +2327,7 @@ static const struct snd_pci_quirk alc882_fixup_tbl[] = {
SND_PCI_QUIRK(0x1462, 0x7350, "MSI-7350", ALC889_FIXUP_CD),
SND_PCI_QUIRK_VENDOR(0x1462, "MSI", ALC882_FIXUP_GPIO3),
SND_PCI_QUIRK(0x1458, 0xa002, "Gigabyte EP45-DS3/Z87X-UD3H", ALC889_FIXUP_FRONT_HP_NO_PRESENCE),
SND_PCI_QUIRK(0x1458, 0xa0b8, "Gigabyte AZ370-Gaming", ALC1220_FIXUP_GB_DUAL_CODECS),
SND_PCI_QUIRK(0x147b, 0x107a, "Abit AW9D-MAX", ALC882_FIXUP_ABIT_AW9D_MAX),
SND_PCI_QUIRK_VENDOR(0x1558, "Clevo laptop", ALC882_FIXUP_EAPD),
SND_PCI_QUIRK(0x161f, 0x2054, "Medion laptop", ALC883_FIXUP_EAPD),
@ -4663,7 +4724,6 @@ static void alc282_fixup_asus_tx300(struct hda_codec *codec,
{ 0x1b, 0x21114000 }, /* dock speaker pin */
{}
};
struct snd_kcontrol *kctl;
switch (action) {
case HDA_FIXUP_ACT_PRE_PROBE:
@ -4678,12 +4738,10 @@ static void alc282_fixup_asus_tx300(struct hda_codec *codec,
/* this is a bit tricky; give more sane names for the main
* (tablet) speaker and the dock speaker, respectively
*/
kctl = snd_hda_find_mixer_ctl(codec, "Speaker Playback Switch");
if (kctl)
strcpy(kctl->id.name, "Dock Speaker Playback Switch");
kctl = snd_hda_find_mixer_ctl(codec, "Bass Speaker Playback Switch");
if (kctl)
strcpy(kctl->id.name, "Speaker Playback Switch");
rename_ctl(codec, "Speaker Playback Switch",
"Dock Speaker Playback Switch");
rename_ctl(codec, "Bass Speaker Playback Switch",
"Speaker Playback Switch");
break;
}
}
@ -4766,6 +4824,30 @@ static void alc280_fixup_hp_9480m(struct hda_codec *codec,
}
}
static void alc233_alc662_fixup_lenovo_dual_codecs(struct hda_codec *codec,
const struct hda_fixup *fix,
int action)
{
alc_fixup_dual_codecs(codec, fix, action);
switch (action) {
case HDA_FIXUP_ACT_PRE_PROBE:
/* override card longname to provide a unique UCM profile */
strcpy(codec->card->longname, "HDAudio-Lenovo-DualCodecs");
break;
case HDA_FIXUP_ACT_BUILD:
/* rename Capture controls depending on the codec */
rename_ctl(codec, "Capture Volume",
codec->addr == 0 ?
"Rear-Panel Capture Volume" :
"Front-Panel Capture Volume");
rename_ctl(codec, "Capture Switch",
codec->addr == 0 ?
"Rear-Panel Capture Switch" :
"Front-Panel Capture Switch");
break;
}
}
/* for hda_fixup_thinkpad_acpi() */
#include "thinkpad_helper.c"
@ -4833,6 +4915,8 @@ enum {
ALC290_FIXUP_SUBWOOFER_HSJACK,
ALC269_FIXUP_THINKPAD_ACPI,
ALC269_FIXUP_DMIC_THINKPAD_ACPI,
ALC255_FIXUP_ACER_MIC_NO_PRESENCE,
ALC255_FIXUP_ASUS_MIC_NO_PRESENCE,
ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
ALC255_FIXUP_DELL2_MIC_NO_PRESENCE,
ALC255_FIXUP_HEADSET_MODE,
@ -4872,6 +4956,12 @@ enum {
ALC256_FIXUP_DELL_INSPIRON_7559_SUBWOOFER,
ALC269_FIXUP_ATIV_BOOK_8,
ALC221_FIXUP_HP_MIC_NO_PRESENCE,
ALC256_FIXUP_ASUS_HEADSET_MODE,
ALC256_FIXUP_ASUS_MIC,
ALC256_FIXUP_ASUS_AIO_GPIO2,
ALC233_FIXUP_ASUS_MIC_NO_PRESENCE,
ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE,
ALC233_FIXUP_LENOVO_MULTI_CODECS,
};
static const struct hda_fixup alc269_fixups[] = {
@ -5289,6 +5379,24 @@ static const struct hda_fixup alc269_fixups[] = {
.chained = true,
.chain_id = ALC269_FIXUP_THINKPAD_ACPI,
},
[ALC255_FIXUP_ACER_MIC_NO_PRESENCE] = {
.type = HDA_FIXUP_PINS,
.v.pins = (const struct hda_pintbl[]) {
{ 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */
{ }
},
.chained = true,
.chain_id = ALC255_FIXUP_HEADSET_MODE
},
[ALC255_FIXUP_ASUS_MIC_NO_PRESENCE] = {
.type = HDA_FIXUP_PINS,
.v.pins = (const struct hda_pintbl[]) {
{ 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */
{ }
},
.chained = true,
.chain_id = ALC255_FIXUP_HEADSET_MODE
},
[ALC255_FIXUP_DELL1_MIC_NO_PRESENCE] = {
.type = HDA_FIXUP_PINS,
.v.pins = (const struct hda_pintbl[]) {
@ -5579,6 +5687,54 @@ static const struct hda_fixup alc269_fixups[] = {
.chained = true,
.chain_id = ALC269_FIXUP_HEADSET_MODE
},
[ALC256_FIXUP_ASUS_HEADSET_MODE] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc_fixup_headset_mode,
},
[ALC256_FIXUP_ASUS_MIC] = {
.type = HDA_FIXUP_PINS,
.v.pins = (const struct hda_pintbl[]) {
{ 0x13, 0x90a60160 }, /* use as internal mic */
{ 0x19, 0x04a11120 }, /* use as headset mic, without its own jack detect */
{ }
},
.chained = true,
.chain_id = ALC256_FIXUP_ASUS_HEADSET_MODE
},
[ALC256_FIXUP_ASUS_AIO_GPIO2] = {
.type = HDA_FIXUP_VERBS,
.v.verbs = (const struct hda_verb[]) {
/* Set up GPIO2 for the speaker amp */
{ 0x01, AC_VERB_SET_GPIO_MASK, 0x04 },
{ 0x01, AC_VERB_SET_GPIO_DIRECTION, 0x04 },
{ 0x01, AC_VERB_SET_GPIO_DATA, 0x04 },
{}
},
},
[ALC233_FIXUP_ASUS_MIC_NO_PRESENCE] = {
.type = HDA_FIXUP_PINS,
.v.pins = (const struct hda_pintbl[]) {
{ 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */
{ }
},
.chained = true,
.chain_id = ALC269_FIXUP_HEADSET_MIC
},
[ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE] = {
.type = HDA_FIXUP_VERBS,
.v.verbs = (const struct hda_verb[]) {
/* Enables internal speaker */
{0x20, AC_VERB_SET_COEF_INDEX, 0x40},
{0x20, AC_VERB_SET_PROC_COEF, 0x8800},
{}
},
.chained = true,
.chain_id = ALC233_FIXUP_ASUS_MIC_NO_PRESENCE
},
[ALC233_FIXUP_LENOVO_MULTI_CODECS] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc233_alc662_fixup_lenovo_dual_codecs,
},
};
static const struct snd_pci_quirk alc269_fixup_tbl[] = {
@ -5692,15 +5848,27 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x103c, 0x8256, "HP", ALC221_FIXUP_HP_FRONT_MIC),
SND_PCI_QUIRK(0x103c, 0x82bf, "HP", ALC221_FIXUP_HP_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x103c, 0x82c0, "HP", ALC221_FIXUP_HP_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1043, 0x103e, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC),
SND_PCI_QUIRK(0x1043, 0x103f, "ASUS TX300", ALC282_FIXUP_ASUS_TX300),
SND_PCI_QUIRK(0x1043, 0x106d, "Asus K53BE", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
SND_PCI_QUIRK(0x1043, 0x10c0, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC),
SND_PCI_QUIRK(0x1043, 0x115d, "Asus 1015E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
SND_PCI_QUIRK(0x1043, 0x12f0, "ASUS X541UV", ALC256_FIXUP_ASUS_MIC),
SND_PCI_QUIRK(0x1043, 0x12e0, "ASUS X541SA", ALC256_FIXUP_ASUS_MIC),
SND_PCI_QUIRK(0x1043, 0x13b0, "ASUS Z550SA", ALC256_FIXUP_ASUS_MIC),
SND_PCI_QUIRK(0x1043, 0x1427, "Asus Zenbook UX31E", ALC269VB_FIXUP_ASUS_ZENBOOK),
SND_PCI_QUIRK(0x1043, 0x1517, "Asus Zenbook UX31A", ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A),
SND_PCI_QUIRK(0x1043, 0x16e3, "ASUS UX50", ALC269_FIXUP_STEREO_DMIC),
SND_PCI_QUIRK(0x1043, 0x1a13, "Asus G73Jw", ALC269_FIXUP_ASUS_G73JW),
SND_PCI_QUIRK(0x1043, 0x1b13, "Asus U41SV", ALC269_FIXUP_INV_DMIC),
SND_PCI_QUIRK(0x1043, 0x1c23, "Asus X55U", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
SND_PCI_QUIRK(0x1043, 0x1bbd, "ASUS Z550MA", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1043, 0x10d0, "ASUS X540LA/X540LJ", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1043, 0x11c0, "ASUS X556UR", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1043, 0x1290, "ASUS X441SA", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1043, 0x12a0, "ASUS X441UV", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1043, 0x1ccd, "ASUS X555UB", ALC256_FIXUP_ASUS_MIC),
SND_PCI_QUIRK(0x1043, 0x3030, "ASUS ZN270IE", ALC256_FIXUP_ASUS_AIO_GPIO2),
SND_PCI_QUIRK(0x1043, 0x831a, "ASUS P901", ALC269_FIXUP_STEREO_DMIC),
SND_PCI_QUIRK(0x1043, 0x834a, "ASUS S101", ALC269_FIXUP_STEREO_DMIC),
SND_PCI_QUIRK(0x1043, 0x8398, "ASUS P1005", ALC269_FIXUP_STEREO_DMIC),
@ -5721,6 +5889,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x144d, 0xc740, "Samsung Ativ book 8 (NP870Z5G)", ALC269_FIXUP_ATIV_BOOK_8),
SND_PCI_QUIRK(0x1458, 0xfa53, "Gigabyte BXBT-2807", ALC283_FIXUP_HEADSET_MIC),
SND_PCI_QUIRK(0x1462, 0xb120, "MSI Cubi MS-B120", ALC283_FIXUP_HEADSET_MIC),
SND_PCI_QUIRK(0x17aa, 0x1036, "Lenovo P520", ALC233_FIXUP_LENOVO_MULTI_CODECS),
SND_PCI_QUIRK(0x17aa, 0x20f2, "Thinkpad SL410/510", ALC269_FIXUP_SKU_IGNORE),
SND_PCI_QUIRK(0x17aa, 0x215e, "Thinkpad L512", ALC269_FIXUP_SKU_IGNORE),
SND_PCI_QUIRK(0x17aa, 0x21b8, "Thinkpad Edge 14", ALC269_FIXUP_SKU_IGNORE),
@ -5875,6 +6044,18 @@ static const struct hda_model_fixup alc269_fixup_models[] = {
{0x21, 0x03211020}
static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = {
SND_HDA_PIN_QUIRK(0x10ec0255, 0x1025, "Acer", ALC255_FIXUP_ACER_MIC_NO_PRESENCE,
{0x12, 0x90a601c0},
{0x14, 0x90171120},
{0x21, 0x02211030}),
SND_HDA_PIN_QUIRK(0x10ec0255, 0x1043, "ASUS", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE,
{0x14, 0x90170110},
{0x1b, 0x90a70130},
{0x21, 0x03211020}),
SND_HDA_PIN_QUIRK(0x10ec0255, 0x1043, "ASUS", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE,
{0x1a, 0x90a70130},
{0x1b, 0x90170110},
{0x21, 0x03211020}),
SND_HDA_PIN_QUIRK(0x10ec0225, 0x1028, "Dell", ALC225_FIXUP_DELL1_MIC_NO_PRESENCE,
ALC225_STANDARD_PINS,
{0x12, 0xb7a60130},
@ -5995,6 +6176,14 @@ static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = {
{0x21, 0x02211020}),
SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
ALC256_STANDARD_PINS),
SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC,
{0x14, 0x90170110},
{0x1b, 0x90a70130},
{0x21, 0x04211020}),
SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC,
{0x14, 0x90170110},
{0x1b, 0x90a70130},
{0x21, 0x03211020}),
SND_HDA_PIN_QUIRK(0x10ec0280, 0x103c, "HP", ALC280_FIXUP_HP_GPIO4,
{0x12, 0x90a60130},
{0x14, 0x90170110},
@ -6714,6 +6903,7 @@ enum {
ALC668_FIXUP_DELL_DISABLE_AAMIX,
ALC668_FIXUP_DELL_XPS13,
ALC662_FIXUP_ASUS_Nx50,
ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE,
ALC668_FIXUP_ASUS_Nx51,
ALC891_FIXUP_HEADSET_MODE,
ALC891_FIXUP_DELL_MIC_NO_PRESENCE,
@ -6721,6 +6911,7 @@ enum {
ALC892_FIXUP_ASROCK_MOBO,
ALC662_FIXUP_USI_FUNC,
ALC662_FIXUP_USI_HEADSET_MODE,
ALC662_FIXUP_LENOVO_MULTI_CODECS,
};
static const struct hda_fixup alc662_fixups[] = {
@ -6967,14 +7158,21 @@ static const struct hda_fixup alc662_fixups[] = {
.chained = true,
.chain_id = ALC662_FIXUP_BASS_1A
},
[ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc_fixup_headset_mode_alc668,
.chain_id = ALC662_FIXUP_BASS_CHMAP
},
[ALC668_FIXUP_ASUS_Nx51] = {
.type = HDA_FIXUP_PINS,
.v.pins = (const struct hda_pintbl[]) {
{0x1a, 0x90170151}, /* bass speaker */
{ 0x19, 0x03a1913d }, /* use as headphone mic, without its own jack detect */
{ 0x1a, 0x90170151 }, /* bass speaker */
{ 0x1b, 0x03a1113c }, /* use as headset mic, without its own jack detect */
{}
},
.chained = true,
.chain_id = ALC662_FIXUP_BASS_CHMAP,
.chain_id = ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE,
},
[ALC891_FIXUP_HEADSET_MODE] = {
.type = HDA_FIXUP_FUNC,
@ -7019,6 +7217,10 @@ static const struct hda_fixup alc662_fixups[] = {
.chained = true,
.chain_id = ALC662_FIXUP_USI_FUNC
},
[ALC662_FIXUP_LENOVO_MULTI_CODECS] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc233_alc662_fixup_lenovo_dual_codecs,
},
};
static const struct snd_pci_quirk alc662_fixup_tbl[] = {
@ -7056,6 +7258,7 @@ static const struct snd_pci_quirk alc662_fixup_tbl[] = {
SND_PCI_QUIRK(0x105b, 0x0cd6, "Foxconn", ALC662_FIXUP_ASUS_MODE2),
SND_PCI_QUIRK(0x144d, 0xc051, "Samsung R720", ALC662_FIXUP_IDEAPAD),
SND_PCI_QUIRK(0x14cd, 0x5003, "USI", ALC662_FIXUP_USI_HEADSET_MODE),
SND_PCI_QUIRK(0x17aa, 0x1036, "Lenovo P520", ALC662_FIXUP_LENOVO_MULTI_CODECS),
SND_PCI_QUIRK(0x17aa, 0x38af, "Lenovo Ideapad Y550P", ALC662_FIXUP_IDEAPAD),
SND_PCI_QUIRK(0x17aa, 0x3a0d, "Lenovo Ideapad Y550", ALC662_FIXUP_IDEAPAD),
SND_PCI_QUIRK(0x1849, 0x5892, "ASRock B150M", ALC892_FIXUP_ASROCK_MOBO),

View file

@ -432,7 +432,7 @@ static int snd_ice1712_delta1010lt_wordclock_status_get(struct snd_kcontrol *kco
return 0;
}
static struct snd_kcontrol_new snd_ice1712_delta1010lt_wordclock_status =
static const struct snd_kcontrol_new snd_ice1712_delta1010lt_wordclock_status =
{
.access = (SNDRV_CTL_ELEM_ACCESS_READ),
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,

View file

@ -719,7 +719,7 @@ static int snd_ice1712_ews88mt_input_sense_put(struct snd_kcontrol *kcontrol, st
return ndata != data;
}
static struct snd_kcontrol_new snd_ice1712_ews88mt_input_sense = {
static const struct snd_kcontrol_new snd_ice1712_ews88mt_input_sense = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Input Sensitivity Switch",
.info = snd_ice1712_ewx_io_sense_info,
@ -728,7 +728,7 @@ static struct snd_kcontrol_new snd_ice1712_ews88mt_input_sense = {
.count = 8,
};
static struct snd_kcontrol_new snd_ice1712_ews88mt_output_sense = {
static const struct snd_kcontrol_new snd_ice1712_ews88mt_output_sense = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Output Sensitivity Switch",
.info = snd_ice1712_ewx_io_sense_info,

View file

@ -279,7 +279,7 @@ static int snd_ice1712_digmix_route_ac97_put(struct snd_kcontrol *kcontrol, stru
return val != nval;
}
static struct snd_kcontrol_new snd_ice1712_mixer_digmix_route_ac97 = {
static const struct snd_kcontrol_new snd_ice1712_mixer_digmix_route_ac97 = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Digital Mixer To AC97",
.info = snd_ice1712_digmix_route_ac97_info,
@ -1410,7 +1410,7 @@ static struct snd_kcontrol_new snd_ice1712_multi_capture_analog_switch = {
.private_value = 10,
};
static struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_switch = {
static const struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_switch = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("Multi ", CAPTURE, SWITCH),
.info = snd_ice1712_pro_mixer_switch_info,
@ -1432,7 +1432,7 @@ static struct snd_kcontrol_new snd_ice1712_multi_capture_analog_volume = {
.tlv = { .p = db_scale_playback }
};
static struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_volume = {
static const struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_volume = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("Multi ", CAPTURE, VOLUME),
.info = snd_ice1712_pro_mixer_volume_info,
@ -1630,7 +1630,7 @@ static int snd_ice1712_eeprom_get(struct snd_kcontrol *kcontrol,
return 0;
}
static struct snd_kcontrol_new snd_ice1712_eeprom = {
static const struct snd_kcontrol_new snd_ice1712_eeprom = {
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
.name = "ICE1712 EEPROM",
.access = SNDRV_CTL_ELEM_ACCESS_READ,
@ -1666,7 +1666,7 @@ static int snd_ice1712_spdif_default_put(struct snd_kcontrol *kcontrol,
return 0;
}
static struct snd_kcontrol_new snd_ice1712_spdif_default =
static const struct snd_kcontrol_new snd_ice1712_spdif_default =
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
@ -1717,7 +1717,7 @@ static int snd_ice1712_spdif_maskp_get(struct snd_kcontrol *kcontrol,
return 0;
}
static struct snd_kcontrol_new snd_ice1712_spdif_maskc =
static const struct snd_kcontrol_new snd_ice1712_spdif_maskc =
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
@ -1726,7 +1726,7 @@ static struct snd_kcontrol_new snd_ice1712_spdif_maskc =
.get = snd_ice1712_spdif_maskc_get,
};
static struct snd_kcontrol_new snd_ice1712_spdif_maskp =
static const struct snd_kcontrol_new snd_ice1712_spdif_maskp =
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
@ -1753,7 +1753,7 @@ static int snd_ice1712_spdif_stream_put(struct snd_kcontrol *kcontrol,
return 0;
}
static struct snd_kcontrol_new snd_ice1712_spdif_stream =
static const struct snd_kcontrol_new snd_ice1712_spdif_stream =
{
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_INACTIVE),
@ -1878,7 +1878,7 @@ static int snd_ice1712_pro_internal_clock_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_ice1712_pro_internal_clock = {
static const struct snd_kcontrol_new snd_ice1712_pro_internal_clock = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Multi Track Internal Clock",
.info = snd_ice1712_pro_internal_clock_info,
@ -1943,7 +1943,7 @@ static int snd_ice1712_pro_internal_clock_default_put(struct snd_kcontrol *kcont
return change;
}
static struct snd_kcontrol_new snd_ice1712_pro_internal_clock_default = {
static const struct snd_kcontrol_new snd_ice1712_pro_internal_clock_default = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Multi Track Internal Clock Default",
.info = snd_ice1712_pro_internal_clock_default_info,
@ -1974,7 +1974,7 @@ static int snd_ice1712_pro_rate_locking_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_ice1712_pro_rate_locking = {
static const struct snd_kcontrol_new snd_ice1712_pro_rate_locking = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Multi Track Rate Locking",
.info = snd_ice1712_pro_rate_locking_info,
@ -2005,7 +2005,7 @@ static int snd_ice1712_pro_rate_reset_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_ice1712_pro_rate_reset = {
static const struct snd_kcontrol_new snd_ice1712_pro_rate_reset = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Multi Track Rate Reset",
.info = snd_ice1712_pro_rate_reset_info,
@ -2173,7 +2173,7 @@ static struct snd_kcontrol_new snd_ice1712_mixer_pro_analog_route = {
.put = snd_ice1712_pro_route_analog_put,
};
static struct snd_kcontrol_new snd_ice1712_mixer_pro_spdif_route = {
static const struct snd_kcontrol_new snd_ice1712_mixer_pro_spdif_route = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, NONE) "Route",
.info = snd_ice1712_pro_route_info,
@ -2215,7 +2215,7 @@ static int snd_ice1712_pro_volume_rate_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_ice1712_mixer_pro_volume_rate = {
static const struct snd_kcontrol_new snd_ice1712_mixer_pro_volume_rate = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Multi Track Volume Rate",
.info = snd_ice1712_pro_volume_rate_info,
@ -2248,7 +2248,7 @@ static int snd_ice1712_pro_peak_get(struct snd_kcontrol *kcontrol,
return 0;
}
static struct snd_kcontrol_new snd_ice1712_mixer_pro_peak = {
static const struct snd_kcontrol_new snd_ice1712_mixer_pro_peak = {
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "Multi Track Peak",
.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,

View file

@ -1598,7 +1598,7 @@ static int snd_vt1724_eeprom_get(struct snd_kcontrol *kcontrol,
return 0;
}
static struct snd_kcontrol_new snd_vt1724_eeprom = {
static const struct snd_kcontrol_new snd_vt1724_eeprom = {
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
.name = "ICE1724 EEPROM",
.access = SNDRV_CTL_ELEM_ACCESS_READ,
@ -1711,7 +1711,7 @@ static int snd_vt1724_spdif_default_put(struct snd_kcontrol *kcontrol,
return val != old;
}
static struct snd_kcontrol_new snd_vt1724_spdif_default =
static const struct snd_kcontrol_new snd_vt1724_spdif_default =
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
@ -1743,7 +1743,7 @@ static int snd_vt1724_spdif_maskp_get(struct snd_kcontrol *kcontrol,
return 0;
}
static struct snd_kcontrol_new snd_vt1724_spdif_maskc =
static const struct snd_kcontrol_new snd_vt1724_spdif_maskc =
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
@ -1752,7 +1752,7 @@ static struct snd_kcontrol_new snd_vt1724_spdif_maskc =
.get = snd_vt1724_spdif_maskc_get,
};
static struct snd_kcontrol_new snd_vt1724_spdif_maskp =
static const struct snd_kcontrol_new snd_vt1724_spdif_maskp =
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
@ -1789,7 +1789,7 @@ static int snd_vt1724_spdif_sw_put(struct snd_kcontrol *kcontrol,
return old != val;
}
static struct snd_kcontrol_new snd_vt1724_spdif_switch =
static const struct snd_kcontrol_new snd_vt1724_spdif_switch =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
/* FIXME: the following conflict with IEC958 Playback Route */
@ -1964,7 +1964,7 @@ static int snd_vt1724_pro_internal_clock_put(struct snd_kcontrol *kcontrol,
return old_rate != new_rate;
}
static struct snd_kcontrol_new snd_vt1724_pro_internal_clock = {
static const struct snd_kcontrol_new snd_vt1724_pro_internal_clock = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Multi Track Internal Clock",
.info = snd_vt1724_pro_internal_clock_info,
@ -1995,7 +1995,7 @@ static int snd_vt1724_pro_rate_locking_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_vt1724_pro_rate_locking = {
static const struct snd_kcontrol_new snd_vt1724_pro_rate_locking = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Multi Track Rate Locking",
.info = snd_vt1724_pro_rate_locking_info,
@ -2026,7 +2026,7 @@ static int snd_vt1724_pro_rate_reset_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_vt1724_pro_rate_reset = {
static const struct snd_kcontrol_new snd_vt1724_pro_rate_reset = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Multi Track Rate Reset",
.info = snd_vt1724_pro_rate_reset_info,
@ -2151,7 +2151,7 @@ static struct snd_kcontrol_new snd_vt1724_mixer_pro_analog_route =
.put = snd_vt1724_pro_route_analog_put,
};
static struct snd_kcontrol_new snd_vt1724_mixer_pro_spdif_route = {
static const struct snd_kcontrol_new snd_vt1724_mixer_pro_spdif_route = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, NONE) "Route",
.info = snd_vt1724_pro_route_info,
@ -2187,7 +2187,7 @@ static int snd_vt1724_pro_peak_get(struct snd_kcontrol *kcontrol,
return 0;
}
static struct snd_kcontrol_new snd_vt1724_mixer_pro_peak = {
static const struct snd_kcontrol_new snd_vt1724_mixer_pro_peak = {
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "Multi Track Peak",
.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,

View file

@ -645,7 +645,7 @@ static int lola_input_src_put(struct snd_kcontrol *kcontrol,
return lola_set_src_config(chip, mask, true);
}
static struct snd_kcontrol_new lola_input_src_mixer = {
static const struct snd_kcontrol_new lola_input_src_mixer = {
.name = "Digital SRC Capture Switch",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.info = lola_input_src_info,

View file

@ -899,7 +899,7 @@ static int lx_control_playback_put(struct snd_kcontrol *kcontrol,
return changed;
}
static struct snd_kcontrol_new lx_control_playback_switch = {
static const struct snd_kcontrol_new lx_control_playback_switch = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Playback Switch",
.index = 0,

View file

@ -448,7 +448,7 @@ static int mixart_audio_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_ele
return changed;
}
static struct snd_kcontrol_new mixart_control_output_switch = {
static const struct snd_kcontrol_new mixart_control_output_switch = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Master Playback Switch",
.info = mixart_sw_info, /* shared */
@ -1024,7 +1024,7 @@ static int mixart_monitor_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_
return changed;
}
static struct snd_kcontrol_new mixart_control_monitor_vol = {
static const struct snd_kcontrol_new mixart_control_monitor_vol = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ),
@ -1091,7 +1091,7 @@ static int mixart_monitor_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_e
return (changed != 0);
}
static struct snd_kcontrol_new mixart_control_monitor_sw = {
static const struct snd_kcontrol_new mixart_control_monitor_sw = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Monitoring Switch",
.info = mixart_sw_info, /* shared */

View file

@ -767,6 +767,8 @@ static int get_oxygen_model(struct oxygen *chip,
[MODEL_FANTASIA] = "TempoTec HiFier Fantasia",
[MODEL_SERENADE] = "TempoTec HiFier Serenade",
[MODEL_HG2PCI] = "CMI8787-HG2PCI",
[MODEL_XONAR_DG] = "Xonar DG",
[MODEL_XONAR_DGX] = "Xonar DGX",
};
chip->model = model_generic;
@ -829,12 +831,8 @@ static int get_oxygen_model(struct oxygen *chip,
chip->model.dac_channels_mixer = 2;
break;
case MODEL_XONAR_DG:
chip->model = model_xonar_dg;
chip->model.shortname = "Xonar DG";
break;
case MODEL_XONAR_DGX:
chip->model = model_xonar_dg;
chip->model.shortname = "Xonar DGX";
break;
}
if (id->driver_data == MODEL_MERIDIAN ||

View file

@ -744,7 +744,7 @@ static int hr222_mic_vol_put(struct snd_kcontrol *kcontrol,
return changed;
}
static struct snd_kcontrol_new hr222_control_mic_level = {
static const struct snd_kcontrol_new hr222_control_mic_level = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ),
@ -794,7 +794,7 @@ static int hr222_mic_boost_put(struct snd_kcontrol *kcontrol,
return changed;
}
static struct snd_kcontrol_new hr222_control_mic_boost = {
static const struct snd_kcontrol_new hr222_control_mic_boost = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ),
@ -836,7 +836,7 @@ static int hr222_phantom_power_put(struct snd_kcontrol *kcontrol,
return changed;
}
static struct snd_kcontrol_new hr222_phantom_power_switch = {
static const struct snd_kcontrol_new hr222_phantom_power_switch = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Phantom Power Switch",
.info = hr222_phantom_power_info,

View file

@ -235,7 +235,7 @@ static int pcxhr_audio_sw_put(struct snd_kcontrol *kcontrol,
return changed;
}
static struct snd_kcontrol_new pcxhr_control_output_switch = {
static const struct snd_kcontrol_new pcxhr_control_output_switch = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Master Playback Switch",
.info = pcxhr_sw_info, /* shared */
@ -460,7 +460,7 @@ static int pcxhr_pcm_sw_put(struct snd_kcontrol *kcontrol,
return changed;
}
static struct snd_kcontrol_new pcxhr_control_pcm_switch = {
static const struct snd_kcontrol_new pcxhr_control_pcm_switch = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Playback Switch",
.count = PCXHR_PLAYBACK_STREAMS,
@ -509,7 +509,7 @@ static int pcxhr_monitor_vol_put(struct snd_kcontrol *kcontrol,
return changed;
}
static struct snd_kcontrol_new pcxhr_control_monitor_vol = {
static const struct snd_kcontrol_new pcxhr_control_monitor_vol = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ),
@ -562,7 +562,7 @@ static int pcxhr_monitor_sw_put(struct snd_kcontrol *kcontrol,
return (changed != 0);
}
static struct snd_kcontrol_new pcxhr_control_monitor_sw = {
static const struct snd_kcontrol_new pcxhr_control_monitor_sw = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Monitoring Playback Switch",
.info = pcxhr_sw_info, /* shared */
@ -697,7 +697,7 @@ static int pcxhr_audio_src_put(struct snd_kcontrol *kcontrol,
return ret;
}
static struct snd_kcontrol_new pcxhr_control_audio_src = {
static const struct snd_kcontrol_new pcxhr_control_audio_src = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Capture Source",
.info = pcxhr_audio_src_info,
@ -798,7 +798,7 @@ static int pcxhr_clock_type_put(struct snd_kcontrol *kcontrol,
return ret;
}
static struct snd_kcontrol_new pcxhr_control_clock_type = {
static const struct snd_kcontrol_new pcxhr_control_clock_type = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Clock Mode",
.info = pcxhr_clock_type_info,
@ -842,7 +842,7 @@ static int pcxhr_clock_rate_get(struct snd_kcontrol *kcontrol,
return 0;
}
static struct snd_kcontrol_new pcxhr_control_clock_rate = {
static const struct snd_kcontrol_new pcxhr_control_clock_rate = {
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
.name = "Clock Rates",
@ -1017,14 +1017,14 @@ static int pcxhr_iec958_put(struct snd_kcontrol *kcontrol,
return changed;
}
static struct snd_kcontrol_new pcxhr_control_playback_iec958_mask = {
static const struct snd_kcontrol_new pcxhr_control_playback_iec958_mask = {
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK),
.info = pcxhr_iec958_info,
.get = pcxhr_iec958_mask_get
};
static struct snd_kcontrol_new pcxhr_control_playback_iec958 = {
static const struct snd_kcontrol_new pcxhr_control_playback_iec958 = {
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
.info = pcxhr_iec958_info,
@ -1033,14 +1033,14 @@ static struct snd_kcontrol_new pcxhr_control_playback_iec958 = {
.private_value = 0 /* playback */
};
static struct snd_kcontrol_new pcxhr_control_capture_iec958_mask = {
static const struct snd_kcontrol_new pcxhr_control_capture_iec958_mask = {
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",CAPTURE,MASK),
.info = pcxhr_iec958_info,
.get = pcxhr_iec958_mask_get
};
static struct snd_kcontrol_new pcxhr_control_capture_iec958 = {
static const struct snd_kcontrol_new pcxhr_control_capture_iec958 = {
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",CAPTURE,DEFAULT),

View file

@ -2356,7 +2356,7 @@ static int snd_trident_spdif_control_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_trident_spdif_control =
static const struct snd_kcontrol_new snd_trident_spdif_control =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH),
@ -2419,7 +2419,7 @@ static int snd_trident_spdif_default_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_trident_spdif_default =
static const struct snd_kcontrol_new snd_trident_spdif_default =
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
@ -2452,7 +2452,7 @@ static int snd_trident_spdif_mask_get(struct snd_kcontrol *kcontrol,
return 0;
}
static struct snd_kcontrol_new snd_trident_spdif_mask =
static const struct snd_kcontrol_new snd_trident_spdif_mask =
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
@ -2514,7 +2514,7 @@ static int snd_trident_spdif_stream_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_trident_spdif_stream =
static const struct snd_kcontrol_new snd_trident_spdif_stream =
{
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
@ -2564,7 +2564,7 @@ static int snd_trident_ac97_control_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_trident_ac97_rear_control =
static const struct snd_kcontrol_new snd_trident_ac97_rear_control =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Rear Path",
@ -2622,7 +2622,7 @@ static int snd_trident_vol_control_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_trident_vol_music_control =
static const struct snd_kcontrol_new snd_trident_vol_music_control =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Music Playback Volume",
@ -2633,7 +2633,7 @@ static struct snd_kcontrol_new snd_trident_vol_music_control =
.tlv = { .p = db_scale_gvol },
};
static struct snd_kcontrol_new snd_trident_vol_wave_control =
static const struct snd_kcontrol_new snd_trident_vol_wave_control =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Wave Playback Volume",
@ -2700,7 +2700,7 @@ static int snd_trident_pcm_vol_control_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_trident_pcm_vol_control =
static const struct snd_kcontrol_new snd_trident_pcm_vol_control =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Front Playback Volume",
@ -2764,7 +2764,7 @@ static int snd_trident_pcm_pan_control_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_trident_pcm_pan_control =
static const struct snd_kcontrol_new snd_trident_pcm_pan_control =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Pan Playback Control",
@ -2821,7 +2821,7 @@ static int snd_trident_pcm_rvol_control_put(struct snd_kcontrol *kcontrol,
static const DECLARE_TLV_DB_SCALE(db_scale_crvol, -3175, 25, 1);
static struct snd_kcontrol_new snd_trident_pcm_rvol_control =
static const struct snd_kcontrol_new snd_trident_pcm_rvol_control =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Reverb Playback Volume",
@ -2877,7 +2877,7 @@ static int snd_trident_pcm_cvol_control_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_trident_pcm_cvol_control =
static const struct snd_kcontrol_new snd_trident_pcm_cvol_control =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Chorus Playback Volume",

View file

@ -1683,7 +1683,7 @@ static int snd_via8233_dxs3_spdif_put(struct snd_kcontrol *kcontrol,
return 0;
}
static struct snd_kcontrol_new snd_via8233_dxs3_spdif_control = {
static const struct snd_kcontrol_new snd_via8233_dxs3_spdif_control = {
.name = SNDRV_CTL_NAME_IEC958("Output ",NONE,SWITCH),
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.info = snd_via8233_dxs3_spdif_info,
@ -1772,7 +1772,7 @@ static int snd_via8233_pcmdxs_volume_put(struct snd_kcontrol *kcontrol,
static const DECLARE_TLV_DB_SCALE(db_scale_dxs, -4650, 150, 1);
static struct snd_kcontrol_new snd_via8233_pcmdxs_volume_control = {
static const struct snd_kcontrol_new snd_via8233_pcmdxs_volume_control = {
.name = "PCM Playback Volume",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
@ -1783,7 +1783,7 @@ static struct snd_kcontrol_new snd_via8233_pcmdxs_volume_control = {
.tlv = { .p = db_scale_dxs }
};
static struct snd_kcontrol_new snd_via8233_dxs_volume_control = {
static const struct snd_kcontrol_new snd_via8233_dxs_volume_control = {
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.device = 0,
/* .subdevice set later */

View file

@ -945,7 +945,7 @@ static int vx_mic_level_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v
return 0;
}
static struct snd_kcontrol_new vx_control_input_level = {
static const struct snd_kcontrol_new vx_control_input_level = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ),
@ -956,7 +956,7 @@ static struct snd_kcontrol_new vx_control_input_level = {
.tlv = { .p = db_scale_mic },
};
static struct snd_kcontrol_new vx_control_mic_level = {
static const struct snd_kcontrol_new vx_control_mic_level = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ),

View file

@ -1316,7 +1316,7 @@ static int snd_ymfpci_spdif_default_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_ymfpci_spdif_default =
static const struct snd_kcontrol_new snd_ymfpci_spdif_default =
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
@ -1344,7 +1344,7 @@ static int snd_ymfpci_spdif_mask_get(struct snd_kcontrol *kcontrol,
return 0;
}
static struct snd_kcontrol_new snd_ymfpci_spdif_mask =
static const struct snd_kcontrol_new snd_ymfpci_spdif_mask =
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
@ -1391,7 +1391,7 @@ static int snd_ymfpci_spdif_stream_put(struct snd_kcontrol *kcontrol,
return change;
}
static struct snd_kcontrol_new snd_ymfpci_spdif_stream =
static const struct snd_kcontrol_new snd_ymfpci_spdif_stream =
{
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
@ -1439,7 +1439,7 @@ static int snd_ymfpci_drec_source_put(struct snd_kcontrol *kcontrol, struct snd_
return reg != old_reg;
}
static struct snd_kcontrol_new snd_ymfpci_drec_source = {
static const struct snd_kcontrol_new snd_ymfpci_drec_source = {
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Direct Recording Source",
@ -1609,7 +1609,7 @@ static int snd_ymfpci_put_dup4ch(struct snd_kcontrol *kcontrol, struct snd_ctl_e
return change;
}
static struct snd_kcontrol_new snd_ymfpci_dup4ch = {
static const struct snd_kcontrol_new snd_ymfpci_dup4ch = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "4ch Duplication",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
@ -1712,7 +1712,7 @@ static int snd_ymfpci_gpio_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_
return 0;
}
static struct snd_kcontrol_new snd_ymfpci_rear_shared = {
static const struct snd_kcontrol_new snd_ymfpci_rear_shared = {
.name = "Shared Rear/Line-In Switch",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.info = snd_ymfpci_gpio_sw_info,
@ -1776,7 +1776,7 @@ static int snd_ymfpci_pcm_vol_put(struct snd_kcontrol *kcontrol,
return 0;
}
static struct snd_kcontrol_new snd_ymfpci_pcm_volume = {
static const struct snd_kcontrol_new snd_ymfpci_pcm_volume = {
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "PCM Playback Volume",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |

View file

@ -47,6 +47,7 @@ source "sound/soc/cirrus/Kconfig"
source "sound/soc/davinci/Kconfig"
source "sound/soc/dwc/Kconfig"
source "sound/soc/fsl/Kconfig"
source "sound/soc/hisilicon/Kconfig"
source "sound/soc/jz4740/Kconfig"
source "sound/soc/nuc900/Kconfig"
source "sound/soc/omap/Kconfig"
@ -63,6 +64,7 @@ source "sound/soc/sh/Kconfig"
source "sound/soc/sirf/Kconfig"
source "sound/soc/spear/Kconfig"
source "sound/soc/sti/Kconfig"
source "sound/soc/stm/Kconfig"
source "sound/soc/sunxi/Kconfig"
source "sound/soc/tegra/Kconfig"
source "sound/soc/txx9/Kconfig"

View file

@ -27,6 +27,7 @@ obj-$(CONFIG_SND_SOC) += cirrus/
obj-$(CONFIG_SND_SOC) += davinci/
obj-$(CONFIG_SND_SOC) += dwc/
obj-$(CONFIG_SND_SOC) += fsl/
obj-$(CONFIG_SND_SOC) += hisilicon/
obj-$(CONFIG_SND_SOC) += jz4740/
obj-$(CONFIG_SND_SOC) += img/
obj-$(CONFIG_SND_SOC) += intel/
@ -43,6 +44,7 @@ obj-$(CONFIG_SND_SOC) += sh/
obj-$(CONFIG_SND_SOC) += sirf/
obj-$(CONFIG_SND_SOC) += spear/
obj-$(CONFIG_SND_SOC) += sti/
obj-$(CONFIG_SND_SOC) += stm/
obj-$(CONFIG_SND_SOC) += sunxi/
obj-$(CONFIG_SND_SOC) += tegra/
obj-$(CONFIG_SND_SOC) += txx9/

View file

@ -119,7 +119,7 @@ static int bfin_eval_adau1373_codec_init(struct snd_soc_pcm_runtime *rtd)
return ret;
}
static struct snd_soc_ops bfin_eval_adau1373_ops = {
static const struct snd_soc_ops bfin_eval_adau1373_ops = {
.hw_params = bfin_eval_adau1373_hw_params,
};

View file

@ -64,7 +64,7 @@ static int bfin_eval_adav80x_codec_init(struct snd_soc_pcm_runtime *rtd)
return 0;
}
static struct snd_soc_ops bfin_eval_adav80x_ops = {
static const struct snd_soc_ops bfin_eval_adav80x_ops = {
.hw_params = bfin_eval_adav80x_hw_params,
};

View file

@ -49,6 +49,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_CS35L32 if I2C
select SND_SOC_CS35L33 if I2C
select SND_SOC_CS35L34 if I2C
select SND_SOC_CS35L35 if I2C
select SND_SOC_CS42L42 if I2C
select SND_SOC_CS42L51_I2C if I2C
select SND_SOC_CS42L52 if I2C && INPUT
@ -69,9 +70,11 @@ config SND_SOC_ALL_CODECS
select SND_SOC_DA7219 if I2C
select SND_SOC_DA732X if I2C
select SND_SOC_DA9055 if I2C
select SND_SOC_DIO2125
select SND_SOC_DMIC
select SND_SOC_ES8328_SPI if SPI_MASTER
select SND_SOC_ES8328_I2C if I2C
select SND_SOC_ES7134
select SND_SOC_GTM601
select SND_SOC_HDAC_HDMI
select SND_SOC_ICS43432
@ -89,6 +92,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_MAX9867 if I2C
select SND_SOC_MAX98925 if I2C
select SND_SOC_MAX98926 if I2C
select SND_SOC_MAX98927 if I2C
select SND_SOC_MAX9850 if I2C
select SND_SOC_MAX9860 if I2C
select SND_SOC_MAX9768 if I2C
@ -97,6 +101,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_ML26124 if I2C
select SND_SOC_NAU8540 if I2C
select SND_SOC_NAU8810 if I2C
select SND_SOC_NAU8824 if I2C
select SND_SOC_NAU8825 if I2C
select SND_SOC_HDMI_CODEC
select SND_SOC_PCM1681 if I2C
@ -303,12 +308,14 @@ config SND_SOC_ADAU1761
select SND_SOC_ADAU17X1
config SND_SOC_ADAU1761_I2C
tristate
tristate "Analog Devices AU1761 CODEC - I2C"
depends on I2C
select SND_SOC_ADAU1761
select REGMAP_I2C
config SND_SOC_ADAU1761_SPI
tristate
tristate "Analog Devices AU1761 CODEC - SPI"
depends on SPI
select SND_SOC_ADAU1761
select REGMAP_SPI
@ -408,6 +415,10 @@ config SND_SOC_CS35L34
tristate "Cirrus Logic CS35L34 CODEC"
depends on I2C
config SND_SOC_CS35L35
tristate "Cirrus Logic CS35L35 CODEC"
depends on I2C
config SND_SOC_CS42L42
tristate "Cirrus Logic CS42L42 CODEC"
depends on I2C
@ -516,6 +527,10 @@ config SND_SOC_DA732X
config SND_SOC_DA9055
tristate
config SND_SOC_DIO2125
tristate "Dioo DIO2125 Amplifier"
select GPIOLIB
config SND_SOC_DMIC
tristate
@ -525,6 +540,9 @@ config SND_SOC_HDMI_CODEC
select SND_PCM_IEC958
select HDMI
config SND_SOC_ES7134
tristate "Everest Semi ES7134 CODEC"
config SND_SOC_ES8328
tristate
@ -588,6 +606,10 @@ config SND_SOC_MAX98925
config SND_SOC_MAX98926
tristate
config SND_SOC_MAX98927
tristate "Maxim Integrated MAX98927 Speaker Amplifier"
depends on I2C
config SND_SOC_MAX9850
tristate
@ -1116,6 +1138,10 @@ config SND_SOC_NAU8810
tristate "Nuvoton Technology Corporation NAU88C10 CODEC"
depends on I2C
config SND_SOC_NAU8824
tristate "Nuvoton Technology Corporation NAU88L24 CODEC"
depends on I2C
config SND_SOC_NAU8825
tristate

View file

@ -39,6 +39,7 @@ snd-soc-cq93vc-objs := cq93vc.o
snd-soc-cs35l32-objs := cs35l32.o
snd-soc-cs35l33-objs := cs35l33.o
snd-soc-cs35l34-objs := cs35l34.o
snd-soc-cs35l35-objs := cs35l35.o
snd-soc-cs42l42-objs := cs42l42.o
snd-soc-cs42l51-objs := cs42l51.o
snd-soc-cs42l51-i2c-objs := cs42l51-i2c.o
@ -63,6 +64,7 @@ snd-soc-da7219-objs := da7219.o da7219-aad.o
snd-soc-da732x-objs := da732x.o
snd-soc-da9055-objs := da9055.o
snd-soc-dmic-objs := dmic.o
snd-soc-es7134-objs := es7134.o
snd-soc-es8328-objs := es8328.o
snd-soc-es8328-i2c-objs := es8328-i2c.o
snd-soc-es8328-spi-objs := es8328-spi.o
@ -84,6 +86,7 @@ snd-soc-max98371-objs := max98371.o
snd-soc-max9867-objs := max9867.o
snd-soc-max98925-objs := max98925.o
snd-soc-max98926-objs := max98926.o
snd-soc-max98927-objs := max98927.o
snd-soc-max9850-objs := max9850.o
snd-soc-max9860-objs := max9860.o
snd-soc-mc13783-objs := mc13783.o
@ -92,6 +95,7 @@ snd-soc-msm8916-analog-objs := msm8916-wcd-analog.o
snd-soc-msm8916-digital-objs := msm8916-wcd-digital.o
snd-soc-nau8540-objs := nau8540.o
snd-soc-nau8810-objs := nau8810.o
snd-soc-nau8824-objs := nau8824.o
snd-soc-nau8825-objs := nau8825.o
snd-soc-hdmi-codec-objs := hdmi-codec.o
snd-soc-pcm1681-objs := pcm1681.o
@ -221,6 +225,7 @@ snd-soc-wm9712-objs := wm9712.o
snd-soc-wm9713-objs := wm9713.o
snd-soc-wm-hubs-objs := wm_hubs.o
# Amp
snd-soc-dio2125-objs := dio2125.o
snd-soc-max9877-objs := max9877.o
snd-soc-max98504-objs := max98504.o
snd-soc-tpa6130a2-objs := tpa6130a2.o
@ -269,6 +274,7 @@ obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o
obj-$(CONFIG_SND_SOC_CS35L32) += snd-soc-cs35l32.o
obj-$(CONFIG_SND_SOC_CS35L33) += snd-soc-cs35l33.o
obj-$(CONFIG_SND_SOC_CS35L34) += snd-soc-cs35l34.o
obj-$(CONFIG_SND_SOC_CS35L35) += snd-soc-cs35l35.o
obj-$(CONFIG_SND_SOC_CS42L42) += snd-soc-cs42l42.o
obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o
obj-$(CONFIG_SND_SOC_CS42L51_I2C) += snd-soc-cs42l51-i2c.o
@ -293,6 +299,7 @@ obj-$(CONFIG_SND_SOC_DA7219) += snd-soc-da7219.o
obj-$(CONFIG_SND_SOC_DA732X) += snd-soc-da732x.o
obj-$(CONFIG_SND_SOC_DA9055) += snd-soc-da9055.o
obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o
obj-$(CONFIG_SND_SOC_ES7134) += snd-soc-es7134.o
obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o
obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o
obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o
@ -313,6 +320,7 @@ obj-$(CONFIG_SND_SOC_MAX98357A) += snd-soc-max98357a.o
obj-$(CONFIG_SND_SOC_MAX9867) += snd-soc-max9867.o
obj-$(CONFIG_SND_SOC_MAX98925) += snd-soc-max98925.o
obj-$(CONFIG_SND_SOC_MAX98926) += snd-soc-max98926.o
obj-$(CONFIG_SND_SOC_MAX98927) += snd-soc-max98927.o
obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o
obj-$(CONFIG_SND_SOC_MAX9860) += snd-soc-max9860.o
obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o
@ -321,6 +329,7 @@ obj-$(CONFIG_SND_SOC_MSM8916_WCD_ANALOG) +=snd-soc-msm8916-analog.o
obj-$(CONFIG_SND_SOC_MSM8916_WCD_DIGITAL) +=snd-soc-msm8916-digital.o
obj-$(CONFIG_SND_SOC_NAU8540) += snd-soc-nau8540.o
obj-$(CONFIG_SND_SOC_NAU8810) += snd-soc-nau8810.o
obj-$(CONFIG_SND_SOC_NAU8824) += snd-soc-nau8824.o
obj-$(CONFIG_SND_SOC_NAU8825) += snd-soc-nau8825.o
obj-$(CONFIG_SND_SOC_HDMI_CODEC) += snd-soc-hdmi-codec.o
obj-$(CONFIG_SND_SOC_PCM1681) += snd-soc-pcm1681.o
@ -448,6 +457,7 @@ obj-$(CONFIG_SND_SOC_WM_ADSP) += snd-soc-wm-adsp.o
obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o
# Amp
obj-$(CONFIG_SND_SOC_DIO2125) += snd-soc-dio2125.o
obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o
obj-$(CONFIG_SND_SOC_MAX98504) += snd-soc-max98504.o
obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o

View file

@ -75,6 +75,12 @@
#define DFS_DOUBLE_SPEED (1 << 2)
#define DFS_QUAD_SPEED (2 << 2)
/* ICTRL */
#define ICTRL_MASK (0x3)
/* OCTRL */
#define OCTRL_MASK (0x3F)
struct ak4613_formats {
unsigned int width;
unsigned int fmt;
@ -365,8 +371,8 @@ static int ak4613_dai_hw_params(struct snd_pcm_substream *substream,
snd_soc_update_bits(codec, CTRL1, FMT_MASK, fmt_ctrl);
snd_soc_update_bits(codec, CTRL2, DFS_MASK, ctrl2);
snd_soc_write(codec, ICTRL, priv->ic);
snd_soc_write(codec, OCTRL, priv->oc);
snd_soc_update_bits(codec, ICTRL, ICTRL_MASK, priv->ic);
snd_soc_update_bits(codec, OCTRL, OCTRL_MASK, priv->oc);
hw_params_end:
if (ret < 0)

1580
sound/soc/codecs/cs35l35.c Normal file

File diff suppressed because it is too large Load diff

294
sound/soc/codecs/cs35l35.h Normal file
View file

@ -0,0 +1,294 @@
/*
* cs35l35.h -- CS35L35 ALSA SoC audio driver
*
* Copyright 2016 Cirrus Logic, Inc.
*
* Author: Brian Austin <brian.austin@cirrus.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#ifndef __CS35L35_H__
#define __CS35L35_H__
#define CS35L35_FIRSTREG 0x01
#define CS35L35_LASTREG 0x7E
#define CS35L35_CHIP_ID 0x00035A35
#define CS35L35_DEVID_AB 0x01 /* Device ID A & B [RO] */
#define CS35L35_DEVID_CD 0x02 /* Device ID C & D [RO] */
#define CS35L35_DEVID_E 0x03 /* Device ID E [RO] */
#define CS35L35_FAB_ID 0x04 /* Fab ID [RO] */
#define CS35L35_REV_ID 0x05 /* Revision ID [RO] */
#define CS35L35_PWRCTL1 0x06 /* Power Ctl 1 */
#define CS35L35_PWRCTL2 0x07 /* Power Ctl 2 */
#define CS35L35_PWRCTL3 0x08 /* Power Ctl 3 */
#define CS35L35_CLK_CTL1 0x0A /* Clocking Ctl 1 */
#define CS35L35_CLK_CTL2 0x0B /* Clocking Ctl 2 */
#define CS35L35_CLK_CTL3 0x0C /* Clocking Ctl 3 */
#define CS35L35_SP_FMT_CTL1 0x0D /* Serial Port Format CTL1 */
#define CS35L35_SP_FMT_CTL2 0x0E /* Serial Port Format CTL2 */
#define CS35L35_SP_FMT_CTL3 0x0F /* Serial Port Format CTL3 */
#define CS35L35_MAG_COMP_CTL 0x13 /* Magnitude Comp CTL */
#define CS35L35_AMP_INP_DRV_CTL 0x14 /* Amp Input Drive Ctl */
#define CS35L35_AMP_DIG_VOL_CTL 0x15 /* Amplifier Dig Volume Ctl */
#define CS35L35_AMP_DIG_VOL 0x16 /* Amplifier Dig Volume */
#define CS35L35_ADV_DIG_VOL 0x17 /* Advisory Digital Volume */
#define CS35L35_PROTECT_CTL 0x18 /* Amp Gain - Prot Ctl Param */
#define CS35L35_AMP_GAIN_AUD_CTL 0x19 /* Amp Serial Port Gain Ctl */
#define CS35L35_AMP_GAIN_PDM_CTL 0x1A /* Amplifier Gain PDM Ctl */
#define CS35L35_AMP_GAIN_ADV_CTL 0x1B /* Amplifier Gain Ctl */
#define CS35L35_GPI_CTL 0x1C /* GPI Ctl */
#define CS35L35_BST_CVTR_V_CTL 0x1D /* Boost Conv Voltage Ctl */
#define CS35L35_BST_PEAK_I 0x1E /* Boost Conv Peak Current */
#define CS35L35_BST_RAMP_CTL 0x20 /* Boost Conv Soft Ramp Ctl */
#define CS35L35_BST_CONV_COEF_1 0x21 /* Boost Conv Coefficients 1 */
#define CS35L35_BST_CONV_COEF_2 0x22 /* Boost Conv Coefficients 2 */
#define CS35L35_BST_CONV_SLOPE_COMP 0x23 /* Boost Conv Slope Comp */
#define CS35L35_BST_CONV_SW_FREQ 0x24 /* Boost Conv L BST SW Freq */
#define CS35L35_CLASS_H_CTL 0x30 /* CLS H Control */
#define CS35L35_CLASS_H_HEADRM_CTL 0x31 /* CLS H Headroom Ctl */
#define CS35L35_CLASS_H_RELEASE_RATE 0x32 /* CLS H Release Rate */
#define CS35L35_CLASS_H_FET_DRIVE_CTL 0x33 /* CLS H Weak FET Drive Ctl */
#define CS35L35_CLASS_H_VP_CTL 0x34 /* CLS H VP Ctl */
#define CS35L35_CLASS_H_STATUS 0x38 /* CLS H Status */
#define CS35L35_VPBR_CTL 0x3A /* VPBR Ctl */
#define CS35L35_VPBR_VOL_CTL 0x3B /* VPBR Volume Ctl */
#define CS35L35_VPBR_TIMING_CTL 0x3C /* VPBR Timing Ctl */
#define CS35L35_VPBR_MODE_VOL_CTL 0x3D /* VPBR Mode/Attack Vol Ctl */
#define CS35L35_VPBR_ATTEN_STATUS 0x4B /* VPBR Attenuation Status */
#define CS35L35_SPKR_MON_CTL 0x4E /* Speaker Monitoring Ctl */
#define CS35L35_IMON_SCALE_CTL 0x51 /* IMON Scale Ctl */
#define CS35L35_AUDIN_RXLOC_CTL 0x52 /* Audio Input RX Loc Ctl */
#define CS35L35_ADVIN_RXLOC_CTL 0x53 /* Advisory Input RX Loc Ctl */
#define CS35L35_VMON_TXLOC_CTL 0x54 /* VMON TX Loc Ctl */
#define CS35L35_IMON_TXLOC_CTL 0x55 /* IMON TX Loc Ctl */
#define CS35L35_VPMON_TXLOC_CTL 0x56 /* VPMON TX Loc Ctl */
#define CS35L35_VBSTMON_TXLOC_CTL 0x57 /* VBSTMON TX Loc Ctl */
#define CS35L35_VPBR_STATUS_TXLOC_CTL 0x58 /* VPBR Status TX Loc Ctl */
#define CS35L35_ZERO_FILL_LOC_CTL 0x59 /* Zero Fill Loc Ctl */
#define CS35L35_AUDIN_DEPTH_CTL 0x5A /* Audio Input Depth Ctl */
#define CS35L35_SPKMON_DEPTH_CTL 0x5B /* SPK Mon Output Depth Ctl */
#define CS35L35_SUPMON_DEPTH_CTL 0x5C /* Supply Mon Out Depth Ctl */
#define CS35L35_ZEROFILL_DEPTH_CTL 0x5D /* Zero Fill Mon Output Ctl */
#define CS35L35_MULT_DEV_SYNCH1 0x62 /* Multidevice Synch */
#define CS35L35_MULT_DEV_SYNCH2 0x63 /* Multidevice Synch 2 */
#define CS35L35_PROT_RELEASE_CTL 0x64 /* Protection Release Ctl */
#define CS35L35_DIAG_MODE_REG_LOCK 0x68 /* Diagnostic Mode Reg Lock */
#define CS35L35_DIAG_MODE_CTL_1 0x69 /* Diagnostic Mode Ctl 1 */
#define CS35L35_DIAG_MODE_CTL_2 0x6A /* Diagnostic Mode Ctl 2 */
#define CS35L35_INT_MASK_1 0x70 /* Interrupt Mask 1 */
#define CS35L35_INT_MASK_2 0x71 /* Interrupt Mask 2 */
#define CS35L35_INT_MASK_3 0x72 /* Interrupt Mask 3 */
#define CS35L35_INT_MASK_4 0x73 /* Interrupt Mask 4 */
#define CS35L35_INT_STATUS_1 0x74 /* Interrupt Status 1 */
#define CS35L35_INT_STATUS_2 0x75 /* Interrupt Status 2 */
#define CS35L35_INT_STATUS_3 0x76 /* Interrupt Status 3 */
#define CS35L35_INT_STATUS_4 0x77 /* Interrupt Status 4 */
#define CS35L35_PLL_STATUS 0x78 /* PLL Status */
#define CS35L35_OTP_TRIM_STATUS 0x7E /* OTP Trim Status */
#define CS35L35_MAX_REGISTER 0x7F
/* CS35L35_PWRCTL1 */
#define CS35L35_SFT_RST 0x80
#define CS35L35_DISCHG_FLT 0x02
#define CS35L35_PDN_ALL 0x01
/* CS35L35_PWRCTL2 */
#define CS35L35_PDN_VMON 0x80
#define CS35L35_PDN_IMON 0x40
#define CS35L35_PDN_CLASSH 0x20
#define CS35L35_PDN_VPBR 0x10
#define CS35L35_PDN_BST 0x04
#define CS35L35_PDN_AMP 0x01
/* CS35L35_PWRCTL3 */
#define CS35L35_PDN_VBSTMON_OUT 0x10
#define CS35L35_PDN_VMON_OUT 0x08
#define CS35L35_AUDIN_DEPTH_MASK 0x03
#define CS35L35_AUDIN_DEPTH_SHIFT 0
#define CS35L35_ADVIN_DEPTH_MASK 0x0C
#define CS35L35_ADVIN_DEPTH_SHIFT 2
#define CS35L35_SDIN_DEPTH_8 0x01
#define CS35L35_SDIN_DEPTH_16 0x02
#define CS35L35_SDIN_DEPTH_24 0x03
#define CS35L35_SDOUT_DEPTH_8 0x01
#define CS35L35_SDOUT_DEPTH_12 0x02
#define CS35L35_SDOUT_DEPTH_16 0x03
#define CS35L35_AUD_IN_LR_MASK 0x80
#define CS35L35_AUD_IN_LR_SHIFT 7
#define CS35L35_ADV_IN_LR_MASK 0x80
#define CS35L35_ADV_IN_LR_SHIFT 7
#define CS35L35_AUD_IN_LOC_MASK 0x0F
#define CS35L35_AUD_IN_LOC_SHIFT 0
#define CS35L35_ADV_IN_LOC_MASK 0x0F
#define CS35L35_ADV_IN_LOC_SHIFT 0
#define CS35L35_IMON_DEPTH_MASK 0x03
#define CS35L35_IMON_DEPTH_SHIFT 0
#define CS35L35_VMON_DEPTH_MASK 0x0C
#define CS35L35_VMON_DEPTH_SHIFT 2
#define CS35L35_VBSTMON_DEPTH_MASK 0x03
#define CS35L35_VBSTMON_DEPTH_SHIFT 0
#define CS35L35_VPMON_DEPTH_MASK 0x0C
#define CS35L35_VPMON_DEPTH_SHIFT 2
#define CS35L35_VPBRSTAT_DEPTH_MASK 0x30
#define CS35L35_VPBRSTAT_DEPTH_SHIFT 4
#define CS35L35_ZEROFILL_DEPTH_MASK 0x03
#define CS35L35_ZEROFILL_DEPTH_SHIFT 0x00
#define CS35L35_MON_TXLOC_MASK 0x3F
#define CS35L35_MON_TXLOC_SHIFT 0
#define CS35L35_MON_FRM_MASK 0x80
#define CS35L35_MON_FRM_SHIFT 7
#define CS35L35_IMON_SCALE_MASK 0xF8
#define CS35L35_IMON_SCALE_SHIFT 3
#define CS35L35_MS_MASK 0x80
#define CS35L35_MS_SHIFT 7
#define CS35L35_SPMODE_MASK 0x40
#define CS35L35_SP_DRV_MASK 0x10
#define CS35L35_SP_DRV_SHIFT 4
#define CS35L35_CLK_CTL2_MASK 0xFF
#define CS35L35_PDM_MODE_MASK 0x40
#define CS35L35_PDM_MODE_SHIFT 6
#define CS35L35_CLK_SOURCE_MASK 0x03
#define CS35L35_CLK_SOURCE_SHIFT 0
#define CS35L35_CLK_SOURCE_MCLK 0
#define CS35L35_CLK_SOURCE_SCLK 1
#define CS35L35_CLK_SOURCE_PDM 2
#define CS35L35_SP_SCLKS_MASK 0x0F
#define CS35L35_SP_SCLKS_SHIFT 0x00
#define CS35L35_SP_SCLKS_16FS 0x03
#define CS35L35_SP_SCLKS_32FS 0x07
#define CS35L35_SP_SCLKS_48FS 0x0B
#define CS35L35_SP_SCLKS_64FS 0x0F
#define CS35L35_SP_RATE_MASK 0xC0
#define CS35L35_PDN_BST_MASK 0x06
#define CS35L35_PDN_BST_FETON_SHIFT 1
#define CS35L35_PDN_BST_FETOFF_SHIFT 2
#define CS35L35_PWR2_PDN_MASK 0xE0
#define CS35L35_PWR3_PDN_MASK 0x1E
#define CS35L35_PDN_ALL_MASK 0x01
#define CS35L35_DISCHG_FILT_MASK 0x02
#define CS35L35_DISCHG_FILT_SHIFT 1
#define CS35L35_MCLK_DIS_MASK 0x04
#define CS35L35_MCLK_DIS_SHIFT 2
#define CS35L35_BST_CTL_MASK 0x7F
#define CS35L35_BST_CTL_SHIFT 0
#define CS35L35_BST_IPK_MASK 0x1F
#define CS35L35_BST_IPK_SHIFT 0
#define CS35L35_AMP_MUTE_MASK 0x20
#define CS35L35_AMP_MUTE_SHIFT 5
#define CS35L35_AMP_GAIN_ZC_MASK 0x10
#define CS35L35_AMP_GAIN_ZC_SHIFT 4
#define CS35L35_AMP_DIGSFT_MASK 0x02
#define CS35L35_AMP_DIGSFT_SHIFT 1
/* CS35L35_SP_FMT_CTL3 */
#define CS35L35_SP_I2S_DRV_MASK 0x03
#define CS35L35_SP_I2S_DRV_SHIFT 0
/* Class H Algorithm Control */
#define CS35L35_CH_STEREO_MASK 0x40
#define CS35L35_CH_STEREO_SHIFT 6
#define CS35L35_CH_BST_OVR_MASK 0x04
#define CS35L35_CH_BST_OVR_SHIFT 2
#define CS35L35_CH_BST_LIM_MASK 0x08
#define CS35L35_CH_BST_LIM_SHIFT 3
#define CS35L35_CH_MEM_DEPTH_MASK 0x01
#define CS35L35_CH_MEM_DEPTH_SHIFT 0
#define CS35L35_CH_HDRM_CTL_MASK 0x3F
#define CS35L35_CH_HDRM_CTL_SHIFT 0
#define CS35L35_CH_REL_RATE_MASK 0xFF
#define CS35L35_CH_REL_RATE_SHIFT 0
#define CS35L35_CH_WKFET_DIS_MASK 0x80
#define CS35L35_CH_WKFET_DIS_SHIFT 7
#define CS35L35_CH_WKFET_DEL_MASK 0x70
#define CS35L35_CH_WKFET_DEL_SHIFT 4
#define CS35L35_CH_WKFET_THLD_MASK 0x0F
#define CS35L35_CH_WKFET_THLD_SHIFT 0
#define CS35L35_CH_VP_AUTO_MASK 0x80
#define CS35L35_CH_VP_AUTO_SHIFT 7
#define CS35L35_CH_VP_RATE_MASK 0x60
#define CS35L35_CH_VP_RATE_SHIFT 5
#define CS35L35_CH_VP_MAN_MASK 0x1F
#define CS35L35_CH_VP_MAN_SHIFT 0
/* CS35L35_PROT_RELEASE_CTL */
#define CS35L35_CAL_ERR_RLS 0x80
#define CS35L35_SHORT_RLS 0x04
#define CS35L35_OTW_RLS 0x02
#define CS35L35_OTE_RLS 0x01
/* INT Mask Registers */
#define CS35L35_INT1_CRIT_MASK 0x38
#define CS35L35_INT2_CRIT_MASK 0xEF
#define CS35L35_INT3_CRIT_MASK 0xEE
#define CS35L35_INT4_CRIT_MASK 0xFF
/* PDN DONE Masks */
#define CS35L35_M_PDN_DONE_SHIFT 4
#define CS35L35_M_PDN_DONE_MASK 0x10
/* CS35L35_INT_1 */
#define CS35L35_CAL_ERR 0x80
#define CS35L35_OTP_ERR 0x40
#define CS35L35_LRCLK_ERR 0x20
#define CS35L35_SPCLK_ERR 0x10
#define CS35L35_MCLK_ERR 0x08
#define CS35L35_AMP_SHORT 0x04
#define CS35L35_OTW 0x02
#define CS35L35_OTE 0x01
/* CS35L35_INT_2 */
#define CS35L35_PDN_DONE 0x10
#define CS35L35_VPBR_ERR 0x02
#define CS35L35_VPBR_CLR 0x01
/* CS35L35_INT_3 */
#define CS35L35_BST_HIGH 0x10
#define CS35L35_BST_HIGH_FLAG 0x08
#define CS35L35_BST_IPK_FLAG 0x04
#define CS35L35_LBST_SHORT 0x01
/* CS35L35_INT_4 */
#define CS35L35_VMON_OVFL 0x08
#define CS35L35_IMON_OVFL 0x04
#define CS35L35_FORMATS (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
struct cs35l35_private {
struct device *dev;
struct cs35l35_platform_data pdata;
struct regmap *regmap;
struct regulator_bulk_data supplies[2];
int num_supplies;
int sysclk;
int sclk;
bool pdm_mode;
bool i2s_mode;
bool slave_mode;
/* GPIO for /RST */
struct gpio_desc *reset_gpio;
struct completion pdn_done;
};
static const char * const cs35l35_supplies[] = {
"VA",
"VP",
};
#endif

View file

@ -498,7 +498,7 @@ static int cs4271_reset(struct snd_soc_codec *codec)
struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
if (gpio_is_valid(cs4271->gpio_nreset)) {
gpio_set_value(cs4271->gpio_nreset, 0);
gpio_direction_output(cs4271->gpio_nreset, 0);
mdelay(1);
gpio_set_value(cs4271->gpio_nreset, 1);
mdelay(1);

View file

@ -1130,6 +1130,7 @@ MODULE_DEVICE_TABLE(i2c, cs53l30_id);
static struct i2c_driver cs53l30_i2c_driver = {
.driver = {
.name = "cs53l30",
.of_match_table = cs53l30_of_match,
.pm = &cs53l30_runtime_pm,
},
.id_table = cs53l30_id,

View file

@ -12,6 +12,7 @@
* option) any later version.
*/
#include <linux/acpi.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/i2c.h>
@ -1528,12 +1529,23 @@ static int da7213_set_bias_level(struct snd_soc_codec *codec,
return 0;
}
#if defined(CONFIG_OF)
/* DT */
static const struct of_device_id da7213_of_match[] = {
{ .compatible = "dlg,da7213", },
{ }
};
MODULE_DEVICE_TABLE(of, da7213_of_match);
#endif
#ifdef CONFIG_ACPI
static const struct acpi_device_id da7213_acpi_match[] = {
{ "DLGS7212", 0},
{ "DLGS7213", 0},
{ },
};
MODULE_DEVICE_TABLE(acpi, da7213_acpi_match);
#endif
static enum da7213_micbias_voltage
da7213_of_micbias_lvl(struct snd_soc_codec *codec, u32 val)
@ -1844,6 +1856,7 @@ static struct i2c_driver da7213_i2c_driver = {
.driver = {
.name = "da7213",
.of_match_table = of_match_ptr(da7213_of_match),
.acpi_match_table = ACPI_PTR(da7213_acpi_match),
},
.probe = da7213_i2c_probe,
.remove = da7213_remove,

120
sound/soc/codecs/dio2125.c Normal file
View file

@ -0,0 +1,120 @@
/*
* Copyright (c) 2017 BayLibre, SAS.
* Author: Jerome Brunet <jbrunet@baylibre.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
* The full GNU General Public License is included in this distribution
* in the file called COPYING.
*/
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <sound/soc.h>
#define DRV_NAME "dio2125"
struct dio2125 {
struct gpio_desc *gpiod_enable;
};
static int drv_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *control, int event)
{
struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
struct dio2125 *priv = snd_soc_component_get_drvdata(c);
int val;
switch (event) {
case SND_SOC_DAPM_POST_PMU:
val = 1;
break;
case SND_SOC_DAPM_PRE_PMD:
val = 0;
break;
default:
WARN(1, "Unexpected event");
return -EINVAL;
}
gpiod_set_value_cansleep(priv->gpiod_enable, val);
return 0;
}
static const struct snd_soc_dapm_widget dio2125_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("INL"),
SND_SOC_DAPM_INPUT("INR"),
SND_SOC_DAPM_OUT_DRV_E("DRV", SND_SOC_NOPM, 0, 0, NULL, 0, drv_event,
(SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD)),
SND_SOC_DAPM_OUTPUT("OUTL"),
SND_SOC_DAPM_OUTPUT("OUTR"),
};
static const struct snd_soc_dapm_route dio2125_dapm_routes[] = {
{ "DRV", NULL, "INL" },
{ "DRV", NULL, "INR" },
{ "OUTL", NULL, "DRV" },
{ "OUTR", NULL, "DRV" },
};
static const struct snd_soc_component_driver dio2125_component_driver = {
.dapm_widgets = dio2125_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(dio2125_dapm_widgets),
.dapm_routes = dio2125_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(dio2125_dapm_routes),
};
static int dio2125_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct dio2125 *priv;
int err;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (priv == NULL)
return -ENOMEM;
platform_set_drvdata(pdev, priv);
priv->gpiod_enable = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
if (IS_ERR(priv->gpiod_enable)) {
err = PTR_ERR(priv->gpiod_enable);
if (err != -EPROBE_DEFER)
dev_err(dev, "Failed to get 'enable' gpio: %d", err);
return err;
}
return devm_snd_soc_register_component(dev, &dio2125_component_driver,
NULL, 0);
}
#ifdef CONFIG_OF
static const struct of_device_id dio2125_ids[] = {
{ .compatible = "dioo,dio2125", },
{ }
};
MODULE_DEVICE_TABLE(of, dio2125_ids);
#endif
static struct platform_driver dio2125_driver = {
.driver = {
.name = DRV_NAME,
.of_match_table = of_match_ptr(dio2125_ids),
},
.probe = dio2125_probe,
};
module_platform_driver(dio2125_driver);
MODULE_DESCRIPTION("ASoC DIO2125 output driver");
MODULE_AUTHOR("Jerome Brunet <[email protected]>");
MODULE_LICENSE("GPL");

Some files were not shown because too many files have changed in this diff Show more