summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
Diffstat (limited to 'sound')
-rw-r--r--sound/aoa/codecs/onyx.c2
-rw-r--r--sound/atmel/ac97c.c15
-rw-r--r--sound/core/pcm_dmaengine.c6
-rw-r--r--sound/core/pcm_lib.c2
-rw-r--r--sound/core/seq/seq_clientmgr.c36
-rw-r--r--sound/core/seq/seq_fifo.c2
-rw-r--r--sound/core/seq/seq_midi.c4
-rw-r--r--sound/core/timer.c4
-rw-r--r--sound/firewire/Kconfig63
-rw-r--r--sound/firewire/Makefile2
-rw-r--r--sound/firewire/amdtp.c797
-rw-r--r--sound/firewire/amdtp.h200
-rw-r--r--sound/firewire/bebob/Makefile4
-rw-r--r--sound/firewire/bebob/bebob.c471
-rw-r--r--sound/firewire/bebob/bebob.h255
-rw-r--r--sound/firewire/bebob/bebob_command.c282
-rw-r--r--sound/firewire/bebob/bebob_focusrite.c279
-rw-r--r--sound/firewire/bebob/bebob_hwdep.c199
-rw-r--r--sound/firewire/bebob/bebob_maudio.c792
-rw-r--r--sound/firewire/bebob/bebob_midi.c168
-rw-r--r--sound/firewire/bebob/bebob_pcm.c378
-rw-r--r--sound/firewire/bebob/bebob_proc.c196
-rw-r--r--sound/firewire/bebob/bebob_stream.c1021
-rw-r--r--sound/firewire/bebob/bebob_terratec.c68
-rw-r--r--sound/firewire/bebob/bebob_yamaha.c50
-rw-r--r--sound/firewire/cmp.c205
-rw-r--r--sound/firewire/cmp.h14
-rw-r--r--sound/firewire/dice.c88
-rw-r--r--sound/firewire/fcp.c191
-rw-r--r--sound/firewire/fcp.h21
-rw-r--r--sound/firewire/fireworks/Makefile4
-rw-r--r--sound/firewire/fireworks/fireworks.c352
-rw-r--r--sound/firewire/fireworks/fireworks.h232
-rw-r--r--sound/firewire/fireworks/fireworks_command.c372
-rw-r--r--sound/firewire/fireworks/fireworks_hwdep.c298
-rw-r--r--sound/firewire/fireworks/fireworks_midi.c168
-rw-r--r--sound/firewire/fireworks/fireworks_pcm.c403
-rw-r--r--sound/firewire/fireworks/fireworks_proc.c232
-rw-r--r--sound/firewire/fireworks/fireworks_stream.c372
-rw-r--r--sound/firewire/fireworks/fireworks_transaction.c326
-rw-r--r--sound/firewire/speakers.c100
-rw-r--r--sound/isa/gus/interwave.c6
-rw-r--r--sound/isa/sb/sb_mixer.c14
-rw-r--r--sound/mips/au1x00.c6
-rw-r--r--sound/oss/mpu401.c4
-rw-r--r--sound/oss/swarm_cs4297a.c4
-rw-r--r--sound/pci/bt87x.c4
-rw-r--r--sound/pci/fm801.c226
-rw-r--r--sound/pci/hda/Kconfig15
-rw-r--r--sound/pci/hda/Makefile2
-rw-r--r--sound/pci/hda/hda_auto_parser.c46
-rw-r--r--sound/pci/hda/hda_codec.h1
-rw-r--r--sound/pci/hda/hda_generic.c2
-rw-r--r--sound/pci/hda/hda_intel.c13
-rw-r--r--sound/pci/hda/hda_local.h14
-rw-r--r--sound/pci/hda/hda_tegra.c588
-rw-r--r--sound/pci/hda/patch_analog.c1
-rw-r--r--sound/pci/hda/patch_hdmi.c18
-rw-r--r--sound/pci/hda/patch_realtek.c562
-rw-r--r--sound/pci/hda/patch_sigmatel.c4
-rw-r--r--sound/pci/intel8x0.c10
-rw-r--r--sound/pci/lola/lola_proc.c2
-rw-r--r--sound/pci/lx6464es/lx_core.c46
-rw-r--r--sound/soc/atmel/sam9g20_wm8731.c1
-rw-r--r--sound/soc/codecs/Kconfig12
-rw-r--r--sound/soc/codecs/Makefile4
-rw-r--r--sound/soc/codecs/sigmadsp-i2c.c35
-rw-r--r--sound/soc/codecs/sigmadsp-regmap.c36
-rw-r--r--sound/soc/codecs/sigmadsp.c65
-rw-r--r--sound/soc/codecs/sigmadsp.h20
-rw-r--r--sound/soc/fsl/fsl_dma.c4
-rw-r--r--sound/soc/fsl/fsl_spdif.c6
-rw-r--r--sound/soc/omap/ams-delta.c2
-rw-r--r--sound/soc/omap/omap-twl4030.c3
-rw-r--r--sound/soc/omap/rx51.c2
-rw-r--r--sound/soc/pxa/Kconfig11
-rw-r--r--sound/soc/pxa/hx4700.c2
-rw-r--r--sound/soc/samsung/h1940_uda1380.c2
-rw-r--r--sound/soc/samsung/rx1950_uda1380.c4
-rw-r--r--sound/soc/samsung/smartq_wm8987.c2
-rw-r--r--sound/soc/soc-dapm.c29
-rw-r--r--sound/synth/emux/soundfont.c1
-rw-r--r--sound/usb/Kconfig13
-rw-r--r--sound/usb/Makefile2
-rw-r--r--sound/usb/bcd2000/Makefile3
-rw-r--r--sound/usb/bcd2000/bcd2000.c461
-rw-r--r--sound/usb/mixer.c411
87 files changed, 10421 insertions, 972 deletions
diff --git a/sound/aoa/codecs/onyx.c b/sound/aoa/codecs/onyx.c
index f01bffb702bc..401107b85d30 100644
--- a/sound/aoa/codecs/onyx.c
+++ b/sound/aoa/codecs/onyx.c
@@ -241,7 +241,7 @@ static struct snd_kcontrol_new inputgain_control = {
static int onyx_snd_capture_source_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
- static char *texts[] = { "Line-In", "Microphone" };
+ static const char * const texts[] = { "Line-In", "Microphone" };
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
diff --git a/sound/atmel/ac97c.c b/sound/atmel/ac97c.c
index 05ec049c9faf..a04d23174dc2 100644
--- a/sound/atmel/ac97c.c
+++ b/sound/atmel/ac97c.c
@@ -1198,6 +1198,7 @@ static int atmel_ac97c_remove(struct platform_device *pdev)
}
static struct platform_driver atmel_ac97c_driver = {
+ .probe = atmel_ac97c_probe,
.remove = atmel_ac97c_remove,
.driver = {
.name = "atmel_ac97c",
@@ -1205,19 +1206,7 @@ static struct platform_driver atmel_ac97c_driver = {
.pm = ATMEL_AC97C_PM_OPS,
},
};
-
-static int __init atmel_ac97c_init(void)
-{
- return platform_driver_probe(&atmel_ac97c_driver,
- atmel_ac97c_probe);
-}
-module_init(atmel_ac97c_init);
-
-static void __exit atmel_ac97c_exit(void)
-{
- platform_driver_unregister(&atmel_ac97c_driver);
-}
-module_exit(atmel_ac97c_exit);
+module_platform_driver(atmel_ac97c_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Driver for Atmel AC97 controller");
diff --git a/sound/core/pcm_dmaengine.c b/sound/core/pcm_dmaengine.c
index 94d08733cb38..76cbb9ec953a 100644
--- a/sound/core/pcm_dmaengine.c
+++ b/sound/core/pcm_dmaengine.c
@@ -182,6 +182,7 @@ static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream)
int snd_dmaengine_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
int ret;
switch (cmd) {
@@ -196,6 +197,11 @@ int snd_dmaengine_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
dmaengine_resume(prtd->dma_chan);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (runtime->info & SNDRV_PCM_INFO_PAUSE)
+ dmaengine_pause(prtd->dma_chan);
+ else
+ dmaengine_terminate_all(prtd->dma_chan);
+ break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
dmaengine_pause(prtd->dma_chan);
break;
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
index ce83def9f43b..9acc77eae487 100644
--- a/sound/core/pcm_lib.c
+++ b/sound/core/pcm_lib.c
@@ -345,7 +345,7 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
snd_pcm_debug_name(substream, name, sizeof(name));
xrun_log_show(substream);
pcm_err(substream->pcm,
- "BUG: %s, pos = %ld, buffer size = %ld, period size = %ld\n",
+ "XRUN: %s, pos = %ld, buffer size = %ld, period size = %ld\n",
name, pos, runtime->buffer_size,
runtime->period_size);
}
diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c
index 9ca5e647e54b..225c73152ee9 100644
--- a/sound/core/seq/seq_clientmgr.c
+++ b/sound/core/seq/seq_clientmgr.c
@@ -660,7 +660,7 @@ static int deliver_to_subscribers(struct snd_seq_client *client,
int atomic, int hop)
{
struct snd_seq_subscribers *subs;
- int err = 0, num_ev = 0;
+ int err, result = 0, num_ev = 0;
struct snd_seq_event event_saved;
struct snd_seq_client_port *src_port;
struct snd_seq_port_subs_info *grp;
@@ -685,8 +685,12 @@ static int deliver_to_subscribers(struct snd_seq_client *client,
subs->info.flags & SNDRV_SEQ_PORT_SUBS_TIME_REAL);
err = snd_seq_deliver_single_event(client, event,
0, atomic, hop);
- if (err < 0)
- break;
+ if (err < 0) {
+ /* save first error that occurs and continue */
+ if (!result)
+ result = err;
+ continue;
+ }
num_ev++;
/* restore original event record */
*event = event_saved;
@@ -697,7 +701,7 @@ static int deliver_to_subscribers(struct snd_seq_client *client,
up_read(&grp->list_mutex);
*event = event_saved; /* restore */
snd_seq_port_unlock(src_port);
- return (err < 0) ? err : num_ev;
+ return (result < 0) ? result : num_ev;
}
@@ -709,7 +713,7 @@ static int port_broadcast_event(struct snd_seq_client *client,
struct snd_seq_event *event,
int atomic, int hop)
{
- int num_ev = 0, err = 0;
+ int num_ev = 0, err, result = 0;
struct snd_seq_client *dest_client;
struct snd_seq_client_port *port;
@@ -724,14 +728,18 @@ static int port_broadcast_event(struct snd_seq_client *client,
err = snd_seq_deliver_single_event(NULL, event,
SNDRV_SEQ_FILTER_BROADCAST,
atomic, hop);
- if (err < 0)
- break;
+ if (err < 0) {
+ /* save first error that occurs and continue */
+ if (!result)
+ result = err;
+ continue;
+ }
num_ev++;
}
read_unlock(&dest_client->ports_lock);
snd_seq_client_unlock(dest_client);
event->dest.port = SNDRV_SEQ_ADDRESS_BROADCAST; /* restore */
- return (err < 0) ? err : num_ev;
+ return (result < 0) ? result : num_ev;
}
/*
@@ -741,7 +749,7 @@ static int port_broadcast_event(struct snd_seq_client *client,
static int broadcast_event(struct snd_seq_client *client,
struct snd_seq_event *event, int atomic, int hop)
{
- int err = 0, num_ev = 0;
+ int err, result = 0, num_ev = 0;
int dest;
struct snd_seq_addr addr;
@@ -760,12 +768,16 @@ static int broadcast_event(struct snd_seq_client *client,
err = snd_seq_deliver_single_event(NULL, event,
SNDRV_SEQ_FILTER_BROADCAST,
atomic, hop);
- if (err < 0)
- break;
+ if (err < 0) {
+ /* save first error that occurs and continue */
+ if (!result)
+ result = err;
+ continue;
+ }
num_ev += err;
}
event->dest = addr; /* restore */
- return (err < 0) ? err : num_ev;
+ return (result < 0) ? result : num_ev;
}
diff --git a/sound/core/seq/seq_fifo.c b/sound/core/seq/seq_fifo.c
index 559989992bef..53a403e17c5b 100644
--- a/sound/core/seq/seq_fifo.c
+++ b/sound/core/seq/seq_fifo.c
@@ -124,7 +124,7 @@ int snd_seq_fifo_event_in(struct snd_seq_fifo *f,
snd_use_lock_use(&f->use_lock);
err = snd_seq_event_dup(f->pool, event, &cell, 1, NULL); /* always non-blocking */
if (err < 0) {
- if (err == -ENOMEM)
+ if ((err == -ENOMEM) || (err == -EAGAIN))
atomic_inc(&f->overflow);
snd_use_lock_free(&f->use_lock);
return err;
diff --git a/sound/core/seq/seq_midi.c b/sound/core/seq/seq_midi.c
index 3e05c55a2880..a1fd77af6059 100644
--- a/sound/core/seq/seq_midi.c
+++ b/sound/core/seq/seq_midi.c
@@ -362,13 +362,13 @@ snd_seq_midisynth_register_port(struct snd_seq_device *dev)
if (! port->name[0]) {
if (info->name[0]) {
if (ports > 1)
- snprintf(port->name, sizeof(port->name), "%s-%d", info->name, p);
+ snprintf(port->name, sizeof(port->name), "%s-%u", info->name, p);
else
snprintf(port->name, sizeof(port->name), "%s", info->name);
} else {
/* last resort */
if (ports > 1)
- sprintf(port->name, "MIDI %d-%d-%d", card->number, device, p);
+ sprintf(port->name, "MIDI %d-%d-%u", card->number, device, p);
else
sprintf(port->name, "MIDI %d-%d", card->number, device);
}
diff --git a/sound/core/timer.c b/sound/core/timer.c
index cfd455a8ac1a..777a45e08e53 100644
--- a/sound/core/timer.c
+++ b/sound/core/timer.c
@@ -390,7 +390,7 @@ static void snd_timer_notify1(struct snd_timer_instance *ti, int event)
struct timespec tstamp;
if (timer_tstamp_monotonic)
- do_posix_clock_monotonic_gettime(&tstamp);
+ ktime_get_ts(&tstamp);
else
getnstimeofday(&tstamp);
if (snd_BUG_ON(event < SNDRV_TIMER_EVENT_START ||
@@ -1203,7 +1203,7 @@ static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri,
}
if (tu->last_resolution != resolution || ticks > 0) {
if (timer_tstamp_monotonic)
- do_posix_clock_monotonic_gettime(&tstamp);
+ ktime_get_ts(&tstamp);
else
getnstimeofday(&tstamp);
}
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig
index b3e274fe4a77..775ef2efc296 100644
--- a/sound/firewire/Kconfig
+++ b/sound/firewire/Kconfig
@@ -9,12 +9,12 @@ if SND_FIREWIRE && FIREWIRE
config SND_FIREWIRE_LIB
tristate
- depends on SND_PCM
+ select SND_PCM
+ select SND_RAWMIDI
config SND_DICE
tristate "DICE-based DACs (EXPERIMENTAL)"
select SND_HWDEP
- select SND_PCM
select SND_FIREWIRE_LIB
help
Say Y here to include support for many DACs based on the DICE
@@ -28,7 +28,6 @@ config SND_DICE
config SND_FIREWIRE_SPEAKERS
tristate "FireWire speakers"
- select SND_PCM
select SND_FIREWIRE_LIB
help
Say Y here to include support for the Griffin FireWave Surround
@@ -39,7 +38,6 @@ config SND_FIREWIRE_SPEAKERS
config SND_ISIGHT
tristate "Apple iSight microphone"
- select SND_PCM
select SND_FIREWIRE_LIB
help
Say Y here to include support for the front and rear microphones
@@ -50,8 +48,6 @@ config SND_ISIGHT
config SND_SCS1X
tristate "Stanton Control System 1 MIDI"
- select SND_PCM
- select SND_RAWMIDI
select SND_FIREWIRE_LIB
help
Say Y here to include support for the MIDI ports of the Stanton
@@ -61,4 +57,59 @@ config SND_SCS1X
To compile this driver as a module, choose M here: the module
will be called snd-scs1x.
+config SND_FIREWORKS
+ tristate "Echo Fireworks board module support"
+ select SND_FIREWIRE_LIB
+ select SND_HWDEP
+ help
+ Say Y here to include support for FireWire devices based
+ on Echo Digital Audio Fireworks board:
+ * Mackie Onyx 400F/1200F
+ * Echo AudioFire12/8(until 2009 July)
+ * Echo AudioFire2/4/Pre8/8(since 2009 July)
+ * Echo Fireworks 8/HDMI
+ * Gibson Robot Interface Pack/GoldTop
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-fireworks.
+
+config SND_BEBOB
+ tristate "BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware"
+ select SND_FIREWIRE_LIB
+ select SND_HWDEP
+ help
+ Say Y here to include support for FireWire devices based
+ on BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware:
+ * Edirol FA-66/FA-101
+ * PreSonus FIREBOX/FIREPOD/FP10/Inspire1394
+ * BridgeCo RDAudio1/Audio5
+ * Mackie Onyx 1220/1620/1640 (Firewire I/O Card)
+ * Mackie d.2 (Firewire Option)
+ * Stanton FinalScratch 2 (ScratchAmp)
+ * Tascam IF-FW/DM
+ * Behringer XENIX UFX 1204/1604
+ * Behringer Digital Mixer X32 series (X-UF Card)
+ * Apogee Rosetta 200/400 (X-FireWire card)
+ * Apogee DA/AD/DD-16X (X-FireWire card)
+ * Apogee Ensemble
+ * ESI Quotafire610
+ * AcousticReality eARMasterOne
+ * CME MatrixKFW
+ * Phonic Helix Board 12 MkII/18 MkII/24 MkII
+ * Phonic Helix Board 12 Universal/18 Universal/24 Universal
+ * Lynx Aurora 8/16 (LT-FW)
+ * ICON FireXon
+ * PrismSound Orpheus/ADA-8XR
+ * TerraTec PHASE 24 FW/PHASE X24 FW/PHASE 88 Rack FW
+ * Terratec EWS MIC2/EWS MIC4
+ * Terratec Aureon 7.1 Firewire
+ * Yamaha GO44/GO46
+ * Focusrite Saffire/Saffire LE/SaffirePro10 IO/SaffirePro26 IO
+ * M-Audio Firewire410/AudioPhile/Solo
+ * M-Audio Ozonic/NRV10/ProfireLightBridge
+ * M-Audio Firewire 1814/ProjectMix IO
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-bebob.
+
endif # SND_FIREWIRE
diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile
index 509955061d30..fad8d49306ab 100644
--- a/sound/firewire/Makefile
+++ b/sound/firewire/Makefile
@@ -10,3 +10,5 @@ obj-$(CONFIG_SND_DICE) += snd-dice.o
obj-$(CONFIG_SND_FIREWIRE_SPEAKERS) += snd-firewire-speakers.o
obj-$(CONFIG_SND_ISIGHT) += snd-isight.o
obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o
+obj-$(CONFIG_SND_FIREWORKS) += fireworks/
+obj-$(CONFIG_SND_BEBOB) += bebob/
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c
index 9048777228e2..f96bf4c7c232 100644
--- a/sound/firewire/amdtp.c
+++ b/sound/firewire/amdtp.c
@@ -11,7 +11,10 @@
#include <linux/firewire.h>
#include <linux/module.h>
#include <linux/slab.h>
+#include <linux/sched.h>
#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/rawmidi.h>
#include "amdtp.h"
#define TICKS_PER_CYCLE 3072
@@ -20,50 +23,78 @@
#define TRANSFER_DELAY_TICKS 0x2e00 /* 479.17 µs */
+/* isochronous header parameters */
+#define ISO_DATA_LENGTH_SHIFT 16
#define TAG_CIP 1
+/* common isochronous packet header parameters */
#define CIP_EOH (1u << 31)
+#define CIP_EOH_MASK 0x80000000
#define CIP_FMT_AM (0x10 << 24)
-#define AMDTP_FDF_AM824 (0 << 19)
-#define AMDTP_FDF_SFC_SHIFT 16
+#define CIP_FMT_MASK 0x3f000000
+#define CIP_SYT_MASK 0x0000ffff
+#define CIP_SYT_NO_INFO 0xffff
+#define CIP_FDF_MASK 0x00ff0000
+#define CIP_FDF_SFC_SHIFT 16
+
+/*
+ * Audio and Music transfer protocol specific parameters
+ * only "Clock-based rate control mode" is supported
+ */
+#define AMDTP_FDF_AM824 (0 << (CIP_FDF_SFC_SHIFT + 3))
+#define AMDTP_FDF_NO_DATA 0xff
+#define AMDTP_DBS_MASK 0x00ff0000
+#define AMDTP_DBS_SHIFT 16
+#define AMDTP_DBC_MASK 0x000000ff
/* TODO: make these configurable */
#define INTERRUPT_INTERVAL 16
#define QUEUE_LENGTH 48
+#define IN_PACKET_HEADER_SIZE 4
+#define OUT_PACKET_HEADER_SIZE 0
+
static void pcm_period_tasklet(unsigned long data);
/**
- * amdtp_out_stream_init - initialize an AMDTP output stream structure
- * @s: the AMDTP output stream to initialize
+ * amdtp_stream_init - initialize an AMDTP stream structure
+ * @s: the AMDTP stream to initialize
* @unit: the target of the stream
+ * @dir: the direction of stream
* @flags: the packet transmission method to use
*/
-int amdtp_out_stream_init(struct amdtp_out_stream *s, struct fw_unit *unit,
- enum cip_out_flags flags)
+int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit,
+ enum amdtp_stream_direction dir, enum cip_flags flags)
{
s->unit = fw_unit_get(unit);
+ s->direction = dir;
s->flags = flags;
s->context = ERR_PTR(-1);
mutex_init(&s->mutex);
tasklet_init(&s->period_tasklet, pcm_period_tasklet, (unsigned long)s);
s->packet_index = 0;
+ init_waitqueue_head(&s->callback_wait);
+ s->callbacked = false;
+ s->sync_slave = NULL;
+
+ s->rx_blocks_for_midi = UINT_MAX;
+
return 0;
}
-EXPORT_SYMBOL(amdtp_out_stream_init);
+EXPORT_SYMBOL(amdtp_stream_init);
/**
- * amdtp_out_stream_destroy - free stream resources
- * @s: the AMDTP output stream to destroy
+ * amdtp_stream_destroy - free stream resources
+ * @s: the AMDTP stream to destroy
*/
-void amdtp_out_stream_destroy(struct amdtp_out_stream *s)
+void amdtp_stream_destroy(struct amdtp_stream *s)
{
- WARN_ON(amdtp_out_stream_running(s));
+ WARN_ON(amdtp_stream_running(s));
mutex_destroy(&s->mutex);
fw_unit_put(s->unit);
}
-EXPORT_SYMBOL(amdtp_out_stream_destroy);
+EXPORT_SYMBOL(amdtp_stream_destroy);
const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT] = {
[CIP_SFC_32000] = 8,
@@ -76,9 +107,75 @@ const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT] = {
};
EXPORT_SYMBOL(amdtp_syt_intervals);
+const unsigned int amdtp_rate_table[CIP_SFC_COUNT] = {
+ [CIP_SFC_32000] = 32000,
+ [CIP_SFC_44100] = 44100,
+ [CIP_SFC_48000] = 48000,
+ [CIP_SFC_88200] = 88200,
+ [CIP_SFC_96000] = 96000,
+ [CIP_SFC_176400] = 176400,
+ [CIP_SFC_192000] = 192000,
+};
+EXPORT_SYMBOL(amdtp_rate_table);
+
+/**
+ * amdtp_stream_add_pcm_hw_constraints - add hw constraints for PCM substream
+ * @s: the AMDTP stream, which must be initialized.
+ * @runtime: the PCM substream runtime
+ */
+int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s,
+ struct snd_pcm_runtime *runtime)
+{
+ int err;
+
+ /* AM824 in IEC 61883-6 can deliver 24bit data */
+ err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+ if (err < 0)
+ goto end;
+
+ /*
+ * Currently firewire-lib processes 16 packets in one software
+ * interrupt callback. This equals to 2msec but actually the
+ * interval of the interrupts has a jitter.
+ * Additionally, even if adding a constraint to fit period size to
+ * 2msec, actual calculated frames per period doesn't equal to 2msec,
+ * depending on sampling rate.
+ * Anyway, the interval to call snd_pcm_period_elapsed() cannot 2msec.
+ * Here let us use 5msec for safe period interrupt.
+ */
+ err = snd_pcm_hw_constraint_minmax(runtime,
+ SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+ 5000, UINT_MAX);
+ if (err < 0)
+ goto end;
+
+ /* Non-Blocking stream has no more constraints */
+ if (!(s->flags & CIP_BLOCKING))
+ goto end;
+
+ /*
+ * One AMDTP packet can include some frames. In blocking mode, the
+ * number equals to SYT_INTERVAL. So the number is 8, 16 or 32,
+ * depending on its sampling rate. For accurate period interrupt, it's
+ * preferrable to aligh period/buffer sizes to current SYT_INTERVAL.
+ *
+ * TODO: These constraints can be improved with propper rules.
+ * Currently apply LCM of SYT_INTEVALs.
+ */
+ err = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 32);
+ if (err < 0)
+ goto end;
+ err = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 32);
+end:
+ return err;
+}
+EXPORT_SYMBOL(amdtp_stream_add_pcm_hw_constraints);
+
/**
- * amdtp_out_stream_set_parameters - set stream parameters
- * @s: the AMDTP output stream to configure
+ * amdtp_stream_set_parameters - set stream parameters
+ * @s: the AMDTP stream to configure
* @rate: the sample rate
* @pcm_channels: the number of PCM samples in each data block, to be encoded
* as AM824 multi-bit linear audio
@@ -87,41 +184,30 @@ EXPORT_SYMBOL(amdtp_syt_intervals);
* The parameters must be set before the stream is started, and must not be
* changed while the stream is running.
*/
-void amdtp_out_stream_set_parameters(struct amdtp_out_stream *s,
- unsigned int rate,
- unsigned int pcm_channels,
- unsigned int midi_ports)
+void amdtp_stream_set_parameters(struct amdtp_stream *s,
+ unsigned int rate,
+ unsigned int pcm_channels,
+ unsigned int midi_ports)
{
- static const unsigned int rates[] = {
- [CIP_SFC_32000] = 32000,
- [CIP_SFC_44100] = 44100,
- [CIP_SFC_48000] = 48000,
- [CIP_SFC_88200] = 88200,
- [CIP_SFC_96000] = 96000,
- [CIP_SFC_176400] = 176400,
- [CIP_SFC_192000] = 192000,
- };
- unsigned int sfc;
+ unsigned int i, sfc, midi_channels;
+
+ midi_channels = DIV_ROUND_UP(midi_ports, 8);
- if (WARN_ON(amdtp_out_stream_running(s)))
+ if (WARN_ON(amdtp_stream_running(s)) |
+ WARN_ON(pcm_channels > AMDTP_MAX_CHANNELS_FOR_PCM) |
+ WARN_ON(midi_channels > AMDTP_MAX_CHANNELS_FOR_MIDI))
return;
- for (sfc = 0; sfc < CIP_SFC_COUNT; ++sfc)
- if (rates[sfc] == rate)
+ for (sfc = 0; sfc < ARRAY_SIZE(amdtp_rate_table); ++sfc)
+ if (amdtp_rate_table[sfc] == rate)
goto sfc_found;
WARN_ON(1);
return;
sfc_found:
- s->dual_wire = (s->flags & CIP_HI_DUALWIRE) && sfc > CIP_SFC_96000;
- if (s->dual_wire) {
- sfc -= 2;
- rate /= 2;
- pcm_channels *= 2;
- }
- s->sfc = sfc;
- s->data_block_quadlets = pcm_channels + DIV_ROUND_UP(midi_ports, 8);
s->pcm_channels = pcm_channels;
+ s->sfc = sfc;
+ s->data_block_quadlets = s->pcm_channels + midi_channels;
s->midi_ports = midi_ports;
s->syt_interval = amdtp_syt_intervals[sfc];
@@ -131,48 +217,50 @@ sfc_found:
if (s->flags & CIP_BLOCKING)
/* additional buffering needed to adjust for no-data packets */
s->transfer_delay += TICKS_PER_SECOND * s->syt_interval / rate;
+
+ /* init the position map for PCM and MIDI channels */
+ for (i = 0; i < pcm_channels; i++)
+ s->pcm_positions[i] = i;
+ s->midi_position = s->pcm_channels;
}
-EXPORT_SYMBOL(amdtp_out_stream_set_parameters);
+EXPORT_SYMBOL(amdtp_stream_set_parameters);
/**
- * amdtp_out_stream_get_max_payload - get the stream's packet size
- * @s: the AMDTP output stream
+ * amdtp_stream_get_max_payload - get the stream's packet size
+ * @s: the AMDTP stream
*
* This function must not be called before the stream has been configured
- * with amdtp_out_stream_set_parameters().
+ * with amdtp_stream_set_parameters().
*/
-unsigned int amdtp_out_stream_get_max_payload(struct amdtp_out_stream *s)
+unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s)
{
return 8 + s->syt_interval * s->data_block_quadlets * 4;
}
-EXPORT_SYMBOL(amdtp_out_stream_get_max_payload);
+EXPORT_SYMBOL(amdtp_stream_get_max_payload);
-static void amdtp_write_s16(struct amdtp_out_stream *s,
+static void amdtp_write_s16(struct amdtp_stream *s,
struct snd_pcm_substream *pcm,
__be32 *buffer, unsigned int frames);
-static void amdtp_write_s32(struct amdtp_out_stream *s,
+static void amdtp_write_s32(struct amdtp_stream *s,
struct snd_pcm_substream *pcm,
__be32 *buffer, unsigned int frames);
-static void amdtp_write_s16_dualwire(struct amdtp_out_stream *s,
- struct snd_pcm_substream *pcm,
- __be32 *buffer, unsigned int frames);
-static void amdtp_write_s32_dualwire(struct amdtp_out_stream *s,
- struct snd_pcm_substream *pcm,
- __be32 *buffer, unsigned int frames);
+static void amdtp_read_s32(struct amdtp_stream *s,
+ struct snd_pcm_substream *pcm,
+ __be32 *buffer, unsigned int frames);
/**
- * amdtp_out_stream_set_pcm_format - set the PCM format
- * @s: the AMDTP output stream to configure
+ * amdtp_stream_set_pcm_format - set the PCM format
+ * @s: the AMDTP stream to configure
* @format: the format of the ALSA PCM device
*
* The sample format must be set after the other paramters (rate/PCM channels/
* MIDI) and before the stream is started, and must not be changed while the
* stream is running.
*/
-void amdtp_out_stream_set_pcm_format(struct amdtp_out_stream *s,
- snd_pcm_format_t format)
+void amdtp_stream_set_pcm_format(struct amdtp_stream *s,
+ snd_pcm_format_t format)
{
- if (WARN_ON(amdtp_out_stream_running(s)))
+ if (WARN_ON(amdtp_stream_pcm_running(s)))
return;
switch (format) {
@@ -180,41 +268,44 @@ void amdtp_out_stream_set_pcm_format(struct amdtp_out_stream *s,
WARN_ON(1);
/* fall through */
case SNDRV_PCM_FORMAT_S16:
- if (s->dual_wire)
- s->transfer_samples = amdtp_write_s16_dualwire;
- else
+ if (s->direction == AMDTP_OUT_STREAM) {
s->transfer_samples = amdtp_write_s16;
- break;
+ break;
+ }
+ WARN_ON(1);
+ /* fall through */
case SNDRV_PCM_FORMAT_S32:
- if (s->dual_wire)
- s->transfer_samples = amdtp_write_s32_dualwire;
- else
+ if (s->direction == AMDTP_OUT_STREAM)
s->transfer_samples = amdtp_write_s32;
+ else
+ s->transfer_samples = amdtp_read_s32;
break;
}
}
-EXPORT_SYMBOL(amdtp_out_stream_set_pcm_format);
+EXPORT_SYMBOL(amdtp_stream_set_pcm_format);
/**
- * amdtp_out_stream_pcm_prepare - prepare PCM device for running
- * @s: the AMDTP output stream
+ * amdtp_stream_pcm_prepare - prepare PCM device for running
+ * @s: the AMDTP stream
*
* This function should be called from the PCM device's .prepare callback.
*/
-void amdtp_out_stream_pcm_prepare(struct amdtp_out_stream *s)
+void amdtp_stream_pcm_prepare(struct amdtp_stream *s)
{
tasklet_kill(&s->period_tasklet);
s->pcm_buffer_pointer = 0;
s->pcm_period_pointer = 0;
s->pointer_flush = true;
}
-EXPORT_SYMBOL(amdtp_out_stream_pcm_prepare);
+EXPORT_SYMBOL(amdtp_stream_pcm_prepare);
-static unsigned int calculate_data_blocks(struct amdtp_out_stream *s)
+static unsigned int calculate_data_blocks(struct amdtp_stream *s)
{
unsigned int phase, data_blocks;
- if (!cip_sfc_is_base_44100(s->sfc)) {
+ if (s->flags & CIP_BLOCKING)
+ data_blocks = s->syt_interval;
+ else if (!cip_sfc_is_base_44100(s->sfc)) {
/* Sample_rate / 8000 is an integer, and precomputed. */
data_blocks = s->data_block_state;
} else {
@@ -243,7 +334,7 @@ static unsigned int calculate_data_blocks(struct amdtp_out_stream *s)
return data_blocks;
}
-static unsigned int calculate_syt(struct amdtp_out_stream *s,
+static unsigned int calculate_syt(struct amdtp_stream *s,
unsigned int cycle)
{
unsigned int syt_offset, phase, index, syt;
@@ -280,175 +371,228 @@ static unsigned int calculate_syt(struct amdtp_out_stream *s,
syt = (cycle + syt_offset / TICKS_PER_CYCLE) << 12;
syt += syt_offset % TICKS_PER_CYCLE;
- return syt & 0xffff;
+ return syt & CIP_SYT_MASK;
} else {
- return 0xffff; /* no info */
+ return CIP_SYT_NO_INFO;
}
}
-static void amdtp_write_s32(struct amdtp_out_stream *s,
+static void amdtp_write_s32(struct amdtp_stream *s,
struct snd_pcm_substream *pcm,
__be32 *buffer, unsigned int frames)
{
struct snd_pcm_runtime *runtime = pcm->runtime;
- unsigned int channels, remaining_frames, frame_step, i, c;
+ unsigned int channels, remaining_frames, i, c;
const u32 *src;
channels = s->pcm_channels;
src = (void *)runtime->dma_area +
frames_to_bytes(runtime, s->pcm_buffer_pointer);
remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
- frame_step = s->data_block_quadlets - channels;
for (i = 0; i < frames; ++i) {
for (c = 0; c < channels; ++c) {
- *buffer = cpu_to_be32((*src >> 8) | 0x40000000);
+ buffer[s->pcm_positions[c]] =
+ cpu_to_be32((*src >> 8) | 0x40000000);
src++;
- buffer++;
}
- buffer += frame_step;
+ buffer += s->data_block_quadlets;
if (--remaining_frames == 0)
src = (void *)runtime->dma_area;
}
}
-static void amdtp_write_s16(struct amdtp_out_stream *s,
+static void amdtp_write_s16(struct amdtp_stream *s,
struct snd_pcm_substream *pcm,
__be32 *buffer, unsigned int frames)
{
struct snd_pcm_runtime *runtime = pcm->runtime;
- unsigned int channels, remaining_frames, frame_step, i, c;
+ unsigned int channels, remaining_frames, i, c;
const u16 *src;
channels = s->pcm_channels;
src = (void *)runtime->dma_area +
frames_to_bytes(runtime, s->pcm_buffer_pointer);
remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
- frame_step = s->data_block_quadlets - channels;
for (i = 0; i < frames; ++i) {
for (c = 0; c < channels; ++c) {
- *buffer = cpu_to_be32((*src << 8) | 0x40000000);
+ buffer[s->pcm_positions[c]] =
+ cpu_to_be32((*src << 8) | 0x42000000);
src++;
- buffer++;
}
- buffer += frame_step;
+ buffer += s->data_block_quadlets;
if (--remaining_frames == 0)
src = (void *)runtime->dma_area;
}
}
-static void amdtp_write_s32_dualwire(struct amdtp_out_stream *s,
- struct snd_pcm_substream *pcm,
- __be32 *buffer, unsigned int frames)
+static void amdtp_read_s32(struct amdtp_stream *s,
+ struct snd_pcm_substream *pcm,
+ __be32 *buffer, unsigned int frames)
{
struct snd_pcm_runtime *runtime = pcm->runtime;
- unsigned int channels, frame_adjust_1, frame_adjust_2, i, c;
- const u32 *src;
+ unsigned int channels, remaining_frames, i, c;
+ u32 *dst;
channels = s->pcm_channels;
- src = (void *)runtime->dma_area +
- s->pcm_buffer_pointer * (runtime->frame_bits / 8);
- frame_adjust_1 = channels - 1;
- frame_adjust_2 = 1 - (s->data_block_quadlets - channels);
+ dst = (void *)runtime->dma_area +
+ frames_to_bytes(runtime, s->pcm_buffer_pointer);
+ remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
- channels /= 2;
for (i = 0; i < frames; ++i) {
for (c = 0; c < channels; ++c) {
- *buffer = cpu_to_be32((*src >> 8) | 0x40000000);
- src++;
- buffer += 2;
- }
- buffer -= frame_adjust_1;
- for (c = 0; c < channels; ++c) {
- *buffer = cpu_to_be32((*src >> 8) | 0x40000000);
- src++;
- buffer += 2;
+ *dst = be32_to_cpu(buffer[s->pcm_positions[c]]) << 8;
+ dst++;
}
- buffer -= frame_adjust_2;
+ buffer += s->data_block_quadlets;
+ if (--remaining_frames == 0)
+ dst = (void *)runtime->dma_area;
}
}
-static void amdtp_write_s16_dualwire(struct amdtp_out_stream *s,
- struct snd_pcm_substream *pcm,
- __be32 *buffer, unsigned int frames)
+static void amdtp_fill_pcm_silence(struct amdtp_stream *s,
+ __be32 *buffer, unsigned int frames)
{
- struct snd_pcm_runtime *runtime = pcm->runtime;
- unsigned int channels, frame_adjust_1, frame_adjust_2, i, c;
- const u16 *src;
-
- channels = s->pcm_channels;
- src = (void *)runtime->dma_area +
- s->pcm_buffer_pointer * (runtime->frame_bits / 8);
- frame_adjust_1 = channels - 1;
- frame_adjust_2 = 1 - (s->data_block_quadlets - channels);
+ unsigned int i, c;
- channels /= 2;
for (i = 0; i < frames; ++i) {
- for (c = 0; c < channels; ++c) {
- *buffer = cpu_to_be32((*src << 8) | 0x40000000);
- src++;
- buffer += 2;
- }
- buffer -= frame_adjust_1;
- for (c = 0; c < channels; ++c) {
- *buffer = cpu_to_be32((*src << 8) | 0x40000000);
- src++;
- buffer += 2;
- }
- buffer -= frame_adjust_2;
+ for (c = 0; c < s->pcm_channels; ++c)
+ buffer[s->pcm_positions[c]] = cpu_to_be32(0x40000000);
+ buffer += s->data_block_quadlets;
}
}
-static void amdtp_fill_pcm_silence(struct amdtp_out_stream *s,
- __be32 *buffer, unsigned int frames)
+static void amdtp_fill_midi(struct amdtp_stream *s,
+ __be32 *buffer, unsigned int frames)
{
- unsigned int i, c;
+ unsigned int f, port;
+ u8 *b;
+
+ for (f = 0; f < frames; f++) {
+ buffer[s->midi_position] = 0;
+ b = (u8 *)&buffer[s->midi_position];
+
+ port = (s->data_block_counter + f) % 8;
+ if ((f >= s->rx_blocks_for_midi) ||
+ (s->midi[port] == NULL) ||
+ (snd_rawmidi_transmit(s->midi[port], b + 1, 1) <= 0))
+ b[0] = 0x80;
+ else
+ b[0] = 0x81;
- for (i = 0; i < frames; ++i) {
- for (c = 0; c < s->pcm_channels; ++c)
- buffer[c] = cpu_to_be32(0x40000000);
buffer += s->data_block_quadlets;
}
}
-static void amdtp_fill_midi(struct amdtp_out_stream *s,
+static void amdtp_pull_midi(struct amdtp_stream *s,
__be32 *buffer, unsigned int frames)
{
- unsigned int i;
+ unsigned int f, port;
+ int len;
+ u8 *b;
+
+ for (f = 0; f < frames; f++) {
+ port = (s->data_block_counter + f) % 8;
+ b = (u8 *)&buffer[s->midi_position];
+
+ len = b[0] - 0x80;
+ if ((1 <= len) && (len <= 3) && (s->midi[port]))
+ snd_rawmidi_receive(s->midi[port], b + 1, len);
+
+ buffer += s->data_block_quadlets;
+ }
+}
+
+static void update_pcm_pointers(struct amdtp_stream *s,
+ struct snd_pcm_substream *pcm,
+ unsigned int frames)
+{ unsigned int ptr;
+
+ ptr = s->pcm_buffer_pointer + frames;
+ if (ptr >= pcm->runtime->buffer_size)
+ ptr -= pcm->runtime->buffer_size;
+ ACCESS_ONCE(s->pcm_buffer_pointer) = ptr;
+
+ s->pcm_period_pointer += frames;
+ if (s->pcm_period_pointer >= pcm->runtime->period_size) {
+ s->pcm_period_pointer -= pcm->runtime->period_size;
+ s->pointer_flush = false;
+ tasklet_hi_schedule(&s->period_tasklet);
+ }
+}
+
+static void pcm_period_tasklet(unsigned long data)
+{
+ struct amdtp_stream *s = (void *)data;
+ struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm);
- for (i = 0; i < frames; ++i)
- buffer[s->pcm_channels + i * s->data_block_quadlets] =
- cpu_to_be32(0x80000000);
+ if (pcm)
+ snd_pcm_period_elapsed(pcm);
}
-static void queue_out_packet(struct amdtp_out_stream *s, unsigned int cycle)
+static int queue_packet(struct amdtp_stream *s,
+ unsigned int header_length,
+ unsigned int payload_length, bool skip)
+{
+ struct fw_iso_packet p = {0};
+ int err = 0;
+
+ if (IS_ERR(s->context))
+ goto end;
+
+ p.interrupt = IS_ALIGNED(s->packet_index + 1, INTERRUPT_INTERVAL);
+ p.tag = TAG_CIP;
+ p.header_length = header_length;
+ p.payload_length = (!skip) ? payload_length : 0;
+ p.skip = skip;
+ err = fw_iso_context_queue(s->context, &p, &s->buffer.iso_buffer,
+ s->buffer.packets[s->packet_index].offset);
+ if (err < 0) {
+ dev_err(&s->unit->device, "queueing error: %d\n", err);
+ goto end;
+ }
+
+ if (++s->packet_index >= QUEUE_LENGTH)
+ s->packet_index = 0;
+end:
+ return err;
+}
+
+static inline int queue_out_packet(struct amdtp_stream *s,
+ unsigned int payload_length, bool skip)
+{
+ return queue_packet(s, OUT_PACKET_HEADER_SIZE,
+ payload_length, skip);
+}
+
+static inline int queue_in_packet(struct amdtp_stream *s)
+{
+ return queue_packet(s, IN_PACKET_HEADER_SIZE,
+ amdtp_stream_get_max_payload(s), false);
+}
+
+static void handle_out_packet(struct amdtp_stream *s, unsigned int syt)
{
__be32 *buffer;
- unsigned int index, data_blocks, syt, ptr;
+ unsigned int data_blocks, payload_length;
struct snd_pcm_substream *pcm;
- struct fw_iso_packet packet;
- int err;
if (s->packet_index < 0)
return;
- index = s->packet_index;
/* this module generate empty packet for 'no data' */
- syt = calculate_syt(s, cycle);
- if (!(s->flags & CIP_BLOCKING))
+ if (!(s->flags & CIP_BLOCKING) || (syt != CIP_SYT_NO_INFO))
data_blocks = calculate_data_blocks(s);
- else if (syt != 0xffff)
- data_blocks = s->syt_interval;
else
data_blocks = 0;
- buffer = s->buffer.packets[index].buffer;
+ buffer = s->buffer.packets[s->packet_index].buffer;
buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) |
- (s->data_block_quadlets << 16) |
+ (s->data_block_quadlets << AMDTP_DBS_SHIFT) |
s->data_block_counter);
buffer[1] = cpu_to_be32(CIP_EOH | CIP_FMT_AM | AMDTP_FDF_AM824 |
- (s->sfc << AMDTP_FDF_SFC_SHIFT) | syt);
+ (s->sfc << CIP_FDF_SFC_SHIFT) | syt);
buffer += 2;
pcm = ACCESS_ONCE(s->pcm);
@@ -461,58 +605,127 @@ static void queue_out_packet(struct amdtp_out_stream *s, unsigned int cycle)
s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff;
- packet.payload_length = 8 + data_blocks * 4 * s->data_block_quadlets;
- packet.interrupt = IS_ALIGNED(index + 1, INTERRUPT_INTERVAL);
- packet.skip = 0;
- packet.tag = TAG_CIP;
- packet.sy = 0;
- packet.header_length = 0;
-
- err = fw_iso_context_queue(s->context, &packet, &s->buffer.iso_buffer,
- s->buffer.packets[index].offset);
- if (err < 0) {
- dev_err(&s->unit->device, "queueing error: %d\n", err);
+ payload_length = 8 + data_blocks * 4 * s->data_block_quadlets;
+ if (queue_out_packet(s, payload_length, false) < 0) {
s->packet_index = -1;
- amdtp_out_stream_pcm_abort(s);
+ amdtp_stream_pcm_abort(s);
return;
}
- if (++index >= QUEUE_LENGTH)
- index = 0;
- s->packet_index = index;
+ if (pcm)
+ update_pcm_pointers(s, pcm, data_blocks);
+}
- if (pcm) {
- if (s->dual_wire)
- data_blocks *= 2;
-
- ptr = s->pcm_buffer_pointer + data_blocks;
- if (ptr >= pcm->runtime->buffer_size)
- ptr -= pcm->runtime->buffer_size;
- ACCESS_ONCE(s->pcm_buffer_pointer) = ptr;
-
- s->pcm_period_pointer += data_blocks;
- if (s->pcm_period_pointer >= pcm->runtime->period_size) {
- s->pcm_period_pointer -= pcm->runtime->period_size;
- s->pointer_flush = false;
- tasklet_hi_schedule(&s->period_tasklet);
+static void handle_in_packet(struct amdtp_stream *s,
+ unsigned int payload_quadlets,
+ __be32 *buffer)
+{
+ u32 cip_header[2];
+ unsigned int data_blocks, data_block_quadlets, data_block_counter,
+ dbc_interval;
+ struct snd_pcm_substream *pcm = NULL;
+ bool lost;
+
+ cip_header[0] = be32_to_cpu(buffer[0]);
+ cip_header[1] = be32_to_cpu(buffer[1]);
+
+ /*
+ * 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) ||
+ ((cip_header[1] & CIP_FMT_MASK) != CIP_FMT_AM)) {
+ dev_info_ratelimited(&s->unit->device,
+ "Invalid CIP header for AMDTP: %08X:%08X\n",
+ cip_header[0], cip_header[1]);
+ goto end;
+ }
+
+ /* Calculate data blocks */
+ if (payload_quadlets < 3 ||
+ ((cip_header[1] & CIP_FDF_MASK) ==
+ (AMDTP_FDF_NO_DATA << CIP_FDF_SFC_SHIFT))) {
+ data_blocks = 0;
+ } else {
+ data_block_quadlets =
+ (cip_header[0] & AMDTP_DBS_MASK) >> AMDTP_DBS_SHIFT;
+ /* avoid division by zero */
+ if (data_block_quadlets == 0) {
+ dev_info_ratelimited(&s->unit->device,
+ "Detect invalid value in dbs field: %08X\n",
+ cip_header[0]);
+ goto err;
}
+ if (s->flags & CIP_WRONG_DBS)
+ data_block_quadlets = s->data_block_quadlets;
+
+ data_blocks = (payload_quadlets - 2) / data_block_quadlets;
}
-}
-static void pcm_period_tasklet(unsigned long data)
-{
- struct amdtp_out_stream *s = (void *)data;
- struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm);
+ /* Check data block counter continuity */
+ data_block_counter = cip_header[0] & AMDTP_DBC_MASK;
+ if (data_blocks == 0 && (s->flags & CIP_EMPTY_HAS_WRONG_DBC) &&
+ s->data_block_counter != UINT_MAX)
+ data_block_counter = s->data_block_counter;
+
+ if (((s->flags & CIP_SKIP_DBC_ZERO_CHECK) && data_block_counter == 0) ||
+ (s->data_block_counter == UINT_MAX)) {
+ lost = false;
+ } else if (!(s->flags & CIP_DBC_IS_END_EVENT)) {
+ lost = data_block_counter != s->data_block_counter;
+ } else {
+ if ((data_blocks > 0) && (s->tx_dbc_interval > 0))
+ dbc_interval = s->tx_dbc_interval;
+ else
+ dbc_interval = data_blocks;
+
+ lost = data_block_counter !=
+ ((s->data_block_counter + dbc_interval) & 0xff);
+ }
+
+ if (lost) {
+ dev_info(&s->unit->device,
+ "Detect discontinuity of CIP: %02X %02X\n",
+ s->data_block_counter, data_block_counter);
+ goto err;
+ }
+
+ if (data_blocks > 0) {
+ buffer += 2;
+
+ pcm = ACCESS_ONCE(s->pcm);
+ if (pcm)
+ s->transfer_samples(s, pcm, buffer, data_blocks);
+
+ if (s->midi_ports)
+ amdtp_pull_midi(s, buffer, data_blocks);
+ }
+
+ if (s->flags & CIP_DBC_IS_END_EVENT)
+ s->data_block_counter = data_block_counter;
+ else
+ s->data_block_counter =
+ (data_block_counter + data_blocks) & 0xff;
+end:
+ if (queue_in_packet(s) < 0)
+ goto err;
if (pcm)
- snd_pcm_period_elapsed(pcm);
+ update_pcm_pointers(s, pcm, data_blocks);
+
+ return;
+err:
+ s->packet_index = -1;
+ amdtp_stream_pcm_abort(s);
}
-static void out_packet_callback(struct fw_iso_context *context, u32 cycle,
- size_t header_length, void *header, void *data)
+static void out_stream_callback(struct fw_iso_context *context, u32 cycle,
+ size_t header_length, void *header,
+ void *private_data)
{
- struct amdtp_out_stream *s = data;
- unsigned int i, packets = header_length / 4;
+ struct amdtp_stream *s = private_data;
+ unsigned int i, syt, packets = header_length / 4;
/*
* Compute the cycle of the last queued packet.
@@ -521,43 +734,102 @@ static void out_packet_callback(struct fw_iso_context *context, u32 cycle,
*/
cycle += QUEUE_LENGTH - packets;
- for (i = 0; i < packets; ++i)
- queue_out_packet(s, ++cycle);
+ for (i = 0; i < packets; ++i) {
+ syt = calculate_syt(s, ++cycle);
+ handle_out_packet(s, syt);
+ }
fw_iso_context_queue_flush(s->context);
}
-static int queue_initial_skip_packets(struct amdtp_out_stream *s)
+static void in_stream_callback(struct fw_iso_context *context, u32 cycle,
+ size_t header_length, void *header,
+ void *private_data)
{
- struct fw_iso_packet skip_packet = {
- .skip = 1,
- };
- unsigned int i;
- int err;
+ struct amdtp_stream *s = private_data;
+ unsigned int p, syt, packets, payload_quadlets;
+ __be32 *buffer, *headers = header;
- for (i = 0; i < QUEUE_LENGTH; ++i) {
- skip_packet.interrupt = IS_ALIGNED(s->packet_index + 1,
- INTERRUPT_INTERVAL);
- err = fw_iso_context_queue(s->context, &skip_packet, NULL, 0);
- if (err < 0)
- return err;
- if (++s->packet_index >= QUEUE_LENGTH)
- s->packet_index = 0;
+ /* The number of packets in buffer */
+ packets = header_length / IN_PACKET_HEADER_SIZE;
+
+ for (p = 0; p < packets; p++) {
+ if (s->packet_index < 0)
+ break;
+
+ buffer = s->buffer.packets[s->packet_index].buffer;
+
+ /* Process sync slave stream */
+ if (s->sync_slave && s->sync_slave->callbacked) {
+ syt = be32_to_cpu(buffer[1]) & CIP_SYT_MASK;
+ handle_out_packet(s->sync_slave, syt);
+ }
+
+ /* The number of quadlets in this packet */
+ payload_quadlets =
+ (be32_to_cpu(headers[p]) >> ISO_DATA_LENGTH_SHIFT) / 4;
+ handle_in_packet(s, payload_quadlets, buffer);
}
- return 0;
+ /* Queueing error or detecting discontinuity */
+ if (s->packet_index < 0) {
+ /* Abort sync slave. */
+ if (s->sync_slave) {
+ s->sync_slave->packet_index = -1;
+ amdtp_stream_pcm_abort(s->sync_slave);
+ }
+ return;
+ }
+
+ /* when sync to device, flush the packets for slave stream */
+ if (s->sync_slave && s->sync_slave->callbacked)
+ fw_iso_context_queue_flush(s->sync_slave->context);
+
+ fw_iso_context_queue_flush(s->context);
+}
+
+/* processing is done by master callback */
+static void slave_stream_callback(struct fw_iso_context *context, u32 cycle,
+ size_t header_length, void *header,
+ void *private_data)
+{
+ return;
+}
+
+/* this is executed one time */
+static void amdtp_stream_first_callback(struct fw_iso_context *context,
+ u32 cycle, size_t header_length,
+ void *header, void *private_data)
+{
+ struct amdtp_stream *s = private_data;
+
+ /*
+ * For in-stream, first packet has come.
+ * For out-stream, prepared to transmit first packet
+ */
+ s->callbacked = true;
+ wake_up(&s->callback_wait);
+
+ if (s->direction == AMDTP_IN_STREAM)
+ context->callback.sc = in_stream_callback;
+ else if ((s->flags & CIP_BLOCKING) && (s->flags & CIP_SYNC_TO_DEVICE))
+ context->callback.sc = slave_stream_callback;
+ else
+ context->callback.sc = out_stream_callback;
+
+ context->callback.sc(context, cycle, header_length, header, s);
}
/**
- * amdtp_out_stream_start - start sending packets
- * @s: the AMDTP output stream to start
+ * amdtp_stream_start - start transferring packets
+ * @s: the AMDTP stream to start
* @channel: the isochronous channel on the bus
* @speed: firewire speed code
*
* The stream cannot be started until it has been configured with
- * amdtp_out_stream_set_parameters() and amdtp_out_stream_set_pcm_format(),
- * and it must be started before any PCM or MIDI device can be started.
+ * amdtp_stream_set_parameters() and it must be started before any PCM or MIDI
+ * device can be started.
*/
-int amdtp_out_stream_start(struct amdtp_out_stream *s, int channel, int speed)
+int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
{
static const struct {
unsigned int data_block;
@@ -571,47 +843,72 @@ int amdtp_out_stream_start(struct amdtp_out_stream *s, int channel, int speed)
[CIP_SFC_88200] = { 0, 67 },
[CIP_SFC_176400] = { 0, 67 },
};
- int err;
+ unsigned int header_size;
+ enum dma_data_direction dir;
+ int type, tag, err;
mutex_lock(&s->mutex);
- if (WARN_ON(amdtp_out_stream_running(s) ||
- (!s->pcm_channels && !s->midi_ports))) {
+ if (WARN_ON(amdtp_stream_running(s) ||
+ (s->data_block_quadlets < 1))) {
err = -EBADFD;
goto err_unlock;
}
+ if (s->direction == AMDTP_IN_STREAM &&
+ s->flags & CIP_SKIP_INIT_DBC_CHECK)
+ s->data_block_counter = UINT_MAX;
+ else
+ s->data_block_counter = 0;
s->data_block_state = initial_state[s->sfc].data_block;
s->syt_offset_state = initial_state[s->sfc].syt_offset;
s->last_syt_offset = TICKS_PER_CYCLE;
+ /* initialize packet buffer */
+ if (s->direction == AMDTP_IN_STREAM) {
+ dir = DMA_FROM_DEVICE;
+ type = FW_ISO_CONTEXT_RECEIVE;
+ header_size = IN_PACKET_HEADER_SIZE;
+ } else {
+ dir = DMA_TO_DEVICE;
+ type = FW_ISO_CONTEXT_TRANSMIT;
+ header_size = OUT_PACKET_HEADER_SIZE;
+ }
err = iso_packets_buffer_init(&s->buffer, s->unit, QUEUE_LENGTH,
- amdtp_out_stream_get_max_payload(s),
- DMA_TO_DEVICE);
+ amdtp_stream_get_max_payload(s), dir);
if (err < 0)
goto err_unlock;
s->context = fw_iso_context_create(fw_parent_device(s->unit)->card,
- FW_ISO_CONTEXT_TRANSMIT,
- channel, speed, 0,
- out_packet_callback, s);
+ type, channel, speed, header_size,
+ amdtp_stream_first_callback, s);
if (IS_ERR(s->context)) {
err = PTR_ERR(s->context);
if (err == -EBUSY)
dev_err(&s->unit->device,
- "no free output stream on this controller\n");
+ "no free stream on this controller\n");
goto err_buffer;
}
- amdtp_out_stream_update(s);
+ amdtp_stream_update(s);
s->packet_index = 0;
- s->data_block_counter = 0;
- err = queue_initial_skip_packets(s);
- if (err < 0)
- goto err_context;
+ do {
+ if (s->direction == AMDTP_IN_STREAM)
+ err = queue_in_packet(s);
+ else
+ err = queue_out_packet(s, 0, true);
+ if (err < 0)
+ goto err_context;
+ } while (s->packet_index > 0);
- err = fw_iso_context_start(s->context, -1, 0, 0);
+ /* NOTE: TAG1 matches CIP. This just affects in stream. */
+ tag = FW_ISO_CONTEXT_MATCH_TAG1;
+ if (s->flags & CIP_EMPTY_WITH_TAG0)
+ tag |= FW_ISO_CONTEXT_MATCH_TAG0;
+
+ s->callbacked = false;
+ err = fw_iso_context_start(s->context, -1, 0, tag);
if (err < 0)
goto err_context;
@@ -629,49 +926,49 @@ err_unlock:
return err;
}
-EXPORT_SYMBOL(amdtp_out_stream_start);
+EXPORT_SYMBOL(amdtp_stream_start);
/**
- * amdtp_out_stream_pcm_pointer - get the PCM buffer position
- * @s: the AMDTP output stream that transports the PCM data
+ * amdtp_stream_pcm_pointer - get the PCM buffer position
+ * @s: the AMDTP stream that transports the PCM data
*
* Returns the current buffer position, in frames.
*/
-unsigned long amdtp_out_stream_pcm_pointer(struct amdtp_out_stream *s)
+unsigned long amdtp_stream_pcm_pointer(struct amdtp_stream *s)
{
/* this optimization is allowed to be racy */
- if (s->pointer_flush)
+ if (s->pointer_flush && amdtp_stream_running(s))
fw_iso_context_flush_completions(s->context);
else
s->pointer_flush = true;
return ACCESS_ONCE(s->pcm_buffer_pointer);
}
-EXPORT_SYMBOL(amdtp_out_stream_pcm_pointer);
+EXPORT_SYMBOL(amdtp_stream_pcm_pointer);
/**
- * amdtp_out_stream_update - update the stream after a bus reset
- * @s: the AMDTP output stream
+ * amdtp_stream_update - update the stream after a bus reset
+ * @s: the AMDTP stream
*/
-void amdtp_out_stream_update(struct amdtp_out_stream *s)
+void amdtp_stream_update(struct amdtp_stream *s)
{
ACCESS_ONCE(s->source_node_id_field) =
(fw_parent_device(s->unit)->card->node_id & 0x3f) << 24;
}
-EXPORT_SYMBOL(amdtp_out_stream_update);
+EXPORT_SYMBOL(amdtp_stream_update);
/**
- * amdtp_out_stream_stop - stop sending packets
- * @s: the AMDTP output stream to stop
+ * amdtp_stream_stop - stop sending packets
+ * @s: the AMDTP stream to stop
*
* All PCM and MIDI devices of the stream must be stopped before the stream
* itself can be stopped.
*/
-void amdtp_out_stream_stop(struct amdtp_out_stream *s)
+void amdtp_stream_stop(struct amdtp_stream *s)
{
mutex_lock(&s->mutex);
- if (!amdtp_out_stream_running(s)) {
+ if (!amdtp_stream_running(s)) {
mutex_unlock(&s->mutex);
return;
}
@@ -682,18 +979,20 @@ void amdtp_out_stream_stop(struct amdtp_out_stream *s)
s->context = ERR_PTR(-1);
iso_packets_buffer_destroy(&s->buffer, s->unit);
+ s->callbacked = false;
+
mutex_unlock(&s->mutex);
}
-EXPORT_SYMBOL(amdtp_out_stream_stop);
+EXPORT_SYMBOL(amdtp_stream_stop);
/**
- * amdtp_out_stream_pcm_abort - abort the running PCM device
+ * amdtp_stream_pcm_abort - abort the running PCM device
* @s: the AMDTP stream about to be stopped
*
* If the isochronous stream needs to be stopped asynchronously, call this
* function first to stop the PCM device.
*/
-void amdtp_out_stream_pcm_abort(struct amdtp_out_stream *s)
+void amdtp_stream_pcm_abort(struct amdtp_stream *s)
{
struct snd_pcm_substream *pcm;
@@ -705,4 +1004,4 @@ void amdtp_out_stream_pcm_abort(struct amdtp_out_stream *s)
snd_pcm_stream_unlock_irq(pcm);
}
}
-EXPORT_SYMBOL(amdtp_out_stream_pcm_abort);
+EXPORT_SYMBOL(amdtp_stream_pcm_abort);
diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h
index 2746ecd291af..d8ee7b0e9386 100644
--- a/sound/firewire/amdtp.h
+++ b/sound/firewire/amdtp.h
@@ -8,7 +8,7 @@
#include "packets-buffer.h"
/**
- * enum cip_out_flags - describes details of the streaming protocol
+ * enum cip_flags - describes details of the streaming protocol
* @CIP_NONBLOCKING: In non-blocking mode, each packet contains
* sample_rate/8000 samples, with rounding up or down to adjust
* for clock skew and left-over fractional samples. This should
@@ -16,15 +16,30 @@
* @CIP_BLOCKING: In blocking mode, each packet contains either zero or
* SYT_INTERVAL samples, with these two types alternating so that
* the overall sample rate comes out right.
- * @CIP_HI_DUALWIRE: At rates above 96 kHz, pretend that the stream runs
- * at half the actual sample rate with twice the number of channels;
- * two samples of a channel are stored consecutively in the packet.
- * Requires blocking mode and SYT_INTERVAL-aligned PCM buffer size.
+ * @CIP_SYNC_TO_DEVICE: In sync to device mode, time stamp in out packets is
+ * generated by in packets. Defaultly this driver generates timestamp.
+ * @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_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.
+ * @SKIP_DBC_ZERO_CHECK: Only for in-stream. Packets with zero in dbc is
+ * skipped for detecting discontinuity.
+ * @CIP_SKIP_INIT_DBC_CHECK: Only for in-stream. The value of dbc in first
+ * packet is not continuous from an initial value.
+ * @CIP_EMPTY_HAS_WRONG_DBC: Only for in-stream. The value of dbc in empty
+ * packet is wrong but the others are correct.
*/
-enum cip_out_flags {
- CIP_NONBLOCKING = 0x00,
- CIP_BLOCKING = 0x01,
- CIP_HI_DUALWIRE = 0x02,
+enum cip_flags {
+ CIP_NONBLOCKING = 0x00,
+ CIP_BLOCKING = 0x01,
+ CIP_SYNC_TO_DEVICE = 0x02,
+ CIP_EMPTY_WITH_TAG0 = 0x04,
+ CIP_DBC_IS_END_EVENT = 0x08,
+ CIP_WRONG_DBS = 0x10,
+ CIP_SKIP_DBC_ZERO_CHECK = 0x20,
+ CIP_SKIP_INIT_DBC_CHECK = 0x40,
+ CIP_EMPTY_HAS_WRONG_DBC = 0x80,
};
/**
@@ -41,27 +56,55 @@ enum cip_sfc {
CIP_SFC_COUNT
};
+#define AMDTP_IN_PCM_FORMAT_BITS SNDRV_PCM_FMTBIT_S32
+
#define AMDTP_OUT_PCM_FORMAT_BITS (SNDRV_PCM_FMTBIT_S16 | \
SNDRV_PCM_FMTBIT_S32)
+
+/*
+ * This module supports maximum 64 PCM channels for one PCM stream
+ * This is for our convenience.
+ */
+#define AMDTP_MAX_CHANNELS_FOR_PCM 64
+
+/*
+ * AMDTP packet can include channels for MIDI conformant data.
+ * Each MIDI conformant data channel includes 8 MPX-MIDI data stream.
+ * Each MPX-MIDI data stream includes one data stream from/to MIDI ports.
+ *
+ * This module supports maximum 1 MIDI conformant data channels.
+ * Then this AMDTP packets can transfer maximum 8 MIDI data streams.
+ */
+#define AMDTP_MAX_CHANNELS_FOR_MIDI 1
+
struct fw_unit;
struct fw_iso_context;
struct snd_pcm_substream;
+struct snd_pcm_runtime;
+struct snd_rawmidi_substream;
-struct amdtp_out_stream {
+enum amdtp_stream_direction {
+ AMDTP_OUT_STREAM = 0,
+ AMDTP_IN_STREAM
+};
+
+struct amdtp_stream {
struct fw_unit *unit;
- enum cip_out_flags flags;
+ enum cip_flags flags;
+ enum amdtp_stream_direction direction;
struct fw_iso_context *context;
struct mutex mutex;
enum cip_sfc sfc;
- bool dual_wire;
unsigned int data_block_quadlets;
unsigned int pcm_channels;
unsigned int midi_ports;
- void (*transfer_samples)(struct amdtp_out_stream *s,
+ void (*transfer_samples)(struct amdtp_stream *s,
struct snd_pcm_substream *pcm,
__be32 *buffer, unsigned int frames);
+ u8 pcm_positions[AMDTP_MAX_CHANNELS_FOR_PCM];
+ u8 midi_position;
unsigned int syt_interval;
unsigned int transfer_delay;
@@ -82,65 +125,148 @@ struct amdtp_out_stream {
unsigned int pcm_buffer_pointer;
unsigned int pcm_period_pointer;
bool pointer_flush;
+
+ struct snd_rawmidi_substream *midi[AMDTP_MAX_CHANNELS_FOR_MIDI * 8];
+
+ /* quirk: fixed interval of dbc between previos/current packets. */
+ unsigned int tx_dbc_interval;
+
+ /* quirk: the first count of data blocks in an rx packet for MIDI */
+ unsigned int rx_blocks_for_midi;
+
+ bool callbacked;
+ wait_queue_head_t callback_wait;
+ struct amdtp_stream *sync_slave;
};
-int amdtp_out_stream_init(struct amdtp_out_stream *s, struct fw_unit *unit,
- enum cip_out_flags flags);
-void amdtp_out_stream_destroy(struct amdtp_out_stream *s);
+int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit,
+ enum amdtp_stream_direction dir,
+ enum cip_flags flags);
+void amdtp_stream_destroy(struct amdtp_stream *s);
-void amdtp_out_stream_set_parameters(struct amdtp_out_stream *s,
- unsigned int rate,
- unsigned int pcm_channels,
- unsigned int midi_ports);
-unsigned int amdtp_out_stream_get_max_payload(struct amdtp_out_stream *s);
+void amdtp_stream_set_parameters(struct amdtp_stream *s,
+ unsigned int rate,
+ unsigned int pcm_channels,
+ unsigned int midi_ports);
+unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s);
-int amdtp_out_stream_start(struct amdtp_out_stream *s, int channel, int speed);
-void amdtp_out_stream_update(struct amdtp_out_stream *s);
-void amdtp_out_stream_stop(struct amdtp_out_stream *s);
+int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed);
+void amdtp_stream_update(struct amdtp_stream *s);
+void amdtp_stream_stop(struct amdtp_stream *s);
-void amdtp_out_stream_set_pcm_format(struct amdtp_out_stream *s,
- snd_pcm_format_t format);
-void amdtp_out_stream_pcm_prepare(struct amdtp_out_stream *s);
-unsigned long amdtp_out_stream_pcm_pointer(struct amdtp_out_stream *s);
-void amdtp_out_stream_pcm_abort(struct amdtp_out_stream *s);
+int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s,
+ struct snd_pcm_runtime *runtime);
+void amdtp_stream_set_pcm_format(struct amdtp_stream *s,
+ snd_pcm_format_t format);
+void amdtp_stream_pcm_prepare(struct amdtp_stream *s);
+unsigned long amdtp_stream_pcm_pointer(struct amdtp_stream *s);
+void amdtp_stream_pcm_abort(struct amdtp_stream *s);
extern const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT];
+extern const unsigned int amdtp_rate_table[CIP_SFC_COUNT];
-static inline bool amdtp_out_stream_running(struct amdtp_out_stream *s)
+/**
+ * amdtp_stream_running - check stream is running or not
+ * @s: the AMDTP stream
+ *
+ * If this function returns true, the stream is running.
+ */
+static inline bool amdtp_stream_running(struct amdtp_stream *s)
{
return !IS_ERR(s->context);
}
/**
- * amdtp_out_streaming_error - check for streaming error
- * @s: the AMDTP output stream
+ * amdtp_streaming_error - check for streaming error
+ * @s: the AMDTP stream
*
* If this function returns true, the stream's packet queue has stopped due to
* an asynchronous error.
*/
-static inline bool amdtp_out_streaming_error(struct amdtp_out_stream *s)
+static inline bool amdtp_streaming_error(struct amdtp_stream *s)
{
return s->packet_index < 0;
}
/**
- * amdtp_out_stream_pcm_trigger - start/stop playback from a PCM device
- * @s: the AMDTP output stream
+ * amdtp_stream_pcm_running - check PCM substream is running or not
+ * @s: the AMDTP stream
+ *
+ * If this function returns true, PCM substream in the AMDTP stream is running.
+ */
+static inline bool amdtp_stream_pcm_running(struct amdtp_stream *s)
+{
+ return !!s->pcm;
+}
+
+/**
+ * amdtp_stream_pcm_trigger - start/stop playback from a PCM device
+ * @s: the AMDTP stream
* @pcm: the PCM device to be started, or %NULL to stop the current device
*
* Call this function on a running isochronous stream to enable the actual
* transmission of PCM data. This function should be called from the PCM
* device's .trigger callback.
*/
-static inline void amdtp_out_stream_pcm_trigger(struct amdtp_out_stream *s,
- struct snd_pcm_substream *pcm)
+static inline void amdtp_stream_pcm_trigger(struct amdtp_stream *s,
+ struct snd_pcm_substream *pcm)
{
ACCESS_ONCE(s->pcm) = pcm;
}
+/**
+ * amdtp_stream_midi_trigger - start/stop playback/capture with a MIDI device
+ * @s: the AMDTP stream
+ * @port: index of MIDI port
+ * @midi: the MIDI device to be started, or %NULL to stop the current device
+ *
+ * Call this function on a running isochronous stream to enable the actual
+ * transmission of MIDI data. This function should be called from the MIDI
+ * device's .trigger callback.
+ */
+static inline void amdtp_stream_midi_trigger(struct amdtp_stream *s,
+ unsigned int port,
+ struct snd_rawmidi_substream *midi)
+{
+ if (port < s->midi_ports)
+ ACCESS_ONCE(s->midi[port]) = midi;
+}
+
static inline bool cip_sfc_is_base_44100(enum cip_sfc sfc)
{
return sfc & 1;
}
+static inline void amdtp_stream_set_sync(enum cip_flags sync_mode,
+ struct amdtp_stream *master,
+ struct amdtp_stream *slave)
+{
+ if (sync_mode == CIP_SYNC_TO_DEVICE) {
+ master->flags |= CIP_SYNC_TO_DEVICE;
+ slave->flags |= CIP_SYNC_TO_DEVICE;
+ master->sync_slave = slave;
+ } else {
+ master->flags &= ~CIP_SYNC_TO_DEVICE;
+ slave->flags &= ~CIP_SYNC_TO_DEVICE;
+ master->sync_slave = NULL;
+ }
+
+ slave->sync_slave = NULL;
+}
+
+/**
+ * amdtp_stream_wait_callback - sleep till callbacked or timeout
+ * @s: the AMDTP stream
+ * @timeout: msec till timeout
+ *
+ * If this function return false, the AMDTP stream should be stopped.
+ */
+static inline bool amdtp_stream_wait_callback(struct amdtp_stream *s,
+ unsigned int timeout)
+{
+ return wait_event_timeout(s->callback_wait,
+ s->callbacked == true,
+ msecs_to_jiffies(timeout)) > 0;
+}
+
#endif
diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile
new file mode 100644
index 000000000000..6cf470c80d1f
--- /dev/null
+++ b/sound/firewire/bebob/Makefile
@@ -0,0 +1,4 @@
+snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_midi.o \
+ bebob_pcm.o bebob_hwdep.o bebob_terratec.o bebob_yamaha.o \
+ bebob_focusrite.o bebob_maudio.o bebob.o
+obj-m += snd-bebob.o
diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c
new file mode 100644
index 000000000000..fc19c99654aa
--- /dev/null
+++ b/sound/firewire/bebob/bebob.c
@@ -0,0 +1,471 @@
+/*
+ * bebob.c - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+/*
+ * BeBoB is 'BridgeCo enhanced Breakout Box'. This is installed to firewire
+ * devices with DM1000/DM1100/DM1500 chipset. It gives common way for host
+ * system to handle BeBoB based devices.
+ */
+
+#include "bebob.h"
+
+MODULE_DESCRIPTION("BridgeCo BeBoB driver");
+MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
+MODULE_LICENSE("GPL v2");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "card index");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "enable BeBoB sound card");
+
+static DEFINE_MUTEX(devices_mutex);
+static DECLARE_BITMAP(devices_used, SNDRV_CARDS);
+
+/* Offsets from information register. */
+#define INFO_OFFSET_GUID 0x10
+#define INFO_OFFSET_HW_MODEL_ID 0x18
+#define INFO_OFFSET_HW_MODEL_REVISION 0x1c
+
+#define VEN_EDIROL 0x000040ab
+#define VEN_PRESONUS 0x00000a92
+#define VEN_BRIDGECO 0x000007f5
+#define VEN_MACKIE 0x0000000f
+#define VEN_STANTON 0x00001260
+#define VEN_TASCAM 0x0000022e
+#define VEN_BEHRINGER 0x00001564
+#define VEN_APOGEE 0x000003db
+#define VEN_ESI 0x00000f1b
+#define VEN_ACOUSTIC 0x00000002
+#define VEN_CME 0x0000000a
+#define VEN_PHONIC 0x00001496
+#define VEN_LYNX 0x000019e5
+#define VEN_ICON 0x00001a9e
+#define VEN_PRISMSOUND 0x00001198
+#define VEN_TERRATEC 0x00000aac
+#define VEN_YAMAHA 0x0000a0de
+#define VEN_FOCUSRITE 0x0000130e
+#define VEN_MAUDIO1 0x00000d6c
+#define VEN_MAUDIO2 0x000007f5
+
+#define MODEL_FOCUSRITE_SAFFIRE_BOTH 0x00000000
+#define MODEL_MAUDIO_AUDIOPHILE_BOTH 0x00010060
+#define MODEL_MAUDIO_FW1814 0x00010071
+#define MODEL_MAUDIO_PROJECTMIX 0x00010091
+
+static int
+name_device(struct snd_bebob *bebob, unsigned int vendor_id)
+{
+ struct fw_device *fw_dev = fw_parent_device(bebob->unit);
+ char vendor[24] = {0};
+ char model[32] = {0};
+ u32 hw_id;
+ u32 data[2] = {0};
+ u32 revision;
+ int err;
+
+ /* get vendor name from root directory */
+ err = fw_csr_string(fw_dev->config_rom + 5, CSR_VENDOR,
+ vendor, sizeof(vendor));
+ if (err < 0)
+ goto end;
+
+ /* get model name from unit directory */
+ err = fw_csr_string(bebob->unit->directory, CSR_MODEL,
+ model, sizeof(model));
+ if (err < 0)
+ goto end;
+
+ /* get hardware id */
+ err = snd_bebob_read_quad(bebob->unit, INFO_OFFSET_HW_MODEL_ID,
+ &hw_id);
+ if (err < 0)
+ goto end;
+
+ /* get hardware revision */
+ err = snd_bebob_read_quad(bebob->unit, INFO_OFFSET_HW_MODEL_REVISION,
+ &revision);
+ if (err < 0)
+ goto end;
+
+ /* get GUID */
+ err = snd_bebob_read_block(bebob->unit, INFO_OFFSET_GUID,
+ data, sizeof(data));
+ if (err < 0)
+ goto end;
+
+ strcpy(bebob->card->driver, "BeBoB");
+ strcpy(bebob->card->shortname, model);
+ strcpy(bebob->card->mixername, model);
+ snprintf(bebob->card->longname, sizeof(bebob->card->longname),
+ "%s %s (id:%d, rev:%d), GUID %08x%08x at %s, S%d",
+ vendor, model, hw_id, revision,
+ data[0], data[1], dev_name(&bebob->unit->device),
+ 100 << fw_dev->max_speed);
+end:
+ return err;
+}
+
+static void
+bebob_card_free(struct snd_card *card)
+{
+ struct snd_bebob *bebob = card->private_data;
+
+ if (bebob->card_index >= 0) {
+ mutex_lock(&devices_mutex);
+ clear_bit(bebob->card_index, devices_used);
+ mutex_unlock(&devices_mutex);
+ }
+
+ mutex_destroy(&bebob->mutex);
+}
+
+static const struct snd_bebob_spec *
+get_saffire_spec(struct fw_unit *unit)
+{
+ char name[24] = {0};
+
+ if (fw_csr_string(unit->directory, CSR_MODEL, name, sizeof(name)) < 0)
+ return NULL;
+
+ if (strcmp(name, "SaffireLE") == 0)
+ return &saffire_le_spec;
+ else
+ return &saffire_spec;
+}
+
+static bool
+check_audiophile_booted(struct fw_unit *unit)
+{
+ char name[24] = {0};
+
+ if (fw_csr_string(unit->directory, CSR_MODEL, name, sizeof(name)) < 0)
+ return false;
+
+ return strncmp(name, "FW Audiophile Bootloader", 15) != 0;
+}
+
+static int
+bebob_probe(struct fw_unit *unit,
+ const struct ieee1394_device_id *entry)
+{
+ struct snd_card *card;
+ struct snd_bebob *bebob;
+ const struct snd_bebob_spec *spec;
+ unsigned int card_index;
+ int err;
+
+ mutex_lock(&devices_mutex);
+
+ for (card_index = 0; card_index < SNDRV_CARDS; card_index++) {
+ if (!test_bit(card_index, devices_used) && enable[card_index])
+ break;
+ }
+ if (card_index >= SNDRV_CARDS) {
+ err = -ENOENT;
+ goto end;
+ }
+
+ if ((entry->vendor_id == VEN_FOCUSRITE) &&
+ (entry->model_id == MODEL_FOCUSRITE_SAFFIRE_BOTH))
+ spec = get_saffire_spec(unit);
+ else if ((entry->vendor_id == VEN_MAUDIO1) &&
+ (entry->model_id == MODEL_MAUDIO_AUDIOPHILE_BOTH) &&
+ !check_audiophile_booted(unit))
+ spec = NULL;
+ else
+ spec = (const struct snd_bebob_spec *)entry->driver_data;
+
+ if (spec == NULL) {
+ if ((entry->vendor_id == VEN_MAUDIO1) ||
+ (entry->vendor_id == VEN_MAUDIO2))
+ err = snd_bebob_maudio_load_firmware(unit);
+ else
+ err = -ENOSYS;
+ goto end;
+ }
+
+ err = snd_card_new(&unit->device, index[card_index], id[card_index],
+ THIS_MODULE, sizeof(struct snd_bebob), &card);
+ if (err < 0)
+ goto end;
+ bebob = card->private_data;
+ bebob->card_index = card_index;
+ set_bit(card_index, devices_used);
+ card->private_free = bebob_card_free;
+
+ bebob->card = card;
+ bebob->unit = unit;
+ bebob->spec = spec;
+ mutex_init(&bebob->mutex);
+ spin_lock_init(&bebob->lock);
+ init_waitqueue_head(&bebob->hwdep_wait);
+
+ err = name_device(bebob, entry->vendor_id);
+ if (err < 0)
+ goto error;
+
+ if ((entry->vendor_id == VEN_MAUDIO1) &&
+ (entry->model_id == MODEL_MAUDIO_FW1814))
+ err = snd_bebob_maudio_special_discover(bebob, true);
+ else if ((entry->vendor_id == VEN_MAUDIO1) &&
+ (entry->model_id == MODEL_MAUDIO_PROJECTMIX))
+ err = snd_bebob_maudio_special_discover(bebob, false);
+ else
+ err = snd_bebob_stream_discover(bebob);
+ if (err < 0)
+ goto error;
+
+ snd_bebob_proc_init(bebob);
+
+ if ((bebob->midi_input_ports > 0) ||
+ (bebob->midi_output_ports > 0)) {
+ err = snd_bebob_create_midi_devices(bebob);
+ if (err < 0)
+ goto error;
+ }
+
+ err = snd_bebob_create_pcm_devices(bebob);
+ if (err < 0)
+ goto error;
+
+ err = snd_bebob_create_hwdep_device(bebob);
+ if (err < 0)
+ goto error;
+
+ err = snd_bebob_stream_init_duplex(bebob);
+ if (err < 0)
+ goto error;
+
+ if (!bebob->maudio_special_quirk) {
+ err = snd_card_register(card);
+ if (err < 0) {
+ snd_bebob_stream_destroy_duplex(bebob);
+ goto error;
+ }
+ } else {
+ /*
+ * This is a workaround. This bus reset seems to have an effect
+ * to make devices correctly handling transactions. Without
+ * this, the devices have gap_count mismatch. This causes much
+ * failure of transaction.
+ *
+ * Just after registration, user-land application receive
+ * signals from dbus and starts I/Os. To avoid I/Os till the
+ * future bus reset, registration is done in next update().
+ */
+ bebob->deferred_registration = true;
+ fw_schedule_bus_reset(fw_parent_device(bebob->unit)->card,
+ false, true);
+ }
+
+ dev_set_drvdata(&unit->device, bebob);
+end:
+ mutex_unlock(&devices_mutex);
+ return err;
+error:
+ mutex_unlock(&devices_mutex);
+ snd_card_free(card);
+ return err;
+}
+
+static void
+bebob_update(struct fw_unit *unit)
+{
+ struct snd_bebob *bebob = dev_get_drvdata(&unit->device);
+
+ if (bebob == NULL)
+ return;
+
+ fcp_bus_reset(bebob->unit);
+ snd_bebob_stream_update_duplex(bebob);
+
+ if (bebob->deferred_registration) {
+ if (snd_card_register(bebob->card) < 0) {
+ snd_bebob_stream_destroy_duplex(bebob);
+ snd_card_free(bebob->card);
+ }
+ bebob->deferred_registration = false;
+ }
+}
+
+static void bebob_remove(struct fw_unit *unit)
+{
+ struct snd_bebob *bebob = dev_get_drvdata(&unit->device);
+
+ if (bebob == NULL)
+ return;
+
+ kfree(bebob->maudio_special_quirk);
+
+ snd_bebob_stream_destroy_duplex(bebob);
+ snd_card_disconnect(bebob->card);
+ snd_card_free_when_closed(bebob->card);
+}
+
+static struct snd_bebob_rate_spec normal_rate_spec = {
+ .get = &snd_bebob_stream_get_rate,
+ .set = &snd_bebob_stream_set_rate
+};
+static const struct snd_bebob_spec spec_normal = {
+ .clock = NULL,
+ .rate = &normal_rate_spec,
+ .meter = NULL
+};
+
+static const struct ieee1394_device_id bebob_id_table[] = {
+ /* Edirol, FA-66 */
+ SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010049, &spec_normal),
+ /* Edirol, FA-101 */
+ SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010048, &spec_normal),
+ /* Presonus, FIREBOX */
+ SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010000, &spec_normal),
+ /* PreSonus, FIREPOD/FP10 */
+ SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010066, &spec_normal),
+ /* PreSonus, Inspire1394 */
+ SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010001, &spec_normal),
+ /* BridgeCo, RDAudio1 */
+ SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010048, &spec_normal),
+ /* BridgeCo, Audio5 */
+ SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010049, &spec_normal),
+ /* Mackie, Onyx 1220/1620/1640 (Firewire I/O Card) */
+ SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010065, &spec_normal),
+ /* Mackie, d.2 (Firewire Option) */
+ SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010067, &spec_normal),
+ /* Stanton, ScratchAmp */
+ SND_BEBOB_DEV_ENTRY(VEN_STANTON, 0x00000001, &spec_normal),
+ /* Tascam, IF-FW DM */
+ SND_BEBOB_DEV_ENTRY(VEN_TASCAM, 0x00010067, &spec_normal),
+ /* Behringer, XENIX UFX 1204 */
+ SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00001204, &spec_normal),
+ /* Behringer, XENIX UFX 1604 */
+ SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00001604, &spec_normal),
+ /* Behringer, Digital Mixer X32 series (X-UF Card) */
+ SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00000006, &spec_normal),
+ /* Apogee Electronics, Rosetta 200/400 (X-FireWire card) */
+ /* Apogee Electronics, DA/AD/DD-16X (X-FireWire card) */
+ SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x00010048, &spec_normal),
+ /* Apogee Electronics, Ensemble */
+ SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x00001eee, &spec_normal),
+ /* ESI, Quatafire610 */
+ SND_BEBOB_DEV_ENTRY(VEN_ESI, 0x00010064, &spec_normal),
+ /* AcousticReality, eARMasterOne */
+ SND_BEBOB_DEV_ENTRY(VEN_ACOUSTIC, 0x00000002, &spec_normal),
+ /* CME, MatrixKFW */
+ SND_BEBOB_DEV_ENTRY(VEN_CME, 0x00030000, &spec_normal),
+ /* Phonic, Helix Board 12 MkII */
+ SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00050000, &spec_normal),
+ /* Phonic, Helix Board 18 MkII */
+ SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00060000, &spec_normal),
+ /* Phonic, Helix Board 24 MkII */
+ SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00070000, &spec_normal),
+ /* Phonic, Helix Board 12 Universal/18 Universal/24 Universal */
+ SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00000000, &spec_normal),
+ /* Lynx, Aurora 8/16 (LT-FW) */
+ SND_BEBOB_DEV_ENTRY(VEN_LYNX, 0x00000001, &spec_normal),
+ /* ICON, FireXon */
+ SND_BEBOB_DEV_ENTRY(VEN_ICON, 0x00000001, &spec_normal),
+ /* PrismSound, Orpheus */
+ SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x00010048, &spec_normal),
+ /* PrismSound, ADA-8XR */
+ SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x0000ada8, &spec_normal),
+ /* TerraTec Electronic GmbH, PHASE 88 Rack FW */
+ SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000003, &phase88_rack_spec),
+ /* TerraTec Electronic GmbH, PHASE 24 FW */
+ SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000004, &phase24_series_spec),
+ /* TerraTec Electronic GmbH, Phase X24 FW */
+ SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000007, &phase24_series_spec),
+ /* TerraTec Electronic GmbH, EWS MIC2/MIC8 */
+ SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000005, &spec_normal),
+ /* Terratec Electronic GmbH, Aureon 7.1 Firewire */
+ SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000002, &spec_normal),
+ /* Yamaha, GO44 */
+ SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000b, &yamaha_go_spec),
+ /* YAMAHA, GO46 */
+ SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000c, &yamaha_go_spec),
+ /* Focusrite, SaffirePro 26 I/O */
+ SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, 0x00000003, &saffirepro_26_spec),
+ /* Focusrite, SaffirePro 10 I/O */
+ SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, 0x00000006, &saffirepro_10_spec),
+ /* Focusrite, Saffire(no label and LE) */
+ SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, MODEL_FOCUSRITE_SAFFIRE_BOTH,
+ &saffire_spec),
+ /* M-Audio, Firewire 410 */
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO2, 0x00010058, NULL), /* bootloader */
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO2, 0x00010046, &maudio_fw410_spec),
+ /* M-Audio, Firewire Audiophile */
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_AUDIOPHILE_BOTH,
+ &maudio_audiophile_spec),
+ /* M-Audio, Firewire Solo */
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010062, &maudio_solo_spec),
+ /* M-Audio, Ozonic */
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x0000000a, &maudio_ozonic_spec),
+ /* M-Audio NRV10 */
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010081, &maudio_nrv10_spec),
+ /* M-Audio, ProFireLightbridge */
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x000100a1, &spec_normal),
+ /* Firewire 1814 */
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010070, NULL), /* bootloader */
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_FW1814,
+ &maudio_special_spec),
+ /* M-Audio ProjectMix */
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_PROJECTMIX,
+ &maudio_special_spec),
+ /* IDs are unknown but able to be supported */
+ /* Apogee, Mini-ME Firewire */
+ /* Apogee, Mini-DAC Firewire */
+ /* Behringer, F-Control Audio 1616 */
+ /* Behringer, F-Control Audio 610 */
+ /* Cakawalk, Sonar Power Studio 66 */
+ /* CME, UF400e */
+ /* ESI, Quotafire XL */
+ /* Infrasonic, DewX */
+ /* Infrasonic, Windy6 */
+ /* Mackie, Digital X Bus x.200 */
+ /* Mackie, Digital X Bus x.400 */
+ /* Phonic, HB 12 */
+ /* Phonic, HB 24 */
+ /* Phonic, HB 18 */
+ /* Phonic, FireFly 202 */
+ /* Phonic, FireFly 302 */
+ /* Rolf Spuler, Firewire Guitar */
+ {}
+};
+MODULE_DEVICE_TABLE(ieee1394, bebob_id_table);
+
+static struct fw_driver bebob_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "snd-bebob",
+ .bus = &fw_bus_type,
+ },
+ .probe = bebob_probe,
+ .update = bebob_update,
+ .remove = bebob_remove,
+ .id_table = bebob_id_table,
+};
+
+static int __init
+snd_bebob_init(void)
+{
+ return driver_register(&bebob_driver.driver);
+}
+
+static void __exit
+snd_bebob_exit(void)
+{
+ driver_unregister(&bebob_driver.driver);
+}
+
+module_init(snd_bebob_init);
+module_exit(snd_bebob_exit);
diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h
new file mode 100644
index 000000000000..e13eef99c27a
--- /dev/null
+++ b/sound/firewire/bebob/bebob.h
@@ -0,0 +1,255 @@
+/*
+ * bebob.h - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#ifndef SOUND_BEBOB_H_INCLUDED
+#define SOUND_BEBOB_H_INCLUDED
+
+#include <linux/compat.h>
+#include <linux/device.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/info.h>
+#include <sound/rawmidi.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/firewire.h>
+#include <sound/hwdep.h>
+
+#include "../lib.h"
+#include "../fcp.h"
+#include "../packets-buffer.h"
+#include "../iso-resources.h"
+#include "../amdtp.h"
+#include "../cmp.h"
+
+/* basic register addresses on DM1000/DM1100/DM1500 */
+#define BEBOB_ADDR_REG_INFO 0xffffc8020000ULL
+#define BEBOB_ADDR_REG_REQ 0xffffc8021000ULL
+
+struct snd_bebob;
+
+#define SND_BEBOB_STRM_FMT_ENTRIES 7
+struct snd_bebob_stream_formation {
+ unsigned int pcm;
+ unsigned int midi;
+};
+/* this is a lookup table for index of stream formations */
+extern const unsigned int snd_bebob_rate_table[SND_BEBOB_STRM_FMT_ENTRIES];
+
+/* device specific operations */
+#define SND_BEBOB_CLOCK_INTERNAL "Internal"
+struct snd_bebob_clock_spec {
+ unsigned int num;
+ char *const *labels;
+ int (*get)(struct snd_bebob *bebob, unsigned int *id);
+};
+struct snd_bebob_rate_spec {
+ int (*get)(struct snd_bebob *bebob, unsigned int *rate);
+ int (*set)(struct snd_bebob *bebob, unsigned int rate);
+};
+struct snd_bebob_meter_spec {
+ unsigned int num;
+ char *const *labels;
+ int (*get)(struct snd_bebob *bebob, u32 *target, unsigned int size);
+};
+struct snd_bebob_spec {
+ struct snd_bebob_clock_spec *clock;
+ struct snd_bebob_rate_spec *rate;
+ struct snd_bebob_meter_spec *meter;
+};
+
+struct snd_bebob {
+ struct snd_card *card;
+ struct fw_unit *unit;
+ int card_index;
+
+ struct mutex mutex;
+ spinlock_t lock;
+
+ const struct snd_bebob_spec *spec;
+
+ unsigned int midi_input_ports;
+ unsigned int midi_output_ports;
+
+ /* for bus reset quirk */
+ struct completion bus_reset;
+ bool connected;
+
+ struct amdtp_stream *master;
+ struct amdtp_stream tx_stream;
+ struct amdtp_stream rx_stream;
+ struct cmp_connection out_conn;
+ struct cmp_connection in_conn;
+ atomic_t capture_substreams;
+ atomic_t playback_substreams;
+
+ struct snd_bebob_stream_formation
+ tx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES];
+ struct snd_bebob_stream_formation
+ rx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES];
+
+ int sync_input_plug;
+
+ /* for uapi */
+ int dev_lock_count;
+ bool dev_lock_changed;
+ wait_queue_head_t hwdep_wait;
+
+ /* for M-Audio special devices */
+ void *maudio_special_quirk;
+ bool deferred_registration;
+};
+
+static inline int
+snd_bebob_read_block(struct fw_unit *unit, u64 addr, void *buf, int size)
+{
+ return snd_fw_transaction(unit, TCODE_READ_BLOCK_REQUEST,
+ BEBOB_ADDR_REG_INFO + addr,
+ buf, size, 0);
+}
+
+static inline int
+snd_bebob_read_quad(struct fw_unit *unit, u64 addr, u32 *buf)
+{
+ return snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST,
+ BEBOB_ADDR_REG_INFO + addr,
+ (void *)buf, sizeof(u32), 0);
+}
+
+/* AV/C Audio Subunit Specification 1.0 (Oct 2000, 1394TA) */
+int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id,
+ unsigned int fb_id, unsigned int num);
+int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id,
+ unsigned int fb_id, unsigned int *num);
+
+/*
+ * AVC command extensions, AV/C Unit and Subunit, Revision 17
+ * (Nov 2003, BridgeCo)
+ */
+#define AVC_BRIDGECO_ADDR_BYTES 6
+enum avc_bridgeco_plug_dir {
+ AVC_BRIDGECO_PLUG_DIR_IN = 0x00,
+ AVC_BRIDGECO_PLUG_DIR_OUT = 0x01
+};
+enum avc_bridgeco_plug_mode {
+ AVC_BRIDGECO_PLUG_MODE_UNIT = 0x00,
+ AVC_BRIDGECO_PLUG_MODE_SUBUNIT = 0x01,
+ AVC_BRIDGECO_PLUG_MODE_FUNCTION_BLOCK = 0x02
+};
+enum avc_bridgeco_plug_unit {
+ AVC_BRIDGECO_PLUG_UNIT_ISOC = 0x00,
+ AVC_BRIDGECO_PLUG_UNIT_EXT = 0x01,
+ AVC_BRIDGECO_PLUG_UNIT_ASYNC = 0x02
+};
+enum avc_bridgeco_plug_type {
+ AVC_BRIDGECO_PLUG_TYPE_ISOC = 0x00,
+ AVC_BRIDGECO_PLUG_TYPE_ASYNC = 0x01,
+ AVC_BRIDGECO_PLUG_TYPE_MIDI = 0x02,
+ AVC_BRIDGECO_PLUG_TYPE_SYNC = 0x03,
+ AVC_BRIDGECO_PLUG_TYPE_ANA = 0x04,
+ AVC_BRIDGECO_PLUG_TYPE_DIG = 0x05
+};
+static inline void
+avc_bridgeco_fill_unit_addr(u8 buf[AVC_BRIDGECO_ADDR_BYTES],
+ enum avc_bridgeco_plug_dir dir,
+ enum avc_bridgeco_plug_unit unit,
+ unsigned int pid)
+{
+ buf[0] = 0xff; /* Unit */
+ buf[1] = dir;
+ buf[2] = AVC_BRIDGECO_PLUG_MODE_UNIT;
+ buf[3] = unit;
+ buf[4] = 0xff & pid;
+ buf[5] = 0xff; /* reserved */
+}
+static inline void
+avc_bridgeco_fill_msu_addr(u8 buf[AVC_BRIDGECO_ADDR_BYTES],
+ enum avc_bridgeco_plug_dir dir,
+ unsigned int pid)
+{
+ buf[0] = 0x60; /* Music subunit */
+ buf[1] = dir;
+ buf[2] = AVC_BRIDGECO_PLUG_MODE_SUBUNIT;
+ buf[3] = 0xff & pid;
+ buf[4] = 0xff; /* reserved */
+ buf[5] = 0xff; /* reserved */
+}
+int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES],
+ u8 *buf, unsigned int len);
+int avc_bridgeco_get_plug_type(struct fw_unit *unit,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES],
+ enum avc_bridgeco_plug_type *type);
+int avc_bridgeco_get_plug_section_type(struct fw_unit *unit,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES],
+ unsigned int id, u8 *type);
+int avc_bridgeco_get_plug_input(struct fw_unit *unit,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES],
+ u8 input[7]);
+int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 *buf,
+ unsigned int *len, unsigned int eid);
+
+/* for AMDTP streaming */
+int snd_bebob_stream_get_rate(struct snd_bebob *bebob, unsigned int *rate);
+int snd_bebob_stream_set_rate(struct snd_bebob *bebob, unsigned int rate);
+int snd_bebob_stream_check_internal_clock(struct snd_bebob *bebob,
+ bool *internal);
+int snd_bebob_stream_discover(struct snd_bebob *bebob);
+int snd_bebob_stream_init_duplex(struct snd_bebob *bebob);
+int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, unsigned int rate);
+void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob);
+void snd_bebob_stream_update_duplex(struct snd_bebob *bebob);
+void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob);
+
+void snd_bebob_stream_lock_changed(struct snd_bebob *bebob);
+int snd_bebob_stream_lock_try(struct snd_bebob *bebob);
+void snd_bebob_stream_lock_release(struct snd_bebob *bebob);
+
+void snd_bebob_proc_init(struct snd_bebob *bebob);
+
+int snd_bebob_create_midi_devices(struct snd_bebob *bebob);
+
+int snd_bebob_create_pcm_devices(struct snd_bebob *bebob);
+
+int snd_bebob_create_hwdep_device(struct snd_bebob *bebob);
+
+/* model specific operations */
+extern struct snd_bebob_spec phase88_rack_spec;
+extern struct snd_bebob_spec phase24_series_spec;
+extern struct snd_bebob_spec yamaha_go_spec;
+extern struct snd_bebob_spec saffirepro_26_spec;
+extern struct snd_bebob_spec saffirepro_10_spec;
+extern struct snd_bebob_spec saffire_le_spec;
+extern struct snd_bebob_spec saffire_spec;
+extern struct snd_bebob_spec maudio_fw410_spec;
+extern struct snd_bebob_spec maudio_audiophile_spec;
+extern struct snd_bebob_spec maudio_solo_spec;
+extern struct snd_bebob_spec maudio_ozonic_spec;
+extern struct snd_bebob_spec maudio_nrv10_spec;
+extern struct snd_bebob_spec maudio_special_spec;
+int snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814);
+int snd_bebob_maudio_load_firmware(struct fw_unit *unit);
+
+#define SND_BEBOB_DEV_ENTRY(vendor, model, data) \
+{ \
+ .match_flags = IEEE1394_MATCH_VENDOR_ID | \
+ IEEE1394_MATCH_MODEL_ID, \
+ .vendor_id = vendor, \
+ .model_id = model, \
+ .driver_data = (kernel_ulong_t)data \
+}
+
+#endif
diff --git a/sound/firewire/bebob/bebob_command.c b/sound/firewire/bebob/bebob_command.c
new file mode 100644
index 000000000000..9402cc15dbc1
--- /dev/null
+++ b/sound/firewire/bebob/bebob_command.c
@@ -0,0 +1,282 @@
+/*
+ * bebob_command.c - driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "./bebob.h"
+
+int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id,
+ unsigned int fb_id, unsigned int num)
+{
+ u8 *buf;
+ int err;
+
+ buf = kzalloc(12, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ buf[0] = 0x00; /* AV/C CONTROL */
+ buf[1] = 0x08 | (0x07 & subunit_id); /* AUDIO SUBUNIT ID */
+ buf[2] = 0xb8; /* FUNCTION BLOCK */
+ buf[3] = 0x80; /* type is 'selector'*/
+ buf[4] = 0xff & fb_id; /* function block id */
+ buf[5] = 0x10; /* control attribute is CURRENT */
+ buf[6] = 0x02; /* selector length is 2 */
+ buf[7] = 0xff & num; /* input function block plug number */
+ buf[8] = 0x01; /* control selector is SELECTOR_CONTROL */
+
+ 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)
+ err = -EIO;
+ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) /* REJECTED */
+ err = -EINVAL;
+ else if (err > 0)
+ err = 0;
+
+ kfree(buf);
+ return err;
+}
+
+int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id,
+ unsigned int fb_id, unsigned int *num)
+{
+ u8 *buf;
+ int err;
+
+ buf = kzalloc(12, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ buf[0] = 0x01; /* AV/C STATUS */
+ buf[1] = 0x08 | (0x07 & subunit_id); /* AUDIO SUBUNIT ID */
+ buf[2] = 0xb8; /* FUNCTION BLOCK */
+ buf[3] = 0x80; /* type is 'selector'*/
+ buf[4] = 0xff & fb_id; /* function block id */
+ buf[5] = 0x10; /* control attribute is CURRENT */
+ buf[6] = 0x02; /* selector length is 2 */
+ buf[7] = 0xff; /* input function block plug number */
+ buf[8] = 0x01; /* control selector is SELECTOR_CONTROL */
+
+ 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)
+ err = -EIO;
+ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) /* REJECTED */
+ err = -EINVAL;
+ else if (buf[0] == 0x0b) /* IN TRANSITION */
+ err = -EAGAIN;
+ if (err < 0)
+ goto end;
+
+ *num = buf[7];
+ err = 0;
+end:
+ kfree(buf);
+ return err;
+}
+
+static inline void
+avc_bridgeco_fill_extension_addr(u8 *buf, u8 *addr)
+{
+ buf[1] = addr[0];
+ memcpy(buf + 4, addr + 1, 5);
+}
+
+static inline void
+avc_bridgeco_fill_plug_info_extension_command(u8 *buf, u8 *addr,
+ unsigned int itype)
+{
+ buf[0] = 0x01; /* AV/C STATUS */
+ buf[2] = 0x02; /* AV/C GENERAL PLUG INFO */
+ buf[3] = 0xc0; /* BridgeCo extension */
+ avc_bridgeco_fill_extension_addr(buf, addr);
+ buf[9] = itype; /* info type */
+}
+
+int avc_bridgeco_get_plug_type(struct fw_unit *unit,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES],
+ enum avc_bridgeco_plug_type *type)
+{
+ u8 *buf;
+ int err;
+
+ buf = kzalloc(12, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ /* Info type is 'plug type'. */
+ avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x00);
+
+ 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))
+ err = -EIO;
+ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) /* REJECTED */
+ err = -EINVAL;
+ else if (buf[0] == 0x0b) /* IN TRANSITION */
+ err = -EAGAIN;
+ if (err < 0)
+ goto end;
+
+ *type = buf[10];
+ err = 0;
+end:
+ kfree(buf);
+ return err;
+}
+
+int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES],
+ u8 *buf, unsigned int len)
+{
+ int err;
+
+ /* Info type is 'channel position'. */
+ avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x03);
+
+ 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))
+ err = -EIO;
+ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) /* REJECTED */
+ err = -EINVAL;
+ else if (buf[0] == 0x0b) /* IN TRANSITION */
+ err = -EAGAIN;
+ if (err < 0)
+ goto end;
+
+ /* Pick up specific data. */
+ memmove(buf, buf + 10, err - 10);
+ err = 0;
+end:
+ return err;
+}
+
+int avc_bridgeco_get_plug_section_type(struct fw_unit *unit,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES],
+ unsigned int id, u8 *type)
+{
+ u8 *buf;
+ int err;
+
+ /* section info includes charactors but this module don't need it */
+ buf = kzalloc(12, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ /* Info type is 'section info'. */
+ avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x07);
+ buf[10] = 0xff & ++id; /* section 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(9) | BIT(10));
+ if ((err >= 0) && (err < 8))
+ err = -EIO;
+ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) /* REJECTED */
+ err = -EINVAL;
+ else if (buf[0] == 0x0b) /* IN TRANSITION */
+ err = -EAGAIN;
+ if (err < 0)
+ goto end;
+
+ *type = buf[11];
+ err = 0;
+end:
+ kfree(buf);
+ return err;
+}
+
+int avc_bridgeco_get_plug_input(struct fw_unit *unit,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 input[7])
+{
+ int err;
+ u8 *buf;
+
+ buf = kzalloc(18, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ /* Info type is 'plug input'. */
+ avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x05);
+
+ 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))
+ err = -EIO;
+ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) /* REJECTED */
+ err = -EINVAL;
+ else if (buf[0] == 0x0b) /* IN TRANSITION */
+ err = -EAGAIN;
+ if (err < 0)
+ goto end;
+
+ memcpy(input, buf + 10, 5);
+ err = 0;
+end:
+ kfree(buf);
+ return err;
+}
+
+int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 *buf,
+ unsigned int *len, unsigned int eid)
+{
+ int err;
+
+ /* check given buffer */
+ if ((buf == NULL) || (*len < 12)) {
+ err = -EINVAL;
+ goto end;
+ }
+
+ buf[0] = 0x01; /* AV/C STATUS */
+ buf[2] = 0x2f; /* AV/C STREAM FORMAT SUPPORT */
+ buf[3] = 0xc1; /* Bridgeco extension - List Request */
+ avc_bridgeco_fill_extension_addr(buf, addr);
+ buf[10] = 0xff & eid; /* Entry ID */
+
+ 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))
+ err = -EIO;
+ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) /* REJECTED */
+ err = -EINVAL;
+ else if (buf[0] == 0x0b) /* IN TRANSITION */
+ err = -EAGAIN;
+ else if (buf[10] != eid)
+ err = -EIO;
+ if (err < 0)
+ goto end;
+
+ /* Pick up 'stream format info'. */
+ memmove(buf, buf + 11, err - 11);
+ *len = err - 11;
+ err = 0;
+end:
+ return err;
+}
diff --git a/sound/firewire/bebob/bebob_focusrite.c b/sound/firewire/bebob/bebob_focusrite.c
new file mode 100644
index 000000000000..45a0eed6d5b1
--- /dev/null
+++ b/sound/firewire/bebob/bebob_focusrite.c
@@ -0,0 +1,279 @@
+/*
+ * bebob_focusrite.c - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "./bebob.h"
+
+#define ANA_IN "Analog In"
+#define DIG_IN "Digital In"
+#define ANA_OUT "Analog Out"
+#define DIG_OUT "Digital Out"
+#define STM_IN "Stream In"
+
+#define SAFFIRE_ADDRESS_BASE 0x000100000000ULL
+
+#define SAFFIRE_OFFSET_CLOCK_SOURCE 0x00f8
+#define SAFFIREPRO_OFFSET_CLOCK_SOURCE 0x0174
+
+/* whether sync to external device or not */
+#define SAFFIRE_OFFSET_CLOCK_SYNC_EXT 0x013c
+#define SAFFIRE_LE_OFFSET_CLOCK_SYNC_EXT 0x0432
+#define SAFFIREPRO_OFFSET_CLOCK_SYNC_EXT 0x0164
+
+#define SAFFIRE_CLOCK_SOURCE_INTERNAL 0
+#define SAFFIRE_CLOCK_SOURCE_SPDIF 1
+
+/* '1' is absent, why... */
+#define SAFFIREPRO_CLOCK_SOURCE_INTERNAL 0
+#define SAFFIREPRO_CLOCK_SOURCE_SPDIF 2
+#define SAFFIREPRO_CLOCK_SOURCE_ADAT1 3
+#define SAFFIREPRO_CLOCK_SOURCE_ADAT2 4
+#define SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK 5
+
+/* S/PDIF, ADAT1, ADAT2 is enabled or not. three quadlets */
+#define SAFFIREPRO_ENABLE_DIG_IFACES 0x01a4
+
+/* saffirepro has its own parameter for sampling frequency */
+#define SAFFIREPRO_RATE_NOREBOOT 0x01cc
+/* index is the value for this register */
+static const unsigned int rates[] = {
+ [0] = 0,
+ [1] = 44100,
+ [2] = 48000,
+ [3] = 88200,
+ [4] = 96000,
+ [5] = 176400,
+ [6] = 192000
+};
+
+/* saffire(no label)/saffire LE has metering */
+#define SAFFIRE_OFFSET_METER 0x0100
+#define SAFFIRE_LE_OFFSET_METER 0x0168
+
+static inline int
+saffire_read_block(struct snd_bebob *bebob, u64 offset,
+ u32 *buf, unsigned int size)
+{
+ unsigned int i;
+ int err;
+ __be32 *tmp = (__be32 *)buf;
+
+ err = snd_fw_transaction(bebob->unit, TCODE_READ_BLOCK_REQUEST,
+ SAFFIRE_ADDRESS_BASE + offset,
+ tmp, size, 0);
+ if (err < 0)
+ goto end;
+
+ for (i = 0; i < size / sizeof(u32); i++)
+ buf[i] = be32_to_cpu(tmp[i]);
+end:
+ return err;
+}
+
+static inline int
+saffire_read_quad(struct snd_bebob *bebob, u64 offset, u32 *value)
+{
+ int err;
+ __be32 tmp;
+
+ err = snd_fw_transaction(bebob->unit, TCODE_READ_QUADLET_REQUEST,
+ SAFFIRE_ADDRESS_BASE + offset,
+ &tmp, sizeof(__be32), 0);
+ if (err < 0)
+ goto end;
+
+ *value = be32_to_cpu(tmp);
+end:
+ return err;
+}
+
+static inline int
+saffire_write_quad(struct snd_bebob *bebob, u64 offset, u32 value)
+{
+ __be32 data = cpu_to_be32(value);
+
+ return snd_fw_transaction(bebob->unit, TCODE_WRITE_QUADLET_REQUEST,
+ SAFFIRE_ADDRESS_BASE + offset,
+ &data, sizeof(__be32), 0);
+}
+
+static char *const saffirepro_26_clk_src_labels[] = {
+ SND_BEBOB_CLOCK_INTERNAL, "S/PDIF", "ADAT1", "ADAT2", "Word Clock"
+};
+
+static char *const saffirepro_10_clk_src_labels[] = {
+ SND_BEBOB_CLOCK_INTERNAL, "S/PDIF", "Word Clock"
+};
+static int
+saffirepro_both_clk_freq_get(struct snd_bebob *bebob, unsigned int *rate)
+{
+ u32 id;
+ int err;
+
+ err = saffire_read_quad(bebob, SAFFIREPRO_RATE_NOREBOOT, &id);
+ if (err < 0)
+ goto end;
+ if (id >= ARRAY_SIZE(rates))
+ err = -EIO;
+ else
+ *rate = rates[id];
+end:
+ return err;
+}
+static int
+saffirepro_both_clk_freq_set(struct snd_bebob *bebob, unsigned int rate)
+{
+ u32 id;
+
+ for (id = 0; id < ARRAY_SIZE(rates); id++) {
+ if (rates[id] == rate)
+ break;
+ }
+ if (id == ARRAY_SIZE(rates))
+ return -EINVAL;
+
+ return saffire_write_quad(bebob, SAFFIREPRO_RATE_NOREBOOT, id);
+}
+static int
+saffirepro_both_clk_src_get(struct snd_bebob *bebob, unsigned int *id)
+{
+ int err;
+ u32 value;
+
+ err = saffire_read_quad(bebob, SAFFIREPRO_OFFSET_CLOCK_SOURCE, &value);
+ if (err < 0)
+ goto end;
+
+ if (bebob->spec->clock->labels == saffirepro_10_clk_src_labels) {
+ if (value == SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK)
+ *id = 2;
+ else if (value == SAFFIREPRO_CLOCK_SOURCE_SPDIF)
+ *id = 1;
+ } else if (value > 1) {
+ *id = value - 1;
+ }
+end:
+ return err;
+}
+
+struct snd_bebob_spec saffire_le_spec;
+static char *const saffire_both_clk_src_labels[] = {
+ SND_BEBOB_CLOCK_INTERNAL, "S/PDIF"
+};
+static int
+saffire_both_clk_src_get(struct snd_bebob *bebob, unsigned int *id)
+{
+ int err;
+ u32 value;
+
+ err = saffire_read_quad(bebob, SAFFIRE_OFFSET_CLOCK_SOURCE, &value);
+ if (err >= 0)
+ *id = 0xff & value;
+
+ return err;
+};
+static char *const saffire_le_meter_labels[] = {
+ ANA_IN, ANA_IN, DIG_IN,
+ ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT,
+ STM_IN, STM_IN
+};
+static char *const saffire_meter_labels[] = {
+ ANA_IN, ANA_IN,
+ STM_IN, STM_IN, STM_IN, STM_IN, STM_IN,
+};
+static int
+saffire_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size)
+{
+ struct snd_bebob_meter_spec *spec = bebob->spec->meter;
+ unsigned int channels;
+ u64 offset;
+ int err;
+
+ if (spec->labels == saffire_le_meter_labels)
+ offset = SAFFIRE_LE_OFFSET_METER;
+ else
+ offset = SAFFIRE_OFFSET_METER;
+
+ channels = spec->num * 2;
+ if (size < channels * sizeof(u32))
+ return -EIO;
+
+ err = saffire_read_block(bebob, offset, buf, size);
+ if (err >= 0 && spec->labels == saffire_le_meter_labels) {
+ swap(buf[1], buf[3]);
+ swap(buf[2], buf[3]);
+ swap(buf[3], buf[4]);
+
+ swap(buf[7], buf[10]);
+ swap(buf[8], buf[10]);
+ swap(buf[9], buf[11]);
+ swap(buf[11], buf[12]);
+
+ swap(buf[15], buf[16]);
+ }
+
+ return err;
+}
+
+static struct snd_bebob_rate_spec saffirepro_both_rate_spec = {
+ .get = &saffirepro_both_clk_freq_get,
+ .set = &saffirepro_both_clk_freq_set,
+};
+/* Saffire Pro 26 I/O */
+static struct snd_bebob_clock_spec saffirepro_26_clk_spec = {
+ .num = ARRAY_SIZE(saffirepro_26_clk_src_labels),
+ .labels = saffirepro_26_clk_src_labels,
+ .get = &saffirepro_both_clk_src_get,
+};
+struct snd_bebob_spec saffirepro_26_spec = {
+ .clock = &saffirepro_26_clk_spec,
+ .rate = &saffirepro_both_rate_spec,
+ .meter = NULL
+};
+/* Saffire Pro 10 I/O */
+static struct snd_bebob_clock_spec saffirepro_10_clk_spec = {
+ .num = ARRAY_SIZE(saffirepro_10_clk_src_labels),
+ .labels = saffirepro_10_clk_src_labels,
+ .get = &saffirepro_both_clk_src_get,
+};
+struct snd_bebob_spec saffirepro_10_spec = {
+ .clock = &saffirepro_10_clk_spec,
+ .rate = &saffirepro_both_rate_spec,
+ .meter = NULL
+};
+
+static struct snd_bebob_rate_spec saffire_both_rate_spec = {
+ .get = &snd_bebob_stream_get_rate,
+ .set = &snd_bebob_stream_set_rate,
+};
+static struct snd_bebob_clock_spec saffire_both_clk_spec = {
+ .num = ARRAY_SIZE(saffire_both_clk_src_labels),
+ .labels = saffire_both_clk_src_labels,
+ .get = &saffire_both_clk_src_get,
+};
+/* Saffire LE */
+static struct snd_bebob_meter_spec saffire_le_meter_spec = {
+ .num = ARRAY_SIZE(saffire_le_meter_labels),
+ .labels = saffire_le_meter_labels,
+ .get = &saffire_meter_get,
+};
+struct snd_bebob_spec saffire_le_spec = {
+ .clock = &saffire_both_clk_spec,
+ .rate = &saffire_both_rate_spec,
+ .meter = &saffire_le_meter_spec
+};
+/* Saffire */
+static struct snd_bebob_meter_spec saffire_meter_spec = {
+ .num = ARRAY_SIZE(saffire_meter_labels),
+ .labels = saffire_meter_labels,
+ .get = &saffire_meter_get,
+};
+struct snd_bebob_spec saffire_spec = {
+ .clock = &saffire_both_clk_spec,
+ .rate = &saffire_both_rate_spec,
+ .meter = &saffire_meter_spec
+};
diff --git a/sound/firewire/bebob/bebob_hwdep.c b/sound/firewire/bebob/bebob_hwdep.c
new file mode 100644
index 000000000000..ce731f4d8b4f
--- /dev/null
+++ b/sound/firewire/bebob/bebob_hwdep.c
@@ -0,0 +1,199 @@
+/*
+ * bebob_hwdep.c - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+/*
+ * This codes give three functionality.
+ *
+ * 1.get firewire node infomation
+ * 2.get notification about starting/stopping stream
+ * 3.lock/unlock stream
+ */
+
+#include "bebob.h"
+
+static long
+hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
+ loff_t *offset)
+{
+ struct snd_bebob *bebob = hwdep->private_data;
+ DEFINE_WAIT(wait);
+ union snd_firewire_event event;
+
+ spin_lock_irq(&bebob->lock);
+
+ while (!bebob->dev_lock_changed) {
+ prepare_to_wait(&bebob->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
+ spin_unlock_irq(&bebob->lock);
+ schedule();
+ finish_wait(&bebob->hwdep_wait, &wait);
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+ spin_lock_irq(&bebob->lock);
+ }
+
+ memset(&event, 0, sizeof(event));
+ if (bebob->dev_lock_changed) {
+ event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
+ event.lock_status.status = (bebob->dev_lock_count > 0);
+ bebob->dev_lock_changed = false;
+
+ count = min_t(long, count, sizeof(event.lock_status));
+ }
+
+ spin_unlock_irq(&bebob->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_bebob *bebob = hwdep->private_data;
+ unsigned int events;
+
+ poll_wait(file, &bebob->hwdep_wait, wait);
+
+ spin_lock_irq(&bebob->lock);
+ if (bebob->dev_lock_changed)
+ events = POLLIN | POLLRDNORM;
+ else
+ events = 0;
+ spin_unlock_irq(&bebob->lock);
+
+ return events;
+}
+
+static int
+hwdep_get_info(struct snd_bebob *bebob, void __user *arg)
+{
+ struct fw_device *dev = fw_parent_device(bebob->unit);
+ struct snd_firewire_get_info info;
+
+ memset(&info, 0, sizeof(info));
+ info.type = SNDRV_FIREWIRE_TYPE_BEBOB;
+ 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_bebob *bebob)
+{
+ int err;
+
+ spin_lock_irq(&bebob->lock);
+
+ if (bebob->dev_lock_count == 0) {
+ bebob->dev_lock_count = -1;
+ err = 0;
+ } else {
+ err = -EBUSY;
+ }
+
+ spin_unlock_irq(&bebob->lock);
+
+ return err;
+}
+
+static int
+hwdep_unlock(struct snd_bebob *bebob)
+{
+ int err;
+
+ spin_lock_irq(&bebob->lock);
+
+ if (bebob->dev_lock_count == -1) {
+ bebob->dev_lock_count = 0;
+ err = 0;
+ } else {
+ err = -EBADFD;
+ }
+
+ spin_unlock_irq(&bebob->lock);
+
+ return err;
+}
+
+static int
+hwdep_release(struct snd_hwdep *hwdep, struct file *file)
+{
+ struct snd_bebob *bebob = hwdep->private_data;
+
+ spin_lock_irq(&bebob->lock);
+ if (bebob->dev_lock_count == -1)
+ bebob->dev_lock_count = 0;
+ spin_unlock_irq(&bebob->lock);
+
+ return 0;
+}
+
+static int
+hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct snd_bebob *bebob = hwdep->private_data;
+
+ switch (cmd) {
+ case SNDRV_FIREWIRE_IOCTL_GET_INFO:
+ return hwdep_get_info(bebob, (void __user *)arg);
+ case SNDRV_FIREWIRE_IOCTL_LOCK:
+ return hwdep_lock(bebob);
+ case SNDRV_FIREWIRE_IOCTL_UNLOCK:
+ return hwdep_unlock(bebob);
+ 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
+
+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,
+};
+
+int snd_bebob_create_hwdep_device(struct snd_bebob *bebob)
+{
+ struct snd_hwdep *hwdep;
+ int err;
+
+ err = snd_hwdep_new(bebob->card, "BeBoB", 0, &hwdep);
+ if (err < 0)
+ goto end;
+ strcpy(hwdep->name, "BeBoB");
+ hwdep->iface = SNDRV_HWDEP_IFACE_FW_BEBOB;
+ hwdep->ops = hwdep_ops;
+ hwdep->private_data = bebob;
+ hwdep->exclusive = true;
+end:
+ return err;
+}
+
diff --git a/sound/firewire/bebob/bebob_maudio.c b/sound/firewire/bebob/bebob_maudio.c
new file mode 100644
index 000000000000..6af50eb80ea7
--- /dev/null
+++ b/sound/firewire/bebob/bebob_maudio.c
@@ -0,0 +1,792 @@
+/*
+ * bebob_maudio.c - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "./bebob.h"
+#include <sound/control.h>
+
+/*
+ * Just powering on, Firewire 410/Audiophile/1814 and ProjectMix I/O wait to
+ * download firmware blob. To enable these devices, drivers should upload
+ * firmware blob and send a command to initialize configuration to factory
+ * settings when completing uploading. Then these devices generate bus reset
+ * and are recognized as new devices with the firmware.
+ *
+ * But with firmware version 5058 or later, the firmware is stored to flash
+ * memory in the device and drivers can tell bootloader to load the firmware
+ * by sending a cue. This cue must be sent one time.
+ *
+ * For streaming, both of output and input streams are needed for Firewire 410
+ * and Ozonic. The single stream is OK for the other devices even if the clock
+ * source is not SYT-Match (I note no devices use SYT-Match).
+ *
+ * Without streaming, the devices except for Firewire Audiophile can mix any
+ * input and output. For this reason, Audiophile cannot be used as standalone
+ * mixer.
+ *
+ * Firewire 1814 and ProjectMix I/O uses special firmware. It will be freezed
+ * when receiving any commands which the firmware can't understand. These
+ * devices utilize completely different system to control. It is some
+ * write-transaction directly into a certain address. All of addresses for mixer
+ * functionality is between 0xffc700700000 to 0xffc70070009c.
+ */
+
+/* Offset from information register */
+#define INFO_OFFSET_SW_DATE 0x20
+
+/* Bootloader Protocol Version 1 */
+#define MAUDIO_BOOTLOADER_CUE1 0x00000001
+/*
+ * Initializing configuration to factory settings (= 0x1101), (swapped in line),
+ * Command code is zero (= 0x00),
+ * the number of operands is zero (= 0x00)(at least significant byte)
+ */
+#define MAUDIO_BOOTLOADER_CUE2 0x01110000
+/* padding */
+#define MAUDIO_BOOTLOADER_CUE3 0x00000000
+
+#define MAUDIO_SPECIFIC_ADDRESS 0xffc700000000ULL
+
+#define METER_OFFSET 0x00600000
+
+/* some device has sync info after metering data */
+#define METER_SIZE_SPECIAL 84 /* with sync info */
+#define METER_SIZE_FW410 76 /* with sync info */
+#define METER_SIZE_AUDIOPHILE 60 /* with sync info */
+#define METER_SIZE_SOLO 52 /* with sync info */
+#define METER_SIZE_OZONIC 48
+#define METER_SIZE_NRV10 80
+
+/* labels for metering */
+#define ANA_IN "Analog In"
+#define ANA_OUT "Analog Out"
+#define DIG_IN "Digital In"
+#define SPDIF_IN "S/PDIF In"
+#define ADAT_IN "ADAT In"
+#define DIG_OUT "Digital Out"
+#define SPDIF_OUT "S/PDIF Out"
+#define ADAT_OUT "ADAT Out"
+#define STRM_IN "Stream In"
+#define AUX_OUT "Aux Out"
+#define HP_OUT "HP Out"
+/* for NRV */
+#define UNKNOWN_METER "Unknown"
+
+struct special_params {
+ bool is1814;
+ unsigned int clk_src;
+ unsigned int dig_in_fmt;
+ unsigned int dig_out_fmt;
+ unsigned int clk_lock;
+ struct snd_ctl_elem_id *ctl_id_sync;
+};
+
+/*
+ * For some M-Audio devices, this module just send cue to load firmware. After
+ * loading, the device generates bus reset and newly detected.
+ *
+ * If we make any transactions to load firmware, the operation may failed.
+ */
+int snd_bebob_maudio_load_firmware(struct fw_unit *unit)
+{
+ struct fw_device *device = fw_parent_device(unit);
+ int err, rcode;
+ u64 date;
+ __be32 cues[3] = {
+ MAUDIO_BOOTLOADER_CUE1,
+ MAUDIO_BOOTLOADER_CUE2,
+ MAUDIO_BOOTLOADER_CUE3
+ };
+
+ /* check date of software used to build */
+ err = snd_bebob_read_block(unit, INFO_OFFSET_SW_DATE,
+ &date, sizeof(u64));
+ if (err < 0)
+ goto end;
+ /*
+ * firmware version 5058 or later has date later than "20070401", but
+ * 'date' is not null-terminated.
+ */
+ if (date < 0x3230303730343031LL) {
+ dev_err(&unit->device,
+ "Use firmware version 5058 or later\n");
+ err = -ENOSYS;
+ goto end;
+ }
+
+ rcode = fw_run_transaction(device->card, TCODE_WRITE_BLOCK_REQUEST,
+ device->node_id, device->generation,
+ device->max_speed, BEBOB_ADDR_REG_REQ,
+ cues, sizeof(cues));
+ if (rcode != RCODE_COMPLETE) {
+ dev_err(&unit->device,
+ "Failed to send a cue to load firmware\n");
+ err = -EIO;
+ }
+end:
+ return err;
+}
+
+static inline int
+get_meter(struct snd_bebob *bebob, void *buf, unsigned int size)
+{
+ return snd_fw_transaction(bebob->unit, TCODE_READ_BLOCK_REQUEST,
+ MAUDIO_SPECIFIC_ADDRESS + METER_OFFSET,
+ buf, size, 0);
+}
+
+static int
+check_clk_sync(struct snd_bebob *bebob, unsigned int size, bool *sync)
+{
+ int err;
+ u8 *buf;
+
+ buf = kmalloc(size, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ err = get_meter(bebob, buf, size);
+ if (err < 0)
+ goto end;
+
+ /* if synced, this value is the same as SFC of FDF in CIP header */
+ *sync = (buf[size - 2] != 0xff);
+end:
+ kfree(buf);
+ return err;
+}
+
+/*
+ * dig_fmt: 0x00:S/PDIF, 0x01:ADAT
+ * clk_lock: 0x00:unlock, 0x01:lock
+ */
+static int
+avc_maudio_set_special_clk(struct snd_bebob *bebob, unsigned int clk_src,
+ unsigned int dig_in_fmt, unsigned int dig_out_fmt,
+ unsigned int clk_lock)
+{
+ struct special_params *params = bebob->maudio_special_quirk;
+ int err;
+ u8 *buf;
+
+ if (amdtp_stream_running(&bebob->rx_stream) ||
+ amdtp_stream_running(&bebob->tx_stream))
+ return -EBUSY;
+
+ buf = kmalloc(12, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ buf[0] = 0x00; /* CONTROL */
+ buf[1] = 0xff; /* UNIT */
+ buf[2] = 0x00; /* vendor dependent */
+ buf[3] = 0x04; /* company ID high */
+ buf[4] = 0x00; /* company ID middle */
+ buf[5] = 0x04; /* company ID low */
+ buf[6] = 0xff & clk_src; /* clock source */
+ buf[7] = 0xff & dig_in_fmt; /* input digital format */
+ buf[8] = 0xff & dig_out_fmt; /* output digital format */
+ buf[9] = 0xff & clk_lock; /* lock these settings */
+ buf[10] = 0x00; /* padding */
+ buf[11] = 0x00; /* padding */
+
+ err = fcp_avc_transaction(bebob->unit, buf, 12, buf, 12,
+ BIT(1) | BIT(2) | BIT(3) | BIT(4) |
+ BIT(5) | BIT(6) | BIT(7) | BIT(8) |
+ BIT(9));
+ if ((err > 0) && (err < 10))
+ err = -EIO;
+ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) /* REJECTED */
+ err = -EINVAL;
+ if (err < 0)
+ goto end;
+
+ params->clk_src = buf[6];
+ params->dig_in_fmt = buf[7];
+ params->dig_out_fmt = buf[8];
+ params->clk_lock = buf[9];
+
+ if (params->ctl_id_sync)
+ snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE,
+ params->ctl_id_sync);
+
+ err = 0;
+end:
+ kfree(buf);
+ return err;
+}
+static void
+special_stream_formation_set(struct snd_bebob *bebob)
+{
+ static const unsigned int ch_table[2][2][3] = {
+ /* AMDTP_OUT_STREAM */
+ { { 6, 6, 4 }, /* SPDIF */
+ { 12, 8, 4 } }, /* ADAT */
+ /* AMDTP_IN_STREAM */
+ { { 10, 10, 2 }, /* SPDIF */
+ { 16, 12, 2 } } /* ADAT */
+ };
+ struct special_params *params = bebob->maudio_special_quirk;
+ unsigned int i, max;
+
+ max = SND_BEBOB_STRM_FMT_ENTRIES - 1;
+ if (!params->is1814)
+ max -= 2;
+
+ for (i = 0; i < max; i++) {
+ bebob->tx_stream_formations[i + 1].pcm =
+ ch_table[AMDTP_IN_STREAM][params->dig_in_fmt][i / 2];
+ bebob->tx_stream_formations[i + 1].midi = 1;
+
+ bebob->rx_stream_formations[i + 1].pcm =
+ ch_table[AMDTP_OUT_STREAM][params->dig_out_fmt][i / 2];
+ bebob->rx_stream_formations[i + 1].midi = 1;
+ }
+}
+
+static int add_special_controls(struct snd_bebob *bebob);
+int
+snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814)
+{
+ struct special_params *params;
+ int err;
+
+ params = kzalloc(sizeof(struct special_params), GFP_KERNEL);
+ if (params == NULL)
+ return -ENOMEM;
+
+ mutex_lock(&bebob->mutex);
+
+ bebob->maudio_special_quirk = (void *)params;
+ params->is1814 = is1814;
+
+ /* initialize these parameters because driver is not allowed to ask */
+ bebob->rx_stream.context = ERR_PTR(-1);
+ bebob->tx_stream.context = ERR_PTR(-1);
+ err = avc_maudio_set_special_clk(bebob, 0x03, 0x00, 0x00, 0x00);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to initialize clock params: %d\n", err);
+ goto end;
+ }
+
+ err = add_special_controls(bebob);
+ if (err < 0)
+ goto end;
+
+ special_stream_formation_set(bebob);
+
+ if (params->is1814) {
+ bebob->midi_input_ports = 1;
+ bebob->midi_output_ports = 1;
+ } else {
+ bebob->midi_input_ports = 2;
+ bebob->midi_output_ports = 2;
+ }
+end:
+ if (err < 0) {
+ kfree(params);
+ bebob->maudio_special_quirk = NULL;
+ }
+ mutex_unlock(&bebob->mutex);
+ return err;
+}
+
+/* Input plug shows actual rate. Output plug is needless for this purpose. */
+static int special_get_rate(struct snd_bebob *bebob, unsigned int *rate)
+{
+ int err, trials;
+
+ trials = 0;
+ do {
+ err = avc_general_get_sig_fmt(bebob->unit, rate,
+ AVC_GENERAL_PLUG_DIR_IN, 0);
+ } while (err == -EAGAIN && ++trials < 3);
+
+ return err;
+}
+static int special_set_rate(struct snd_bebob *bebob, unsigned int rate)
+{
+ struct special_params *params = bebob->maudio_special_quirk;
+ int err;
+
+ err = avc_general_set_sig_fmt(bebob->unit, rate,
+ AVC_GENERAL_PLUG_DIR_OUT, 0);
+ if (err < 0)
+ goto end;
+
+ /*
+ * Just after changing sampling rate for output, a followed command
+ * for input is easy to fail. This is a workaround fot this issue.
+ */
+ msleep(100);
+
+ err = avc_general_set_sig_fmt(bebob->unit, rate,
+ AVC_GENERAL_PLUG_DIR_IN, 0);
+ if (err < 0)
+ goto end;
+
+ if (params->ctl_id_sync)
+ snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE,
+ params->ctl_id_sync);
+end:
+ return err;
+}
+
+/* Clock source control for special firmware */
+static char *const special_clk_labels[] = {
+ SND_BEBOB_CLOCK_INTERNAL " with Digital Mute", "Digital",
+ "Word Clock", SND_BEBOB_CLOCK_INTERNAL};
+static int special_clk_get(struct snd_bebob *bebob, unsigned int *id)
+{
+ struct special_params *params = bebob->maudio_special_quirk;
+ *id = params->clk_src;
+ return 0;
+}
+static int special_clk_ctl_info(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_info *einf)
+{
+ einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ einf->count = 1;
+ einf->value.enumerated.items = ARRAY_SIZE(special_clk_labels);
+
+ if (einf->value.enumerated.item >= einf->value.enumerated.items)
+ einf->value.enumerated.item = einf->value.enumerated.items - 1;
+
+ strcpy(einf->value.enumerated.name,
+ special_clk_labels[einf->value.enumerated.item]);
+
+ return 0;
+}
+static int special_clk_ctl_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uval)
+{
+ struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
+ struct special_params *params = bebob->maudio_special_quirk;
+ uval->value.enumerated.item[0] = params->clk_src;
+ return 0;
+}
+static int special_clk_ctl_put(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uval)
+{
+ struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
+ struct special_params *params = bebob->maudio_special_quirk;
+ int err, id;
+
+ mutex_lock(&bebob->mutex);
+
+ id = uval->value.enumerated.item[0];
+ if (id >= ARRAY_SIZE(special_clk_labels))
+ return 0;
+
+ err = avc_maudio_set_special_clk(bebob, id,
+ params->dig_in_fmt,
+ params->dig_out_fmt,
+ params->clk_lock);
+ mutex_unlock(&bebob->mutex);
+
+ return err >= 0;
+}
+static struct snd_kcontrol_new special_clk_ctl = {
+ .name = "Clock Source",
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = special_clk_ctl_info,
+ .get = special_clk_ctl_get,
+ .put = special_clk_ctl_put
+};
+
+/* Clock synchronization control for special firmware */
+static int special_sync_ctl_info(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_info *einf)
+{
+ einf->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ einf->count = 1;
+ einf->value.integer.min = 0;
+ einf->value.integer.max = 1;
+
+ return 0;
+}
+static int special_sync_ctl_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uval)
+{
+ struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
+ int err;
+ bool synced = 0;
+
+ err = check_clk_sync(bebob, METER_SIZE_SPECIAL, &synced);
+ if (err >= 0)
+ uval->value.integer.value[0] = synced;
+
+ return 0;
+}
+static struct snd_kcontrol_new special_sync_ctl = {
+ .name = "Sync Status",
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .info = special_sync_ctl_info,
+ .get = special_sync_ctl_get,
+};
+
+/* Digital interface control for special firmware */
+static char *const special_dig_iface_labels[] = {
+ "S/PDIF Optical", "S/PDIF Coaxial", "ADAT Optical"
+};
+static int special_dig_in_iface_ctl_info(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_info *einf)
+{
+ einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ einf->count = 1;
+ einf->value.enumerated.items = ARRAY_SIZE(special_dig_iface_labels);
+
+ if (einf->value.enumerated.item >= einf->value.enumerated.items)
+ einf->value.enumerated.item = einf->value.enumerated.items - 1;
+
+ strcpy(einf->value.enumerated.name,
+ special_dig_iface_labels[einf->value.enumerated.item]);
+
+ return 0;
+}
+static int special_dig_in_iface_ctl_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uval)
+{
+ struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
+ struct special_params *params = bebob->maudio_special_quirk;
+ unsigned int dig_in_iface;
+ int err, val;
+
+ mutex_lock(&bebob->mutex);
+
+ err = avc_audio_get_selector(bebob->unit, 0x00, 0x04,
+ &dig_in_iface);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get digital input interface: %d\n", err);
+ goto end;
+ }
+
+ /* encoded id for user value */
+ val = (params->dig_in_fmt << 1) | (dig_in_iface & 0x01);
+
+ /* for ADAT Optical */
+ if (val > 2)
+ val = 2;
+
+ uval->value.enumerated.item[0] = val;
+end:
+ mutex_unlock(&bebob->mutex);
+ return err;
+}
+static int special_dig_in_iface_ctl_set(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uval)
+{
+ struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
+ struct special_params *params = bebob->maudio_special_quirk;
+ unsigned int id, dig_in_fmt, dig_in_iface;
+ int err;
+
+ mutex_lock(&bebob->mutex);
+
+ id = uval->value.enumerated.item[0];
+
+ /* decode user value */
+ dig_in_fmt = (id >> 1) & 0x01;
+ dig_in_iface = id & 0x01;
+
+ err = avc_maudio_set_special_clk(bebob,
+ params->clk_src,
+ dig_in_fmt,
+ params->dig_out_fmt,
+ params->clk_lock);
+ if ((err < 0) || (params->dig_in_fmt > 0)) /* ADAT */
+ goto end;
+
+ err = avc_audio_set_selector(bebob->unit, 0x00, 0x04, dig_in_iface);
+ if (err < 0)
+ dev_err(&bebob->unit->device,
+ "fail to set digital input interface: %d\n", err);
+end:
+ special_stream_formation_set(bebob);
+ mutex_unlock(&bebob->mutex);
+ return err;
+}
+static struct snd_kcontrol_new special_dig_in_iface_ctl = {
+ .name = "Digital Input Interface",
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = special_dig_in_iface_ctl_info,
+ .get = special_dig_in_iface_ctl_get,
+ .put = special_dig_in_iface_ctl_set
+};
+
+static int special_dig_out_iface_ctl_info(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_info *einf)
+{
+ einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ einf->count = 1;
+ einf->value.enumerated.items = ARRAY_SIZE(special_dig_iface_labels) - 1;
+
+ if (einf->value.enumerated.item >= einf->value.enumerated.items)
+ einf->value.enumerated.item = einf->value.enumerated.items - 1;
+
+ strcpy(einf->value.enumerated.name,
+ special_dig_iface_labels[einf->value.enumerated.item + 1]);
+
+ return 0;
+}
+static int special_dig_out_iface_ctl_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uval)
+{
+ struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
+ struct special_params *params = bebob->maudio_special_quirk;
+ mutex_lock(&bebob->mutex);
+ uval->value.enumerated.item[0] = params->dig_out_fmt;
+ mutex_unlock(&bebob->mutex);
+ return 0;
+}
+static int special_dig_out_iface_ctl_set(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uval)
+{
+ struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
+ struct special_params *params = bebob->maudio_special_quirk;
+ unsigned int id;
+ int err;
+
+ mutex_lock(&bebob->mutex);
+
+ id = uval->value.enumerated.item[0];
+
+ err = avc_maudio_set_special_clk(bebob,
+ params->clk_src,
+ params->dig_in_fmt,
+ id, params->clk_lock);
+ if (err >= 0)
+ special_stream_formation_set(bebob);
+
+ mutex_unlock(&bebob->mutex);
+ return err;
+}
+static struct snd_kcontrol_new special_dig_out_iface_ctl = {
+ .name = "Digital Output Interface",
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = special_dig_out_iface_ctl_info,
+ .get = special_dig_out_iface_ctl_get,
+ .put = special_dig_out_iface_ctl_set
+};
+
+static int add_special_controls(struct snd_bebob *bebob)
+{
+ struct snd_kcontrol *kctl;
+ struct special_params *params = bebob->maudio_special_quirk;
+ int err;
+
+ kctl = snd_ctl_new1(&special_clk_ctl, bebob);
+ err = snd_ctl_add(bebob->card, kctl);
+ if (err < 0)
+ goto end;
+
+ kctl = snd_ctl_new1(&special_sync_ctl, bebob);
+ err = snd_ctl_add(bebob->card, kctl);
+ if (err < 0)
+ goto end;
+ params->ctl_id_sync = &kctl->id;
+
+ kctl = snd_ctl_new1(&special_dig_in_iface_ctl, bebob);
+ err = snd_ctl_add(bebob->card, kctl);
+ if (err < 0)
+ goto end;
+
+ kctl = snd_ctl_new1(&special_dig_out_iface_ctl, bebob);
+ err = snd_ctl_add(bebob->card, kctl);
+end:
+ return err;
+}
+
+/* Hardware metering for special firmware */
+static char *const special_meter_labels[] = {
+ ANA_IN, ANA_IN, ANA_IN, ANA_IN,
+ SPDIF_IN,
+ ADAT_IN, ADAT_IN, ADAT_IN, ADAT_IN,
+ ANA_OUT, ANA_OUT,
+ SPDIF_OUT,
+ ADAT_OUT, ADAT_OUT, ADAT_OUT, ADAT_OUT,
+ HP_OUT, HP_OUT,
+ AUX_OUT
+};
+static int
+special_meter_get(struct snd_bebob *bebob, u32 *target, unsigned int size)
+{
+ u16 *buf;
+ unsigned int i, c, channels;
+ int err;
+
+ channels = ARRAY_SIZE(special_meter_labels) * 2;
+ if (size < channels * sizeof(u32))
+ return -EINVAL;
+
+ /* omit last 4 bytes because it's clock info. */
+ buf = kmalloc(METER_SIZE_SPECIAL - 4, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ err = get_meter(bebob, (void *)buf, METER_SIZE_SPECIAL - 4);
+ if (err < 0)
+ goto end;
+
+ /* Its format is u16 and some channels are unknown. */
+ i = 0;
+ for (c = 2; c < channels + 2; c++)
+ target[i++] = be16_to_cpu(buf[c]) << 16;
+end:
+ kfree(buf);
+ return err;
+}
+
+/* last 4 bytes are omitted because it's clock info. */
+static char *const fw410_meter_labels[] = {
+ ANA_IN, DIG_IN,
+ ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, DIG_OUT,
+ HP_OUT
+};
+static char *const audiophile_meter_labels[] = {
+ ANA_IN, DIG_IN,
+ ANA_OUT, ANA_OUT, DIG_OUT,
+ HP_OUT, AUX_OUT,
+};
+static char *const solo_meter_labels[] = {
+ ANA_IN, DIG_IN,
+ STRM_IN, STRM_IN,
+ ANA_OUT, DIG_OUT
+};
+
+/* no clock info */
+static char *const ozonic_meter_labels[] = {
+ ANA_IN, ANA_IN,
+ STRM_IN, STRM_IN,
+ ANA_OUT, ANA_OUT
+};
+/* TODO: need testers. these positions are based on authour's assumption */
+static char *const nrv10_meter_labels[] = {
+ ANA_IN, ANA_IN, ANA_IN, ANA_IN,
+ DIG_IN,
+ ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT,
+ DIG_IN
+};
+static int
+normal_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size)
+{
+ struct snd_bebob_meter_spec *spec = bebob->spec->meter;
+ unsigned int c, channels;
+ int err;
+
+ channels = spec->num * 2;
+ if (size < channels * sizeof(u32))
+ return -EINVAL;
+
+ err = get_meter(bebob, (void *)buf, size);
+ if (err < 0)
+ goto end;
+
+ for (c = 0; c < channels; c++)
+ be32_to_cpus(&buf[c]);
+
+ /* swap stream channels because inverted */
+ if (spec->labels == solo_meter_labels) {
+ swap(buf[4], buf[6]);
+ swap(buf[5], buf[7]);
+ }
+end:
+ return err;
+}
+
+/* for special customized devices */
+static struct snd_bebob_rate_spec special_rate_spec = {
+ .get = &special_get_rate,
+ .set = &special_set_rate,
+};
+static struct snd_bebob_clock_spec special_clk_spec = {
+ .num = ARRAY_SIZE(special_clk_labels),
+ .labels = special_clk_labels,
+ .get = &special_clk_get,
+};
+static struct snd_bebob_meter_spec special_meter_spec = {
+ .num = ARRAY_SIZE(special_meter_labels),
+ .labels = special_meter_labels,
+ .get = &special_meter_get
+};
+struct snd_bebob_spec maudio_special_spec = {
+ .clock = &special_clk_spec,
+ .rate = &special_rate_spec,
+ .meter = &special_meter_spec
+};
+
+/* Firewire 410 specification */
+static struct snd_bebob_rate_spec usual_rate_spec = {
+ .get = &snd_bebob_stream_get_rate,
+ .set = &snd_bebob_stream_set_rate,
+};
+static struct snd_bebob_meter_spec fw410_meter_spec = {
+ .num = ARRAY_SIZE(fw410_meter_labels),
+ .labels = fw410_meter_labels,
+ .get = &normal_meter_get
+};
+struct snd_bebob_spec maudio_fw410_spec = {
+ .clock = NULL,
+ .rate = &usual_rate_spec,
+ .meter = &fw410_meter_spec
+};
+
+/* Firewire Audiophile specification */
+static struct snd_bebob_meter_spec audiophile_meter_spec = {
+ .num = ARRAY_SIZE(audiophile_meter_labels),
+ .labels = audiophile_meter_labels,
+ .get = &normal_meter_get
+};
+struct snd_bebob_spec maudio_audiophile_spec = {
+ .clock = NULL,
+ .rate = &usual_rate_spec,
+ .meter = &audiophile_meter_spec
+};
+
+/* Firewire Solo specification */
+static struct snd_bebob_meter_spec solo_meter_spec = {
+ .num = ARRAY_SIZE(solo_meter_labels),
+ .labels = solo_meter_labels,
+ .get = &normal_meter_get
+};
+struct snd_bebob_spec maudio_solo_spec = {
+ .clock = NULL,
+ .rate = &usual_rate_spec,
+ .meter = &solo_meter_spec
+};
+
+/* Ozonic specification */
+static struct snd_bebob_meter_spec ozonic_meter_spec = {
+ .num = ARRAY_SIZE(ozonic_meter_labels),
+ .labels = ozonic_meter_labels,
+ .get = &normal_meter_get
+};
+struct snd_bebob_spec maudio_ozonic_spec = {
+ .clock = NULL,
+ .rate = &usual_rate_spec,
+ .meter = &ozonic_meter_spec
+};
+
+/* NRV10 specification */
+static struct snd_bebob_meter_spec nrv10_meter_spec = {
+ .num = ARRAY_SIZE(nrv10_meter_labels),
+ .labels = nrv10_meter_labels,
+ .get = &normal_meter_get
+};
+struct snd_bebob_spec maudio_nrv10_spec = {
+ .clock = NULL,
+ .rate = &usual_rate_spec,
+ .meter = &nrv10_meter_spec
+};
diff --git a/sound/firewire/bebob/bebob_midi.c b/sound/firewire/bebob/bebob_midi.c
new file mode 100644
index 000000000000..63343d578df3
--- /dev/null
+++ b/sound/firewire/bebob/bebob_midi.c
@@ -0,0 +1,168 @@
+/*
+ * bebob_midi.c - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "bebob.h"
+
+static int midi_capture_open(struct snd_rawmidi_substream *substream)
+{
+ struct snd_bebob *bebob = substream->rmidi->private_data;
+ int err;
+
+ err = snd_bebob_stream_lock_try(bebob);
+ if (err < 0)
+ goto end;
+
+ atomic_inc(&bebob->capture_substreams);
+ err = snd_bebob_stream_start_duplex(bebob, 0);
+ if (err < 0)
+ snd_bebob_stream_lock_release(bebob);
+end:
+ return err;
+}
+
+static int midi_playback_open(struct snd_rawmidi_substream *substream)
+{
+ struct snd_bebob *bebob = substream->rmidi->private_data;
+ int err;
+
+ err = snd_bebob_stream_lock_try(bebob);
+ if (err < 0)
+ goto end;
+
+ atomic_inc(&bebob->playback_substreams);
+ err = snd_bebob_stream_start_duplex(bebob, 0);
+ if (err < 0)
+ snd_bebob_stream_lock_release(bebob);
+end:
+ return err;
+}
+
+static int midi_capture_close(struct snd_rawmidi_substream *substream)
+{
+ struct snd_bebob *bebob = substream->rmidi->private_data;
+
+ atomic_dec(&bebob->capture_substreams);
+ snd_bebob_stream_stop_duplex(bebob);
+
+ snd_bebob_stream_lock_release(bebob);
+ return 0;
+}
+
+static int midi_playback_close(struct snd_rawmidi_substream *substream)
+{
+ struct snd_bebob *bebob = substream->rmidi->private_data;
+
+ atomic_dec(&bebob->playback_substreams);
+ snd_bebob_stream_stop_duplex(bebob);
+
+ snd_bebob_stream_lock_release(bebob);
+ return 0;
+}
+
+static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
+{
+ struct snd_bebob *bebob = substrm->rmidi->private_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&bebob->lock, flags);
+
+ if (up)
+ amdtp_stream_midi_trigger(&bebob->tx_stream,
+ substrm->number, substrm);
+ else
+ amdtp_stream_midi_trigger(&bebob->tx_stream,
+ substrm->number, NULL);
+
+ spin_unlock_irqrestore(&bebob->lock, flags);
+}
+
+static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
+{
+ struct snd_bebob *bebob = substrm->rmidi->private_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&bebob->lock, flags);
+
+ if (up)
+ amdtp_stream_midi_trigger(&bebob->rx_stream,
+ substrm->number, substrm);
+ else
+ amdtp_stream_midi_trigger(&bebob->rx_stream,
+ substrm->number, NULL);
+
+ spin_unlock_irqrestore(&bebob->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_bebob *bebob,
+ 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",
+ bebob->card->shortname, subs->number + 1);
+ }
+}
+
+int snd_bebob_create_midi_devices(struct snd_bebob *bebob)
+{
+ struct snd_rawmidi *rmidi;
+ struct snd_rawmidi_str *str;
+ int err;
+
+ /* create midi ports */
+ err = snd_rawmidi_new(bebob->card, bebob->card->driver, 0,
+ bebob->midi_output_ports, bebob->midi_input_ports,
+ &rmidi);
+ if (err < 0)
+ return err;
+
+ snprintf(rmidi->name, sizeof(rmidi->name),
+ "%s MIDI", bebob->card->shortname);
+ rmidi->private_data = bebob;
+
+ if (bebob->midi_input_ports > 0) {
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+ &midi_capture_ops);
+
+ str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT];
+
+ set_midi_substream_names(bebob, str);
+ }
+
+ if (bebob->midi_output_ports > 0) {
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
+
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+ &midi_playback_ops);
+
+ str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
+
+ set_midi_substream_names(bebob, str);
+ }
+
+ if ((bebob->midi_output_ports > 0) && (bebob->midi_input_ports > 0))
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
+
+ return 0;
+}
diff --git a/sound/firewire/bebob/bebob_pcm.c b/sound/firewire/bebob/bebob_pcm.c
new file mode 100644
index 000000000000..4a55561ed4ec
--- /dev/null
+++ b/sound/firewire/bebob/bebob_pcm.c
@@ -0,0 +1,378 @@
+/*
+ * bebob_pcm.c - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "./bebob.h"
+
+static int
+hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule)
+{
+ struct snd_bebob_stream_formation *formations = 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;
+
+ for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) {
+ /* entry is invalid */
+ if (formations[i].pcm == 0)
+ continue;
+
+ if (!snd_interval_test(c, formations[i].pcm))
+ continue;
+
+ t.min = min(t.min, snd_bebob_rate_table[i]);
+ t.max = max(t.max, snd_bebob_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)
+{
+ struct snd_bebob_stream_formation *formations = 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;
+
+ for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) {
+ /* entry is invalid */
+ if (formations[i].pcm == 0)
+ continue;
+
+ if (!snd_interval_test(r, snd_bebob_rate_table[i]))
+ continue;
+
+ t.min = min(t.min, formations[i].pcm);
+ t.max = max(t.max, formations[i].pcm);
+ }
+
+ return snd_interval_refine(c, &t);
+}
+
+static void
+limit_channels_and_rates(struct snd_pcm_hardware *hw,
+ struct snd_bebob_stream_formation *formations)
+{
+ unsigned int i;
+
+ hw->channels_min = UINT_MAX;
+ hw->channels_max = 0;
+
+ hw->rate_min = UINT_MAX;
+ hw->rate_max = 0;
+ hw->rates = 0;
+
+ for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) {
+ /* entry has no PCM channels */
+ if (formations[i].pcm == 0)
+ continue;
+
+ hw->channels_min = min(hw->channels_min, formations[i].pcm);
+ hw->channels_max = max(hw->channels_max, formations[i].pcm);
+
+ hw->rate_min = min(hw->rate_min, snd_bebob_rate_table[i]);
+ hw->rate_max = max(hw->rate_max, snd_bebob_rate_table[i]);
+ hw->rates |= snd_pcm_rate_to_rate_bit(snd_bebob_rate_table[i]);
+ }
+}
+
+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_bebob *bebob,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct amdtp_stream *s;
+ struct snd_bebob_stream_formation *formations;
+ 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 = AMDTP_IN_PCM_FORMAT_BITS;
+ s = &bebob->tx_stream;
+ formations = bebob->tx_stream_formations;
+ } else {
+ runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS;
+ s = &bebob->rx_stream;
+ formations = bebob->rx_stream_formations;
+ }
+
+ limit_channels_and_rates(&runtime->hw, formations);
+ limit_period_and_buffer(&runtime->hw);
+
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ hw_rule_channels, formations,
+ SNDRV_PCM_HW_PARAM_RATE, -1);
+ if (err < 0)
+ goto end;
+
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ hw_rule_rate, formations,
+ SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+ if (err < 0)
+ goto end;
+
+ err = amdtp_stream_add_pcm_hw_constraints(s, runtime);
+end:
+ return err;
+}
+
+static int
+pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_bebob *bebob = substream->private_data;
+ struct snd_bebob_rate_spec *spec = bebob->spec->rate;
+ unsigned int sampling_rate;
+ bool internal;
+ int err;
+
+ err = snd_bebob_stream_lock_try(bebob);
+ if (err < 0)
+ goto end;
+
+ err = pcm_init_hw_params(bebob, substream);
+ if (err < 0)
+ goto err_locked;
+
+ err = snd_bebob_stream_check_internal_clock(bebob, &internal);
+ if (err < 0)
+ goto err_locked;
+
+ /*
+ * When source of clock is internal or any PCM stream are running,
+ * the available sampling rate is limited at current sampling rate.
+ */
+ if (!internal ||
+ amdtp_stream_pcm_running(&bebob->tx_stream) ||
+ amdtp_stream_pcm_running(&bebob->rx_stream)) {
+ err = spec->get(bebob, &sampling_rate);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get sampling rate: %d\n", err);
+ goto err_locked;
+ }
+
+ substream->runtime->hw.rate_min = sampling_rate;
+ substream->runtime->hw.rate_max = sampling_rate;
+ }
+
+ snd_pcm_set_sync(substream);
+end:
+ return err;
+err_locked:
+ snd_bebob_stream_lock_release(bebob);
+ return err;
+}
+
+static int
+pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_bebob *bebob = substream->private_data;
+ snd_bebob_stream_lock_release(bebob);
+ return 0;
+}
+
+static int
+pcm_capture_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_bebob *bebob = substream->private_data;
+
+ if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ atomic_inc(&bebob->capture_substreams);
+ amdtp_stream_set_pcm_format(&bebob->tx_stream,
+ params_format(hw_params));
+ return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+ params_buffer_bytes(hw_params));
+}
+static int
+pcm_playback_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_bebob *bebob = substream->private_data;
+
+ if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ atomic_inc(&bebob->playback_substreams);
+ amdtp_stream_set_pcm_format(&bebob->rx_stream,
+ params_format(hw_params));
+ return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+ params_buffer_bytes(hw_params));
+}
+
+static int
+pcm_capture_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_bebob *bebob = substream->private_data;
+
+ if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+ atomic_dec(&bebob->capture_substreams);
+
+ snd_bebob_stream_stop_duplex(bebob);
+
+ return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+static int
+pcm_playback_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_bebob *bebob = substream->private_data;
+
+ if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+ atomic_dec(&bebob->playback_substreams);
+
+ snd_bebob_stream_stop_duplex(bebob);
+
+ return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int
+pcm_capture_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_bebob *bebob = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int err;
+
+ err = snd_bebob_stream_start_duplex(bebob, runtime->rate);
+ if (err >= 0)
+ amdtp_stream_pcm_prepare(&bebob->tx_stream);
+
+ return err;
+}
+static int
+pcm_playback_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_bebob *bebob = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int err;
+
+ err = snd_bebob_stream_start_duplex(bebob, runtime->rate);
+ if (err >= 0)
+ amdtp_stream_pcm_prepare(&bebob->rx_stream);
+
+ return err;
+}
+
+static int
+pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_bebob *bebob = substream->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ amdtp_stream_pcm_trigger(&bebob->tx_stream, substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ amdtp_stream_pcm_trigger(&bebob->tx_stream, NULL);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+static int
+pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_bebob *bebob = substream->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ amdtp_stream_pcm_trigger(&bebob->rx_stream, substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ amdtp_stream_pcm_trigger(&bebob->rx_stream, NULL);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t
+pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
+{
+ struct snd_bebob *bebob = sbstrm->private_data;
+ return amdtp_stream_pcm_pointer(&bebob->tx_stream);
+}
+static snd_pcm_uframes_t
+pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
+{
+ struct snd_bebob *bebob = sbstrm->private_data;
+ return amdtp_stream_pcm_pointer(&bebob->rx_stream);
+}
+
+static const 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 const 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_bebob_create_pcm_devices(struct snd_bebob *bebob)
+{
+ struct snd_pcm *pcm;
+ int err;
+
+ err = snd_pcm_new(bebob->card, bebob->card->driver, 0, 1, 1, &pcm);
+ if (err < 0)
+ goto end;
+
+ pcm->private_data = bebob;
+ snprintf(pcm->name, sizeof(pcm->name),
+ "%s PCM", bebob->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);
+end:
+ return err;
+}
diff --git a/sound/firewire/bebob/bebob_proc.c b/sound/firewire/bebob/bebob_proc.c
new file mode 100644
index 000000000000..335da64506e0
--- /dev/null
+++ b/sound/firewire/bebob/bebob_proc.c
@@ -0,0 +1,196 @@
+/*
+ * bebob_proc.c - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "./bebob.h"
+
+/* contents of information register */
+struct hw_info {
+ u64 manufacturer;
+ u32 protocol_ver;
+ u32 bld_ver;
+ u32 guid[2];
+ u32 model_id;
+ u32 model_rev;
+ u64 fw_date;
+ u64 fw_time;
+ u32 fw_id;
+ u32 fw_ver;
+ u32 base_addr;
+ u32 max_size;
+ u64 bld_date;
+ u64 bld_time;
+/* may not used in product
+ u64 dbg_date;
+ u64 dbg_time;
+ u32 dbg_id;
+ u32 dbg_version;
+*/
+} __packed;
+
+static void
+proc_read_hw_info(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_bebob *bebob = entry->private_data;
+ struct hw_info *info;
+
+ info = kzalloc(sizeof(struct hw_info), GFP_KERNEL);
+ if (info == NULL)
+ return;
+
+ if (snd_bebob_read_block(bebob->unit, 0,
+ info, sizeof(struct hw_info)) < 0)
+ goto end;
+
+ snd_iprintf(buffer, "Manufacturer:\t%.8s\n",
+ (char *)&info->manufacturer);
+ snd_iprintf(buffer, "Protocol Ver:\t%d\n", info->protocol_ver);
+ snd_iprintf(buffer, "Build Ver:\t%d\n", info->bld_ver);
+ snd_iprintf(buffer, "GUID:\t\t0x%.8X%.8X\n",
+ info->guid[0], info->guid[1]);
+ snd_iprintf(buffer, "Model ID:\t0x%02X\n", info->model_id);
+ snd_iprintf(buffer, "Model Rev:\t%d\n", info->model_rev);
+ snd_iprintf(buffer, "Firmware Date:\t%.8s\n", (char *)&info->fw_date);
+ snd_iprintf(buffer, "Firmware Time:\t%.8s\n", (char *)&info->fw_time);
+ snd_iprintf(buffer, "Firmware ID:\t0x%X\n", info->fw_id);
+ snd_iprintf(buffer, "Firmware Ver:\t%d\n", info->fw_ver);
+ snd_iprintf(buffer, "Base Addr:\t0x%X\n", info->base_addr);
+ snd_iprintf(buffer, "Max Size:\t%d\n", info->max_size);
+ snd_iprintf(buffer, "Loader Date:\t%.8s\n", (char *)&info->bld_date);
+ snd_iprintf(buffer, "Loader Time:\t%.8s\n", (char *)&info->bld_time);
+
+end:
+ kfree(info);
+}
+
+static void
+proc_read_meters(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_bebob *bebob = entry->private_data;
+ struct snd_bebob_meter_spec *spec = bebob->spec->meter;
+ u32 *buf;
+ unsigned int i, c, channels, size;
+
+ if (spec == NULL)
+ return;
+
+ channels = spec->num * 2;
+ size = channels * sizeof(u32);
+ buf = kmalloc(size, GFP_KERNEL);
+ if (buf == NULL)
+ return;
+
+ if (spec->get(bebob, buf, size) < 0)
+ goto end;
+
+ for (i = 0, c = 1; i < channels; i++) {
+ snd_iprintf(buffer, "%s %d:\t%d\n",
+ spec->labels[i / 2], c++, buf[i]);
+ if ((i + 1 < channels - 1) &&
+ (strcmp(spec->labels[i / 2],
+ spec->labels[(i + 1) / 2]) != 0))
+ c = 1;
+ }
+end:
+ kfree(buf);
+}
+
+static void
+proc_read_formation(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_bebob *bebob = entry->private_data;
+ struct snd_bebob_stream_formation *formation;
+ unsigned int i;
+
+ snd_iprintf(buffer, "Output Stream from device:\n");
+ snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n");
+ formation = bebob->tx_stream_formations;
+ for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) {
+ snd_iprintf(buffer,
+ "\t%d\t%d\t%d\n", snd_bebob_rate_table[i],
+ formation[i].pcm, formation[i].midi);
+ }
+
+ snd_iprintf(buffer, "Input Stream to device:\n");
+ snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n");
+ formation = bebob->rx_stream_formations;
+ for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) {
+ snd_iprintf(buffer,
+ "\t%d\t%d\t%d\n", snd_bebob_rate_table[i],
+ formation[i].pcm, formation[i].midi);
+ }
+}
+
+static void
+proc_read_clock(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_bebob *bebob = entry->private_data;
+ struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate;
+ struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
+ unsigned int rate, id;
+ bool internal;
+
+ if (rate_spec->get(bebob, &rate) >= 0)
+ snd_iprintf(buffer, "Sampling rate: %d\n", rate);
+
+ if (clk_spec) {
+ if (clk_spec->get(bebob, &id) >= 0)
+ snd_iprintf(buffer, "Clock Source: %s\n",
+ clk_spec->labels[id]);
+ } else {
+ if (snd_bebob_stream_check_internal_clock(bebob,
+ &internal) >= 0)
+ snd_iprintf(buffer, "Clock Source: %s (MSU-dest: %d)\n",
+ (internal) ? "Internal" : "External",
+ bebob->sync_input_plug);
+ }
+}
+
+static void
+add_node(struct snd_bebob *bebob, 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(bebob->card, name, root);
+ if (entry == NULL)
+ return;
+
+ snd_info_set_text_ops(entry, bebob, op);
+ if (snd_info_register(entry) < 0)
+ snd_info_free_entry(entry);
+}
+
+void snd_bebob_proc_init(struct snd_bebob *bebob)
+{
+ 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(bebob->card, "firewire",
+ bebob->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(bebob, root, "clock", proc_read_clock);
+ add_node(bebob, root, "firmware", proc_read_hw_info);
+ add_node(bebob, root, "formation", proc_read_formation);
+
+ if (bebob->spec->meter != NULL)
+ add_node(bebob, root, "meter", proc_read_meters);
+}
diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c
new file mode 100644
index 000000000000..ef4d0c9f6578
--- /dev/null
+++ b/sound/firewire/bebob/bebob_stream.c
@@ -0,0 +1,1021 @@
+/*
+ * bebob_stream.c - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "./bebob.h"
+
+#define CALLBACK_TIMEOUT 1000
+#define FW_ISO_RESOURCE_DELAY 1000
+
+/*
+ * NOTE;
+ * For BeBoB streams, Both of input and output CMP connection are important.
+ *
+ * For most devices, each CMP connection starts to transmit/receive a
+ * corresponding stream. But for a few devices, both of CMP connection needs
+ * to start transmitting stream. An example is 'M-Audio Firewire 410'.
+ */
+
+/* 128 is an arbitrary length but it seems to be enough */
+#define FORMAT_MAXIMUM_LENGTH 128
+
+const unsigned int snd_bebob_rate_table[SND_BEBOB_STRM_FMT_ENTRIES] = {
+ [0] = 32000,
+ [1] = 44100,
+ [2] = 48000,
+ [3] = 88200,
+ [4] = 96000,
+ [5] = 176400,
+ [6] = 192000,
+};
+
+/*
+ * See: Table 51: Extended Stream Format Info ‘Sampling Frequency’
+ * in Additional AVC commands (Nov 2003, BridgeCo)
+ */
+static const unsigned int bridgeco_freq_table[] = {
+ [0] = 0x02,
+ [1] = 0x03,
+ [2] = 0x04,
+ [3] = 0x0a,
+ [4] = 0x05,
+ [5] = 0x06,
+ [6] = 0x07,
+};
+
+static unsigned int
+get_formation_index(unsigned int rate)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(snd_bebob_rate_table); i++) {
+ if (snd_bebob_rate_table[i] == rate)
+ return i;
+ }
+ return -EINVAL;
+}
+
+int
+snd_bebob_stream_get_rate(struct snd_bebob *bebob, unsigned int *curr_rate)
+{
+ unsigned int tx_rate, rx_rate, trials;
+ int err;
+
+ trials = 0;
+ do {
+ err = avc_general_get_sig_fmt(bebob->unit, &tx_rate,
+ AVC_GENERAL_PLUG_DIR_OUT, 0);
+ } while (err == -EAGAIN && ++trials < 3);
+ if (err < 0)
+ goto end;
+
+ trials = 0;
+ do {
+ err = avc_general_get_sig_fmt(bebob->unit, &rx_rate,
+ AVC_GENERAL_PLUG_DIR_IN, 0);
+ } while (err == -EAGAIN && ++trials < 3);
+ if (err < 0)
+ goto end;
+
+ *curr_rate = rx_rate;
+ if (rx_rate == tx_rate)
+ goto end;
+
+ /* synchronize receive stream rate to transmit stream rate */
+ err = avc_general_set_sig_fmt(bebob->unit, rx_rate,
+ AVC_GENERAL_PLUG_DIR_IN, 0);
+end:
+ return err;
+}
+
+int
+snd_bebob_stream_set_rate(struct snd_bebob *bebob, unsigned int rate)
+{
+ int err;
+
+ err = avc_general_set_sig_fmt(bebob->unit, rate,
+ AVC_GENERAL_PLUG_DIR_OUT, 0);
+ if (err < 0)
+ goto end;
+
+ err = avc_general_set_sig_fmt(bebob->unit, rate,
+ AVC_GENERAL_PLUG_DIR_IN, 0);
+ if (err < 0)
+ goto end;
+
+ /*
+ * Some devices need a bit time for transition.
+ * 300msec is got by some experiments.
+ */
+ msleep(300);
+end:
+ return err;
+}
+
+int
+snd_bebob_stream_check_internal_clock(struct snd_bebob *bebob, bool *internal)
+{
+ struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES], input[7];
+ unsigned int id;
+ int err = 0;
+
+ *internal = false;
+
+ /* 1.The device has its own operation to switch source of clock */
+ if (clk_spec) {
+ err = clk_spec->get(bebob, &id);
+ if (err < 0)
+ dev_err(&bebob->unit->device,
+ "fail to get clock source: %d\n", err);
+ else if (strncmp(clk_spec->labels[id], SND_BEBOB_CLOCK_INTERNAL,
+ strlen(SND_BEBOB_CLOCK_INTERNAL)) == 0)
+ *internal = true;
+ goto end;
+ }
+
+ /*
+ * 2.The device don't support to switch source of clock then assumed
+ * to use internal clock always
+ */
+ if (bebob->sync_input_plug < 0) {
+ *internal = true;
+ goto end;
+ }
+
+ /*
+ * 3.The device supports to switch source of clock by an usual way.
+ * Let's check input for 'Music Sub Unit Sync Input' plug.
+ */
+ avc_bridgeco_fill_msu_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN,
+ bebob->sync_input_plug);
+ err = avc_bridgeco_get_plug_input(bebob->unit, addr, input);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get an input for MSU in plug %d: %d\n",
+ bebob->sync_input_plug, err);
+ goto end;
+ }
+
+ /*
+ * If there are no input plugs, all of fields are 0xff.
+ * Here check the first field. This field is used for direction.
+ */
+ if (input[0] == 0xff) {
+ *internal = true;
+ goto end;
+ }
+
+ /*
+ * If source of clock is internal CSR, Music Sub Unit Sync Input is
+ * a destination of Music Sub Unit Sync Output.
+ */
+ *internal = ((input[0] == AVC_BRIDGECO_PLUG_DIR_OUT) &&
+ (input[1] == AVC_BRIDGECO_PLUG_MODE_SUBUNIT) &&
+ (input[2] == 0x0c) &&
+ (input[3] == 0x00));
+end:
+ return err;
+}
+
+static unsigned int
+map_data_channels(struct snd_bebob *bebob, struct amdtp_stream *s)
+{
+ unsigned int sec, sections, ch, channels;
+ unsigned int pcm, midi, location;
+ unsigned int stm_pos, sec_loc, pos;
+ u8 *buf, addr[AVC_BRIDGECO_ADDR_BYTES], type;
+ enum avc_bridgeco_plug_dir dir;
+ int err;
+
+ /*
+ * The length of return value of this command cannot be expected. Here
+ * use the maximum length of FCP.
+ */
+ buf = kzalloc(256, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ if (s == &bebob->tx_stream)
+ dir = AVC_BRIDGECO_PLUG_DIR_OUT;
+ else
+ dir = AVC_BRIDGECO_PLUG_DIR_IN;
+
+ avc_bridgeco_fill_unit_addr(addr, dir, AVC_BRIDGECO_PLUG_UNIT_ISOC, 0);
+ err = avc_bridgeco_get_plug_ch_pos(bebob->unit, addr, buf, 256);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get channel position for isoc %s plug 0: %d\n",
+ (dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" : "out",
+ err);
+ goto end;
+ }
+ pos = 0;
+
+ /* positions in I/O buffer */
+ pcm = 0;
+ midi = 0;
+
+ /* the number of sections in AMDTP packet */
+ sections = buf[pos++];
+
+ for (sec = 0; sec < sections; sec++) {
+ /* type of this section */
+ avc_bridgeco_fill_unit_addr(addr, dir,
+ AVC_BRIDGECO_PLUG_UNIT_ISOC, 0);
+ err = avc_bridgeco_get_plug_section_type(bebob->unit, addr,
+ sec, &type);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get section type for isoc %s plug 0: %d\n",
+ (dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" :
+ "out",
+ err);
+ goto end;
+ }
+ /* NoType */
+ if (type == 0xff) {
+ err = -ENOSYS;
+ goto end;
+ }
+
+ /* the number of channels in this section */
+ channels = buf[pos++];
+
+ for (ch = 0; ch < channels; ch++) {
+ /* position of this channel in AMDTP packet */
+ stm_pos = buf[pos++] - 1;
+ /* location of this channel in this section */
+ sec_loc = buf[pos++] - 1;
+
+ /*
+ * Basically the number of location is within the
+ * number of channels in this section. But some models
+ * of M-Audio don't follow this. Its location for MIDI
+ * is the position of MIDI channels in AMDTP packet.
+ */
+ if (sec_loc >= channels)
+ sec_loc = ch;
+
+ switch (type) {
+ /* for MIDI conformant data channel */
+ case 0x0a:
+ /* AMDTP_MAX_CHANNELS_FOR_MIDI is 1. */
+ if ((midi > 0) && (stm_pos != midi)) {
+ err = -ENOSYS;
+ goto end;
+ }
+ s->midi_position = stm_pos;
+ midi = stm_pos;
+ break;
+ /* for PCM data channel */
+ case 0x01: /* Headphone */
+ case 0x02: /* Microphone */
+ case 0x03: /* Line */
+ case 0x04: /* SPDIF */
+ case 0x05: /* ADAT */
+ case 0x06: /* TDIF */
+ case 0x07: /* MADI */
+ /* for undefined/changeable signal */
+ case 0x08: /* Analog */
+ case 0x09: /* Digital */
+ default:
+ location = pcm + sec_loc;
+ if (location >= AMDTP_MAX_CHANNELS_FOR_PCM) {
+ err = -ENOSYS;
+ goto end;
+ }
+ s->pcm_positions[location] = stm_pos;
+ break;
+ }
+ }
+
+ if (type != 0x0a)
+ pcm += channels;
+ else
+ midi += channels;
+ }
+end:
+ kfree(buf);
+ return err;
+}
+
+static int
+init_both_connections(struct snd_bebob *bebob)
+{
+ int err;
+
+ err = cmp_connection_init(&bebob->in_conn,
+ bebob->unit, CMP_INPUT, 0);
+ if (err < 0)
+ goto end;
+
+ err = cmp_connection_init(&bebob->out_conn,
+ bebob->unit, CMP_OUTPUT, 0);
+ if (err < 0)
+ cmp_connection_destroy(&bebob->in_conn);
+end:
+ return err;
+}
+
+static int
+check_connection_used_by_others(struct snd_bebob *bebob, struct amdtp_stream *s)
+{
+ struct cmp_connection *conn;
+ bool used;
+ int err;
+
+ if (s == &bebob->tx_stream)
+ conn = &bebob->out_conn;
+ else
+ conn = &bebob->in_conn;
+
+ err = cmp_connection_check_used(conn, &used);
+ if ((err >= 0) && used && !amdtp_stream_running(s)) {
+ dev_err(&bebob->unit->device,
+ "Connection established by others: %cPCR[%d]\n",
+ (conn->direction == CMP_OUTPUT) ? 'o' : 'i',
+ conn->pcr_index);
+ err = -EBUSY;
+ }
+
+ return err;
+}
+
+static int
+make_both_connections(struct snd_bebob *bebob, unsigned int rate)
+{
+ int index, pcm_channels, midi_channels, err = 0;
+
+ if (bebob->connected)
+ goto end;
+
+ /* confirm params for both streams */
+ index = get_formation_index(rate);
+ pcm_channels = bebob->tx_stream_formations[index].pcm;
+ midi_channels = bebob->tx_stream_formations[index].midi;
+ amdtp_stream_set_parameters(&bebob->tx_stream,
+ rate, pcm_channels, midi_channels * 8);
+ pcm_channels = bebob->rx_stream_formations[index].pcm;
+ midi_channels = bebob->rx_stream_formations[index].midi;
+ amdtp_stream_set_parameters(&bebob->rx_stream,
+ rate, pcm_channels, midi_channels * 8);
+
+ /* establish connections for both streams */
+ err = cmp_connection_establish(&bebob->out_conn,
+ amdtp_stream_get_max_payload(&bebob->tx_stream));
+ if (err < 0)
+ goto end;
+ err = cmp_connection_establish(&bebob->in_conn,
+ amdtp_stream_get_max_payload(&bebob->rx_stream));
+ if (err < 0) {
+ cmp_connection_break(&bebob->out_conn);
+ goto end;
+ }
+
+ bebob->connected = true;
+end:
+ return err;
+}
+
+static void
+break_both_connections(struct snd_bebob *bebob)
+{
+ cmp_connection_break(&bebob->in_conn);
+ cmp_connection_break(&bebob->out_conn);
+
+ bebob->connected = false;
+
+ /* These models seems to be in transition state for a longer time. */
+ if (bebob->maudio_special_quirk != NULL)
+ msleep(200);
+}
+
+static void
+destroy_both_connections(struct snd_bebob *bebob)
+{
+ break_both_connections(bebob);
+
+ cmp_connection_destroy(&bebob->in_conn);
+ cmp_connection_destroy(&bebob->out_conn);
+}
+
+static int
+get_sync_mode(struct snd_bebob *bebob, enum cip_flags *sync_mode)
+{
+ /* currently this module doesn't support SYT-Match mode */
+ *sync_mode = CIP_SYNC_TO_DEVICE;
+ return 0;
+}
+
+static int
+start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream,
+ unsigned int rate)
+{
+ struct cmp_connection *conn;
+ int err = 0;
+
+ if (stream == &bebob->rx_stream)
+ conn = &bebob->in_conn;
+ else
+ conn = &bebob->out_conn;
+
+ /* channel mapping */
+ if (bebob->maudio_special_quirk == NULL) {
+ err = map_data_channels(bebob, stream);
+ if (err < 0)
+ goto end;
+ }
+
+ /* start amdtp stream */
+ err = amdtp_stream_start(stream,
+ conn->resources.channel,
+ conn->speed);
+end:
+ return err;
+}
+
+int snd_bebob_stream_init_duplex(struct snd_bebob *bebob)
+{
+ int err;
+
+ err = init_both_connections(bebob);
+ if (err < 0)
+ goto end;
+
+ err = amdtp_stream_init(&bebob->tx_stream, bebob->unit,
+ AMDTP_IN_STREAM, CIP_BLOCKING);
+ if (err < 0) {
+ amdtp_stream_destroy(&bebob->tx_stream);
+ destroy_both_connections(bebob);
+ goto end;
+ }
+ /* See comments in next function */
+ init_completion(&bebob->bus_reset);
+ bebob->tx_stream.flags |= CIP_SKIP_INIT_DBC_CHECK;
+ /*
+ * At high sampling rate, M-Audio special firmware transmits empty
+ * packet with the value of dbc incremented by 8 but the others are
+ * valid to IEC 61883-1.
+ */
+ if (bebob->maudio_special_quirk)
+ bebob->tx_stream.flags |= CIP_EMPTY_HAS_WRONG_DBC;
+
+ err = amdtp_stream_init(&bebob->rx_stream, bebob->unit,
+ AMDTP_OUT_STREAM, CIP_BLOCKING);
+ if (err < 0) {
+ amdtp_stream_destroy(&bebob->tx_stream);
+ amdtp_stream_destroy(&bebob->rx_stream);
+ destroy_both_connections(bebob);
+ }
+ /*
+ * The firmware for these devices ignore MIDI messages in more than
+ * first 8 data blocks of an received AMDTP packet.
+ */
+ if (bebob->spec == &maudio_fw410_spec ||
+ bebob->spec == &maudio_special_spec)
+ bebob->rx_stream.rx_blocks_for_midi = 8;
+end:
+ return err;
+}
+
+int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, unsigned int rate)
+{
+ struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate;
+ struct amdtp_stream *master, *slave;
+ atomic_t *slave_substreams;
+ enum cip_flags sync_mode;
+ unsigned int curr_rate;
+ bool updated = false;
+ int err = 0;
+
+ /*
+ * Normal BeBoB firmware has a quirk at bus reset to transmits packets
+ * with discontinuous value in dbc field.
+ *
+ * This 'struct completion' is used to call .update() at first to update
+ * connections/streams. Next following codes handle streaming error.
+ */
+ if (amdtp_streaming_error(&bebob->tx_stream)) {
+ if (completion_done(&bebob->bus_reset))
+ reinit_completion(&bebob->bus_reset);
+
+ updated = (wait_for_completion_interruptible_timeout(
+ &bebob->bus_reset,
+ msecs_to_jiffies(FW_ISO_RESOURCE_DELAY)) > 0);
+ }
+
+ mutex_lock(&bebob->mutex);
+
+ /* Need no substreams */
+ if (atomic_read(&bebob->playback_substreams) == 0 &&
+ atomic_read(&bebob->capture_substreams) == 0)
+ goto end;
+
+ err = get_sync_mode(bebob, &sync_mode);
+ if (err < 0)
+ goto end;
+ if (sync_mode == CIP_SYNC_TO_DEVICE) {
+ master = &bebob->tx_stream;
+ slave = &bebob->rx_stream;
+ slave_substreams = &bebob->playback_substreams;
+ } else {
+ master = &bebob->rx_stream;
+ slave = &bebob->tx_stream;
+ slave_substreams = &bebob->capture_substreams;
+ }
+
+ /*
+ * Considering JACK/FFADO streaming:
+ * TODO: This can be removed hwdep functionality becomes popular.
+ */
+ err = check_connection_used_by_others(bebob, master);
+ if (err < 0)
+ goto end;
+
+ /*
+ * packet queueing error or detecting discontinuity
+ *
+ * At bus reset, connections should not be broken here. So streams need
+ * to be re-started. This is a reason to use SKIP_INIT_DBC_CHECK flag.
+ */
+ if (amdtp_streaming_error(master))
+ amdtp_stream_stop(master);
+ if (amdtp_streaming_error(slave))
+ amdtp_stream_stop(slave);
+ if (!updated &&
+ !amdtp_stream_running(master) && !amdtp_stream_running(slave))
+ break_both_connections(bebob);
+
+ /* stop streams if rate is different */
+ err = rate_spec->get(bebob, &curr_rate);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get sampling rate: %d\n", err);
+ goto end;
+ }
+ if (rate == 0)
+ rate = curr_rate;
+ if (rate != curr_rate) {
+ amdtp_stream_stop(master);
+ amdtp_stream_stop(slave);
+ break_both_connections(bebob);
+ }
+
+ /* master should be always running */
+ if (!amdtp_stream_running(master)) {
+ amdtp_stream_set_sync(sync_mode, master, slave);
+ bebob->master = master;
+
+ /*
+ * NOTE:
+ * If establishing connections at first, Yamaha GO46
+ * (and maybe Terratec X24) don't generate sound.
+ *
+ * For firmware customized by M-Audio, refer to next NOTE.
+ */
+ if (bebob->maudio_special_quirk == NULL) {
+ err = rate_spec->set(bebob, rate);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to set sampling rate: %d\n",
+ err);
+ goto end;
+ }
+ }
+
+ err = make_both_connections(bebob, rate);
+ if (err < 0)
+ goto end;
+
+ err = start_stream(bebob, master, rate);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to run AMDTP master stream:%d\n", err);
+ break_both_connections(bebob);
+ goto end;
+ }
+
+ /*
+ * NOTE:
+ * The firmware customized by M-Audio uses these commands to
+ * start transmitting stream. This is not usual way.
+ */
+ if (bebob->maudio_special_quirk != NULL) {
+ err = rate_spec->set(bebob, rate);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to ensure sampling rate: %d\n",
+ err);
+ amdtp_stream_stop(master);
+ break_both_connections(bebob);
+ goto end;
+ }
+ }
+
+ /* wait first callback */
+ if (!amdtp_stream_wait_callback(master, CALLBACK_TIMEOUT)) {
+ amdtp_stream_stop(master);
+ break_both_connections(bebob);
+ err = -ETIMEDOUT;
+ goto end;
+ }
+ }
+
+ /* start slave if needed */
+ if (atomic_read(slave_substreams) > 0 && !amdtp_stream_running(slave)) {
+ err = start_stream(bebob, slave, rate);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to run AMDTP slave stream:%d\n", err);
+ amdtp_stream_stop(master);
+ break_both_connections(bebob);
+ goto end;
+ }
+
+ /* wait first callback */
+ if (!amdtp_stream_wait_callback(slave, CALLBACK_TIMEOUT)) {
+ amdtp_stream_stop(slave);
+ amdtp_stream_stop(master);
+ break_both_connections(bebob);
+ err = -ETIMEDOUT;
+ }
+ }
+end:
+ mutex_unlock(&bebob->mutex);
+ return err;
+}
+
+void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob)
+{
+ struct amdtp_stream *master, *slave;
+ atomic_t *master_substreams, *slave_substreams;
+
+ if (bebob->master == &bebob->rx_stream) {
+ slave = &bebob->tx_stream;
+ master = &bebob->rx_stream;
+ slave_substreams = &bebob->capture_substreams;
+ master_substreams = &bebob->playback_substreams;
+ } else {
+ slave = &bebob->rx_stream;
+ master = &bebob->tx_stream;
+ slave_substreams = &bebob->playback_substreams;
+ master_substreams = &bebob->capture_substreams;
+ }
+
+ mutex_lock(&bebob->mutex);
+
+ if (atomic_read(slave_substreams) == 0) {
+ amdtp_stream_pcm_abort(slave);
+ amdtp_stream_stop(slave);
+
+ if (atomic_read(master_substreams) == 0) {
+ amdtp_stream_pcm_abort(master);
+ amdtp_stream_stop(master);
+ break_both_connections(bebob);
+ }
+ }
+
+ mutex_unlock(&bebob->mutex);
+}
+
+void snd_bebob_stream_update_duplex(struct snd_bebob *bebob)
+{
+ /* vs. XRUN recovery due to discontinuity at bus reset */
+ mutex_lock(&bebob->mutex);
+
+ if ((cmp_connection_update(&bebob->in_conn) < 0) ||
+ (cmp_connection_update(&bebob->out_conn) < 0)) {
+ amdtp_stream_pcm_abort(&bebob->rx_stream);
+ amdtp_stream_pcm_abort(&bebob->tx_stream);
+ amdtp_stream_stop(&bebob->rx_stream);
+ amdtp_stream_stop(&bebob->tx_stream);
+ break_both_connections(bebob);
+ } else {
+ amdtp_stream_update(&bebob->rx_stream);
+ amdtp_stream_update(&bebob->tx_stream);
+ }
+
+ /* wake up stream_start_duplex() */
+ if (!completion_done(&bebob->bus_reset))
+ complete_all(&bebob->bus_reset);
+
+ mutex_unlock(&bebob->mutex);
+}
+
+void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob)
+{
+ mutex_lock(&bebob->mutex);
+
+ amdtp_stream_pcm_abort(&bebob->rx_stream);
+ amdtp_stream_pcm_abort(&bebob->tx_stream);
+
+ amdtp_stream_stop(&bebob->rx_stream);
+ amdtp_stream_stop(&bebob->tx_stream);
+
+ amdtp_stream_destroy(&bebob->rx_stream);
+ amdtp_stream_destroy(&bebob->tx_stream);
+
+ destroy_both_connections(bebob);
+
+ mutex_unlock(&bebob->mutex);
+}
+
+/*
+ * See: Table 50: Extended Stream Format Info Format Hierarchy Level 2’
+ * in Additional AVC commands (Nov 2003, BridgeCo)
+ * Also 'Clause 12 AM824 sequence adaption layers' in IEC 61883-6:2005
+ */
+static int
+parse_stream_formation(u8 *buf, unsigned int len,
+ struct snd_bebob_stream_formation *formation)
+{
+ unsigned int i, e, channels, format;
+
+ /*
+ * this module can support a hierarchy combination that:
+ * Root: Audio and Music (0x90)
+ * Level 1: AM824 Compound (0x40)
+ */
+ if ((buf[0] != 0x90) || (buf[1] != 0x40))
+ return -ENOSYS;
+
+ /* check sampling rate */
+ for (i = 0; i < ARRAY_SIZE(bridgeco_freq_table); i++) {
+ if (buf[2] == bridgeco_freq_table[i])
+ break;
+ }
+ if (i == ARRAY_SIZE(bridgeco_freq_table))
+ return -ENOSYS;
+
+ /* Avoid double count by different entries for the same rate. */
+ memset(&formation[i], 0, sizeof(struct snd_bebob_stream_formation));
+
+ for (e = 0; e < buf[4]; e++) {
+ channels = buf[5 + e * 2];
+ format = buf[6 + e * 2];
+
+ switch (format) {
+ /* IEC 60958 Conformant, currently handled as MBLA */
+ case 0x00:
+ /* Multi bit linear audio */
+ case 0x06: /* Raw */
+ formation[i].pcm += channels;
+ break;
+ /* MIDI Conformant */
+ case 0x0d:
+ formation[i].midi += channels;
+ break;
+ /* IEC 61937-3 to 7 */
+ case 0x01:
+ case 0x02:
+ case 0x03:
+ case 0x04:
+ case 0x05:
+ /* Multi bit linear audio */
+ case 0x07: /* DVD-Audio */
+ case 0x0c: /* High Precision */
+ /* One Bit Audio */
+ case 0x08: /* (Plain) Raw */
+ case 0x09: /* (Plain) SACD */
+ case 0x0a: /* (Encoded) Raw */
+ case 0x0b: /* (Encoded) SACD */
+ /* Synchronization Stream (Stereo Raw audio) */
+ case 0x40:
+ /* Don't care */
+ case 0xff:
+ default:
+ return -ENOSYS; /* not supported */
+ }
+ }
+
+ if (formation[i].pcm > AMDTP_MAX_CHANNELS_FOR_PCM ||
+ formation[i].midi > AMDTP_MAX_CHANNELS_FOR_MIDI)
+ return -ENOSYS;
+
+ return 0;
+}
+
+static int
+fill_stream_formations(struct snd_bebob *bebob, enum avc_bridgeco_plug_dir dir,
+ unsigned short pid)
+{
+ u8 *buf;
+ struct snd_bebob_stream_formation *formations;
+ unsigned int len, eid;
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES];
+ int err;
+
+ buf = kmalloc(FORMAT_MAXIMUM_LENGTH, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ if (dir == AVC_BRIDGECO_PLUG_DIR_IN)
+ formations = bebob->rx_stream_formations;
+ else
+ formations = bebob->tx_stream_formations;
+
+ for (eid = 0; eid < SND_BEBOB_STRM_FMT_ENTRIES; eid++) {
+ len = FORMAT_MAXIMUM_LENGTH;
+ avc_bridgeco_fill_unit_addr(addr, dir,
+ AVC_BRIDGECO_PLUG_UNIT_ISOC, pid);
+ err = avc_bridgeco_get_plug_strm_fmt(bebob->unit, addr, buf,
+ &len, eid);
+ /* No entries remained. */
+ if (err == -EINVAL && eid > 0) {
+ err = 0;
+ break;
+ } else if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get stream format %d for isoc %s plug %d:%d\n",
+ eid,
+ (dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" :
+ "out",
+ pid, err);
+ break;
+ }
+
+ err = parse_stream_formation(buf, len, formations);
+ if (err < 0)
+ break;
+ }
+
+ kfree(buf);
+ return err;
+}
+
+static int
+seek_msu_sync_input_plug(struct snd_bebob *bebob)
+{
+ u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES];
+ unsigned int i;
+ enum avc_bridgeco_plug_type type;
+ int err;
+
+ /* Get the number of Music Sub Unit for both direction. */
+ err = avc_general_get_plug_info(bebob->unit, 0x0c, 0x00, 0x00, plugs);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get info for MSU in/out plugs: %d\n",
+ err);
+ goto end;
+ }
+
+ /* seek destination plugs for 'MSU sync input' */
+ bebob->sync_input_plug = -1;
+ for (i = 0; i < plugs[0]; i++) {
+ avc_bridgeco_fill_msu_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, i);
+ err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get type for MSU in plug %d: %d\n",
+ i, err);
+ goto end;
+ }
+
+ if (type == AVC_BRIDGECO_PLUG_TYPE_SYNC) {
+ bebob->sync_input_plug = i;
+ break;
+ }
+ }
+end:
+ return err;
+}
+
+int snd_bebob_stream_discover(struct snd_bebob *bebob)
+{
+ struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
+ u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES];
+ enum avc_bridgeco_plug_type type;
+ unsigned int i;
+ int err;
+
+ /* the number of plugs for isoc in/out, ext in/out */
+ err = avc_general_get_plug_info(bebob->unit, 0x1f, 0x07, 0x00, plugs);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get info for isoc/external in/out plugs: %d\n",
+ err);
+ goto end;
+ }
+
+ /*
+ * This module supports at least one isoc input plug and one isoc
+ * output plug.
+ */
+ if ((plugs[0] == 0) || (plugs[1] == 0)) {
+ err = -ENOSYS;
+ goto end;
+ }
+
+ avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN,
+ AVC_BRIDGECO_PLUG_UNIT_ISOC, 0);
+ err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get type for isoc in plug 0: %d\n", err);
+ goto end;
+ } else if (type != AVC_BRIDGECO_PLUG_TYPE_ISOC) {
+ err = -ENOSYS;
+ goto end;
+ }
+ err = fill_stream_formations(bebob, AVC_BRIDGECO_PLUG_DIR_IN, 0);
+ if (err < 0)
+ goto end;
+
+ avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_OUT,
+ AVC_BRIDGECO_PLUG_UNIT_ISOC, 0);
+ err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get type for isoc out plug 0: %d\n", err);
+ goto end;
+ } else if (type != AVC_BRIDGECO_PLUG_TYPE_ISOC) {
+ err = -ENOSYS;
+ goto end;
+ }
+ err = fill_stream_formations(bebob, AVC_BRIDGECO_PLUG_DIR_OUT, 0);
+ if (err < 0)
+ goto end;
+
+ /* count external input plugs for MIDI */
+ bebob->midi_input_ports = 0;
+ for (i = 0; i < plugs[2]; i++) {
+ avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN,
+ AVC_BRIDGECO_PLUG_UNIT_EXT, i);
+ err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get type for external in plug %d: %d\n",
+ i, err);
+ goto end;
+ } else if (type == AVC_BRIDGECO_PLUG_TYPE_MIDI) {
+ bebob->midi_input_ports++;
+ }
+ }
+
+ /* count external output plugs for MIDI */
+ bebob->midi_output_ports = 0;
+ for (i = 0; i < plugs[3]; i++) {
+ avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_OUT,
+ AVC_BRIDGECO_PLUG_UNIT_EXT, i);
+ err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get type for external out plug %d: %d\n",
+ i, err);
+ goto end;
+ } else if (type == AVC_BRIDGECO_PLUG_TYPE_MIDI) {
+ bebob->midi_output_ports++;
+ }
+ }
+
+ /* for check source of clock later */
+ if (!clk_spec)
+ err = seek_msu_sync_input_plug(bebob);
+end:
+ return err;
+}
+
+void snd_bebob_stream_lock_changed(struct snd_bebob *bebob)
+{
+ bebob->dev_lock_changed = true;
+ wake_up(&bebob->hwdep_wait);
+}
+
+int snd_bebob_stream_lock_try(struct snd_bebob *bebob)
+{
+ int err;
+
+ spin_lock_irq(&bebob->lock);
+
+ /* user land lock this */
+ if (bebob->dev_lock_count < 0) {
+ err = -EBUSY;
+ goto end;
+ }
+
+ /* this is the first time */
+ if (bebob->dev_lock_count++ == 0)
+ snd_bebob_stream_lock_changed(bebob);
+ err = 0;
+end:
+ spin_unlock_irq(&bebob->lock);
+ return err;
+}
+
+void snd_bebob_stream_lock_release(struct snd_bebob *bebob)
+{
+ spin_lock_irq(&bebob->lock);
+
+ if (WARN_ON(bebob->dev_lock_count <= 0))
+ goto end;
+ if (--bebob->dev_lock_count == 0)
+ snd_bebob_stream_lock_changed(bebob);
+end:
+ spin_unlock_irq(&bebob->lock);
+}
diff --git a/sound/firewire/bebob/bebob_terratec.c b/sound/firewire/bebob/bebob_terratec.c
new file mode 100644
index 000000000000..eef8ea7d9b97
--- /dev/null
+++ b/sound/firewire/bebob/bebob_terratec.c
@@ -0,0 +1,68 @@
+/*
+ * bebob_terratec.c - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "./bebob.h"
+
+static char *const phase88_rack_clk_src_labels[] = {
+ SND_BEBOB_CLOCK_INTERNAL, "Digital In", "Word Clock"
+};
+static int
+phase88_rack_clk_src_get(struct snd_bebob *bebob, unsigned int *id)
+{
+ unsigned int enable_ext, enable_word;
+ int err;
+
+ err = avc_audio_get_selector(bebob->unit, 0, 0, &enable_ext);
+ if (err < 0)
+ goto end;
+ err = avc_audio_get_selector(bebob->unit, 0, 0, &enable_word);
+ if (err < 0)
+ goto end;
+
+ *id = (enable_ext & 0x01) | ((enable_word & 0x01) << 1);
+end:
+ return err;
+}
+
+static char *const phase24_series_clk_src_labels[] = {
+ SND_BEBOB_CLOCK_INTERNAL, "Digital In"
+};
+static int
+phase24_series_clk_src_get(struct snd_bebob *bebob, unsigned int *id)
+{
+ return avc_audio_get_selector(bebob->unit, 0, 4, id);
+}
+
+static struct snd_bebob_rate_spec phase_series_rate_spec = {
+ .get = &snd_bebob_stream_get_rate,
+ .set = &snd_bebob_stream_set_rate,
+};
+
+/* PHASE 88 Rack FW */
+static struct snd_bebob_clock_spec phase88_rack_clk = {
+ .num = ARRAY_SIZE(phase88_rack_clk_src_labels),
+ .labels = phase88_rack_clk_src_labels,
+ .get = &phase88_rack_clk_src_get,
+};
+struct snd_bebob_spec phase88_rack_spec = {
+ .clock = &phase88_rack_clk,
+ .rate = &phase_series_rate_spec,
+ .meter = NULL
+};
+
+/* 'PHASE 24 FW' and 'PHASE X24 FW' */
+static struct snd_bebob_clock_spec phase24_series_clk = {
+ .num = ARRAY_SIZE(phase24_series_clk_src_labels),
+ .labels = phase24_series_clk_src_labels,
+ .get = &phase24_series_clk_src_get,
+};
+struct snd_bebob_spec phase24_series_spec = {
+ .clock = &phase24_series_clk,
+ .rate = &phase_series_rate_spec,
+ .meter = NULL
+};
diff --git a/sound/firewire/bebob/bebob_yamaha.c b/sound/firewire/bebob/bebob_yamaha.c
new file mode 100644
index 000000000000..9b7e798180ff
--- /dev/null
+++ b/sound/firewire/bebob/bebob_yamaha.c
@@ -0,0 +1,50 @@
+/*
+ * bebob_yamaha.c - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "./bebob.h"
+
+/*
+ * NOTE:
+ * Yamaha GO44 is not designed to be used as stand-alone mixer. So any streams
+ * must be accompanied. If changing the state, a LED on the device starts to
+ * blink and its sync status is false. In this state, the device sounds nothing
+ * even if streaming. To start streaming at the current sampling rate is only
+ * way to revocer this state. GO46 is better for stand-alone mixer.
+ *
+ * Both of them have a capability to change its sampling rate up to 192.0kHz.
+ * At 192.0kHz, the device reports 4 PCM-in, 1 MIDI-in, 6 PCM-out, 1 MIDI-out.
+ * But Yamaha's driver reduce 2 PCM-in, 1 MIDI-in, 2 PCM-out, 1 MIDI-out to use
+ * 'Extended Stream Format Information Command - Single Request' in 'Additional
+ * AVC commands' defined by BridgeCo.
+ * This ALSA driver don't do this because a bit tiresome. Then isochronous
+ * streaming with many asynchronous transactions brings sounds with noises.
+ * Unfortunately current 'ffado-mixer' generated many asynchronous transaction
+ * to observe device's state, mainly check cmp connection and signal format. I
+ * reccomend users to close ffado-mixer at 192.0kHz if mixer is needless.
+ */
+
+static char *const clk_src_labels[] = {SND_BEBOB_CLOCK_INTERNAL, "SPDIF"};
+static int
+clk_src_get(struct snd_bebob *bebob, unsigned int *id)
+{
+ return avc_audio_get_selector(bebob->unit, 0, 4, id);
+}
+static struct snd_bebob_clock_spec clock_spec = {
+ .num = ARRAY_SIZE(clk_src_labels),
+ .labels = clk_src_labels,
+ .get = &clk_src_get,
+};
+static struct snd_bebob_rate_spec rate_spec = {
+ .get = &snd_bebob_stream_get_rate,
+ .set = &snd_bebob_stream_set_rate,
+};
+struct snd_bebob_spec yamaha_go_spec = {
+ .clock = &clock_spec,
+ .rate = &rate_spec,
+ .meter = NULL
+};
diff --git a/sound/firewire/cmp.c b/sound/firewire/cmp.c
index efdbf585e404..ba8df5a1be39 100644
--- a/sound/firewire/cmp.c
+++ b/sound/firewire/cmp.c
@@ -14,18 +14,28 @@
#include "iso-resources.h"
#include "cmp.h"
-#define IMPR_SPEED_MASK 0xc0000000
-#define IMPR_SPEED_SHIFT 30
-#define IMPR_XSPEED_MASK 0x00000060
-#define IMPR_XSPEED_SHIFT 5
-#define IMPR_PLUGS_MASK 0x0000001f
-
-#define IPCR_ONLINE 0x80000000
-#define IPCR_BCAST_CONN 0x40000000
-#define IPCR_P2P_CONN_MASK 0x3f000000
-#define IPCR_P2P_CONN_SHIFT 24
-#define IPCR_CHANNEL_MASK 0x003f0000
-#define IPCR_CHANNEL_SHIFT 16
+/* MPR common fields */
+#define MPR_SPEED_MASK 0xc0000000
+#define MPR_SPEED_SHIFT 30
+#define MPR_XSPEED_MASK 0x00000060
+#define MPR_XSPEED_SHIFT 5
+#define MPR_PLUGS_MASK 0x0000001f
+
+/* PCR common fields */
+#define PCR_ONLINE 0x80000000
+#define PCR_BCAST_CONN 0x40000000
+#define PCR_P2P_CONN_MASK 0x3f000000
+#define PCR_P2P_CONN_SHIFT 24
+#define PCR_CHANNEL_MASK 0x003f0000
+#define PCR_CHANNEL_SHIFT 16
+
+/* oPCR specific fields */
+#define OPCR_XSPEED_MASK 0x00C00000
+#define OPCR_XSPEED_SHIFT 22
+#define OPCR_SPEED_MASK 0x0000C000
+#define OPCR_SPEED_SHIFT 14
+#define OPCR_OVERHEAD_ID_MASK 0x00003C00
+#define OPCR_OVERHEAD_ID_SHIFT 10
enum bus_reset_handling {
ABORT_ON_BUS_RESET,
@@ -39,10 +49,27 @@ void cmp_error(struct cmp_connection *c, const char *fmt, ...)
va_start(va, fmt);
dev_err(&c->resources.unit->device, "%cPCR%u: %pV",
- 'i', c->pcr_index, &(struct va_format){ fmt, &va });
+ (c->direction == CMP_INPUT) ? 'i' : 'o',
+ c->pcr_index, &(struct va_format){ fmt, &va });
va_end(va);
}
+static u64 mpr_address(struct cmp_connection *c)
+{
+ if (c->direction == CMP_INPUT)
+ return CSR_REGISTER_BASE + CSR_IMPR;
+ else
+ return CSR_REGISTER_BASE + CSR_OMPR;
+}
+
+static u64 pcr_address(struct cmp_connection *c)
+{
+ if (c->direction == CMP_INPUT)
+ return CSR_REGISTER_BASE + CSR_IPCR(c->pcr_index);
+ else
+ return CSR_REGISTER_BASE + CSR_OPCR(c->pcr_index);
+}
+
static int pcr_modify(struct cmp_connection *c,
__be32 (*modify)(struct cmp_connection *c, __be32 old),
int (*check)(struct cmp_connection *c, __be32 pcr),
@@ -58,8 +85,7 @@ static int pcr_modify(struct cmp_connection *c,
err = snd_fw_transaction(
c->resources.unit, TCODE_LOCK_COMPARE_SWAP,
- CSR_REGISTER_BASE + CSR_IPCR(c->pcr_index),
- buffer, 8,
+ pcr_address(c), buffer, 8,
FW_FIXED_GENERATION | c->resources.generation);
if (err < 0) {
@@ -88,24 +114,25 @@ static int pcr_modify(struct cmp_connection *c,
* cmp_connection_init - initializes a connection manager
* @c: the connection manager to initialize
* @unit: a unit of the target device
- * @ipcr_index: the index of the iPCR on the target device
+ * @pcr_index: the index of the iPCR/oPCR on the target device
*/
int cmp_connection_init(struct cmp_connection *c,
struct fw_unit *unit,
- unsigned int ipcr_index)
+ enum cmp_direction direction,
+ unsigned int pcr_index)
{
- __be32 impr_be;
- u32 impr;
+ __be32 mpr_be;
+ u32 mpr;
int err;
+ c->direction = direction;
err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST,
- CSR_REGISTER_BASE + CSR_IMPR,
- &impr_be, 4, 0);
+ mpr_address(c), &mpr_be, 4, 0);
if (err < 0)
return err;
- impr = be32_to_cpu(impr_be);
+ mpr = be32_to_cpu(mpr_be);
- if (ipcr_index >= (impr & IMPR_PLUGS_MASK))
+ if (pcr_index >= (mpr & MPR_PLUGS_MASK))
return -EINVAL;
err = fw_iso_resources_init(&c->resources, unit);
@@ -115,16 +142,36 @@ int cmp_connection_init(struct cmp_connection *c,
c->connected = false;
mutex_init(&c->mutex);
c->last_pcr_value = cpu_to_be32(0x80000000);
- c->pcr_index = ipcr_index;
- c->max_speed = (impr & IMPR_SPEED_MASK) >> IMPR_SPEED_SHIFT;
+ c->pcr_index = pcr_index;
+ c->max_speed = (mpr & MPR_SPEED_MASK) >> MPR_SPEED_SHIFT;
if (c->max_speed == SCODE_BETA)
- c->max_speed += (impr & IMPR_XSPEED_MASK) >> IMPR_XSPEED_SHIFT;
+ c->max_speed += (mpr & MPR_XSPEED_MASK) >> MPR_XSPEED_SHIFT;
return 0;
}
EXPORT_SYMBOL(cmp_connection_init);
/**
+ * cmp_connection_check_used - check connection is already esablished or not
+ * @c: the connection manager to be checked
+ */
+int cmp_connection_check_used(struct cmp_connection *c, bool *used)
+{
+ __be32 pcr;
+ int err;
+
+ err = snd_fw_transaction(
+ c->resources.unit, TCODE_READ_QUADLET_REQUEST,
+ pcr_address(c), &pcr, 4, 0);
+ if (err >= 0)
+ *used = !!(pcr & cpu_to_be32(PCR_BCAST_CONN |
+ PCR_P2P_CONN_MASK));
+
+ return err;
+}
+EXPORT_SYMBOL(cmp_connection_check_used);
+
+/**
* cmp_connection_destroy - free connection manager resources
* @c: the connection manager
*/
@@ -139,23 +186,70 @@ EXPORT_SYMBOL(cmp_connection_destroy);
static __be32 ipcr_set_modify(struct cmp_connection *c, __be32 ipcr)
{
- ipcr &= ~cpu_to_be32(IPCR_BCAST_CONN |
- IPCR_P2P_CONN_MASK |
- IPCR_CHANNEL_MASK);
- ipcr |= cpu_to_be32(1 << IPCR_P2P_CONN_SHIFT);
- ipcr |= cpu_to_be32(c->resources.channel << IPCR_CHANNEL_SHIFT);
+ ipcr &= ~cpu_to_be32(PCR_BCAST_CONN |
+ PCR_P2P_CONN_MASK |
+ PCR_CHANNEL_MASK);
+ ipcr |= cpu_to_be32(1 << PCR_P2P_CONN_SHIFT);
+ ipcr |= cpu_to_be32(c->resources.channel << PCR_CHANNEL_SHIFT);
return ipcr;
}
-static int ipcr_set_check(struct cmp_connection *c, __be32 ipcr)
+static int get_overhead_id(struct cmp_connection *c)
{
- if (ipcr & cpu_to_be32(IPCR_BCAST_CONN |
- IPCR_P2P_CONN_MASK)) {
+ int id;
+
+ /*
+ * apply "oPCR overhead ID encoding"
+ * the encoding table can convert up to 512.
+ * here the value over 512 is converted as the same way as 512.
+ */
+ for (id = 1; id < 16; id++) {
+ if (c->resources.bandwidth_overhead < (id << 5))
+ break;
+ }
+ if (id == 16)
+ id = 0;
+
+ return id;
+}
+
+static __be32 opcr_set_modify(struct cmp_connection *c, __be32 opcr)
+{
+ unsigned int spd, xspd;
+
+ /* generate speed and extended speed field value */
+ if (c->speed > SCODE_400) {
+ spd = SCODE_800;
+ xspd = c->speed - SCODE_800;
+ } else {
+ spd = c->speed;
+ xspd = 0;
+ }
+
+ opcr &= ~cpu_to_be32(PCR_BCAST_CONN |
+ PCR_P2P_CONN_MASK |
+ OPCR_XSPEED_MASK |
+ PCR_CHANNEL_MASK |
+ OPCR_SPEED_MASK |
+ OPCR_OVERHEAD_ID_MASK);
+ opcr |= cpu_to_be32(1 << PCR_P2P_CONN_SHIFT);
+ opcr |= cpu_to_be32(xspd << OPCR_XSPEED_SHIFT);
+ opcr |= cpu_to_be32(c->resources.channel << PCR_CHANNEL_SHIFT);
+ opcr |= cpu_to_be32(spd << OPCR_SPEED_SHIFT);
+ opcr |= cpu_to_be32(get_overhead_id(c) << OPCR_OVERHEAD_ID_SHIFT);
+
+ return opcr;
+}
+
+static int pcr_set_check(struct cmp_connection *c, __be32 pcr)
+{
+ if (pcr & cpu_to_be32(PCR_BCAST_CONN |
+ PCR_P2P_CONN_MASK)) {
cmp_error(c, "plug is already in use\n");
return -EBUSY;
}
- if (!(ipcr & cpu_to_be32(IPCR_ONLINE))) {
+ if (!(pcr & cpu_to_be32(PCR_ONLINE))) {
cmp_error(c, "plug is not on-line\n");
return -ECONNREFUSED;
}
@@ -170,9 +264,9 @@ static int ipcr_set_check(struct cmp_connection *c, __be32 ipcr)
*
* This function establishes a point-to-point connection from the local
* computer to the target by allocating isochronous resources (channel and
- * bandwidth) and setting the target's input plug control register. When this
- * function succeeds, the caller is responsible for starting transmitting
- * packets.
+ * bandwidth) and setting the target's input/output plug control register.
+ * When this function succeeds, the caller is responsible for starting
+ * transmitting packets.
*/
int cmp_connection_establish(struct cmp_connection *c,
unsigned int max_payload_bytes)
@@ -193,8 +287,13 @@ retry_after_bus_reset:
if (err < 0)
goto err_mutex;
- err = pcr_modify(c, ipcr_set_modify, ipcr_set_check,
- ABORT_ON_BUS_RESET);
+ if (c->direction == CMP_OUTPUT)
+ err = pcr_modify(c, opcr_set_modify, pcr_set_check,
+ ABORT_ON_BUS_RESET);
+ else
+ err = pcr_modify(c, ipcr_set_modify, pcr_set_check,
+ ABORT_ON_BUS_RESET);
+
if (err == -EAGAIN) {
fw_iso_resources_free(&c->resources);
goto retry_after_bus_reset;
@@ -221,8 +320,8 @@ EXPORT_SYMBOL(cmp_connection_establish);
* cmp_connection_update - update the connection after a bus reset
* @c: the connection manager
*
- * This function must be called from the driver's .update handler to reestablish
- * any connection that might have been active.
+ * This function must be called from the driver's .update handler to
+ * reestablish any connection that might have been active.
*
* Returns zero on success, or a negative error code. On an error, the
* connection is broken and the caller must stop transmitting iso packets.
@@ -242,8 +341,13 @@ int cmp_connection_update(struct cmp_connection *c)
if (err < 0)
goto err_unconnect;
- err = pcr_modify(c, ipcr_set_modify, ipcr_set_check,
- SUCCEED_ON_BUS_RESET);
+ if (c->direction == CMP_OUTPUT)
+ err = pcr_modify(c, opcr_set_modify, pcr_set_check,
+ SUCCEED_ON_BUS_RESET);
+ else
+ err = pcr_modify(c, ipcr_set_modify, pcr_set_check,
+ SUCCEED_ON_BUS_RESET);
+
if (err < 0)
goto err_resources;
@@ -261,19 +365,18 @@ err_unconnect:
}
EXPORT_SYMBOL(cmp_connection_update);
-
-static __be32 ipcr_break_modify(struct cmp_connection *c, __be32 ipcr)
+static __be32 pcr_break_modify(struct cmp_connection *c, __be32 pcr)
{
- return ipcr & ~cpu_to_be32(IPCR_BCAST_CONN | IPCR_P2P_CONN_MASK);
+ return pcr & ~cpu_to_be32(PCR_BCAST_CONN | PCR_P2P_CONN_MASK);
}
/**
* cmp_connection_break - break the connection to the target
* @c: the connection manager
*
- * This function deactives the connection in the target's input plug control
- * register, and frees the isochronous resources of the connection. Before
- * calling this function, the caller should cease transmitting packets.
+ * This function deactives the connection in the target's input/output plug
+ * control register, and frees the isochronous resources of the connection.
+ * Before calling this function, the caller should cease transmitting packets.
*/
void cmp_connection_break(struct cmp_connection *c)
{
@@ -286,7 +389,7 @@ void cmp_connection_break(struct cmp_connection *c)
return;
}
- err = pcr_modify(c, ipcr_break_modify, NULL, SUCCEED_ON_BUS_RESET);
+ err = pcr_modify(c, pcr_break_modify, NULL, SUCCEED_ON_BUS_RESET);
if (err < 0)
cmp_error(c, "plug is still connected\n");
diff --git a/sound/firewire/cmp.h b/sound/firewire/cmp.h
index f47de08feb12..ebcb48484fca 100644
--- a/sound/firewire/cmp.h
+++ b/sound/firewire/cmp.h
@@ -7,12 +7,17 @@
struct fw_unit;
+enum cmp_direction {
+ CMP_INPUT = 0,
+ CMP_OUTPUT,
+};
+
/**
* struct cmp_connection - manages an isochronous connection to a device
* @speed: the connection's actual speed
*
- * This structure manages (using CMP) an isochronous stream from the local
- * computer to a device's input plug (iPCR).
+ * This structure manages (using CMP) an isochronous stream between the local
+ * computer and a device's input plug (iPCR) and output plug (oPCR).
*
* There is no corresponding oPCR created on the local computer, so it is not
* possible to overlay connections on top of this one.
@@ -26,11 +31,14 @@ struct cmp_connection {
__be32 last_pcr_value;
unsigned int pcr_index;
unsigned int max_speed;
+ enum cmp_direction direction;
};
int cmp_connection_init(struct cmp_connection *connection,
struct fw_unit *unit,
- unsigned int ipcr_index);
+ enum cmp_direction direction,
+ unsigned int pcr_index);
+int cmp_connection_check_used(struct cmp_connection *connection, bool *used);
void cmp_connection_destroy(struct cmp_connection *connection);
int cmp_connection_establish(struct cmp_connection *connection,
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c
index 0c3948630cf7..a9a30c0161f1 100644
--- a/sound/firewire/dice.c
+++ b/sound/firewire/dice.c
@@ -51,7 +51,7 @@ struct dice {
wait_queue_head_t hwdep_wait;
u32 notification_bits;
struct fw_iso_resources resources;
- struct amdtp_out_stream stream;
+ struct amdtp_stream stream;
};
MODULE_DESCRIPTION("DICE driver");
@@ -420,22 +420,7 @@ static int dice_open(struct snd_pcm_substream *substream)
if (err < 0)
goto err_lock;
- err = snd_pcm_hw_constraint_step(runtime, 0,
- SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 32);
- if (err < 0)
- goto err_lock;
- err = snd_pcm_hw_constraint_step(runtime, 0,
- SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 32);
- if (err < 0)
- goto err_lock;
-
- err = snd_pcm_hw_constraint_minmax(runtime,
- SNDRV_PCM_HW_PARAM_PERIOD_TIME,
- 5000, UINT_MAX);
- if (err < 0)
- goto err_lock;
-
- err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+ err = amdtp_stream_add_pcm_hw_constraints(&dice->stream, runtime);
if (err < 0)
goto err_lock;
@@ -460,17 +445,17 @@ static int dice_stream_start_packets(struct dice *dice)
{
int err;
- if (amdtp_out_stream_running(&dice->stream))
+ if (amdtp_stream_running(&dice->stream))
return 0;
- err = amdtp_out_stream_start(&dice->stream, dice->resources.channel,
- fw_parent_device(dice->unit)->max_speed);
+ err = amdtp_stream_start(&dice->stream, dice->resources.channel,
+ fw_parent_device(dice->unit)->max_speed);
if (err < 0)
return err;
err = dice_enable_set(dice);
if (err < 0) {
- amdtp_out_stream_stop(&dice->stream);
+ amdtp_stream_stop(&dice->stream);
return err;
}
@@ -484,7 +469,7 @@ static int dice_stream_start(struct dice *dice)
if (!dice->resources.allocated) {
err = fw_iso_resources_allocate(&dice->resources,
- amdtp_out_stream_get_max_payload(&dice->stream),
+ amdtp_stream_get_max_payload(&dice->stream),
fw_parent_device(dice->unit)->max_speed);
if (err < 0)
goto error;
@@ -516,9 +501,9 @@ error:
static void dice_stream_stop_packets(struct dice *dice)
{
- if (amdtp_out_stream_running(&dice->stream)) {
+ if (amdtp_stream_running(&dice->stream)) {
dice_enable_clear(dice);
- amdtp_out_stream_stop(&dice->stream);
+ amdtp_stream_stop(&dice->stream);
}
}
@@ -563,7 +548,7 @@ static int dice_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct dice *dice = substream->private_data;
- unsigned int rate_index, mode;
+ unsigned int rate_index, mode, rate, channels, i;
int err;
mutex_lock(&dice->mutex);
@@ -575,18 +560,39 @@ static int dice_hw_params(struct snd_pcm_substream *substream,
if (err < 0)
return err;
- rate_index = rate_to_index(params_rate(hw_params));
+ rate = params_rate(hw_params);
+ rate_index = rate_to_index(rate);
err = dice_change_rate(dice, rate_index << CLOCK_RATE_SHIFT);
if (err < 0)
return err;
+ /*
+ * At rates above 96 kHz, pretend that the stream runs at half the
+ * actual sample rate with twice the number of channels; two samples
+ * of a channel are stored consecutively in the packet. Requires
+ * blocking mode and PCM buffer size should be aligned to SYT_INTERVAL.
+ */
+ channels = params_channels(hw_params);
+ if (rate_index > 4) {
+ if (channels > AMDTP_MAX_CHANNELS_FOR_PCM / 2) {
+ err = -ENOSYS;
+ return err;
+ }
+
+ for (i = 0; i < channels; i++) {
+ dice->stream.pcm_positions[i * 2] = i;
+ dice->stream.pcm_positions[i * 2 + 1] = i + channels;
+ }
+
+ rate /= 2;
+ channels *= 2;
+ }
+
mode = rate_index_to_mode(rate_index);
- amdtp_out_stream_set_parameters(&dice->stream,
- params_rate(hw_params),
- params_channels(hw_params),
- dice->rx_midi_ports[mode]);
- amdtp_out_stream_set_pcm_format(&dice->stream,
- params_format(hw_params));
+ amdtp_stream_set_parameters(&dice->stream, rate, channels,
+ dice->rx_midi_ports[mode]);
+ amdtp_stream_set_pcm_format(&dice->stream,
+ params_format(hw_params));
return 0;
}
@@ -609,7 +615,7 @@ static int dice_prepare(struct snd_pcm_substream *substream)
mutex_lock(&dice->mutex);
- if (amdtp_out_streaming_error(&dice->stream))
+ if (amdtp_streaming_error(&dice->stream))
dice_stream_stop_packets(dice);
err = dice_stream_start(dice);
@@ -620,7 +626,7 @@ static int dice_prepare(struct snd_pcm_substream *substream)
mutex_unlock(&dice->mutex);
- amdtp_out_stream_pcm_prepare(&dice->stream);
+ amdtp_stream_pcm_prepare(&dice->stream);
return 0;
}
@@ -640,7 +646,7 @@ static int dice_trigger(struct snd_pcm_substream *substream, int cmd)
default:
return -EINVAL;
}
- amdtp_out_stream_pcm_trigger(&dice->stream, pcm);
+ amdtp_stream_pcm_trigger(&dice->stream, pcm);
return 0;
}
@@ -649,7 +655,7 @@ static snd_pcm_uframes_t dice_pointer(struct snd_pcm_substream *substream)
{
struct dice *dice = substream->private_data;
- return amdtp_out_stream_pcm_pointer(&dice->stream);
+ return amdtp_stream_pcm_pointer(&dice->stream);
}
static int dice_create_pcm(struct dice *dice)
@@ -1104,7 +1110,7 @@ static void dice_card_free(struct snd_card *card)
{
struct dice *dice = card->private_data;
- amdtp_out_stream_destroy(&dice->stream);
+ amdtp_stream_destroy(&dice->stream);
fw_core_remove_address_handler(&dice->notification_handler);
mutex_destroy(&dice->mutex);
}
@@ -1360,8 +1366,8 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id)
goto err_owner;
dice->resources.channels_mask = 0x00000000ffffffffuLL;
- err = amdtp_out_stream_init(&dice->stream, unit,
- CIP_BLOCKING | CIP_HI_DUALWIRE);
+ err = amdtp_stream_init(&dice->stream, unit, AMDTP_OUT_STREAM,
+ CIP_BLOCKING);
if (err < 0)
goto err_resources;
@@ -1417,7 +1423,7 @@ static void dice_remove(struct fw_unit *unit)
{
struct dice *dice = dev_get_drvdata(&unit->device);
- amdtp_out_stream_pcm_abort(&dice->stream);
+ amdtp_stream_pcm_abort(&dice->stream);
snd_card_disconnect(dice->card);
@@ -1443,7 +1449,7 @@ static void dice_bus_reset(struct fw_unit *unit)
* to stop so that the application can restart them in an orderly
* manner.
*/
- amdtp_out_stream_pcm_abort(&dice->stream);
+ amdtp_stream_pcm_abort(&dice->stream);
mutex_lock(&dice->mutex);
diff --git a/sound/firewire/fcp.c b/sound/firewire/fcp.c
index 860c08073c59..0619597e3a3f 100644
--- a/sound/firewire/fcp.c
+++ b/sound/firewire/fcp.c
@@ -10,12 +10,14 @@
#include <linux/firewire-constants.h>
#include <linux/list.h>
#include <linux/module.h>
+#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include "fcp.h"
#include "lib.h"
+#include "amdtp.h"
#define CTS_AVC 0x00
@@ -23,6 +25,158 @@
#define ERROR_DELAY_MS 5
#define FCP_TIMEOUT_MS 125
+int avc_general_set_sig_fmt(struct fw_unit *unit, unsigned int rate,
+ enum avc_general_plug_dir dir,
+ unsigned short pid)
+{
+ unsigned int sfc;
+ u8 *buf;
+ bool flag;
+ int err;
+
+ flag = false;
+ for (sfc = 0; sfc < CIP_SFC_COUNT; sfc++) {
+ if (amdtp_rate_table[sfc] == rate) {
+ flag = true;
+ break;
+ }
+ }
+ if (!flag)
+ return -EINVAL;
+
+ buf = kzalloc(8, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ buf[0] = 0x00; /* AV/C CONTROL */
+ buf[1] = 0xff; /* UNIT */
+ if (dir == AVC_GENERAL_PLUG_DIR_IN)
+ buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */
+ else
+ buf[2] = 0x18; /* OUTPUT PLUG SIGNAL FORMAT */
+ buf[3] = 0xff & pid; /* plug id */
+ buf[4] = 0x90; /* EOH_1, Form_1, FMT. AM824 */
+ buf[5] = 0x07 & sfc; /* FDF-hi. AM824, frequency */
+ buf[6] = 0xff; /* FDF-mid. AM824, SYT hi (not used)*/
+ buf[7] = 0xff; /* FDF-low. AM824, SYT lo (not used) */
+
+ /* 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)
+ err = -EIO;
+ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) /* REJECTED */
+ err = -EINVAL;
+ if (err < 0)
+ goto end;
+
+ err = 0;
+end:
+ kfree(buf);
+ return err;
+}
+EXPORT_SYMBOL(avc_general_set_sig_fmt);
+
+int avc_general_get_sig_fmt(struct fw_unit *unit, unsigned int *rate,
+ enum avc_general_plug_dir dir,
+ unsigned short pid)
+{
+ unsigned int sfc;
+ u8 *buf;
+ int err;
+
+ buf = kzalloc(8, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ buf[0] = 0x01; /* AV/C STATUS */
+ buf[1] = 0xff; /* Unit */
+ if (dir == AVC_GENERAL_PLUG_DIR_IN)
+ buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */
+ else
+ buf[2] = 0x18; /* OUTPUT PLUG SIGNAL FORMAT */
+ buf[3] = 0xff & pid; /* plug id */
+ buf[4] = 0x90; /* EOH_1, Form_1, FMT. AM824 */
+ buf[5] = 0xff; /* FDF-hi. AM824, frequency */
+ buf[6] = 0xff; /* FDF-mid. AM824, SYT hi (not used) */
+ buf[7] = 0xff; /* FDF-low. AM824, SYT lo (not used) */
+
+ /* 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)
+ err = -EIO;
+ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) /* REJECTED */
+ err = -EINVAL;
+ else if (buf[0] == 0x0b) /* IN TRANSITION */
+ err = -EAGAIN;
+ if (err < 0)
+ goto end;
+
+ /* check sfc field and pick up rate */
+ sfc = 0x07 & buf[5];
+ if (sfc >= CIP_SFC_COUNT) {
+ err = -EAGAIN; /* also in transition */
+ goto end;
+ }
+
+ *rate = amdtp_rate_table[sfc];
+ err = 0;
+end:
+ kfree(buf);
+ return err;
+}
+EXPORT_SYMBOL(avc_general_get_sig_fmt);
+
+int avc_general_get_plug_info(struct fw_unit *unit, unsigned int subunit_type,
+ unsigned int subunit_id, unsigned int subfunction,
+ u8 info[AVC_PLUG_INFO_BUF_BYTES])
+{
+ u8 *buf;
+ int err;
+
+ /* extended subunit in spec.4.2 is not supported */
+ if ((subunit_type == 0x1E) || (subunit_id == 5))
+ return -EINVAL;
+
+ buf = kzalloc(8, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ buf[0] = 0x01; /* AV/C STATUS */
+ /* UNIT or Subunit, Functionblock */
+ buf[1] = ((subunit_type & 0x1f) << 3) | (subunit_id & 0x7);
+ buf[2] = 0x02; /* PLUG INFO */
+ buf[3] = 0xff & subfunction;
+
+ err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2));
+ if (err >= 0 && err < 8)
+ err = -EIO;
+ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) /* REJECTED */
+ err = -EINVAL;
+ else if (buf[0] == 0x0b) /* IN TRANSITION */
+ err = -EAGAIN;
+ if (err < 0)
+ goto end;
+
+ info[0] = buf[4];
+ info[1] = buf[5];
+ info[2] = buf[6];
+ info[3] = buf[7];
+
+ err = 0;
+end:
+ kfree(buf);
+ return err;
+}
+EXPORT_SYMBOL(avc_general_get_plug_info);
+
static DEFINE_SPINLOCK(transactions_lock);
static LIST_HEAD(transactions);
@@ -30,6 +184,7 @@ enum fcp_state {
STATE_PENDING,
STATE_BUS_RESET,
STATE_COMPLETE,
+ STATE_DEFERRED,
};
struct fcp_transaction {
@@ -40,6 +195,7 @@ struct fcp_transaction {
unsigned int response_match_bytes;
enum fcp_state state;
wait_queue_head_t wait;
+ bool deferrable;
};
/**
@@ -62,8 +218,6 @@ struct fcp_transaction {
*
* @command and @response can point to the same buffer.
*
- * Asynchronous operation (INTERIM, NOTIFY) is not supported at the moment.
- *
* Returns the actual size of the response frame, or a negative error code.
*/
int fcp_avc_transaction(struct fw_unit *unit,
@@ -81,6 +235,9 @@ int fcp_avc_transaction(struct fw_unit *unit,
t.state = STATE_PENDING;
init_waitqueue_head(&t.wait);
+ if (*(const u8 *)command == 0x00 || *(const u8 *)command == 0x03)
+ t.deferrable = true;
+
spin_lock_irq(&transactions_lock);
list_add_tail(&t.list, &transactions);
spin_unlock_irq(&transactions_lock);
@@ -93,11 +250,21 @@ int fcp_avc_transaction(struct fw_unit *unit,
(void *)command, command_size, 0);
if (ret < 0)
break;
-
+deferred:
wait_event_timeout(t.wait, t.state != STATE_PENDING,
msecs_to_jiffies(FCP_TIMEOUT_MS));
- if (t.state == STATE_COMPLETE) {
+ if (t.state == STATE_DEFERRED) {
+ /*
+ * 'AV/C General Specification' define no time limit
+ * on command completion once an INTERIM response has
+ * been sent. but we promise to finish this function
+ * for a caller. Here we use FCP_TIMEOUT_MS for next
+ * interval. This is not in the specification.
+ */
+ t.state = STATE_PENDING;
+ goto deferred;
+ } else if (t.state == STATE_COMPLETE) {
ret = t.response_size;
break;
} else if (t.state == STATE_BUS_RESET) {
@@ -132,7 +299,8 @@ void fcp_bus_reset(struct fw_unit *unit)
spin_lock_irq(&transactions_lock);
list_for_each_entry(t, &transactions, list) {
if (t->unit == unit &&
- t->state == STATE_PENDING) {
+ (t->state == STATE_PENDING ||
+ t->state == STATE_DEFERRED)) {
t->state = STATE_BUS_RESET;
wake_up(&t->wait);
}
@@ -186,10 +354,15 @@ static void fcp_response(struct fw_card *card, struct fw_request *request,
if (t->state == STATE_PENDING &&
is_matching_response(t, data, length)) {
- t->state = STATE_COMPLETE;
- t->response_size = min((unsigned int)length,
- t->response_size);
- memcpy(t->response_buffer, data, t->response_size);
+ if (t->deferrable && *(const u8 *)data == 0x0f) {
+ t->state = STATE_DEFERRED;
+ } else {
+ t->state = STATE_COMPLETE;
+ t->response_size = min_t(unsigned int, length,
+ t->response_size);
+ memcpy(t->response_buffer, data,
+ t->response_size);
+ }
wake_up(&t->wait);
}
}
diff --git a/sound/firewire/fcp.h b/sound/firewire/fcp.h
index 86595688bd91..63ae4f7ce3af 100644
--- a/sound/firewire/fcp.h
+++ b/sound/firewire/fcp.h
@@ -1,8 +1,29 @@
#ifndef SOUND_FIREWIRE_FCP_H_INCLUDED
#define SOUND_FIREWIRE_FCP_H_INCLUDED
+#define AVC_PLUG_INFO_BUF_BYTES 4
+
struct fw_unit;
+/*
+ * AV/C Digital Interface Command Set General Specification 4.2
+ * (Sep 2004, 1394TA)
+ */
+enum avc_general_plug_dir {
+ AVC_GENERAL_PLUG_DIR_IN = 0,
+ AVC_GENERAL_PLUG_DIR_OUT = 1,
+ AVC_GENERAL_PLUG_DIR_COUNT
+};
+int avc_general_set_sig_fmt(struct fw_unit *unit, unsigned int rate,
+ enum avc_general_plug_dir dir,
+ unsigned short plug);
+int avc_general_get_sig_fmt(struct fw_unit *unit, unsigned int *rate,
+ enum avc_general_plug_dir dir,
+ unsigned short plug);
+int avc_general_get_plug_info(struct fw_unit *unit, unsigned int subunit_type,
+ unsigned int subunit_id, unsigned int subfunction,
+ u8 info[AVC_PLUG_INFO_BUF_BYTES]);
+
int fcp_avc_transaction(struct fw_unit *unit,
const void *command, unsigned int command_size,
void *response, unsigned int response_size,
diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile
new file mode 100644
index 000000000000..0c7440826db8
--- /dev/null
+++ b/sound/firewire/fireworks/Makefile
@@ -0,0 +1,4 @@
+snd-fireworks-objs := fireworks_transaction.o fireworks_command.o \
+ fireworks_stream.o fireworks_proc.o fireworks_midi.o \
+ fireworks_pcm.o fireworks_hwdep.o fireworks.o
+obj-m += snd-fireworks.o
diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c
new file mode 100644
index 000000000000..3e2ed8e82cbc
--- /dev/null
+++ b/sound/firewire/fireworks/fireworks.c
@@ -0,0 +1,352 @@
+/*
+ * fireworks.c - a part of driver for Fireworks based devices
+ *
+ * Copyright (c) 2009-2010 Clemens Ladisch
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+/*
+ * Fireworks is a board module which Echo Audio produced. This module consists
+ * of three chipsets:
+ * - Communication chipset for IEEE1394 PHY/Link and IEC 61883-1/6
+ * - DSP or/and FPGA for signal processing
+ * - Flash Memory to store firmwares
+ */
+
+#include "fireworks.h"
+
+MODULE_DESCRIPTION("Echo Fireworks driver");
+MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
+MODULE_LICENSE("GPL v2");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+unsigned int snd_efw_resp_buf_size = 1024;
+bool snd_efw_resp_buf_debug = false;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "card index");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "enable Fireworks sound card");
+module_param_named(resp_buf_size, snd_efw_resp_buf_size, uint, 0444);
+MODULE_PARM_DESC(resp_buf_size,
+ "response buffer size (max 4096, default 1024)");
+module_param_named(resp_buf_debug, snd_efw_resp_buf_debug, bool, 0444);
+MODULE_PARM_DESC(resp_buf_debug, "store all responses to buffer");
+
+static DEFINE_MUTEX(devices_mutex);
+static DECLARE_BITMAP(devices_used, SNDRV_CARDS);
+
+#define VENDOR_LOUD 0x000ff2
+#define MODEL_MACKIE_400F 0x00400f
+#define MODEL_MACKIE_1200F 0x01200f
+
+#define VENDOR_ECHO 0x001486
+#define MODEL_ECHO_AUDIOFIRE_12 0x00af12
+#define MODEL_ECHO_AUDIOFIRE_12HD 0x0af12d
+#define MODEL_ECHO_AUDIOFIRE_12_APPLE 0x0af12a
+/* This is applied for AudioFire8 (until 2009 July) */
+#define MODEL_ECHO_AUDIOFIRE_8 0x000af8
+#define MODEL_ECHO_AUDIOFIRE_2 0x000af2
+#define MODEL_ECHO_AUDIOFIRE_4 0x000af4
+/* AudioFire9 is applied for AudioFire8(since 2009 July) and AudioFirePre8 */
+#define MODEL_ECHO_AUDIOFIRE_9 0x000af9
+/* unknown as product */
+#define MODEL_ECHO_FIREWORKS_8 0x0000f8
+#define MODEL_ECHO_FIREWORKS_HDMI 0x00afd1
+
+#define VENDOR_GIBSON 0x00075b
+/* for Robot Interface Pack of Dark Fire, Dusk Tiger, Les Paul Standard 2010 */
+#define MODEL_GIBSON_RIP 0x00afb2
+/* unknown as product */
+#define MODEL_GIBSON_GOLDTOP 0x00afb9
+
+/* part of hardware capability flags */
+#define FLAG_RESP_ADDR_CHANGABLE 0
+
+static int
+get_hardware_info(struct snd_efw *efw)
+{
+ struct fw_device *fw_dev = fw_parent_device(efw->unit);
+ struct snd_efw_hwinfo *hwinfo;
+ char version[12] = {0};
+ int err;
+
+ hwinfo = kzalloc(sizeof(struct snd_efw_hwinfo), GFP_KERNEL);
+ if (hwinfo == NULL)
+ return -ENOMEM;
+
+ err = snd_efw_command_get_hwinfo(efw, hwinfo);
+ if (err < 0)
+ goto end;
+
+ /* firmware version for communication chipset */
+ snprintf(version, sizeof(version), "%u.%u",
+ (hwinfo->arm_version >> 24) & 0xff,
+ (hwinfo->arm_version >> 16) & 0xff);
+ efw->firmware_version = hwinfo->arm_version;
+
+ strcpy(efw->card->driver, "Fireworks");
+ strcpy(efw->card->shortname, hwinfo->model_name);
+ strcpy(efw->card->mixername, hwinfo->model_name);
+ snprintf(efw->card->longname, sizeof(efw->card->longname),
+ "%s %s v%s, GUID %08x%08x at %s, S%d",
+ hwinfo->vendor_name, hwinfo->model_name, version,
+ hwinfo->guid_hi, hwinfo->guid_lo,
+ dev_name(&efw->unit->device), 100 << fw_dev->max_speed);
+
+ if (hwinfo->flags & BIT(FLAG_RESP_ADDR_CHANGABLE))
+ efw->resp_addr_changable = true;
+
+ efw->supported_sampling_rate = 0;
+ if ((hwinfo->min_sample_rate <= 22050)
+ && (22050 <= hwinfo->max_sample_rate))
+ efw->supported_sampling_rate |= SNDRV_PCM_RATE_22050;
+ if ((hwinfo->min_sample_rate <= 32000)
+ && (32000 <= hwinfo->max_sample_rate))
+ efw->supported_sampling_rate |= SNDRV_PCM_RATE_32000;
+ if ((hwinfo->min_sample_rate <= 44100)
+ && (44100 <= hwinfo->max_sample_rate))
+ efw->supported_sampling_rate |= SNDRV_PCM_RATE_44100;
+ if ((hwinfo->min_sample_rate <= 48000)
+ && (48000 <= hwinfo->max_sample_rate))
+ efw->supported_sampling_rate |= SNDRV_PCM_RATE_48000;
+ if ((hwinfo->min_sample_rate <= 88200)
+ && (88200 <= hwinfo->max_sample_rate))
+ efw->supported_sampling_rate |= SNDRV_PCM_RATE_88200;
+ if ((hwinfo->min_sample_rate <= 96000)
+ && (96000 <= hwinfo->max_sample_rate))
+ efw->supported_sampling_rate |= SNDRV_PCM_RATE_96000;
+ if ((hwinfo->min_sample_rate <= 176400)
+ && (176400 <= hwinfo->max_sample_rate))
+ efw->supported_sampling_rate |= SNDRV_PCM_RATE_176400;
+ if ((hwinfo->min_sample_rate <= 192000)
+ && (192000 <= hwinfo->max_sample_rate))
+ efw->supported_sampling_rate |= SNDRV_PCM_RATE_192000;
+
+ /* the number of MIDI ports, not of MIDI conformant data channels */
+ if (hwinfo->midi_out_ports > SND_EFW_MAX_MIDI_OUT_PORTS ||
+ hwinfo->midi_in_ports > SND_EFW_MAX_MIDI_IN_PORTS) {
+ err = -EIO;
+ goto end;
+ }
+ efw->midi_out_ports = hwinfo->midi_out_ports;
+ efw->midi_in_ports = hwinfo->midi_in_ports;
+
+ if (hwinfo->amdtp_tx_pcm_channels > AMDTP_MAX_CHANNELS_FOR_PCM ||
+ hwinfo->amdtp_tx_pcm_channels_2x > AMDTP_MAX_CHANNELS_FOR_PCM ||
+ hwinfo->amdtp_tx_pcm_channels_4x > AMDTP_MAX_CHANNELS_FOR_PCM ||
+ hwinfo->amdtp_rx_pcm_channels > AMDTP_MAX_CHANNELS_FOR_PCM ||
+ hwinfo->amdtp_rx_pcm_channels_2x > AMDTP_MAX_CHANNELS_FOR_PCM ||
+ hwinfo->amdtp_rx_pcm_channels_4x > AMDTP_MAX_CHANNELS_FOR_PCM) {
+ err = -ENOSYS;
+ goto end;
+ }
+ efw->pcm_capture_channels[0] = hwinfo->amdtp_tx_pcm_channels;
+ efw->pcm_capture_channels[1] = hwinfo->amdtp_tx_pcm_channels_2x;
+ efw->pcm_capture_channels[2] = hwinfo->amdtp_tx_pcm_channels_4x;
+ efw->pcm_playback_channels[0] = hwinfo->amdtp_rx_pcm_channels;
+ efw->pcm_playback_channels[1] = hwinfo->amdtp_rx_pcm_channels_2x;
+ efw->pcm_playback_channels[2] = hwinfo->amdtp_rx_pcm_channels_4x;
+
+ /* Hardware metering. */
+ if (hwinfo->phys_in_grp_count > HWINFO_MAX_CAPS_GROUPS ||
+ hwinfo->phys_out_grp_count > HWINFO_MAX_CAPS_GROUPS) {
+ err = -EIO;
+ goto end;
+ }
+ efw->phys_in = hwinfo->phys_in;
+ efw->phys_out = hwinfo->phys_out;
+ efw->phys_in_grp_count = hwinfo->phys_in_grp_count;
+ efw->phys_out_grp_count = hwinfo->phys_out_grp_count;
+ memcpy(&efw->phys_in_grps, hwinfo->phys_in_grps,
+ sizeof(struct snd_efw_phys_grp) * hwinfo->phys_in_grp_count);
+ memcpy(&efw->phys_out_grps, hwinfo->phys_out_grps,
+ sizeof(struct snd_efw_phys_grp) * hwinfo->phys_out_grp_count);
+end:
+ kfree(hwinfo);
+ return err;
+}
+
+static void
+efw_card_free(struct snd_card *card)
+{
+ struct snd_efw *efw = card->private_data;
+
+ if (efw->card_index >= 0) {
+ mutex_lock(&devices_mutex);
+ clear_bit(efw->card_index, devices_used);
+ mutex_unlock(&devices_mutex);
+ }
+
+ mutex_destroy(&efw->mutex);
+ kfree(efw->resp_buf);
+}
+
+static int
+efw_probe(struct fw_unit *unit,
+ const struct ieee1394_device_id *entry)
+{
+ struct snd_card *card;
+ struct snd_efw *efw;
+ int card_index, err;
+
+ mutex_lock(&devices_mutex);
+
+ /* check registered cards */
+ for (card_index = 0; card_index < SNDRV_CARDS; ++card_index) {
+ if (!test_bit(card_index, devices_used) && enable[card_index])
+ break;
+ }
+ if (card_index >= SNDRV_CARDS) {
+ err = -ENOENT;
+ goto end;
+ }
+
+ err = snd_card_new(&unit->device, index[card_index], id[card_index],
+ THIS_MODULE, sizeof(struct snd_efw), &card);
+ if (err < 0)
+ goto end;
+ efw = card->private_data;
+ efw->card_index = card_index;
+ set_bit(card_index, devices_used);
+ card->private_free = efw_card_free;
+
+ efw->card = card;
+ efw->unit = unit;
+ mutex_init(&efw->mutex);
+ spin_lock_init(&efw->lock);
+ init_waitqueue_head(&efw->hwdep_wait);
+
+ /* prepare response buffer */
+ snd_efw_resp_buf_size = clamp(snd_efw_resp_buf_size,
+ SND_EFW_RESPONSE_MAXIMUM_BYTES, 4096U);
+ efw->resp_buf = kzalloc(snd_efw_resp_buf_size, GFP_KERNEL);
+ if (efw->resp_buf == NULL) {
+ err = -ENOMEM;
+ goto error;
+ }
+ efw->pull_ptr = efw->push_ptr = efw->resp_buf;
+ snd_efw_transaction_add_instance(efw);
+
+ err = get_hardware_info(efw);
+ if (err < 0)
+ goto error;
+ if (entry->model_id == MODEL_ECHO_AUDIOFIRE_9)
+ efw->is_af9 = true;
+
+ snd_efw_proc_init(efw);
+
+ if (efw->midi_out_ports || efw->midi_in_ports) {
+ err = snd_efw_create_midi_devices(efw);
+ if (err < 0)
+ goto error;
+ }
+
+ err = snd_efw_create_pcm_devices(efw);
+ if (err < 0)
+ goto error;
+
+ err = snd_efw_create_hwdep_device(efw);
+ if (err < 0)
+ goto error;
+
+ err = snd_efw_stream_init_duplex(efw);
+ if (err < 0)
+ goto error;
+
+ err = snd_card_register(card);
+ if (err < 0) {
+ snd_efw_stream_destroy_duplex(efw);
+ goto error;
+ }
+
+ dev_set_drvdata(&unit->device, efw);
+end:
+ mutex_unlock(&devices_mutex);
+ return err;
+error:
+ snd_efw_transaction_remove_instance(efw);
+ mutex_unlock(&devices_mutex);
+ snd_card_free(card);
+ return err;
+}
+
+static void efw_update(struct fw_unit *unit)
+{
+ struct snd_efw *efw = dev_get_drvdata(&unit->device);
+
+ snd_efw_transaction_bus_reset(efw->unit);
+ snd_efw_stream_update_duplex(efw);
+}
+
+static void efw_remove(struct fw_unit *unit)
+{
+ struct snd_efw *efw = dev_get_drvdata(&unit->device);
+
+ snd_efw_stream_destroy_duplex(efw);
+ snd_efw_transaction_remove_instance(efw);
+
+ snd_card_disconnect(efw->card);
+ snd_card_free_when_closed(efw->card);
+}
+
+static const struct ieee1394_device_id efw_id_table[] = {
+ SND_EFW_DEV_ENTRY(VENDOR_LOUD, MODEL_MACKIE_400F),
+ SND_EFW_DEV_ENTRY(VENDOR_LOUD, MODEL_MACKIE_1200F),
+ SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_8),
+ SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12),
+ SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12HD),
+ SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12_APPLE),
+ SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_2),
+ SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_4),
+ SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_9),
+ SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_FIREWORKS_8),
+ SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_FIREWORKS_HDMI),
+ SND_EFW_DEV_ENTRY(VENDOR_GIBSON, MODEL_GIBSON_RIP),
+ SND_EFW_DEV_ENTRY(VENDOR_GIBSON, MODEL_GIBSON_GOLDTOP),
+ {}
+};
+MODULE_DEVICE_TABLE(ieee1394, efw_id_table);
+
+static struct fw_driver efw_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "snd-fireworks",
+ .bus = &fw_bus_type,
+ },
+ .probe = efw_probe,
+ .update = efw_update,
+ .remove = efw_remove,
+ .id_table = efw_id_table,
+};
+
+static int __init snd_efw_init(void)
+{
+ int err;
+
+ err = snd_efw_transaction_register();
+ if (err < 0)
+ goto end;
+
+ err = driver_register(&efw_driver.driver);
+ if (err < 0)
+ snd_efw_transaction_unregister();
+
+end:
+ return err;
+}
+
+static void __exit snd_efw_exit(void)
+{
+ snd_efw_transaction_unregister();
+ driver_unregister(&efw_driver.driver);
+}
+
+module_init(snd_efw_init);
+module_exit(snd_efw_exit);
diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h
new file mode 100644
index 000000000000..4f0201a95222
--- /dev/null
+++ b/sound/firewire/fireworks/fireworks.h
@@ -0,0 +1,232 @@
+/*
+ * fireworks.h - a part of driver for Fireworks based devices
+ *
+ * Copyright (c) 2009-2010 Clemens Ladisch
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+#ifndef SOUND_FIREWORKS_H_INCLUDED
+#define SOUND_FIREWORKS_H_INCLUDED
+
+#include <linux/compat.h>
+#include <linux/device.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/info.h>
+#include <sound/rawmidi.h>
+#include <sound/pcm_params.h>
+#include <sound/firewire.h>
+#include <sound/hwdep.h>
+
+#include "../packets-buffer.h"
+#include "../iso-resources.h"
+#include "../amdtp.h"
+#include "../cmp.h"
+#include "../lib.h"
+
+#define SND_EFW_MAX_MIDI_OUT_PORTS 2
+#define SND_EFW_MAX_MIDI_IN_PORTS 2
+
+#define SND_EFW_MULTIPLIER_MODES 3
+#define HWINFO_NAME_SIZE_BYTES 32
+#define HWINFO_MAX_CAPS_GROUPS 8
+
+/*
+ * This should be greater than maximum bytes for EFW response content.
+ * Currently response against command for isochronous channel mapping is
+ * confirmed to be the maximum one. But for flexibility, use maximum data
+ * payload for asynchronous primary packets at S100 (Cable base rate) in
+ * IEEE Std 1394-1995.
+ */
+#define SND_EFW_RESPONSE_MAXIMUM_BYTES 0x200U
+
+extern unsigned int snd_efw_resp_buf_size;
+extern bool snd_efw_resp_buf_debug;
+
+struct snd_efw_phys_grp {
+ u8 type; /* see enum snd_efw_grp_type */
+ u8 count;
+} __packed;
+
+struct snd_efw {
+ struct snd_card *card;
+ struct fw_unit *unit;
+ int card_index;
+
+ struct mutex mutex;
+ spinlock_t lock;
+
+ /* for transaction */
+ u32 seqnum;
+ bool resp_addr_changable;
+
+ /* for quirks */
+ bool is_af9;
+ u32 firmware_version;
+
+ unsigned int midi_in_ports;
+ unsigned int midi_out_ports;
+
+ unsigned int supported_sampling_rate;
+ unsigned int pcm_capture_channels[SND_EFW_MULTIPLIER_MODES];
+ unsigned int pcm_playback_channels[SND_EFW_MULTIPLIER_MODES];
+
+ struct amdtp_stream *master;
+ struct amdtp_stream tx_stream;
+ struct amdtp_stream rx_stream;
+ struct cmp_connection out_conn;
+ struct cmp_connection in_conn;
+ atomic_t capture_substreams;
+ atomic_t playback_substreams;
+
+ /* hardware metering parameters */
+ unsigned int phys_out;
+ unsigned int phys_in;
+ unsigned int phys_out_grp_count;
+ unsigned int phys_in_grp_count;
+ struct snd_efw_phys_grp phys_out_grps[HWINFO_MAX_CAPS_GROUPS];
+ struct snd_efw_phys_grp phys_in_grps[HWINFO_MAX_CAPS_GROUPS];
+
+ /* for uapi */
+ int dev_lock_count;
+ bool dev_lock_changed;
+ wait_queue_head_t hwdep_wait;
+
+ /* response queue */
+ u8 *resp_buf;
+ u8 *pull_ptr;
+ u8 *push_ptr;
+ unsigned int resp_queues;
+};
+
+int snd_efw_transaction_cmd(struct fw_unit *unit,
+ const void *cmd, unsigned int size);
+int snd_efw_transaction_run(struct fw_unit *unit,
+ const void *cmd, unsigned int cmd_size,
+ void *resp, unsigned int resp_size);
+int snd_efw_transaction_register(void);
+void snd_efw_transaction_unregister(void);
+void snd_efw_transaction_bus_reset(struct fw_unit *unit);
+void snd_efw_transaction_add_instance(struct snd_efw *efw);
+void snd_efw_transaction_remove_instance(struct snd_efw *efw);
+
+struct snd_efw_hwinfo {
+ u32 flags;
+ u32 guid_hi;
+ u32 guid_lo;
+ u32 type;
+ u32 version;
+ char vendor_name[HWINFO_NAME_SIZE_BYTES];
+ char model_name[HWINFO_NAME_SIZE_BYTES];
+ u32 supported_clocks;
+ u32 amdtp_rx_pcm_channels;
+ u32 amdtp_tx_pcm_channels;
+ u32 phys_out;
+ u32 phys_in;
+ u32 phys_out_grp_count;
+ struct snd_efw_phys_grp phys_out_grps[HWINFO_MAX_CAPS_GROUPS];
+ u32 phys_in_grp_count;
+ struct snd_efw_phys_grp phys_in_grps[HWINFO_MAX_CAPS_GROUPS];
+ u32 midi_out_ports;
+ u32 midi_in_ports;
+ u32 max_sample_rate;
+ u32 min_sample_rate;
+ u32 dsp_version;
+ u32 arm_version;
+ u32 mixer_playback_channels;
+ u32 mixer_capture_channels;
+ u32 fpga_version;
+ u32 amdtp_rx_pcm_channels_2x;
+ u32 amdtp_tx_pcm_channels_2x;
+ u32 amdtp_rx_pcm_channels_4x;
+ u32 amdtp_tx_pcm_channels_4x;
+ u32 reserved[16];
+} __packed;
+enum snd_efw_grp_type {
+ SND_EFW_CH_TYPE_ANALOG = 0,
+ SND_EFW_CH_TYPE_SPDIF = 1,
+ SND_EFW_CH_TYPE_ADAT = 2,
+ SND_EFW_CH_TYPE_SPDIF_OR_ADAT = 3,
+ SND_EFW_CH_TYPE_ANALOG_MIRRORING = 4,
+ SND_EFW_CH_TYPE_HEADPHONES = 5,
+ SND_EFW_CH_TYPE_I2S = 6,
+ SND_EFW_CH_TYPE_GUITAR = 7,
+ SND_EFW_CH_TYPE_PIEZO_GUITAR = 8,
+ SND_EFW_CH_TYPE_GUITAR_STRING = 9,
+ SND_EFW_CH_TYPE_DUMMY
+};
+struct snd_efw_phys_meters {
+ u32 status; /* guitar state/midi signal/clock input detect */
+ u32 reserved0;
+ u32 reserved1;
+ u32 reserved2;
+ u32 reserved3;
+ u32 out_meters;
+ u32 in_meters;
+ u32 reserved4;
+ u32 reserved5;
+ u32 values[0];
+} __packed;
+enum snd_efw_clock_source {
+ SND_EFW_CLOCK_SOURCE_INTERNAL = 0,
+ SND_EFW_CLOCK_SOURCE_SYTMATCH = 1,
+ SND_EFW_CLOCK_SOURCE_WORDCLOCK = 2,
+ SND_EFW_CLOCK_SOURCE_SPDIF = 3,
+ SND_EFW_CLOCK_SOURCE_ADAT_1 = 4,
+ SND_EFW_CLOCK_SOURCE_ADAT_2 = 5,
+ SND_EFW_CLOCK_SOURCE_CONTINUOUS = 6 /* internal variable clock */
+};
+enum snd_efw_transport_mode {
+ SND_EFW_TRANSPORT_MODE_WINDOWS = 0,
+ SND_EFW_TRANSPORT_MODE_IEC61883 = 1,
+};
+int snd_efw_command_set_resp_addr(struct snd_efw *efw,
+ u16 addr_high, u32 addr_low);
+int snd_efw_command_set_tx_mode(struct snd_efw *efw,
+ enum snd_efw_transport_mode mode);
+int snd_efw_command_get_hwinfo(struct snd_efw *efw,
+ struct snd_efw_hwinfo *hwinfo);
+int snd_efw_command_get_phys_meters(struct snd_efw *efw,
+ struct snd_efw_phys_meters *meters,
+ unsigned int len);
+int snd_efw_command_get_clock_source(struct snd_efw *efw,
+ enum snd_efw_clock_source *source);
+int snd_efw_command_get_sampling_rate(struct snd_efw *efw, unsigned int *rate);
+int snd_efw_command_set_sampling_rate(struct snd_efw *efw, unsigned int rate);
+
+int snd_efw_stream_init_duplex(struct snd_efw *efw);
+int snd_efw_stream_start_duplex(struct snd_efw *efw, unsigned int rate);
+void snd_efw_stream_stop_duplex(struct snd_efw *efw);
+void snd_efw_stream_update_duplex(struct snd_efw *efw);
+void snd_efw_stream_destroy_duplex(struct snd_efw *efw);
+void snd_efw_stream_lock_changed(struct snd_efw *efw);
+int snd_efw_stream_lock_try(struct snd_efw *efw);
+void snd_efw_stream_lock_release(struct snd_efw *efw);
+
+void snd_efw_proc_init(struct snd_efw *efw);
+
+int snd_efw_create_midi_devices(struct snd_efw *efw);
+
+int snd_efw_create_pcm_devices(struct snd_efw *efw);
+int snd_efw_get_multiplier_mode(unsigned int sampling_rate, unsigned int *mode);
+
+int snd_efw_create_hwdep_device(struct snd_efw *efw);
+
+#define SND_EFW_DEV_ENTRY(vendor, model) \
+{ \
+ .match_flags = IEEE1394_MATCH_VENDOR_ID | \
+ IEEE1394_MATCH_MODEL_ID, \
+ .vendor_id = vendor,\
+ .model_id = model \
+}
+
+#endif
diff --git a/sound/firewire/fireworks/fireworks_command.c b/sound/firewire/fireworks/fireworks_command.c
new file mode 100644
index 000000000000..166f80584c2a
--- /dev/null
+++ b/sound/firewire/fireworks/fireworks_command.c
@@ -0,0 +1,372 @@
+/*
+ * fireworks_command.c - a part of driver for Fireworks based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "./fireworks.h"
+
+/*
+ * This driver uses transaction version 1 or later to use extended hardware
+ * information. Then too old devices are not available.
+ *
+ * Each commands are not required to have continuous sequence numbers. This
+ * number is just used to match command and response.
+ *
+ * This module support a part of commands. Please see FFADO if you want to see
+ * whole commands. But there are some commands which FFADO don't implement.
+ *
+ * Fireworks also supports AV/C general commands and AV/C Stream Format
+ * Information commands. But this module don't use them.
+ */
+
+#define KERNEL_SEQNUM_MIN (SND_EFW_TRANSACTION_USER_SEQNUM_MAX + 2)
+#define KERNEL_SEQNUM_MAX ((u32)~0)
+
+/* for clock source and sampling rate */
+struct efc_clock {
+ u32 source;
+ u32 sampling_rate;
+ u32 index;
+};
+
+/* command categories */
+enum efc_category {
+ EFC_CAT_HWINFO = 0,
+ EFC_CAT_TRANSPORT = 2,
+ EFC_CAT_HWCTL = 3,
+};
+
+/* hardware info category commands */
+enum efc_cmd_hwinfo {
+ EFC_CMD_HWINFO_GET_CAPS = 0,
+ EFC_CMD_HWINFO_GET_POLLED = 1,
+ EFC_CMD_HWINFO_SET_RESP_ADDR = 2
+};
+
+enum efc_cmd_transport {
+ EFC_CMD_TRANSPORT_SET_TX_MODE = 0
+};
+
+/* hardware control category commands */
+enum efc_cmd_hwctl {
+ EFC_CMD_HWCTL_SET_CLOCK = 0,
+ EFC_CMD_HWCTL_GET_CLOCK = 1,
+ EFC_CMD_HWCTL_IDENTIFY = 5
+};
+
+/* return values in response */
+enum efr_status {
+ EFR_STATUS_OK = 0,
+ EFR_STATUS_BAD = 1,
+ EFR_STATUS_BAD_COMMAND = 2,
+ EFR_STATUS_COMM_ERR = 3,
+ EFR_STATUS_BAD_QUAD_COUNT = 4,
+ EFR_STATUS_UNSUPPORTED = 5,
+ EFR_STATUS_1394_TIMEOUT = 6,
+ EFR_STATUS_DSP_TIMEOUT = 7,
+ EFR_STATUS_BAD_RATE = 8,
+ EFR_STATUS_BAD_CLOCK = 9,
+ EFR_STATUS_BAD_CHANNEL = 10,
+ EFR_STATUS_BAD_PAN = 11,
+ EFR_STATUS_FLASH_BUSY = 12,
+ EFR_STATUS_BAD_MIRROR = 13,
+ EFR_STATUS_BAD_LED = 14,
+ EFR_STATUS_BAD_PARAMETER = 15,
+ EFR_STATUS_INCOMPLETE = 0x80000000
+};
+
+static const char *const efr_status_names[] = {
+ [EFR_STATUS_OK] = "OK",
+ [EFR_STATUS_BAD] = "bad",
+ [EFR_STATUS_BAD_COMMAND] = "bad command",
+ [EFR_STATUS_COMM_ERR] = "comm err",
+ [EFR_STATUS_BAD_QUAD_COUNT] = "bad quad count",
+ [EFR_STATUS_UNSUPPORTED] = "unsupported",
+ [EFR_STATUS_1394_TIMEOUT] = "1394 timeout",
+ [EFR_STATUS_DSP_TIMEOUT] = "DSP timeout",
+ [EFR_STATUS_BAD_RATE] = "bad rate",
+ [EFR_STATUS_BAD_CLOCK] = "bad clock",
+ [EFR_STATUS_BAD_CHANNEL] = "bad channel",
+ [EFR_STATUS_BAD_PAN] = "bad pan",
+ [EFR_STATUS_FLASH_BUSY] = "flash busy",
+ [EFR_STATUS_BAD_MIRROR] = "bad mirror",
+ [EFR_STATUS_BAD_LED] = "bad LED",
+ [EFR_STATUS_BAD_PARAMETER] = "bad parameter",
+ [EFR_STATUS_BAD_PARAMETER + 1] = "incomplete"
+};
+
+static int
+efw_transaction(struct snd_efw *efw, unsigned int category,
+ unsigned int command,
+ const __be32 *params, unsigned int param_bytes,
+ const __be32 *resp, unsigned int resp_bytes)
+{
+ struct snd_efw_transaction *header;
+ __be32 *buf;
+ u32 seqnum;
+ unsigned int buf_bytes, cmd_bytes;
+ int err;
+
+ /* calculate buffer size*/
+ buf_bytes = sizeof(struct snd_efw_transaction) +
+ max(param_bytes, resp_bytes);
+
+ /* keep buffer */
+ buf = kzalloc(buf_bytes, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ /* to keep consistency of sequence number */
+ spin_lock(&efw->lock);
+ if ((efw->seqnum < KERNEL_SEQNUM_MIN) ||
+ (efw->seqnum >= KERNEL_SEQNUM_MAX - 2))
+ efw->seqnum = KERNEL_SEQNUM_MIN;
+ else
+ efw->seqnum += 2;
+ seqnum = efw->seqnum;
+ spin_unlock(&efw->lock);
+
+ /* fill transaction header fields */
+ cmd_bytes = sizeof(struct snd_efw_transaction) + param_bytes;
+ header = (struct snd_efw_transaction *)buf;
+ header->length = cpu_to_be32(cmd_bytes / sizeof(__be32));
+ header->version = cpu_to_be32(1);
+ header->seqnum = cpu_to_be32(seqnum);
+ header->category = cpu_to_be32(category);
+ header->command = cpu_to_be32(command);
+ header->status = 0;
+
+ /* fill transaction command parameters */
+ memcpy(header->params, params, param_bytes);
+
+ err = snd_efw_transaction_run(efw->unit, buf, cmd_bytes,
+ buf, buf_bytes);
+ if (err < 0)
+ goto end;
+
+ /* check transaction header fields */
+ if ((be32_to_cpu(header->version) < 1) ||
+ (be32_to_cpu(header->category) != category) ||
+ (be32_to_cpu(header->command) != command) ||
+ (be32_to_cpu(header->status) != EFR_STATUS_OK)) {
+ dev_err(&efw->unit->device, "EFW command failed [%u/%u]: %s\n",
+ be32_to_cpu(header->category),
+ be32_to_cpu(header->command),
+ efr_status_names[be32_to_cpu(header->status)]);
+ err = -EIO;
+ goto end;
+ }
+
+ if (resp == NULL)
+ goto end;
+
+ /* fill transaction response parameters */
+ memset((void *)resp, 0, resp_bytes);
+ resp_bytes = min_t(unsigned int, resp_bytes,
+ be32_to_cpu(header->length) * sizeof(__be32) -
+ sizeof(struct snd_efw_transaction));
+ memcpy((void *)resp, &buf[6], resp_bytes);
+end:
+ kfree(buf);
+ return err;
+}
+
+/*
+ * The address in host system for transaction response is changable when the
+ * device supports. struct hwinfo.flags includes its flag. The default is
+ * MEMORY_SPACE_EFW_RESPONSE.
+ */
+int snd_efw_command_set_resp_addr(struct snd_efw *efw,
+ u16 addr_high, u32 addr_low)
+{
+ __be32 addr[2];
+
+ addr[0] = cpu_to_be32(addr_high);
+ addr[1] = cpu_to_be32(addr_low);
+
+ if (!efw->resp_addr_changable)
+ return -ENOSYS;
+
+ return efw_transaction(efw, EFC_CAT_HWCTL,
+ EFC_CMD_HWINFO_SET_RESP_ADDR,
+ addr, sizeof(addr), NULL, 0);
+}
+
+/*
+ * This is for timestamp processing. In Windows mode, all 32bit fields of second
+ * CIP header in AMDTP transmit packet is used for 'presentation timestamp'. In
+ * 'no data' packet the value of this field is 0x90ffffff.
+ */
+int snd_efw_command_set_tx_mode(struct snd_efw *efw,
+ enum snd_efw_transport_mode mode)
+{
+ __be32 param = cpu_to_be32(mode);
+ return efw_transaction(efw, EFC_CAT_TRANSPORT,
+ EFC_CMD_TRANSPORT_SET_TX_MODE,
+ &param, sizeof(param), NULL, 0);
+}
+
+int snd_efw_command_get_hwinfo(struct snd_efw *efw,
+ struct snd_efw_hwinfo *hwinfo)
+{
+ int err;
+
+ err = efw_transaction(efw, EFC_CAT_HWINFO,
+ EFC_CMD_HWINFO_GET_CAPS,
+ NULL, 0, (__be32 *)hwinfo, sizeof(*hwinfo));
+ if (err < 0)
+ goto end;
+
+ be32_to_cpus(&hwinfo->flags);
+ be32_to_cpus(&hwinfo->guid_hi);
+ be32_to_cpus(&hwinfo->guid_lo);
+ be32_to_cpus(&hwinfo->type);
+ be32_to_cpus(&hwinfo->version);
+ be32_to_cpus(&hwinfo->supported_clocks);
+ be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels);
+ be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels);
+ be32_to_cpus(&hwinfo->phys_out);
+ be32_to_cpus(&hwinfo->phys_in);
+ be32_to_cpus(&hwinfo->phys_out_grp_count);
+ be32_to_cpus(&hwinfo->phys_in_grp_count);
+ be32_to_cpus(&hwinfo->midi_out_ports);
+ be32_to_cpus(&hwinfo->midi_in_ports);
+ be32_to_cpus(&hwinfo->max_sample_rate);
+ be32_to_cpus(&hwinfo->min_sample_rate);
+ be32_to_cpus(&hwinfo->dsp_version);
+ be32_to_cpus(&hwinfo->arm_version);
+ be32_to_cpus(&hwinfo->mixer_playback_channels);
+ be32_to_cpus(&hwinfo->mixer_capture_channels);
+ be32_to_cpus(&hwinfo->fpga_version);
+ be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels_2x);
+ be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels_2x);
+ be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels_4x);
+ be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels_4x);
+
+ /* ensure terminated */
+ hwinfo->vendor_name[HWINFO_NAME_SIZE_BYTES - 1] = '\0';
+ hwinfo->model_name[HWINFO_NAME_SIZE_BYTES - 1] = '\0';
+end:
+ return err;
+}
+
+int snd_efw_command_get_phys_meters(struct snd_efw *efw,
+ struct snd_efw_phys_meters *meters,
+ unsigned int len)
+{
+ __be32 *buf = (__be32 *)meters;
+ unsigned int i;
+ int err;
+
+ err = efw_transaction(efw, EFC_CAT_HWINFO,
+ EFC_CMD_HWINFO_GET_POLLED,
+ NULL, 0, (__be32 *)meters, len);
+ if (err >= 0)
+ for (i = 0; i < len / sizeof(u32); i++)
+ be32_to_cpus(&buf[i]);
+
+ return err;
+}
+
+static int
+command_get_clock(struct snd_efw *efw, struct efc_clock *clock)
+{
+ int err;
+
+ err = efw_transaction(efw, EFC_CAT_HWCTL,
+ EFC_CMD_HWCTL_GET_CLOCK,
+ NULL, 0,
+ (__be32 *)clock, sizeof(struct efc_clock));
+ if (err >= 0) {
+ be32_to_cpus(&clock->source);
+ be32_to_cpus(&clock->sampling_rate);
+ be32_to_cpus(&clock->index);
+ }
+
+ return err;
+}
+
+/* give UINT_MAX if set nothing */
+static int
+command_set_clock(struct snd_efw *efw,
+ unsigned int source, unsigned int rate)
+{
+ struct efc_clock clock = {0};
+ int err;
+
+ /* check arguments */
+ if ((source == UINT_MAX) && (rate == UINT_MAX)) {
+ err = -EINVAL;
+ goto end;
+ }
+
+ /* get current status */
+ err = command_get_clock(efw, &clock);
+ if (err < 0)
+ goto end;
+
+ /* no need */
+ if ((clock.source == source) && (clock.sampling_rate == rate))
+ goto end;
+
+ /* set params */
+ if ((source != UINT_MAX) && (clock.source != source))
+ clock.source = source;
+ if ((rate != UINT_MAX) && (clock.sampling_rate != rate))
+ clock.sampling_rate = rate;
+ clock.index = 0;
+
+ cpu_to_be32s(&clock.source);
+ cpu_to_be32s(&clock.sampling_rate);
+ cpu_to_be32s(&clock.index);
+
+ err = efw_transaction(efw, EFC_CAT_HWCTL,
+ EFC_CMD_HWCTL_SET_CLOCK,
+ (__be32 *)&clock, sizeof(struct efc_clock),
+ NULL, 0);
+ if (err < 0)
+ goto end;
+
+ /*
+ * With firmware version 5.8, just after changing clock state, these
+ * parameters are not immediately retrieved by get command. In my
+ * trial, there needs to be 100msec to get changed parameters.
+ */
+ msleep(150);
+end:
+ return err;
+}
+
+int snd_efw_command_get_clock_source(struct snd_efw *efw,
+ enum snd_efw_clock_source *source)
+{
+ int err;
+ struct efc_clock clock = {0};
+
+ err = command_get_clock(efw, &clock);
+ if (err >= 0)
+ *source = clock.source;
+
+ return err;
+}
+
+int snd_efw_command_get_sampling_rate(struct snd_efw *efw, unsigned int *rate)
+{
+ int err;
+ struct efc_clock clock = {0};
+
+ err = command_get_clock(efw, &clock);
+ if (err >= 0)
+ *rate = clock.sampling_rate;
+
+ return err;
+}
+
+int snd_efw_command_set_sampling_rate(struct snd_efw *efw, unsigned int rate)
+{
+ return command_set_clock(efw, UINT_MAX, rate);
+}
+
diff --git a/sound/firewire/fireworks/fireworks_hwdep.c b/sound/firewire/fireworks/fireworks_hwdep.c
new file mode 100644
index 000000000000..33df8655fe81
--- /dev/null
+++ b/sound/firewire/fireworks/fireworks_hwdep.c
@@ -0,0 +1,298 @@
+/*
+ * fireworks_hwdep.c - a part of driver for Fireworks based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * 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
+ * 4.transmit command of EFW transaction
+ * 5.receive response of EFW transaction
+ *
+ */
+
+#include "fireworks.h"
+
+static long
+hwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained,
+ loff_t *offset)
+{
+ unsigned int length, till_end, type;
+ struct snd_efw_transaction *t;
+ long count = 0;
+
+ if (remained < sizeof(type) + sizeof(struct snd_efw_transaction))
+ return -ENOSPC;
+
+ /* data type is SNDRV_FIREWIRE_EVENT_EFW_RESPONSE */
+ type = SNDRV_FIREWIRE_EVENT_EFW_RESPONSE;
+ if (copy_to_user(buf, &type, sizeof(type)))
+ return -EFAULT;
+ remained -= sizeof(type);
+ buf += sizeof(type);
+
+ /* write into buffer as many responses as possible */
+ while (efw->resp_queues > 0) {
+ t = (struct snd_efw_transaction *)(efw->pull_ptr);
+ length = be32_to_cpu(t->length) * sizeof(__be32);
+
+ /* confirm enough space for this response */
+ if (remained < length)
+ break;
+
+ /* copy from ring buffer to user buffer */
+ while (length > 0) {
+ till_end = snd_efw_resp_buf_size -
+ (unsigned int)(efw->pull_ptr - efw->resp_buf);
+ till_end = min_t(unsigned int, length, till_end);
+
+ if (copy_to_user(buf, efw->pull_ptr, till_end))
+ return -EFAULT;
+
+ efw->pull_ptr += till_end;
+ if (efw->pull_ptr >= efw->resp_buf +
+ snd_efw_resp_buf_size)
+ efw->pull_ptr -= snd_efw_resp_buf_size;
+
+ length -= till_end;
+ buf += till_end;
+ count += till_end;
+ remained -= till_end;
+ }
+
+ efw->resp_queues--;
+ }
+
+ return count;
+}
+
+static long
+hwdep_read_locked(struct snd_efw *efw, char __user *buf, long count,
+ loff_t *offset)
+{
+ union snd_firewire_event event;
+
+ memset(&event, 0, sizeof(event));
+
+ event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
+ event.lock_status.status = (efw->dev_lock_count > 0);
+ efw->dev_lock_changed = false;
+
+ count = min_t(long, count, sizeof(event.lock_status));
+
+ if (copy_to_user(buf, &event, count))
+ return -EFAULT;
+
+ return count;
+}
+
+static long
+hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
+ loff_t *offset)
+{
+ struct snd_efw *efw = hwdep->private_data;
+ DEFINE_WAIT(wait);
+
+ spin_lock_irq(&efw->lock);
+
+ while ((!efw->dev_lock_changed) && (efw->resp_queues == 0)) {
+ prepare_to_wait(&efw->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
+ spin_unlock_irq(&efw->lock);
+ schedule();
+ finish_wait(&efw->hwdep_wait, &wait);
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+ spin_lock_irq(&efw->lock);
+ }
+
+ if (efw->dev_lock_changed)
+ count = hwdep_read_locked(efw, buf, count, offset);
+ else if (efw->resp_queues > 0)
+ count = hwdep_read_resp_buf(efw, buf, count, offset);
+
+ spin_unlock_irq(&efw->lock);
+
+ return count;
+}
+
+static long
+hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count,
+ loff_t *offset)
+{
+ struct snd_efw *efw = hwdep->private_data;
+ u32 seqnum;
+ u8 *buf;
+
+ if (count < sizeof(struct snd_efw_transaction) ||
+ SND_EFW_RESPONSE_MAXIMUM_BYTES < count)
+ return -EINVAL;
+
+ buf = memdup_user(data, count);
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ /* check seqnum is not for kernel-land */
+ seqnum = be32_to_cpu(((struct snd_efw_transaction *)buf)->seqnum);
+ if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX) {
+ count = -EINVAL;
+ goto end;
+ }
+
+ if (snd_efw_transaction_cmd(efw->unit, buf, count) < 0)
+ count = -EIO;
+end:
+ kfree(buf);
+ return count;
+}
+
+static unsigned int
+hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait)
+{
+ struct snd_efw *efw = hwdep->private_data;
+ unsigned int events;
+
+ poll_wait(file, &efw->hwdep_wait, wait);
+
+ spin_lock_irq(&efw->lock);
+ if (efw->dev_lock_changed || (efw->resp_queues > 0))
+ events = POLLIN | POLLRDNORM;
+ else
+ events = 0;
+ spin_unlock_irq(&efw->lock);
+
+ return events | POLLOUT;
+}
+
+static int
+hwdep_get_info(struct snd_efw *efw, void __user *arg)
+{
+ struct fw_device *dev = fw_parent_device(efw->unit);
+ struct snd_firewire_get_info info;
+
+ memset(&info, 0, sizeof(info));
+ info.type = SNDRV_FIREWIRE_TYPE_FIREWORKS;
+ 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_efw *efw)
+{
+ int err;
+
+ spin_lock_irq(&efw->lock);
+
+ if (efw->dev_lock_count == 0) {
+ efw->dev_lock_count = -1;
+ err = 0;
+ } else {
+ err = -EBUSY;
+ }
+
+ spin_unlock_irq(&efw->lock);
+
+ return err;
+}
+
+static int
+hwdep_unlock(struct snd_efw *efw)
+{
+ int err;
+
+ spin_lock_irq(&efw->lock);
+
+ if (efw->dev_lock_count == -1) {
+ efw->dev_lock_count = 0;
+ err = 0;
+ } else {
+ err = -EBADFD;
+ }
+
+ spin_unlock_irq(&efw->lock);
+
+ return err;
+}
+
+static int
+hwdep_release(struct snd_hwdep *hwdep, struct file *file)
+{
+ struct snd_efw *efw = hwdep->private_data;
+
+ spin_lock_irq(&efw->lock);
+ if (efw->dev_lock_count == -1)
+ efw->dev_lock_count = 0;
+ spin_unlock_irq(&efw->lock);
+
+ return 0;
+}
+
+static int
+hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct snd_efw *efw = hwdep->private_data;
+
+ switch (cmd) {
+ case SNDRV_FIREWIRE_IOCTL_GET_INFO:
+ return hwdep_get_info(efw, (void __user *)arg);
+ case SNDRV_FIREWIRE_IOCTL_LOCK:
+ return hwdep_lock(efw);
+ case SNDRV_FIREWIRE_IOCTL_UNLOCK:
+ return hwdep_unlock(efw);
+ 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
+
+static const struct snd_hwdep_ops hwdep_ops = {
+ .read = hwdep_read,
+ .write = hwdep_write,
+ .release = hwdep_release,
+ .poll = hwdep_poll,
+ .ioctl = hwdep_ioctl,
+ .ioctl_compat = hwdep_compat_ioctl,
+};
+
+int snd_efw_create_hwdep_device(struct snd_efw *efw)
+{
+ struct snd_hwdep *hwdep;
+ int err;
+
+ err = snd_hwdep_new(efw->card, "Fireworks", 0, &hwdep);
+ if (err < 0)
+ goto end;
+ strcpy(hwdep->name, "Fireworks");
+ hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREWORKS;
+ hwdep->ops = hwdep_ops;
+ hwdep->private_data = efw;
+ hwdep->exclusive = true;
+end:
+ return err;
+}
+
diff --git a/sound/firewire/fireworks/fireworks_midi.c b/sound/firewire/fireworks/fireworks_midi.c
new file mode 100644
index 000000000000..cf9c65260439
--- /dev/null
+++ b/sound/firewire/fireworks/fireworks_midi.c
@@ -0,0 +1,168 @@
+/*
+ * fireworks_midi.c - a part of driver for Fireworks based devices
+ *
+ * Copyright (c) 2009-2010 Clemens Ladisch
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+#include "fireworks.h"
+
+static int midi_capture_open(struct snd_rawmidi_substream *substream)
+{
+ struct snd_efw *efw = substream->rmidi->private_data;
+ int err;
+
+ err = snd_efw_stream_lock_try(efw);
+ if (err < 0)
+ goto end;
+
+ atomic_inc(&efw->capture_substreams);
+ err = snd_efw_stream_start_duplex(efw, 0);
+ if (err < 0)
+ snd_efw_stream_lock_release(efw);
+
+end:
+ return err;
+}
+
+static int midi_playback_open(struct snd_rawmidi_substream *substream)
+{
+ struct snd_efw *efw = substream->rmidi->private_data;
+ int err;
+
+ err = snd_efw_stream_lock_try(efw);
+ if (err < 0)
+ goto end;
+
+ atomic_inc(&efw->playback_substreams);
+ err = snd_efw_stream_start_duplex(efw, 0);
+ if (err < 0)
+ snd_efw_stream_lock_release(efw);
+end:
+ return err;
+}
+
+static int midi_capture_close(struct snd_rawmidi_substream *substream)
+{
+ struct snd_efw *efw = substream->rmidi->private_data;
+
+ atomic_dec(&efw->capture_substreams);
+ snd_efw_stream_stop_duplex(efw);
+
+ snd_efw_stream_lock_release(efw);
+ return 0;
+}
+
+static int midi_playback_close(struct snd_rawmidi_substream *substream)
+{
+ struct snd_efw *efw = substream->rmidi->private_data;
+
+ atomic_dec(&efw->playback_substreams);
+ snd_efw_stream_stop_duplex(efw);
+
+ snd_efw_stream_lock_release(efw);
+ return 0;
+}
+
+static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
+{
+ struct snd_efw *efw = substrm->rmidi->private_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&efw->lock, flags);
+
+ if (up)
+ amdtp_stream_midi_trigger(&efw->tx_stream,
+ substrm->number, substrm);
+ else
+ amdtp_stream_midi_trigger(&efw->tx_stream,
+ substrm->number, NULL);
+
+ spin_unlock_irqrestore(&efw->lock, flags);
+}
+
+static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
+{
+ struct snd_efw *efw = substrm->rmidi->private_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&efw->lock, flags);
+
+ if (up)
+ amdtp_stream_midi_trigger(&efw->rx_stream,
+ substrm->number, substrm);
+ else
+ amdtp_stream_midi_trigger(&efw->rx_stream,
+ substrm->number, NULL);
+
+ spin_unlock_irqrestore(&efw->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_efw *efw,
+ 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", efw->card->shortname, subs->number + 1);
+ }
+}
+
+int snd_efw_create_midi_devices(struct snd_efw *efw)
+{
+ struct snd_rawmidi *rmidi;
+ struct snd_rawmidi_str *str;
+ int err;
+
+ /* create midi ports */
+ err = snd_rawmidi_new(efw->card, efw->card->driver, 0,
+ efw->midi_out_ports, efw->midi_in_ports,
+ &rmidi);
+ if (err < 0)
+ return err;
+
+ snprintf(rmidi->name, sizeof(rmidi->name),
+ "%s MIDI", efw->card->shortname);
+ rmidi->private_data = efw;
+
+ if (efw->midi_in_ports > 0) {
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+ &midi_capture_ops);
+
+ str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT];
+
+ set_midi_substream_names(efw, str);
+ }
+
+ if (efw->midi_out_ports > 0) {
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
+
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+ &midi_playback_ops);
+
+ str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
+
+ set_midi_substream_names(efw, str);
+ }
+
+ if ((efw->midi_out_ports > 0) && (efw->midi_in_ports > 0))
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
+
+ return 0;
+}
diff --git a/sound/firewire/fireworks/fireworks_pcm.c b/sound/firewire/fireworks/fireworks_pcm.c
new file mode 100644
index 000000000000..8a34753de210
--- /dev/null
+++ b/sound/firewire/fireworks/fireworks_pcm.c
@@ -0,0 +1,403 @@
+/*
+ * fireworks_pcm.c - a part of driver for Fireworks based devices
+ *
+ * Copyright (c) 2009-2010 Clemens Ladisch
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+#include "./fireworks.h"
+
+/*
+ * NOTE:
+ * Fireworks changes its AMDTP channels for PCM data according to its sampling
+ * rate. There are three modes. Here _XX is either _rx or _tx.
+ * 0: 32.0- 48.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels applied
+ * 1: 88.2- 96.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels_2x applied
+ * 2: 176.4-192.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels_4x applied
+ *
+ * The number of PCM channels for analog input and output are always fixed but
+ * the number of PCM channels for digital input and output are differed.
+ *
+ * Additionally, according to "AudioFire Owner's Manual Version 2.2", in some
+ * model, the number of PCM channels for digital input has more restriction
+ * depending on which digital interface is selected.
+ * - S/PDIF coaxial and optical : use input 1-2
+ * - ADAT optical at 32.0-48.0 kHz : use input 1-8
+ * - ADAT optical at 88.2-96.0 kHz : use input 1-4 (S/MUX format)
+ *
+ * The data in AMDTP channels for blank PCM channels are zero.
+ */
+static const unsigned int freq_table[] = {
+ /* multiplier mode 0 */
+ [0] = 32000,
+ [1] = 44100,
+ [2] = 48000,
+ /* multiplier mode 1 */
+ [3] = 88200,
+ [4] = 96000,
+ /* multiplier mode 2 */
+ [5] = 176400,
+ [6] = 192000,
+};
+
+static inline unsigned int
+get_multiplier_mode_with_index(unsigned int index)
+{
+ return ((int)index - 1) / 2;
+}
+
+int snd_efw_get_multiplier_mode(unsigned int sampling_rate, unsigned int *mode)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(freq_table); i++) {
+ if (freq_table[i] == sampling_rate) {
+ *mode = get_multiplier_mode_with_index(i);
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int
+hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule)
+{
+ 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(freq_table); i++) {
+ mode = get_multiplier_mode_with_index(i);
+ if (!snd_interval_test(c, pcm_channels[mode]))
+ continue;
+
+ t.min = min(t.min, freq_table[i]);
+ t.max = max(t.max, freq_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)
+{
+ 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(freq_table); i++) {
+ mode = get_multiplier_mode_with_index(i);
+ if (!snd_interval_test(r, freq_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(struct snd_pcm_hardware *hw, unsigned int *pcm_channels)
+{
+ unsigned int i, mode;
+
+ hw->channels_min = UINT_MAX;
+ hw->channels_max = 0;
+
+ for (i = 0; i < ARRAY_SIZE(freq_table); i++) {
+ mode = get_multiplier_mode_with_index(i);
+ if (pcm_channels[mode] == 0)
+ continue;
+
+ hw->channels_min = min(hw->channels_min, pcm_channels[mode]);
+ hw->channels_max = max(hw->channels_max, pcm_channels[mode]);
+ }
+}
+
+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_efw *efw,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct amdtp_stream *s;
+ 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 = AMDTP_IN_PCM_FORMAT_BITS;
+ s = &efw->tx_stream;
+ pcm_channels = efw->pcm_capture_channels;
+ } else {
+ runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS;
+ s = &efw->rx_stream;
+ pcm_channels = efw->pcm_playback_channels;
+ }
+
+ /* limit rates */
+ runtime->hw.rates = efw->supported_sampling_rate,
+ snd_pcm_limit_hw_rates(runtime);
+
+ limit_channels(&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, pcm_channels,
+ SNDRV_PCM_HW_PARAM_RATE, -1);
+ if (err < 0)
+ goto end;
+
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ hw_rule_rate, pcm_channels,
+ SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+ if (err < 0)
+ goto end;
+
+ err = amdtp_stream_add_pcm_hw_constraints(s, runtime);
+end:
+ return err;
+}
+
+static int pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_efw *efw = substream->private_data;
+ unsigned int sampling_rate;
+ enum snd_efw_clock_source clock_source;
+ int err;
+
+ err = snd_efw_stream_lock_try(efw);
+ if (err < 0)
+ goto end;
+
+ err = pcm_init_hw_params(efw, substream);
+ if (err < 0)
+ goto err_locked;
+
+ err = snd_efw_command_get_clock_source(efw, &clock_source);
+ 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.
+ */
+ if ((clock_source != SND_EFW_CLOCK_SOURCE_INTERNAL) ||
+ amdtp_stream_pcm_running(&efw->tx_stream) ||
+ amdtp_stream_pcm_running(&efw->rx_stream)) {
+ err = snd_efw_command_get_sampling_rate(efw, &sampling_rate);
+ if (err < 0)
+ goto err_locked;
+ substream->runtime->hw.rate_min = sampling_rate;
+ substream->runtime->hw.rate_max = sampling_rate;
+ }
+
+ snd_pcm_set_sync(substream);
+end:
+ return err;
+err_locked:
+ snd_efw_stream_lock_release(efw);
+ return err;
+}
+
+static int pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_efw *efw = substream->private_data;
+ snd_efw_stream_lock_release(efw);
+ return 0;
+}
+
+static int pcm_capture_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_efw *efw = substream->private_data;
+
+ if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ atomic_inc(&efw->capture_substreams);
+ amdtp_stream_set_pcm_format(&efw->tx_stream, params_format(hw_params));
+
+ return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+ params_buffer_bytes(hw_params));
+}
+static int pcm_playback_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_efw *efw = substream->private_data;
+
+ if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ atomic_inc(&efw->playback_substreams);
+ amdtp_stream_set_pcm_format(&efw->rx_stream, params_format(hw_params));
+
+ return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+ params_buffer_bytes(hw_params));
+}
+
+static int pcm_capture_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_efw *efw = substream->private_data;
+
+ if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+ atomic_dec(&efw->capture_substreams);
+
+ snd_efw_stream_stop_duplex(efw);
+
+ return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+static int pcm_playback_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_efw *efw = substream->private_data;
+
+ if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+ atomic_dec(&efw->playback_substreams);
+
+ snd_efw_stream_stop_duplex(efw);
+
+ return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int pcm_capture_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_efw *efw = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int err;
+
+ err = snd_efw_stream_start_duplex(efw, runtime->rate);
+ if (err >= 0)
+ amdtp_stream_pcm_prepare(&efw->tx_stream);
+
+ return err;
+}
+static int pcm_playback_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_efw *efw = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int err;
+
+ err = snd_efw_stream_start_duplex(efw, runtime->rate);
+ if (err >= 0)
+ amdtp_stream_pcm_prepare(&efw->rx_stream);
+
+ return err;
+}
+
+static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_efw *efw = substream->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ amdtp_stream_pcm_trigger(&efw->tx_stream, substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ amdtp_stream_pcm_trigger(&efw->tx_stream, NULL);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_efw *efw = substream->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ amdtp_stream_pcm_trigger(&efw->rx_stream, substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ amdtp_stream_pcm_trigger(&efw->rx_stream, NULL);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
+{
+ struct snd_efw *efw = sbstrm->private_data;
+ return amdtp_stream_pcm_pointer(&efw->tx_stream);
+}
+static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
+{
+ struct snd_efw *efw = sbstrm->private_data;
+ return amdtp_stream_pcm_pointer(&efw->rx_stream);
+}
+
+static const 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 const 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_efw_create_pcm_devices(struct snd_efw *efw)
+{
+ struct snd_pcm *pcm;
+ int err;
+
+ err = snd_pcm_new(efw->card, efw->card->driver, 0, 1, 1, &pcm);
+ if (err < 0)
+ goto end;
+
+ pcm->private_data = efw;
+ snprintf(pcm->name, sizeof(pcm->name), "%s PCM", efw->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);
+end:
+ return err;
+}
+
diff --git a/sound/firewire/fireworks/fireworks_proc.c b/sound/firewire/fireworks/fireworks_proc.c
new file mode 100644
index 000000000000..f29d4aaf56a1
--- /dev/null
+++ b/sound/firewire/fireworks/fireworks_proc.c
@@ -0,0 +1,232 @@
+/*
+ * fireworks_proc.c - a part of driver for Fireworks based devices
+ *
+ * Copyright (c) 2009-2010 Clemens Ladisch
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "./fireworks.h"
+
+static inline const char*
+get_phys_name(struct snd_efw_phys_grp *grp, bool input)
+{
+ const char *const ch_type[] = {
+ "Analog", "S/PDIF", "ADAT", "S/PDIF or ADAT", "Mirroring",
+ "Headphones", "I2S", "Guitar", "Pirzo Guitar", "Guitar String",
+ };
+
+ if (grp->type < ARRAY_SIZE(ch_type))
+ return ch_type[grp->type];
+ else if (input)
+ return "Input";
+ else
+ return "Output";
+}
+
+static void
+proc_read_hwinfo(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+ struct snd_efw *efw = entry->private_data;
+ unsigned short i;
+ struct snd_efw_hwinfo *hwinfo;
+
+ hwinfo = kmalloc(sizeof(struct snd_efw_hwinfo), GFP_KERNEL);
+ if (hwinfo == NULL)
+ return;
+
+ if (snd_efw_command_get_hwinfo(efw, hwinfo) < 0)
+ goto end;
+
+ snd_iprintf(buffer, "guid_hi: 0x%X\n", hwinfo->guid_hi);
+ snd_iprintf(buffer, "guid_lo: 0x%X\n", hwinfo->guid_lo);
+ snd_iprintf(buffer, "type: 0x%X\n", hwinfo->type);
+ snd_iprintf(buffer, "version: 0x%X\n", hwinfo->version);
+ snd_iprintf(buffer, "vendor_name: %s\n", hwinfo->vendor_name);
+ snd_iprintf(buffer, "model_name: %s\n", hwinfo->model_name);
+
+ snd_iprintf(buffer, "dsp_version: 0x%X\n", hwinfo->dsp_version);
+ snd_iprintf(buffer, "arm_version: 0x%X\n", hwinfo->arm_version);
+ snd_iprintf(buffer, "fpga_version: 0x%X\n", hwinfo->fpga_version);
+
+ snd_iprintf(buffer, "flags: 0x%X\n", hwinfo->flags);
+
+ snd_iprintf(buffer, "max_sample_rate: 0x%X\n", hwinfo->max_sample_rate);
+ snd_iprintf(buffer, "min_sample_rate: 0x%X\n", hwinfo->min_sample_rate);
+ snd_iprintf(buffer, "supported_clock: 0x%X\n",
+ hwinfo->supported_clocks);
+
+ snd_iprintf(buffer, "phys out: 0x%X\n", hwinfo->phys_out);
+ snd_iprintf(buffer, "phys in: 0x%X\n", hwinfo->phys_in);
+
+ snd_iprintf(buffer, "phys in grps: 0x%X\n",
+ hwinfo->phys_in_grp_count);
+ for (i = 0; i < hwinfo->phys_in_grp_count; i++) {
+ snd_iprintf(buffer,
+ "phys in grp[0x%d]: type 0x%d, count 0x%d\n",
+ i, hwinfo->phys_out_grps[i].type,
+ hwinfo->phys_out_grps[i].count);
+ }
+
+ snd_iprintf(buffer, "phys out grps: 0x%X\n",
+ hwinfo->phys_out_grp_count);
+ for (i = 0; i < hwinfo->phys_out_grp_count; i++) {
+ snd_iprintf(buffer,
+ "phys out grps[0x%d]: type 0x%d, count 0x%d\n",
+ i, hwinfo->phys_out_grps[i].type,
+ hwinfo->phys_out_grps[i].count);
+ }
+
+ snd_iprintf(buffer, "amdtp rx pcm channels 1x: 0x%X\n",
+ hwinfo->amdtp_rx_pcm_channels);
+ snd_iprintf(buffer, "amdtp tx pcm channels 1x: 0x%X\n",
+ hwinfo->amdtp_tx_pcm_channels);
+ snd_iprintf(buffer, "amdtp rx pcm channels 2x: 0x%X\n",
+ hwinfo->amdtp_rx_pcm_channels_2x);
+ snd_iprintf(buffer, "amdtp tx pcm channels 2x: 0x%X\n",
+ hwinfo->amdtp_tx_pcm_channels_2x);
+ snd_iprintf(buffer, "amdtp rx pcm channels 4x: 0x%X\n",
+ hwinfo->amdtp_rx_pcm_channels_4x);
+ snd_iprintf(buffer, "amdtp tx pcm channels 4x: 0x%X\n",
+ hwinfo->amdtp_tx_pcm_channels_4x);
+
+ snd_iprintf(buffer, "midi out ports: 0x%X\n", hwinfo->midi_out_ports);
+ snd_iprintf(buffer, "midi in ports: 0x%X\n", hwinfo->midi_in_ports);
+
+ snd_iprintf(buffer, "mixer playback channels: 0x%X\n",
+ hwinfo->mixer_playback_channels);
+ snd_iprintf(buffer, "mixer capture channels: 0x%X\n",
+ hwinfo->mixer_capture_channels);
+end:
+ kfree(hwinfo);
+}
+
+static void
+proc_read_clock(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+ struct snd_efw *efw = entry->private_data;
+ enum snd_efw_clock_source clock_source;
+ unsigned int sampling_rate;
+
+ if (snd_efw_command_get_clock_source(efw, &clock_source) < 0)
+ return;
+
+ if (snd_efw_command_get_sampling_rate(efw, &sampling_rate) < 0)
+ return;
+
+ snd_iprintf(buffer, "Clock Source: %d\n", clock_source);
+ snd_iprintf(buffer, "Sampling Rate: %d\n", sampling_rate);
+}
+
+/*
+ * NOTE:
+ * dB = 20 * log10(linear / 0x01000000)
+ * -144.0 dB when linear is 0
+ */
+static void
+proc_read_phys_meters(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_efw *efw = entry->private_data;
+ struct snd_efw_phys_meters *meters;
+ unsigned int g, c, m, max, size;
+ const char *name;
+ u32 *linear;
+ int err;
+
+ size = sizeof(struct snd_efw_phys_meters) +
+ (efw->phys_in + efw->phys_out) * sizeof(u32);
+ meters = kzalloc(size, GFP_KERNEL);
+ if (meters == NULL)
+ return;
+
+ err = snd_efw_command_get_phys_meters(efw, meters, size);
+ if (err < 0)
+ goto end;
+
+ snd_iprintf(buffer, "Physical Meters:\n");
+
+ m = 0;
+ max = min(efw->phys_out, meters->out_meters);
+ linear = meters->values;
+ snd_iprintf(buffer, " %d Outputs:\n", max);
+ for (g = 0; g < efw->phys_out_grp_count; g++) {
+ name = get_phys_name(&efw->phys_out_grps[g], false);
+ for (c = 0; c < efw->phys_out_grps[g].count; c++) {
+ if (m < max)
+ snd_iprintf(buffer, "\t%s [%d]: %d\n",
+ name, c, linear[m++]);
+ }
+ }
+
+ m = 0;
+ max = min(efw->phys_in, meters->in_meters);
+ linear = meters->values + meters->out_meters;
+ snd_iprintf(buffer, " %d Inputs:\n", max);
+ for (g = 0; g < efw->phys_in_grp_count; g++) {
+ name = get_phys_name(&efw->phys_in_grps[g], true);
+ for (c = 0; c < efw->phys_in_grps[g].count; c++)
+ if (m < max)
+ snd_iprintf(buffer, "\t%s [%d]: %d\n",
+ name, c, linear[m++]);
+ }
+end:
+ kfree(meters);
+}
+
+static void
+proc_read_queues_state(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_efw *efw = entry->private_data;
+ unsigned int consumed;
+
+ if (efw->pull_ptr > efw->push_ptr)
+ consumed = snd_efw_resp_buf_size -
+ (unsigned int)(efw->pull_ptr - efw->push_ptr);
+ else
+ consumed = (unsigned int)(efw->push_ptr - efw->pull_ptr);
+
+ snd_iprintf(buffer, "%d %d/%d\n",
+ efw->resp_queues, consumed, snd_efw_resp_buf_size);
+}
+
+static void
+add_node(struct snd_efw *efw, 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(efw->card, name, root);
+ if (entry == NULL)
+ return;
+
+ snd_info_set_text_ops(entry, efw, op);
+ if (snd_info_register(entry) < 0)
+ snd_info_free_entry(entry);
+}
+
+void snd_efw_proc_init(struct snd_efw *efw)
+{
+ 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(efw->card, "firewire",
+ efw->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(efw, root, "clock", proc_read_clock);
+ add_node(efw, root, "firmware", proc_read_hwinfo);
+ add_node(efw, root, "meters", proc_read_phys_meters);
+ add_node(efw, root, "queues", proc_read_queues_state);
+}
diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c
new file mode 100644
index 000000000000..b985fc5ebdc6
--- /dev/null
+++ b/sound/firewire/fireworks/fireworks_stream.c
@@ -0,0 +1,372 @@
+/*
+ * fireworks_stream.c - a part of driver for Fireworks based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+#include "./fireworks.h"
+
+#define CALLBACK_TIMEOUT 100
+
+static int
+init_stream(struct snd_efw *efw, struct amdtp_stream *stream)
+{
+ struct cmp_connection *conn;
+ enum cmp_direction c_dir;
+ enum amdtp_stream_direction s_dir;
+ int err;
+
+ if (stream == &efw->tx_stream) {
+ conn = &efw->out_conn;
+ c_dir = CMP_OUTPUT;
+ s_dir = AMDTP_IN_STREAM;
+ } else {
+ conn = &efw->in_conn;
+ c_dir = CMP_INPUT;
+ s_dir = AMDTP_OUT_STREAM;
+ }
+
+ err = cmp_connection_init(conn, efw->unit, c_dir, 0);
+ if (err < 0)
+ goto end;
+
+ err = amdtp_stream_init(stream, efw->unit, s_dir, CIP_BLOCKING);
+ if (err < 0) {
+ amdtp_stream_destroy(stream);
+ cmp_connection_destroy(conn);
+ }
+end:
+ return err;
+}
+
+static void
+stop_stream(struct snd_efw *efw, struct amdtp_stream *stream)
+{
+ amdtp_stream_pcm_abort(stream);
+ amdtp_stream_stop(stream);
+
+ if (stream == &efw->tx_stream)
+ cmp_connection_break(&efw->out_conn);
+ else
+ cmp_connection_break(&efw->in_conn);
+}
+
+static int
+start_stream(struct snd_efw *efw, struct amdtp_stream *stream,
+ unsigned int sampling_rate)
+{
+ struct cmp_connection *conn;
+ unsigned int mode, pcm_channels, midi_ports;
+ int err;
+
+ err = snd_efw_get_multiplier_mode(sampling_rate, &mode);
+ if (err < 0)
+ goto end;
+ if (stream == &efw->tx_stream) {
+ conn = &efw->out_conn;
+ pcm_channels = efw->pcm_capture_channels[mode];
+ midi_ports = efw->midi_out_ports;
+ } else {
+ conn = &efw->in_conn;
+ pcm_channels = efw->pcm_playback_channels[mode];
+ midi_ports = efw->midi_in_ports;
+ }
+
+ amdtp_stream_set_parameters(stream, sampling_rate,
+ pcm_channels, midi_ports);
+
+ /* establish connection via CMP */
+ err = cmp_connection_establish(conn,
+ amdtp_stream_get_max_payload(stream));
+ if (err < 0)
+ goto end;
+
+ /* start amdtp stream */
+ err = amdtp_stream_start(stream,
+ conn->resources.channel,
+ conn->speed);
+ if (err < 0) {
+ stop_stream(efw, stream);
+ goto end;
+ }
+
+ /* wait first callback */
+ if (!amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT)) {
+ stop_stream(efw, stream);
+ err = -ETIMEDOUT;
+ }
+end:
+ return err;
+}
+
+static void
+destroy_stream(struct snd_efw *efw, struct amdtp_stream *stream)
+{
+ stop_stream(efw, stream);
+
+ amdtp_stream_destroy(stream);
+
+ if (stream == &efw->tx_stream)
+ cmp_connection_destroy(&efw->out_conn);
+ else
+ cmp_connection_destroy(&efw->in_conn);
+}
+
+static int
+get_sync_mode(struct snd_efw *efw, enum cip_flags *sync_mode)
+{
+ enum snd_efw_clock_source clock_source;
+ int err;
+
+ err = snd_efw_command_get_clock_source(efw, &clock_source);
+ if (err < 0)
+ return err;
+
+ if (clock_source == SND_EFW_CLOCK_SOURCE_SYTMATCH)
+ return -ENOSYS;
+
+ *sync_mode = CIP_SYNC_TO_DEVICE;
+ return 0;
+}
+
+static int
+check_connection_used_by_others(struct snd_efw *efw, struct amdtp_stream *s)
+{
+ struct cmp_connection *conn;
+ bool used;
+ int err;
+
+ if (s == &efw->tx_stream)
+ conn = &efw->out_conn;
+ else
+ conn = &efw->in_conn;
+
+ err = cmp_connection_check_used(conn, &used);
+ if ((err >= 0) && used && !amdtp_stream_running(s)) {
+ dev_err(&efw->unit->device,
+ "Connection established by others: %cPCR[%d]\n",
+ (conn->direction == CMP_OUTPUT) ? 'o' : 'i',
+ conn->pcr_index);
+ err = -EBUSY;
+ }
+
+ return err;
+}
+
+int snd_efw_stream_init_duplex(struct snd_efw *efw)
+{
+ int err;
+
+ err = init_stream(efw, &efw->tx_stream);
+ if (err < 0)
+ goto end;
+ /* Fireworks transmits NODATA packets with TAG0. */
+ efw->tx_stream.flags |= CIP_EMPTY_WITH_TAG0;
+ /* Fireworks has its own meaning for dbc. */
+ efw->tx_stream.flags |= CIP_DBC_IS_END_EVENT;
+ /* Fireworks reset dbc at bus reset. */
+ efw->tx_stream.flags |= CIP_SKIP_DBC_ZERO_CHECK;
+ /* AudioFire9 always reports wrong dbs. */
+ if (efw->is_af9)
+ efw->tx_stream.flags |= CIP_WRONG_DBS;
+ /* Firmware version 5.5 reports fixed interval for dbc. */
+ if (efw->firmware_version == 0x5050000)
+ efw->tx_stream.tx_dbc_interval = 8;
+
+ err = init_stream(efw, &efw->rx_stream);
+ if (err < 0) {
+ destroy_stream(efw, &efw->tx_stream);
+ goto end;
+ }
+ /*
+ * Fireworks ignores MIDI messages in more than first 8 data
+ * blocks of an received AMDTP packet.
+ */
+ efw->rx_stream.rx_blocks_for_midi = 8;
+
+ /* set IEC61883 compliant mode (actually not fully compliant...) */
+ err = snd_efw_command_set_tx_mode(efw, SND_EFW_TRANSPORT_MODE_IEC61883);
+ if (err < 0) {
+ destroy_stream(efw, &efw->tx_stream);
+ destroy_stream(efw, &efw->rx_stream);
+ }
+end:
+ return err;
+}
+
+int snd_efw_stream_start_duplex(struct snd_efw *efw, unsigned int rate)
+{
+ struct amdtp_stream *master, *slave;
+ atomic_t *slave_substreams;
+ enum cip_flags sync_mode;
+ unsigned int curr_rate;
+ int err = 0;
+
+ mutex_lock(&efw->mutex);
+
+ /* Need no substreams */
+ if ((atomic_read(&efw->playback_substreams) == 0) &&
+ (atomic_read(&efw->capture_substreams) == 0))
+ goto end;
+
+ err = get_sync_mode(efw, &sync_mode);
+ if (err < 0)
+ goto end;
+ if (sync_mode == CIP_SYNC_TO_DEVICE) {
+ master = &efw->tx_stream;
+ slave = &efw->rx_stream;
+ slave_substreams = &efw->playback_substreams;
+ } else {
+ master = &efw->rx_stream;
+ slave = &efw->tx_stream;
+ slave_substreams = &efw->capture_substreams;
+ }
+
+ /*
+ * Considering JACK/FFADO streaming:
+ * TODO: This can be removed hwdep functionality becomes popular.
+ */
+ err = check_connection_used_by_others(efw, master);
+ if (err < 0)
+ goto end;
+
+ /* packet queueing error */
+ if (amdtp_streaming_error(slave))
+ stop_stream(efw, slave);
+ if (amdtp_streaming_error(master))
+ stop_stream(efw, master);
+
+ /* stop streams if rate is different */
+ err = snd_efw_command_get_sampling_rate(efw, &curr_rate);
+ if (err < 0)
+ goto end;
+ if (rate == 0)
+ rate = curr_rate;
+ if (rate != curr_rate) {
+ stop_stream(efw, slave);
+ stop_stream(efw, master);
+ }
+
+ /* master should be always running */
+ if (!amdtp_stream_running(master)) {
+ amdtp_stream_set_sync(sync_mode, master, slave);
+ efw->master = master;
+
+ err = snd_efw_command_set_sampling_rate(efw, rate);
+ if (err < 0)
+ goto end;
+
+ err = start_stream(efw, master, rate);
+ if (err < 0) {
+ dev_err(&efw->unit->device,
+ "fail to start AMDTP master stream:%d\n", err);
+ goto end;
+ }
+ }
+
+ /* start slave if needed */
+ if (atomic_read(slave_substreams) > 0 && !amdtp_stream_running(slave)) {
+ err = start_stream(efw, slave, rate);
+ if (err < 0) {
+ dev_err(&efw->unit->device,
+ "fail to start AMDTP slave stream:%d\n", err);
+ stop_stream(efw, master);
+ }
+ }
+end:
+ mutex_unlock(&efw->mutex);
+ return err;
+}
+
+void snd_efw_stream_stop_duplex(struct snd_efw *efw)
+{
+ struct amdtp_stream *master, *slave;
+ atomic_t *master_substreams, *slave_substreams;
+
+ if (efw->master == &efw->rx_stream) {
+ slave = &efw->tx_stream;
+ master = &efw->rx_stream;
+ slave_substreams = &efw->capture_substreams;
+ master_substreams = &efw->playback_substreams;
+ } else {
+ slave = &efw->rx_stream;
+ master = &efw->tx_stream;
+ slave_substreams = &efw->playback_substreams;
+ master_substreams = &efw->capture_substreams;
+ }
+
+ mutex_lock(&efw->mutex);
+
+ if (atomic_read(slave_substreams) == 0) {
+ stop_stream(efw, slave);
+
+ if (atomic_read(master_substreams) == 0)
+ stop_stream(efw, master);
+ }
+
+ mutex_unlock(&efw->mutex);
+}
+
+void snd_efw_stream_update_duplex(struct snd_efw *efw)
+{
+ if ((cmp_connection_update(&efw->out_conn) < 0) ||
+ (cmp_connection_update(&efw->in_conn) < 0)) {
+ mutex_lock(&efw->mutex);
+ stop_stream(efw, &efw->rx_stream);
+ stop_stream(efw, &efw->tx_stream);
+ mutex_unlock(&efw->mutex);
+ } else {
+ amdtp_stream_update(&efw->rx_stream);
+ amdtp_stream_update(&efw->tx_stream);
+ }
+}
+
+void snd_efw_stream_destroy_duplex(struct snd_efw *efw)
+{
+ mutex_lock(&efw->mutex);
+
+ destroy_stream(efw, &efw->rx_stream);
+ destroy_stream(efw, &efw->tx_stream);
+
+ mutex_unlock(&efw->mutex);
+}
+
+void snd_efw_stream_lock_changed(struct snd_efw *efw)
+{
+ efw->dev_lock_changed = true;
+ wake_up(&efw->hwdep_wait);
+}
+
+int snd_efw_stream_lock_try(struct snd_efw *efw)
+{
+ int err;
+
+ spin_lock_irq(&efw->lock);
+
+ /* user land lock this */
+ if (efw->dev_lock_count < 0) {
+ err = -EBUSY;
+ goto end;
+ }
+
+ /* this is the first time */
+ if (efw->dev_lock_count++ == 0)
+ snd_efw_stream_lock_changed(efw);
+ err = 0;
+end:
+ spin_unlock_irq(&efw->lock);
+ return err;
+}
+
+void snd_efw_stream_lock_release(struct snd_efw *efw)
+{
+ spin_lock_irq(&efw->lock);
+
+ if (WARN_ON(efw->dev_lock_count <= 0))
+ goto end;
+ if (--efw->dev_lock_count == 0)
+ snd_efw_stream_lock_changed(efw);
+end:
+ spin_unlock_irq(&efw->lock);
+}
diff --git a/sound/firewire/fireworks/fireworks_transaction.c b/sound/firewire/fireworks/fireworks_transaction.c
new file mode 100644
index 000000000000..255dabc6fc33
--- /dev/null
+++ b/sound/firewire/fireworks/fireworks_transaction.c
@@ -0,0 +1,326 @@
+/*
+ * fireworks_transaction.c - a part of driver for Fireworks based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+/*
+ * Fireworks have its own transaction. The transaction can be delivered by AV/C
+ * Vendor Specific command frame or usual asynchronous transaction. At least,
+ * Windows driver and firmware version 5.5 or later don't use AV/C command.
+ *
+ * Transaction substance:
+ * At first, 6 data exist. Following to the data, parameters for each command
+ * exist. All of the parameters are 32 bit alighed to big endian.
+ * data[0]: Length of transaction substance
+ * data[1]: Transaction version
+ * data[2]: Sequence number. This is incremented by the device
+ * data[3]: Transaction category
+ * data[4]: Transaction command
+ * data[5]: Return value in response.
+ * data[6-]: Parameters
+ *
+ * Transaction address:
+ * command: 0xecc000000000
+ * response: 0xecc080000000 (default)
+ *
+ * I note that the address for response can be changed by command. But this
+ * module uses the default address.
+ */
+#include "./fireworks.h"
+
+#define MEMORY_SPACE_EFW_COMMAND 0xecc000000000ULL
+#define MEMORY_SPACE_EFW_RESPONSE 0xecc080000000ULL
+
+#define ERROR_RETRIES 3
+#define ERROR_DELAY_MS 5
+#define EFC_TIMEOUT_MS 125
+
+static DEFINE_SPINLOCK(instances_lock);
+static struct snd_efw *instances[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+static DEFINE_SPINLOCK(transaction_queues_lock);
+static LIST_HEAD(transaction_queues);
+
+enum transaction_queue_state {
+ STATE_PENDING,
+ STATE_BUS_RESET,
+ STATE_COMPLETE
+};
+
+struct transaction_queue {
+ struct list_head list;
+ struct fw_unit *unit;
+ void *buf;
+ unsigned int size;
+ u32 seqnum;
+ enum transaction_queue_state state;
+ wait_queue_head_t wait;
+};
+
+int snd_efw_transaction_cmd(struct fw_unit *unit,
+ const void *cmd, unsigned int size)
+{
+ return snd_fw_transaction(unit, TCODE_WRITE_BLOCK_REQUEST,
+ MEMORY_SPACE_EFW_COMMAND,
+ (void *)cmd, size, 0);
+}
+
+int snd_efw_transaction_run(struct fw_unit *unit,
+ const void *cmd, unsigned int cmd_size,
+ void *resp, unsigned int resp_size)
+{
+ struct transaction_queue t;
+ unsigned int tries;
+ int ret;
+
+ t.unit = unit;
+ t.buf = resp;
+ t.size = resp_size;
+ t.seqnum = be32_to_cpu(((struct snd_efw_transaction *)cmd)->seqnum) + 1;
+ t.state = STATE_PENDING;
+ init_waitqueue_head(&t.wait);
+
+ spin_lock_irq(&transaction_queues_lock);
+ list_add_tail(&t.list, &transaction_queues);
+ spin_unlock_irq(&transaction_queues_lock);
+
+ tries = 0;
+ do {
+ ret = snd_efw_transaction_cmd(t.unit, (void *)cmd, cmd_size);
+ if (ret < 0)
+ break;
+
+ wait_event_timeout(t.wait, t.state != STATE_PENDING,
+ msecs_to_jiffies(EFC_TIMEOUT_MS));
+
+ if (t.state == STATE_COMPLETE) {
+ ret = t.size;
+ break;
+ } else if (t.state == STATE_BUS_RESET) {
+ msleep(ERROR_DELAY_MS);
+ } else if (++tries >= ERROR_RETRIES) {
+ dev_err(&t.unit->device, "EFW transaction timed out\n");
+ ret = -EIO;
+ break;
+ }
+ } while (1);
+
+ spin_lock_irq(&transaction_queues_lock);
+ list_del(&t.list);
+ spin_unlock_irq(&transaction_queues_lock);
+
+ return ret;
+}
+
+static void
+copy_resp_to_buf(struct snd_efw *efw, void *data, size_t length, int *rcode)
+{
+ size_t capacity, till_end;
+ struct snd_efw_transaction *t;
+
+ spin_lock_irq(&efw->lock);
+
+ t = (struct snd_efw_transaction *)data;
+ length = min_t(size_t, t->length * sizeof(t->length), length);
+
+ if (efw->push_ptr < efw->pull_ptr)
+ capacity = (unsigned int)(efw->pull_ptr - efw->push_ptr);
+ else
+ capacity = snd_efw_resp_buf_size -
+ (unsigned int)(efw->push_ptr - efw->pull_ptr);
+
+ /* confirm enough space for this response */
+ if (capacity < length) {
+ *rcode = RCODE_CONFLICT_ERROR;
+ goto end;
+ }
+
+ /* copy to ring buffer */
+ while (length > 0) {
+ till_end = snd_efw_resp_buf_size -
+ (unsigned int)(efw->push_ptr - efw->resp_buf);
+ till_end = min_t(unsigned int, length, till_end);
+
+ memcpy(efw->push_ptr, data, till_end);
+
+ efw->push_ptr += till_end;
+ if (efw->push_ptr >= efw->resp_buf + snd_efw_resp_buf_size)
+ efw->push_ptr -= snd_efw_resp_buf_size;
+
+ length -= till_end;
+ data += till_end;
+ }
+
+ /* for hwdep */
+ efw->resp_queues++;
+ wake_up(&efw->hwdep_wait);
+
+ *rcode = RCODE_COMPLETE;
+end:
+ spin_unlock_irq(&efw->lock);
+}
+
+static void
+handle_resp_for_user(struct fw_card *card, int generation, int source,
+ void *data, size_t length, int *rcode)
+{
+ struct fw_device *device;
+ struct snd_efw *efw;
+ unsigned int i;
+
+ spin_lock_irq(&instances_lock);
+
+ for (i = 0; i < SNDRV_CARDS; i++) {
+ efw = instances[i];
+ if (efw == NULL)
+ continue;
+ device = fw_parent_device(efw->unit);
+ if ((device->card != card) ||
+ (device->generation != generation))
+ continue;
+ smp_rmb(); /* node id vs. generation */
+ if (device->node_id != source)
+ continue;
+
+ break;
+ }
+ if (i == SNDRV_CARDS)
+ goto end;
+
+ copy_resp_to_buf(efw, data, length, rcode);
+end:
+ spin_unlock_irq(&instances_lock);
+}
+
+static void
+handle_resp_for_kernel(struct fw_card *card, int generation, int source,
+ void *data, size_t length, int *rcode, u32 seqnum)
+{
+ struct fw_device *device;
+ struct transaction_queue *t;
+ unsigned long flags;
+
+ spin_lock_irqsave(&transaction_queues_lock, flags);
+ list_for_each_entry(t, &transaction_queues, list) {
+ device = fw_parent_device(t->unit);
+ if ((device->card != card) ||
+ (device->generation != generation))
+ continue;
+ smp_rmb(); /* node_id vs. generation */
+ if (device->node_id != source)
+ continue;
+
+ if ((t->state == STATE_PENDING) && (t->seqnum == seqnum)) {
+ t->state = STATE_COMPLETE;
+ t->size = min_t(unsigned int, length, t->size);
+ memcpy(t->buf, data, t->size);
+ wake_up(&t->wait);
+ *rcode = RCODE_COMPLETE;
+ }
+ }
+ spin_unlock_irqrestore(&transaction_queues_lock, flags);
+}
+
+static void
+efw_response(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)
+{
+ int rcode, dummy;
+ u32 seqnum;
+
+ rcode = RCODE_TYPE_ERROR;
+ if (length < sizeof(struct snd_efw_transaction)) {
+ rcode = RCODE_DATA_ERROR;
+ goto end;
+ } else if (offset != MEMORY_SPACE_EFW_RESPONSE) {
+ rcode = RCODE_ADDRESS_ERROR;
+ goto end;
+ }
+
+ seqnum = be32_to_cpu(((struct snd_efw_transaction *)data)->seqnum);
+ if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX + 1) {
+ handle_resp_for_kernel(card, generation, source,
+ data, length, &rcode, seqnum);
+ if (snd_efw_resp_buf_debug)
+ handle_resp_for_user(card, generation, source,
+ data, length, &dummy);
+ } else {
+ handle_resp_for_user(card, generation, source,
+ data, length, &rcode);
+ }
+end:
+ fw_send_response(card, request, rcode);
+}
+
+void snd_efw_transaction_add_instance(struct snd_efw *efw)
+{
+ unsigned int i;
+
+ spin_lock_irq(&instances_lock);
+
+ for (i = 0; i < SNDRV_CARDS; i++) {
+ if (instances[i] != NULL)
+ continue;
+ instances[i] = efw;
+ break;
+ }
+
+ spin_unlock_irq(&instances_lock);
+}
+
+void snd_efw_transaction_remove_instance(struct snd_efw *efw)
+{
+ unsigned int i;
+
+ spin_lock_irq(&instances_lock);
+
+ for (i = 0; i < SNDRV_CARDS; i++) {
+ if (instances[i] != efw)
+ continue;
+ instances[i] = NULL;
+ }
+
+ spin_unlock_irq(&instances_lock);
+}
+
+void snd_efw_transaction_bus_reset(struct fw_unit *unit)
+{
+ struct transaction_queue *t;
+
+ spin_lock_irq(&transaction_queues_lock);
+ list_for_each_entry(t, &transaction_queues, list) {
+ if ((t->unit == unit) &&
+ (t->state == STATE_PENDING)) {
+ t->state = STATE_BUS_RESET;
+ wake_up(&t->wait);
+ }
+ }
+ spin_unlock_irq(&transaction_queues_lock);
+}
+
+static struct fw_address_handler resp_register_handler = {
+ .length = SND_EFW_RESPONSE_MAXIMUM_BYTES,
+ .address_callback = efw_response
+};
+
+int snd_efw_transaction_register(void)
+{
+ static const struct fw_address_region resp_register_region = {
+ .start = MEMORY_SPACE_EFW_RESPONSE,
+ .end = MEMORY_SPACE_EFW_RESPONSE +
+ SND_EFW_RESPONSE_MAXIMUM_BYTES
+ };
+ return fw_core_add_address_handler(&resp_register_handler,
+ &resp_register_region);
+}
+
+void snd_efw_transaction_unregister(void)
+{
+ WARN_ON(!list_empty(&transaction_queues));
+ fw_core_remove_address_handler(&resp_register_handler);
+}
diff --git a/sound/firewire/speakers.c b/sound/firewire/speakers.c
index 9f7ef219b109..768d40ddfebb 100644
--- a/sound/firewire/speakers.c
+++ b/sound/firewire/speakers.c
@@ -51,7 +51,7 @@ struct fwspk {
const struct device_info *device_info;
struct mutex mutex;
struct cmp_connection connection;
- struct amdtp_out_stream stream;
+ struct amdtp_stream stream;
bool mute;
s16 volume[6];
s16 volume_min;
@@ -167,13 +167,7 @@ static int fwspk_open(struct snd_pcm_substream *substream)
if (err < 0)
return err;
- err = snd_pcm_hw_constraint_minmax(runtime,
- SNDRV_PCM_HW_PARAM_PERIOD_TIME,
- 5000, UINT_MAX);
- if (err < 0)
- return err;
-
- err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+ err = amdtp_stream_add_pcm_hw_constraints(&fwspk->stream, runtime);
if (err < 0)
return err;
@@ -187,48 +181,12 @@ static int fwspk_close(struct snd_pcm_substream *substream)
static void fwspk_stop_stream(struct fwspk *fwspk)
{
- if (amdtp_out_stream_running(&fwspk->stream)) {
- amdtp_out_stream_stop(&fwspk->stream);
+ if (amdtp_stream_running(&fwspk->stream)) {
+ amdtp_stream_stop(&fwspk->stream);
cmp_connection_break(&fwspk->connection);
}
}
-static int fwspk_set_rate(struct fwspk *fwspk, unsigned int sfc)
-{
- u8 *buf;
- int err;
-
- buf = kmalloc(8, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
-
- buf[0] = 0x00; /* AV/C, CONTROL */
- buf[1] = 0xff; /* unit */
- buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */
- buf[3] = 0x00; /* plug 0 */
- buf[4] = 0x90; /* format: audio */
- buf[5] = 0x00 | sfc; /* AM824, frequency */
- buf[6] = 0xff; /* SYT (not used) */
- buf[7] = 0xff;
-
- err = fcp_avc_transaction(fwspk->unit, buf, 8, buf, 8,
- BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5));
- if (err < 0)
- goto error;
- if (err < 6 || buf[0] != 0x09 /* ACCEPTED */) {
- dev_err(&fwspk->unit->device, "failed to set sample rate\n");
- err = -EIO;
- goto error;
- }
-
- err = 0;
-
-error:
- kfree(buf);
-
- return err;
-}
-
static int fwspk_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
@@ -244,17 +202,20 @@ static int fwspk_hw_params(struct snd_pcm_substream *substream,
if (err < 0)
goto error;
- amdtp_out_stream_set_parameters(&fwspk->stream,
- params_rate(hw_params),
- params_channels(hw_params),
- 0);
+ amdtp_stream_set_parameters(&fwspk->stream,
+ params_rate(hw_params),
+ params_channels(hw_params),
+ 0);
- amdtp_out_stream_set_pcm_format(&fwspk->stream,
- params_format(hw_params));
+ amdtp_stream_set_pcm_format(&fwspk->stream,
+ params_format(hw_params));
- err = fwspk_set_rate(fwspk, fwspk->stream.sfc);
- if (err < 0)
+ err = avc_general_set_sig_fmt(fwspk->unit, params_rate(hw_params),
+ AVC_GENERAL_PLUG_DIR_IN, 0);
+ if (err < 0) {
+ dev_err(&fwspk->unit->device, "failed to set sample rate\n");
goto err_buffer;
+ }
return 0;
@@ -282,25 +243,25 @@ static int fwspk_prepare(struct snd_pcm_substream *substream)
mutex_lock(&fwspk->mutex);
- if (amdtp_out_streaming_error(&fwspk->stream))
+ if (amdtp_streaming_error(&fwspk->stream))
fwspk_stop_stream(fwspk);
- if (!amdtp_out_stream_running(&fwspk->stream)) {
+ if (!amdtp_stream_running(&fwspk->stream)) {
err = cmp_connection_establish(&fwspk->connection,
- amdtp_out_stream_get_max_payload(&fwspk->stream));
+ amdtp_stream_get_max_payload(&fwspk->stream));
if (err < 0)
goto err_mutex;
- err = amdtp_out_stream_start(&fwspk->stream,
- fwspk->connection.resources.channel,
- fwspk->connection.speed);
+ err = amdtp_stream_start(&fwspk->stream,
+ fwspk->connection.resources.channel,
+ fwspk->connection.speed);
if (err < 0)
goto err_connection;
}
mutex_unlock(&fwspk->mutex);
- amdtp_out_stream_pcm_prepare(&fwspk->stream);
+ amdtp_stream_pcm_prepare(&fwspk->stream);
return 0;
@@ -327,7 +288,7 @@ static int fwspk_trigger(struct snd_pcm_substream *substream, int cmd)
default:
return -EINVAL;
}
- amdtp_out_stream_pcm_trigger(&fwspk->stream, pcm);
+ amdtp_stream_pcm_trigger(&fwspk->stream, pcm);
return 0;
}
@@ -335,7 +296,7 @@ static snd_pcm_uframes_t fwspk_pointer(struct snd_pcm_substream *substream)
{
struct fwspk *fwspk = substream->private_data;
- return amdtp_out_stream_pcm_pointer(&fwspk->stream);
+ return amdtp_stream_pcm_pointer(&fwspk->stream);
}
static int fwspk_create_pcm(struct fwspk *fwspk)
@@ -653,7 +614,7 @@ static void fwspk_card_free(struct snd_card *card)
{
struct fwspk *fwspk = card->private_data;
- amdtp_out_stream_destroy(&fwspk->stream);
+ amdtp_stream_destroy(&fwspk->stream);
cmp_connection_destroy(&fwspk->connection);
fw_unit_put(fwspk->unit);
mutex_destroy(&fwspk->mutex);
@@ -679,11 +640,12 @@ static int fwspk_probe(struct fw_unit *unit,
fwspk->unit = fw_unit_get(unit);
fwspk->device_info = (const struct device_info *)id->driver_data;
- err = cmp_connection_init(&fwspk->connection, unit, 0);
+ err = cmp_connection_init(&fwspk->connection, unit, CMP_INPUT, 0);
if (err < 0)
goto err_unit;
- err = amdtp_out_stream_init(&fwspk->stream, unit, CIP_NONBLOCKING);
+ err = amdtp_stream_init(&fwspk->stream, unit, AMDTP_OUT_STREAM,
+ CIP_NONBLOCKING);
if (err < 0)
goto err_connection;
@@ -733,21 +695,21 @@ static void fwspk_bus_reset(struct fw_unit *unit)
fcp_bus_reset(fwspk->unit);
if (cmp_connection_update(&fwspk->connection) < 0) {
- amdtp_out_stream_pcm_abort(&fwspk->stream);
+ amdtp_stream_pcm_abort(&fwspk->stream);
mutex_lock(&fwspk->mutex);
fwspk_stop_stream(fwspk);
mutex_unlock(&fwspk->mutex);
return;
}
- amdtp_out_stream_update(&fwspk->stream);
+ amdtp_stream_update(&fwspk->stream);
}
static void fwspk_remove(struct fw_unit *unit)
{
struct fwspk *fwspk = dev_get_drvdata(&unit->device);
- amdtp_out_stream_pcm_abort(&fwspk->stream);
+ amdtp_stream_pcm_abort(&fwspk->stream);
snd_card_disconnect(fwspk->card);
mutex_lock(&fwspk->mutex);
diff --git a/sound/isa/gus/interwave.c b/sound/isa/gus/interwave.c
index 5abbbe477d16..ad55e5cb8e94 100644
--- a/sound/isa/gus/interwave.c
+++ b/sound/isa/gus/interwave.c
@@ -442,17 +442,11 @@ static void snd_interwave_detect_memory(struct snd_gus_card *gus)
for (bank_pos = 0; bank_pos < 16L * 1024L * 1024L; bank_pos += 4L * 1024L * 1024L) {
for (i = 0; i < 8; ++i)
iwave[i] = snd_gf1_peek(gus, bank_pos + i);
-#ifdef CONFIG_SND_DEBUG_ROM
- printk(KERN_DEBUG "ROM at 0x%06x = %8phC\n", bank_pos, iwave);
-#endif
if (strncmp(iwave, "INTRWAVE", 8))
continue; /* first check */
csum = 0;
for (i = 0; i < sizeof(struct rom_hdr); i++)
csum += snd_gf1_peek(gus, bank_pos + i);
-#ifdef CONFIG_SND_DEBUG_ROM
- printk(KERN_DEBUG "ROM checksum = 0x%x (computed)\n", csum);
-#endif
if (csum != 0)
continue; /* not valid rom */
gus->gf1.rom_banks++;
diff --git a/sound/isa/sb/sb_mixer.c b/sound/isa/sb/sb_mixer.c
index 6496822c1808..1ff78ec9f0ac 100644
--- a/sound/isa/sb/sb_mixer.c
+++ b/sound/isa/sb/sb_mixer.c
@@ -818,12 +818,14 @@ int snd_sbmixer_new(struct snd_sb *chip)
return err;
break;
case SB_HW_DT019X:
- if ((err = snd_sbmixer_init(chip,
- snd_dt019x_controls,
- ARRAY_SIZE(snd_dt019x_controls),
- snd_dt019x_init_values,
- ARRAY_SIZE(snd_dt019x_init_values),
- "DT019X")) < 0)
+ err = snd_sbmixer_init(chip,
+ snd_dt019x_controls,
+ ARRAY_SIZE(snd_dt019x_controls),
+ snd_dt019x_init_values,
+ ARRAY_SIZE(snd_dt019x_init_values),
+ "DT019X");
+ if (err < 0)
+ return err;
break;
default:
strcpy(card->mixername, "???");
diff --git a/sound/mips/au1x00.c b/sound/mips/au1x00.c
index d10ef7675268..fbcaa5434fd8 100644
--- a/sound/mips/au1x00.c
+++ b/sound/mips/au1x00.c
@@ -648,14 +648,14 @@ static int au1000_ac97_probe(struct platform_device *pdev)
goto out;
err = -EBUSY;
- au1000->ac97_res_port = request_mem_region(r->start,
- r->end - r->start + 1, pdev->name);
+ au1000->ac97_res_port = request_mem_region(r->start, resource_size(r),
+ pdev->name);
if (!au1000->ac97_res_port) {
snd_printk(KERN_ERR "ALSA AC97: can't grab AC97 port\n");
goto out;
}
- io = ioremap(r->start, r->end - r->start + 1);
+ io = ioremap(r->start, resource_size(r));
if (!io)
goto out;
diff --git a/sound/oss/mpu401.c b/sound/oss/mpu401.c
index 25e4609f8339..3bbc3ec5be82 100644
--- a/sound/oss/mpu401.c
+++ b/sound/oss/mpu401.c
@@ -567,7 +567,6 @@ static int mpu401_out(int dev, unsigned char midi_byte)
static int mpu401_command(int dev, mpu_command_rec * cmd)
{
int i, timeout, ok;
- int ret = 0;
unsigned long flags;
struct mpu_config *devc;
@@ -644,7 +643,6 @@ retry:
}
}
}
- ret = 0;
cmd->data[0] = 0;
if (cmd->nr_returns)
@@ -666,7 +664,7 @@ retry:
}
}
spin_unlock_irqrestore(&devc->lock,flags);
- return ret;
+ return 0;
}
static int mpu_cmd(int dev, int cmd, int data)
diff --git a/sound/oss/swarm_cs4297a.c b/sound/oss/swarm_cs4297a.c
index f851fd0e199c..a33e8ce8085b 100644
--- a/sound/oss/swarm_cs4297a.c
+++ b/sound/oss/swarm_cs4297a.c
@@ -2625,15 +2625,12 @@ static int __init cs4297a_init(void)
u32 pwr, id;
mm_segment_t fs;
int rval;
-#ifndef CONFIG_BCM_CS4297A_CSWARM
u64 cfg;
int mdio_val;
-#endif
CS_DBGOUT(CS_INIT | CS_FUNCTION, 2, printk(KERN_INFO
"cs4297a: cs4297a_init_module()+ \n"));
-#ifndef CONFIG_BCM_CS4297A_CSWARM
mdio_val = __raw_readq(KSEG1 + A_MAC_REGISTER(2, R_MAC_MDIO)) &
(M_MAC_MDIO_DIR|M_MAC_MDIO_OUT);
@@ -2659,7 +2656,6 @@ static int __init cs4297a_init(void)
__raw_writeq(mdio_val | M_MAC_GENC, KSEG1+A_MAC_REGISTER(2, R_MAC_MDIO));
/* Give the codec some time to finish resetting (start the bit clock) */
udelay(100);
-#endif
if (!(s = kzalloc(sizeof(struct cs4297a_state), GFP_KERNEL))) {
CS_DBGOUT(CS_ERROR, 1, printk(KERN_ERR
diff --git a/sound/pci/bt87x.c b/sound/pci/bt87x.c
index 8546711d12f9..70951fd9b354 100644
--- a/sound/pci/bt87x.c
+++ b/sound/pci/bt87x.c
@@ -443,7 +443,7 @@ static int snd_bt87x_pcm_open(struct snd_pcm_substream *substream)
_error:
clear_bit(0, &chip->opened);
- smp_mb__after_clear_bit();
+ smp_mb__after_atomic();
return err;
}
@@ -458,7 +458,7 @@ static int snd_bt87x_close(struct snd_pcm_substream *substream)
chip->substream = NULL;
clear_bit(0, &chip->opened);
- smp_mb__after_clear_bit();
+ smp_mb__after_atomic();
return 0;
}
diff --git a/sound/pci/fm801.c b/sound/pci/fm801.c
index db18ccabadd6..529f5f4f4c9c 100644
--- a/sound/pci/fm801.c
+++ b/sound/pci/fm801.c
@@ -23,6 +23,7 @@
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/interrupt.h>
+#include <linux/io.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/module.h>
@@ -34,8 +35,6 @@
#include <sound/opl3.h>
#include <sound/initval.h>
-#include <asm/io.h>
-
#ifdef CONFIG_SND_FM801_TEA575X_BOOL
#include <media/tea575x.h>
#endif
@@ -80,7 +79,10 @@ MODULE_PARM_DESC(radio_nr, "Radio device numbers");
* Direct registers
*/
-#define FM801_REG(chip, reg) (chip->port + FM801_##reg)
+#define fm801_writew(chip,reg,value) outw((value), chip->port + FM801_##reg)
+#define fm801_readw(chip,reg) inw(chip->port + FM801_##reg)
+
+#define fm801_writel(chip,reg,value) outl((value), chip->port + FM801_##reg)
#define FM801_PCM_VOL 0x00 /* PCM Output Volume */
#define FM801_FM_VOL 0x02 /* FM Output Volume */
@@ -156,21 +158,27 @@ MODULE_PARM_DESC(radio_nr, "Radio device numbers");
#define FM801_GPIO_GS3 (1<<15)
#define FM801_GPIO_GS(x) (1<<(12+(x)))
-/*
-
+/**
+ * struct fm801 - describes FM801 chip
+ * @port: I/O port number
+ * @multichannel: multichannel support
+ * @secondary: secondary codec
+ * @secondary_addr: address of the secondary codec
+ * @tea575x_tuner: tuner access method & flags
+ * @ply_ctrl: playback control
+ * @cap_ctrl: capture control
*/
-
struct fm801 {
int irq;
- unsigned long port; /* I/O port number */
- unsigned int multichannel: 1, /* multichannel support */
- secondary: 1; /* secondary codec */
- unsigned char secondary_addr; /* address of the secondary codec */
- unsigned int tea575x_tuner; /* tuner access method & flags */
+ unsigned long port;
+ unsigned int multichannel: 1,
+ secondary: 1;
+ unsigned char secondary_addr;
+ unsigned int tea575x_tuner;
- unsigned short ply_ctrl; /* playback control */
- unsigned short cap_ctrl; /* capture control */
+ unsigned short ply_ctrl;
+ unsigned short cap_ctrl;
unsigned long ply_buffer;
unsigned int ply_buf;
@@ -222,6 +230,30 @@ MODULE_DEVICE_TABLE(pci, snd_fm801_ids);
* common I/O routines
*/
+static bool fm801_ac97_is_ready(struct fm801 *chip, unsigned int iterations)
+{
+ unsigned int idx;
+
+ for (idx = 0; idx < iterations; idx++) {
+ if (!(fm801_readw(chip, AC97_CMD) & FM801_AC97_BUSY))
+ return true;
+ udelay(10);
+ }
+ return false;
+}
+
+static bool fm801_ac97_is_valid(struct fm801 *chip, unsigned int iterations)
+{
+ unsigned int idx;
+
+ for (idx = 0; idx < iterations; idx++) {
+ if (fm801_readw(chip, AC97_CMD) & FM801_AC97_VALID)
+ return true;
+ udelay(10);
+ }
+ return false;
+}
+
static int snd_fm801_update_bits(struct fm801 *chip, unsigned short reg,
unsigned short mask, unsigned short value)
{
@@ -244,73 +276,54 @@ static void snd_fm801_codec_write(struct snd_ac97 *ac97,
unsigned short val)
{
struct fm801 *chip = ac97->private_data;
- int idx;
/*
* Wait until the codec interface is not ready..
*/
- for (idx = 0; idx < 100; idx++) {
- if (!(inw(FM801_REG(chip, AC97_CMD)) & FM801_AC97_BUSY))
- goto ok1;
- udelay(10);
+ if (!fm801_ac97_is_ready(chip, 100)) {
+ dev_err(chip->card->dev, "AC'97 interface is busy (1)\n");
+ return;
}
- dev_err(chip->card->dev, "AC'97 interface is busy (1)\n");
- return;
- ok1:
/* write data and address */
- outw(val, FM801_REG(chip, AC97_DATA));
- outw(reg | (ac97->addr << FM801_AC97_ADDR_SHIFT), FM801_REG(chip, AC97_CMD));
+ fm801_writew(chip, AC97_DATA, val);
+ fm801_writew(chip, AC97_CMD, reg | (ac97->addr << FM801_AC97_ADDR_SHIFT));
/*
* Wait until the write command is not completed..
- */
- for (idx = 0; idx < 1000; idx++) {
- if (!(inw(FM801_REG(chip, AC97_CMD)) & FM801_AC97_BUSY))
- return;
- udelay(10);
- }
- dev_err(chip->card->dev, "AC'97 interface #%d is busy (2)\n", ac97->num);
+ */
+ if (!fm801_ac97_is_ready(chip, 1000))
+ dev_err(chip->card->dev, "AC'97 interface #%d is busy (2)\n",
+ ac97->num);
}
static unsigned short snd_fm801_codec_read(struct snd_ac97 *ac97, unsigned short reg)
{
struct fm801 *chip = ac97->private_data;
- int idx;
/*
* Wait until the codec interface is not ready..
*/
- for (idx = 0; idx < 100; idx++) {
- if (!(inw(FM801_REG(chip, AC97_CMD)) & FM801_AC97_BUSY))
- goto ok1;
- udelay(10);
+ if (!fm801_ac97_is_ready(chip, 100)) {
+ dev_err(chip->card->dev, "AC'97 interface is busy (1)\n");
+ return 0;
}
- dev_err(chip->card->dev, "AC'97 interface is busy (1)\n");
- return 0;
- ok1:
/* read command */
- outw(reg | (ac97->addr << FM801_AC97_ADDR_SHIFT) | FM801_AC97_READ,
- FM801_REG(chip, AC97_CMD));
- for (idx = 0; idx < 100; idx++) {
- if (!(inw(FM801_REG(chip, AC97_CMD)) & FM801_AC97_BUSY))
- goto ok2;
- udelay(10);
+ fm801_writew(chip, AC97_CMD,
+ reg | (ac97->addr << FM801_AC97_ADDR_SHIFT) | FM801_AC97_READ);
+ if (!fm801_ac97_is_ready(chip, 100)) {
+ dev_err(chip->card->dev, "AC'97 interface #%d is busy (2)\n",
+ ac97->num);
+ return 0;
}
- dev_err(chip->card->dev, "AC'97 interface #%d is busy (2)\n", ac97->num);
- return 0;
- ok2:
- for (idx = 0; idx < 1000; idx++) {
- if (inw(FM801_REG(chip, AC97_CMD)) & FM801_AC97_VALID)
- goto ok3;
- udelay(10);
+ if (!fm801_ac97_is_valid(chip, 1000)) {
+ dev_err(chip->card->dev,
+ "AC'97 interface #%d is not valid (2)\n", ac97->num);
+ return 0;
}
- dev_err(chip->card->dev, "AC'97 interface #%d is not valid (2)\n", ac97->num);
- return 0;
- ok3:
- return inw(FM801_REG(chip, AC97_DATA));
+ return fm801_readw(chip, AC97_DATA);
}
static unsigned int rates[] = {
@@ -384,7 +397,7 @@ static int snd_fm801_playback_trigger(struct snd_pcm_substream *substream,
snd_BUG();
return -EINVAL;
}
- outw(chip->ply_ctrl, FM801_REG(chip, PLY_CTRL));
+ fm801_writew(chip, PLY_CTRL, chip->ply_ctrl);
spin_unlock(&chip->reg_lock);
return 0;
}
@@ -419,7 +432,7 @@ static int snd_fm801_capture_trigger(struct snd_pcm_substream *substream,
snd_BUG();
return -EINVAL;
}
- outw(chip->cap_ctrl, FM801_REG(chip, CAP_CTRL));
+ fm801_writew(chip, CAP_CTRL, chip->cap_ctrl);
spin_unlock(&chip->reg_lock);
return 0;
}
@@ -457,12 +470,13 @@ static int snd_fm801_playback_prepare(struct snd_pcm_substream *substream)
}
chip->ply_ctrl |= snd_fm801_rate_bits(runtime->rate) << FM801_RATE_SHIFT;
chip->ply_buf = 0;
- outw(chip->ply_ctrl, FM801_REG(chip, PLY_CTRL));
- outw(chip->ply_count - 1, FM801_REG(chip, PLY_COUNT));
+ fm801_writew(chip, PLY_CTRL, chip->ply_ctrl);
+ fm801_writew(chip, PLY_COUNT, chip->ply_count - 1);
chip->ply_buffer = runtime->dma_addr;
chip->ply_pos = 0;
- outl(chip->ply_buffer, FM801_REG(chip, PLY_BUF1));
- outl(chip->ply_buffer + (chip->ply_count % chip->ply_size), FM801_REG(chip, PLY_BUF2));
+ fm801_writel(chip, PLY_BUF1, chip->ply_buffer);
+ fm801_writel(chip, PLY_BUF2,
+ chip->ply_buffer + (chip->ply_count % chip->ply_size));
spin_unlock_irq(&chip->reg_lock);
return 0;
}
@@ -483,12 +497,13 @@ static int snd_fm801_capture_prepare(struct snd_pcm_substream *substream)
chip->cap_ctrl |= FM801_STEREO;
chip->cap_ctrl |= snd_fm801_rate_bits(runtime->rate) << FM801_RATE_SHIFT;
chip->cap_buf = 0;
- outw(chip->cap_ctrl, FM801_REG(chip, CAP_CTRL));
- outw(chip->cap_count - 1, FM801_REG(chip, CAP_COUNT));
+ fm801_writew(chip, CAP_CTRL, chip->cap_ctrl);
+ fm801_writew(chip, CAP_COUNT, chip->cap_count - 1);
chip->cap_buffer = runtime->dma_addr;
chip->cap_pos = 0;
- outl(chip->cap_buffer, FM801_REG(chip, CAP_BUF1));
- outl(chip->cap_buffer + (chip->cap_count % chip->cap_size), FM801_REG(chip, CAP_BUF2));
+ fm801_writel(chip, CAP_BUF1, chip->cap_buffer);
+ fm801_writel(chip, CAP_BUF2,
+ chip->cap_buffer + (chip->cap_count % chip->cap_size));
spin_unlock_irq(&chip->reg_lock);
return 0;
}
@@ -501,8 +516,8 @@ static snd_pcm_uframes_t snd_fm801_playback_pointer(struct snd_pcm_substream *su
if (!(chip->ply_ctrl & FM801_START))
return 0;
spin_lock(&chip->reg_lock);
- ptr = chip->ply_pos + (chip->ply_count - 1) - inw(FM801_REG(chip, PLY_COUNT));
- if (inw(FM801_REG(chip, IRQ_STATUS)) & FM801_IRQ_PLAYBACK) {
+ ptr = chip->ply_pos + (chip->ply_count - 1) - fm801_readw(chip, PLY_COUNT);
+ if (fm801_readw(chip, IRQ_STATUS) & FM801_IRQ_PLAYBACK) {
ptr += chip->ply_count;
ptr %= chip->ply_size;
}
@@ -518,8 +533,8 @@ static snd_pcm_uframes_t snd_fm801_capture_pointer(struct snd_pcm_substream *sub
if (!(chip->cap_ctrl & FM801_START))
return 0;
spin_lock(&chip->reg_lock);
- ptr = chip->cap_pos + (chip->cap_count - 1) - inw(FM801_REG(chip, CAP_COUNT));
- if (inw(FM801_REG(chip, IRQ_STATUS)) & FM801_IRQ_CAPTURE) {
+ ptr = chip->cap_pos + (chip->cap_count - 1) - fm801_readw(chip, CAP_COUNT);
+ if (fm801_readw(chip, IRQ_STATUS) & FM801_IRQ_CAPTURE) {
ptr += chip->cap_count;
ptr %= chip->cap_size;
}
@@ -533,12 +548,12 @@ static irqreturn_t snd_fm801_interrupt(int irq, void *dev_id)
unsigned short status;
unsigned int tmp;
- status = inw(FM801_REG(chip, IRQ_STATUS));
+ status = fm801_readw(chip, IRQ_STATUS);
status &= FM801_IRQ_PLAYBACK|FM801_IRQ_CAPTURE|FM801_IRQ_MPU|FM801_IRQ_VOLUME;
if (! status)
return IRQ_NONE;
/* ack first */
- outw(status, FM801_REG(chip, IRQ_STATUS));
+ fm801_writew(chip, IRQ_STATUS, status);
if (chip->pcm && (status & FM801_IRQ_PLAYBACK) && chip->playback_substream) {
spin_lock(&chip->reg_lock);
chip->ply_buf++;
@@ -546,10 +561,10 @@ static irqreturn_t snd_fm801_interrupt(int irq, void *dev_id)
chip->ply_pos %= chip->ply_size;
tmp = chip->ply_pos + chip->ply_count;
tmp %= chip->ply_size;
- outl(chip->ply_buffer + tmp,
- (chip->ply_buf & 1) ?
- FM801_REG(chip, PLY_BUF1) :
- FM801_REG(chip, PLY_BUF2));
+ if (chip->ply_buf & 1)
+ fm801_writel(chip, PLY_BUF1, chip->ply_buffer + tmp);
+ else
+ fm801_writel(chip, PLY_BUF2, chip->ply_buffer + tmp);
spin_unlock(&chip->reg_lock);
snd_pcm_period_elapsed(chip->playback_substream);
}
@@ -560,10 +575,10 @@ static irqreturn_t snd_fm801_interrupt(int irq, void *dev_id)
chip->cap_pos %= chip->cap_size;
tmp = chip->cap_pos + chip->cap_count;
tmp %= chip->cap_size;
- outl(chip->cap_buffer + tmp,
- (chip->cap_buf & 1) ?
- FM801_REG(chip, CAP_BUF1) :
- FM801_REG(chip, CAP_BUF2));
+ if (chip->cap_buf & 1)
+ fm801_writel(chip, CAP_BUF1, chip->cap_buffer + tmp);
+ else
+ fm801_writel(chip, CAP_BUF2, chip->cap_buffer + tmp);
spin_unlock(&chip->reg_lock);
snd_pcm_period_elapsed(chip->capture_substream);
}
@@ -747,7 +762,7 @@ static struct snd_fm801_tea575x_gpio snd_fm801_tea575x_gpios[] = {
static void snd_fm801_tea575x_set_pins(struct snd_tea575x *tea, u8 pins)
{
struct fm801 *chip = tea->private_data;
- unsigned short reg = inw(FM801_REG(chip, GPIO_CTRL));
+ unsigned short reg = fm801_readw(chip, GPIO_CTRL);
struct snd_fm801_tea575x_gpio gpio = *get_tea575x_gpio(chip);
reg &= ~(FM801_GPIO_GP(gpio.data) |
@@ -759,13 +774,13 @@ static void snd_fm801_tea575x_set_pins(struct snd_tea575x *tea, u8 pins)
/* WRITE_ENABLE is inverted */
reg |= (pins & TEA575X_WREN) ? 0 : FM801_GPIO_GP(gpio.wren);
- outw(reg, FM801_REG(chip, GPIO_CTRL));
+ fm801_writew(chip, GPIO_CTRL, reg);
}
static u8 snd_fm801_tea575x_get_pins(struct snd_tea575x *tea)
{
struct fm801 *chip = tea->private_data;
- unsigned short reg = inw(FM801_REG(chip, GPIO_CTRL));
+ unsigned short reg = fm801_readw(chip, GPIO_CTRL);
struct snd_fm801_tea575x_gpio gpio = *get_tea575x_gpio(chip);
u8 ret;
@@ -780,7 +795,7 @@ static u8 snd_fm801_tea575x_get_pins(struct snd_tea575x *tea)
static void snd_fm801_tea575x_set_direction(struct snd_tea575x *tea, bool output)
{
struct fm801 *chip = tea->private_data;
- unsigned short reg = inw(FM801_REG(chip, GPIO_CTRL));
+ unsigned short reg = fm801_readw(chip, GPIO_CTRL);
struct snd_fm801_tea575x_gpio gpio = *get_tea575x_gpio(chip);
/* use GPIO lines and set write enable bit */
@@ -811,7 +826,7 @@ static void snd_fm801_tea575x_set_direction(struct snd_tea575x *tea, bool output
FM801_GPIO_GP(gpio.clk));
}
- outw(reg, FM801_REG(chip, GPIO_CTRL));
+ fm801_writew(chip, GPIO_CTRL, reg);
}
static struct snd_tea575x_ops snd_fm801_tea_ops = {
@@ -962,7 +977,7 @@ static int snd_fm801_get_mux(struct snd_kcontrol *kcontrol,
struct fm801 *chip = snd_kcontrol_chip(kcontrol);
unsigned short val;
- val = inw(FM801_REG(chip, REC_SRC)) & 7;
+ val = fm801_readw(chip, REC_SRC) & 7;
if (val > 4)
val = 4;
ucontrol->value.enumerated.item[0] = val;
@@ -1073,12 +1088,12 @@ static int wait_for_codec(struct fm801 *chip, unsigned int codec_id,
{
unsigned long timeout = jiffies + waits;
- outw(FM801_AC97_READ | (codec_id << FM801_AC97_ADDR_SHIFT) | reg,
- FM801_REG(chip, AC97_CMD));
+ fm801_writew(chip, AC97_CMD,
+ reg | (codec_id << FM801_AC97_ADDR_SHIFT) | FM801_AC97_READ);
udelay(5);
do {
- if ((inw(FM801_REG(chip, AC97_CMD)) & (FM801_AC97_VALID|FM801_AC97_BUSY))
- == FM801_AC97_VALID)
+ if ((fm801_readw(chip, AC97_CMD) &
+ (FM801_AC97_VALID | FM801_AC97_BUSY)) == FM801_AC97_VALID)
return 0;
schedule_timeout_uninterruptible(1);
} while (time_after(timeout, jiffies));
@@ -1093,10 +1108,10 @@ static int snd_fm801_chip_init(struct fm801 *chip, int resume)
goto __ac97_ok;
/* codec cold reset + AC'97 warm reset */
- outw((1<<5) | (1<<6), FM801_REG(chip, CODEC_CTRL));
- inw(FM801_REG(chip, CODEC_CTRL)); /* flush posting data */
+ fm801_writew(chip, CODEC_CTRL, (1 << 5) | (1 << 6));
+ fm801_readw(chip, CODEC_CTRL); /* flush posting data */
udelay(100);
- outw(0, FM801_REG(chip, CODEC_CTRL));
+ fm801_writew(chip, CODEC_CTRL, 0);
if (wait_for_codec(chip, 0, AC97_RESET, msecs_to_jiffies(750)) < 0)
if (!resume) {
@@ -1117,7 +1132,7 @@ static int snd_fm801_chip_init(struct fm801 *chip, int resume)
for (i = 3; i > 0; i--) {
if (!wait_for_codec(chip, i, AC97_VENDOR_ID1,
msecs_to_jiffies(50))) {
- cmdw = inw(FM801_REG(chip, AC97_DATA));
+ cmdw = fm801_readw(chip, AC97_DATA);
if (cmdw != 0xffff && cmdw != 0) {
chip->secondary = 1;
chip->secondary_addr = i;
@@ -1135,23 +1150,24 @@ static int snd_fm801_chip_init(struct fm801 *chip, int resume)
__ac97_ok:
/* init volume */
- outw(0x0808, FM801_REG(chip, PCM_VOL));
- outw(0x9f1f, FM801_REG(chip, FM_VOL));
- outw(0x8808, FM801_REG(chip, I2S_VOL));
+ fm801_writew(chip, PCM_VOL, 0x0808);
+ fm801_writew(chip, FM_VOL, 0x9f1f);
+ fm801_writew(chip, I2S_VOL, 0x8808);
/* I2S control - I2S mode */
- outw(0x0003, FM801_REG(chip, I2S_MODE));
+ fm801_writew(chip, I2S_MODE, 0x0003);
/* interrupt setup */
- cmdw = inw(FM801_REG(chip, IRQ_MASK));
+ cmdw = fm801_readw(chip, IRQ_MASK);
if (chip->irq < 0)
cmdw |= 0x00c3; /* mask everything, no PCM nor MPU */
else
cmdw &= ~0x0083; /* unmask MPU, PLAYBACK & CAPTURE */
- outw(cmdw, FM801_REG(chip, IRQ_MASK));
+ fm801_writew(chip, IRQ_MASK, cmdw);
/* interrupt clear */
- outw(FM801_IRQ_PLAYBACK|FM801_IRQ_CAPTURE|FM801_IRQ_MPU, FM801_REG(chip, IRQ_STATUS));
+ fm801_writew(chip, IRQ_STATUS,
+ FM801_IRQ_PLAYBACK | FM801_IRQ_CAPTURE | FM801_IRQ_MPU);
return 0;
}
@@ -1165,9 +1181,9 @@ static int snd_fm801_free(struct fm801 *chip)
goto __end_hw;
/* interrupt setup - mask everything */
- cmdw = inw(FM801_REG(chip, IRQ_MASK));
+ cmdw = fm801_readw(chip, IRQ_MASK);
cmdw |= 0x00c3;
- outw(cmdw, FM801_REG(chip, IRQ_MASK));
+ fm801_writew(chip, IRQ_MASK, cmdw);
__end_hw:
#ifdef CONFIG_SND_FM801_TEA575X_BOOL
@@ -1339,15 +1355,15 @@ static int snd_card_fm801_probe(struct pci_dev *pci,
return err;
}
if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_FM801,
- FM801_REG(chip, MPU401_DATA),
+ chip->port + FM801_MPU401_DATA,
MPU401_INFO_INTEGRATED |
MPU401_INFO_IRQ_HOOK,
-1, &chip->rmidi)) < 0) {
snd_card_free(card);
return err;
}
- if ((err = snd_opl3_create(card, FM801_REG(chip, OPL3_BANK0),
- FM801_REG(chip, OPL3_BANK1),
+ if ((err = snd_opl3_create(card, chip->port + FM801_OPL3_BANK0,
+ chip->port + FM801_OPL3_BANK1,
OPL3_HW_OPL3_FM801, 1, &opl3)) < 0) {
snd_card_free(card);
return err;
diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig
index ac17c3fc9388..ebf4c2fb99df 100644
--- a/sound/pci/hda/Kconfig
+++ b/sound/pci/hda/Kconfig
@@ -20,6 +20,21 @@ config SND_HDA_INTEL
To compile this driver as a module, choose M here: the module
will be called snd-hda-intel.
+config SND_HDA_TEGRA
+ tristate "NVIDIA Tegra HD Audio"
+ depends on ARCH_TEGRA
+ select SND_HDA
+ help
+ Say Y here to support the HDA controller present in NVIDIA
+ Tegra SoCs
+
+ This options enables support for the HD Audio controller
+ present in some NVIDIA Tegra SoCs, used to communicate audio
+ to the HDMI output.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-hda-tegra.
+
if SND_HDA
config SND_HDA_DSP_LOADER
diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile
index d0d0c19ddfc2..194f30935e77 100644
--- a/sound/pci/hda/Makefile
+++ b/sound/pci/hda/Makefile
@@ -1,5 +1,6 @@
snd-hda-intel-objs := hda_intel.o
snd-hda-controller-objs := hda_controller.o
+snd-hda-tegra-objs := hda_tegra.o
# for haswell power well
snd-hda-intel-$(CONFIG_SND_HDA_I915) += hda_i915.o
@@ -47,3 +48,4 @@ obj-$(CONFIG_SND_HDA_CODEC_HDMI) += snd-hda-codec-hdmi.o
# otherwise the codec patches won't be hooked before the PCI probe
# when built in kernel
obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-intel.o
+obj-$(CONFIG_SND_HDA_TEGRA) += snd-hda-tegra.o
diff --git a/sound/pci/hda/hda_auto_parser.c b/sound/pci/hda/hda_auto_parser.c
index 90d2fda6c8f9..b684c6e4f301 100644
--- a/sound/pci/hda/hda_auto_parser.c
+++ b/sound/pci/hda/hda_auto_parser.c
@@ -839,6 +839,43 @@ void snd_hda_apply_fixup(struct hda_codec *codec, int action)
}
EXPORT_SYMBOL_GPL(snd_hda_apply_fixup);
+static bool pin_config_match(struct hda_codec *codec,
+ const struct hda_pintbl *pins)
+{
+ for (; pins->nid; pins++) {
+ u32 def_conf = snd_hda_codec_get_pincfg(codec, pins->nid);
+ if (pins->val != def_conf)
+ return false;
+ }
+ return true;
+}
+
+void snd_hda_pick_pin_fixup(struct hda_codec *codec,
+ const struct snd_hda_pin_quirk *pin_quirk,
+ const struct hda_fixup *fixlist)
+{
+ const struct snd_hda_pin_quirk *pq;
+
+ if (codec->fixup_forced)
+ return;
+
+ for (pq = pin_quirk; pq->subvendor; pq++) {
+ if ((codec->subsystem_id & 0xffff0000) != (pq->subvendor << 16))
+ continue;
+ if (codec->vendor_id != pq->codec)
+ continue;
+ if (pin_config_match(codec, pq->pins)) {
+ codec->fixup_id = pq->value;
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ codec->fixup_name = pq->name;
+#endif
+ codec->fixup_list = fixlist;
+ return;
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hda_pick_pin_fixup);
+
void snd_hda_pick_fixup(struct hda_codec *codec,
const struct hda_model_fixup *models,
const struct snd_pci_quirk *quirk,
@@ -852,15 +889,17 @@ void snd_hda_pick_fixup(struct hda_codec *codec,
if (codec->modelname && !strcmp(codec->modelname, "nofixup")) {
codec->fixup_list = NULL;
codec->fixup_id = -1;
+ codec->fixup_forced = 1;
return;
}
if (codec->modelname && models) {
while (models->name) {
if (!strcmp(codec->modelname, models->name)) {
- id = models->id;
- name = models->name;
- break;
+ codec->fixup_id = models->id;
+ codec->fixup_name = models->name;
+ codec->fixup_forced = 1;
+ return;
}
models++;
}
@@ -889,6 +928,7 @@ void snd_hda_pick_fixup(struct hda_codec *codec,
}
}
+ codec->fixup_forced = 0;
codec->fixup_id = id;
if (id >= 0) {
codec->fixup_list = fixlist;
diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h
index a4233136cb93..5825aa17d8e3 100644
--- a/sound/pci/hda/hda_codec.h
+++ b/sound/pci/hda/hda_codec.h
@@ -402,6 +402,7 @@ struct hda_codec {
/* fix-up list */
int fixup_id;
+ unsigned int fixup_forced:1; /* fixup explicitly set by user */
const struct hda_fixup *fixup_list;
const char *fixup_name;
diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c
index 16133881e967..589e47c5aeb3 100644
--- a/sound/pci/hda/hda_generic.c
+++ b/sound/pci/hda/hda_generic.c
@@ -3722,7 +3722,7 @@ static void parse_digital(struct hda_codec *codec)
} else {
spec->multiout.slave_dig_outs = spec->slave_dig_outs;
if (nums >= ARRAY_SIZE(spec->slave_dig_outs) - 1)
- break;
+ break;
spec->slave_dig_outs[nums - 1] = dig_nid;
}
nums++;
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
index b540ad71eb0d..bb65a124e006 100644
--- a/sound/pci/hda/hda_intel.c
+++ b/sound/pci/hda/hda_intel.c
@@ -237,6 +237,12 @@ enum {
AZX_DCAPS_COUNT_LPIB_DELAY | AZX_DCAPS_PM_RUNTIME | \
AZX_DCAPS_I915_POWERWELL)
+/* Broadwell HDMI can't use position buffer reliably, force to use LPIB */
+#define AZX_DCAPS_INTEL_BROADWELL \
+ (AZX_DCAPS_SCH_SNOOP | AZX_DCAPS_ALIGN_BUFSIZE | \
+ AZX_DCAPS_POSFIX_LPIB | AZX_DCAPS_PM_RUNTIME | \
+ AZX_DCAPS_I915_POWERWELL)
+
/* quirks for ATI SB / AMD Hudson */
#define AZX_DCAPS_PRESET_ATI_SB \
(AZX_DCAPS_ATI_SNOOP | AZX_DCAPS_NO_TCSEL | \
@@ -1724,7 +1730,7 @@ static void azx_remove(struct pci_dev *pci)
}
/* PCI IDs */
-static DEFINE_PCI_DEVICE_TABLE(azx_ids) = {
+static const struct pci_device_id azx_ids[] = {
/* CPT */
{ PCI_DEVICE(0x8086, 0x1c20),
.driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH_NOPM },
@@ -1737,6 +1743,9 @@ static DEFINE_PCI_DEVICE_TABLE(azx_ids) = {
/* Lynx Point */
{ PCI_DEVICE(0x8086, 0x8c20),
.driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH },
+ /* 9 Series */
+ { PCI_DEVICE(0x8086, 0x8ca0),
+ .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH },
/* Wellsburg */
{ PCI_DEVICE(0x8086, 0x8d20),
.driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH },
@@ -1760,7 +1769,7 @@ static DEFINE_PCI_DEVICE_TABLE(azx_ids) = {
.driver_data = AZX_DRIVER_HDMI | AZX_DCAPS_INTEL_HASWELL },
/* Broadwell */
{ PCI_DEVICE(0x8086, 0x160c),
- .driver_data = AZX_DRIVER_HDMI | AZX_DCAPS_INTEL_HASWELL },
+ .driver_data = AZX_DRIVER_HDMI | AZX_DCAPS_INTEL_BROADWELL },
/* 5 Series/3400 */
{ PCI_DEVICE(0x8086, 0x3b56),
.driver_data = AZX_DRIVER_SCH | AZX_DCAPS_INTEL_PCH_NOPM },
diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h
index e51d15529215..ebd1fa6f015c 100644
--- a/sound/pci/hda/hda_local.h
+++ b/sound/pci/hda/hda_local.h
@@ -407,6 +407,16 @@ struct hda_fixup {
} v;
};
+struct snd_hda_pin_quirk {
+ unsigned int codec; /* Codec vendor/device ID */
+ unsigned short subvendor; /* PCI subvendor ID */
+ const struct hda_pintbl *pins; /* list of matching pins */
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ const char *name;
+#endif
+ int value; /* quirk value */
+};
+
/* fixup types */
enum {
HDA_FIXUP_INVALID,
@@ -434,6 +444,10 @@ void snd_hda_pick_fixup(struct hda_codec *codec,
const struct hda_model_fixup *models,
const struct snd_pci_quirk *quirk,
const struct hda_fixup *fixlist);
+void snd_hda_pick_pin_fixup(struct hda_codec *codec,
+ const struct snd_hda_pin_quirk *pin_quirk,
+ const struct hda_fixup *fixlist);
+
/*
* unsolicited event handler
diff --git a/sound/pci/hda/hda_tegra.c b/sound/pci/hda/hda_tegra.c
new file mode 100644
index 000000000000..a366ba9293a8
--- /dev/null
+++ b/sound/pci/hda/hda_tegra.c
@@ -0,0 +1,588 @@
+/*
+ *
+ * Implementation of primary ALSA driver code base for NVIDIA Tegra HDA.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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/>.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/clocksource.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/of_device.h>
+#include <linux/reboot.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+
+#include "hda_codec.h"
+#include "hda_controller.h"
+#include "hda_priv.h"
+
+/* Defines for Nvidia Tegra HDA support */
+#define HDA_BAR0 0x8000
+
+#define HDA_CFG_CMD 0x1004
+#define HDA_CFG_BAR0 0x1010
+
+#define HDA_ENABLE_IO_SPACE (1 << 0)
+#define HDA_ENABLE_MEM_SPACE (1 << 1)
+#define HDA_ENABLE_BUS_MASTER (1 << 2)
+#define HDA_ENABLE_SERR (1 << 8)
+#define HDA_DISABLE_INTR (1 << 10)
+#define HDA_BAR0_INIT_PROGRAM 0xFFFFFFFF
+#define HDA_BAR0_FINAL_PROGRAM (1 << 14)
+
+/* IPFS */
+#define HDA_IPFS_CONFIG 0x180
+#define HDA_IPFS_EN_FPCI 0x1
+
+#define HDA_IPFS_FPCI_BAR0 0x80
+#define HDA_FPCI_BAR0_START 0x40
+
+#define HDA_IPFS_INTR_MASK 0x188
+#define HDA_IPFS_EN_INTR (1 << 16)
+
+/* max number of SDs */
+#define NUM_CAPTURE_SD 1
+#define NUM_PLAYBACK_SD 1
+
+struct hda_tegra {
+ struct azx chip;
+ struct device *dev;
+ struct clk *hda_clk;
+ struct clk *hda2codec_2x_clk;
+ struct clk *hda2hdmi_clk;
+ void __iomem *regs;
+};
+
+#ifdef CONFIG_PM
+static int power_save = CONFIG_SND_HDA_POWER_SAVE_DEFAULT;
+module_param(power_save, bint, 0644);
+MODULE_PARM_DESC(power_save,
+ "Automatic power-saving timeout (in seconds, 0 = disable).");
+#else
+static int power_save = 0;
+#endif
+
+/*
+ * DMA page allocation ops.
+ */
+static int dma_alloc_pages(struct azx *chip, int type, size_t size,
+ struct snd_dma_buffer *buf)
+{
+ return snd_dma_alloc_pages(type, chip->card->dev, size, buf);
+}
+
+static void dma_free_pages(struct azx *chip, struct snd_dma_buffer *buf)
+{
+ snd_dma_free_pages(buf);
+}
+
+static int substream_alloc_pages(struct azx *chip,
+ struct snd_pcm_substream *substream,
+ size_t size)
+{
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+
+ azx_dev->bufsize = 0;
+ azx_dev->period_bytes = 0;
+ azx_dev->format_val = 0;
+ return snd_pcm_lib_malloc_pages(substream, size);
+}
+
+static int substream_free_pages(struct azx *chip,
+ struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_pages(substream);
+}
+
+/*
+ * Register access ops. Tegra HDA register access is DWORD only.
+ */
+static void hda_tegra_writel(u32 value, u32 *addr)
+{
+ writel(value, addr);
+}
+
+static u32 hda_tegra_readl(u32 *addr)
+{
+ return readl(addr);
+}
+
+static void hda_tegra_writew(u16 value, u16 *addr)
+{
+ unsigned int shift = ((unsigned long)(addr) & 0x3) << 3;
+ void *dword_addr = (void *)((unsigned long)(addr) & ~0x3);
+ u32 v;
+
+ v = readl(dword_addr);
+ v &= ~(0xffff << shift);
+ v |= value << shift;
+ writel(v, dword_addr);
+}
+
+static u16 hda_tegra_readw(u16 *addr)
+{
+ unsigned int shift = ((unsigned long)(addr) & 0x3) << 3;
+ void *dword_addr = (void *)((unsigned long)(addr) & ~0x3);
+ u32 v;
+
+ v = readl(dword_addr);
+ return (v >> shift) & 0xffff;
+}
+
+static void hda_tegra_writeb(u8 value, u8 *addr)
+{
+ unsigned int shift = ((unsigned long)(addr) & 0x3) << 3;
+ void *dword_addr = (void *)((unsigned long)(addr) & ~0x3);
+ u32 v;
+
+ v = readl(dword_addr);
+ v &= ~(0xff << shift);
+ v |= value << shift;
+ writel(v, dword_addr);
+}
+
+static u8 hda_tegra_readb(u8 *addr)
+{
+ unsigned int shift = ((unsigned long)(addr) & 0x3) << 3;
+ void *dword_addr = (void *)((unsigned long)(addr) & ~0x3);
+ u32 v;
+
+ v = readl(dword_addr);
+ return (v >> shift) & 0xff;
+}
+
+static const struct hda_controller_ops hda_tegra_ops = {
+ .reg_writel = hda_tegra_writel,
+ .reg_readl = hda_tegra_readl,
+ .reg_writew = hda_tegra_writew,
+ .reg_readw = hda_tegra_readw,
+ .reg_writeb = hda_tegra_writeb,
+ .reg_readb = hda_tegra_readb,
+ .dma_alloc_pages = dma_alloc_pages,
+ .dma_free_pages = dma_free_pages,
+ .substream_alloc_pages = substream_alloc_pages,
+ .substream_free_pages = substream_free_pages,
+};
+
+static void hda_tegra_init(struct hda_tegra *hda)
+{
+ u32 v;
+
+ /* Enable PCI access */
+ v = readl(hda->regs + HDA_IPFS_CONFIG);
+ v |= HDA_IPFS_EN_FPCI;
+ writel(v, hda->regs + HDA_IPFS_CONFIG);
+
+ /* Enable MEM/IO space and bus master */
+ v = readl(hda->regs + HDA_CFG_CMD);
+ v &= ~HDA_DISABLE_INTR;
+ v |= HDA_ENABLE_MEM_SPACE | HDA_ENABLE_IO_SPACE |
+ HDA_ENABLE_BUS_MASTER | HDA_ENABLE_SERR;
+ writel(v, hda->regs + HDA_CFG_CMD);
+
+ writel(HDA_BAR0_INIT_PROGRAM, hda->regs + HDA_CFG_BAR0);
+ writel(HDA_BAR0_FINAL_PROGRAM, hda->regs + HDA_CFG_BAR0);
+ writel(HDA_FPCI_BAR0_START, hda->regs + HDA_IPFS_FPCI_BAR0);
+
+ v = readl(hda->regs + HDA_IPFS_INTR_MASK);
+ v |= HDA_IPFS_EN_INTR;
+ writel(v, hda->regs + HDA_IPFS_INTR_MASK);
+}
+
+static int hda_tegra_enable_clocks(struct hda_tegra *data)
+{
+ int rc;
+
+ rc = clk_prepare_enable(data->hda_clk);
+ if (rc)
+ return rc;
+ rc = clk_prepare_enable(data->hda2codec_2x_clk);
+ if (rc)
+ goto disable_hda;
+ rc = clk_prepare_enable(data->hda2hdmi_clk);
+ if (rc)
+ goto disable_codec_2x;
+
+ return 0;
+
+disable_codec_2x:
+ clk_disable_unprepare(data->hda2codec_2x_clk);
+disable_hda:
+ clk_disable_unprepare(data->hda_clk);
+ return rc;
+}
+
+static void hda_tegra_disable_clocks(struct hda_tegra *data)
+{
+ clk_disable_unprepare(data->hda2hdmi_clk);
+ clk_disable_unprepare(data->hda2codec_2x_clk);
+ clk_disable_unprepare(data->hda_clk);
+}
+
+#ifdef CONFIG_PM_SLEEP
+/*
+ * power management
+ */
+static int hda_tegra_suspend(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct azx *chip = card->private_data;
+ struct azx_pcm *p;
+ struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip);
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ list_for_each_entry(p, &chip->pcm_list, list)
+ snd_pcm_suspend_all(p->pcm);
+ if (chip->initialized)
+ snd_hda_suspend(chip->bus);
+
+ azx_stop_chip(chip);
+ azx_enter_link_reset(chip);
+ hda_tegra_disable_clocks(hda);
+
+ return 0;
+}
+
+static int hda_tegra_resume(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct azx *chip = card->private_data;
+ struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip);
+ int status;
+
+ hda_tegra_enable_clocks(hda);
+
+ /* Read STATESTS before controller reset */
+ status = azx_readw(chip, STATESTS);
+
+ hda_tegra_init(hda);
+
+ azx_init_chip(chip, 1);
+
+ snd_hda_resume(chip->bus);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops hda_tegra_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(hda_tegra_suspend, hda_tegra_resume)
+};
+
+/*
+ * reboot notifier for hang-up problem at power-down
+ */
+static int hda_tegra_halt(struct notifier_block *nb, unsigned long event,
+ void *buf)
+{
+ struct azx *chip = container_of(nb, struct azx, reboot_notifier);
+ snd_hda_bus_reboot_notify(chip->bus);
+ azx_stop_chip(chip);
+ return NOTIFY_OK;
+}
+
+static void hda_tegra_notifier_register(struct azx *chip)
+{
+ chip->reboot_notifier.notifier_call = hda_tegra_halt;
+ register_reboot_notifier(&chip->reboot_notifier);
+}
+
+static void hda_tegra_notifier_unregister(struct azx *chip)
+{
+ if (chip->reboot_notifier.notifier_call)
+ unregister_reboot_notifier(&chip->reboot_notifier);
+}
+
+/*
+ * destructor
+ */
+static int hda_tegra_dev_free(struct snd_device *device)
+{
+ int i;
+ struct azx *chip = device->device_data;
+
+ hda_tegra_notifier_unregister(chip);
+
+ if (chip->initialized) {
+ for (i = 0; i < chip->num_streams; i++)
+ azx_stream_stop(chip, &chip->azx_dev[i]);
+ azx_stop_chip(chip);
+ }
+
+ azx_free_stream_pages(chip);
+
+ return 0;
+}
+
+static int hda_tegra_init_chip(struct azx *chip, struct platform_device *pdev)
+{
+ struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip);
+ struct device *dev = hda->dev;
+ struct resource *res;
+ int err;
+
+ hda->hda_clk = devm_clk_get(dev, "hda");
+ if (IS_ERR(hda->hda_clk))
+ return PTR_ERR(hda->hda_clk);
+ hda->hda2codec_2x_clk = devm_clk_get(dev, "hda2codec_2x");
+ if (IS_ERR(hda->hda2codec_2x_clk))
+ return PTR_ERR(hda->hda2codec_2x_clk);
+ hda->hda2hdmi_clk = devm_clk_get(dev, "hda2hdmi");
+ if (IS_ERR(hda->hda2hdmi_clk))
+ return PTR_ERR(hda->hda2hdmi_clk);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ hda->regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(chip->remap_addr))
+ return PTR_ERR(chip->remap_addr);
+
+ chip->remap_addr = hda->regs + HDA_BAR0;
+ chip->addr = res->start + HDA_BAR0;
+
+ err = hda_tegra_enable_clocks(hda);
+ if (err)
+ return err;
+
+ hda_tegra_init(hda);
+
+ return 0;
+}
+
+/*
+ * The codecs were powered up in snd_hda_codec_new().
+ * Now all initialization done, so turn them down if possible
+ */
+static void power_down_all_codecs(struct azx *chip)
+{
+ struct hda_codec *codec;
+ list_for_each_entry(codec, &chip->bus->codec_list, list)
+ snd_hda_power_down(codec);
+}
+
+static int hda_tegra_first_init(struct azx *chip, struct platform_device *pdev)
+{
+ struct snd_card *card = chip->card;
+ int err;
+ unsigned short gcap;
+ int irq_id = platform_get_irq(pdev, 0);
+
+ err = hda_tegra_init_chip(chip, pdev);
+ if (err)
+ return err;
+
+ err = devm_request_irq(chip->card->dev, irq_id, azx_interrupt,
+ IRQF_SHARED, KBUILD_MODNAME, chip);
+ if (err) {
+ dev_err(chip->card->dev,
+ "unable to request IRQ %d, disabling device\n",
+ irq_id);
+ return err;
+ }
+ chip->irq = irq_id;
+
+ synchronize_irq(chip->irq);
+
+ gcap = azx_readw(chip, GCAP);
+ dev_dbg(card->dev, "chipset global capabilities = 0x%x\n", gcap);
+
+ /* read number of streams from GCAP register instead of using
+ * hardcoded value
+ */
+ chip->capture_streams = (gcap >> 8) & 0x0f;
+ chip->playback_streams = (gcap >> 12) & 0x0f;
+ if (!chip->playback_streams && !chip->capture_streams) {
+ /* gcap didn't give any info, switching to old method */
+ chip->playback_streams = NUM_PLAYBACK_SD;
+ chip->capture_streams = NUM_CAPTURE_SD;
+ }
+ chip->capture_index_offset = 0;
+ chip->playback_index_offset = chip->capture_streams;
+ chip->num_streams = chip->playback_streams + chip->capture_streams;
+ chip->azx_dev = devm_kcalloc(card->dev, chip->num_streams,
+ sizeof(*chip->azx_dev), GFP_KERNEL);
+ if (!chip->azx_dev)
+ return -ENOMEM;
+
+ err = azx_alloc_stream_pages(chip);
+ if (err < 0)
+ return err;
+
+ /* initialize streams */
+ azx_init_stream(chip);
+
+ /* initialize chip */
+ azx_init_chip(chip, 1);
+
+ /* codec detection */
+ if (!chip->codec_mask) {
+ dev_err(card->dev, "no codecs found!\n");
+ return -ENODEV;
+ }
+
+ strcpy(card->driver, "tegra-hda");
+ strcpy(card->shortname, "tegra-hda");
+ snprintf(card->longname, sizeof(card->longname),
+ "%s at 0x%lx irq %i",
+ card->shortname, chip->addr, chip->irq);
+
+ return 0;
+}
+
+/*
+ * constructor
+ */
+static int hda_tegra_create(struct snd_card *card,
+ unsigned int driver_caps,
+ const struct hda_controller_ops *hda_ops,
+ struct hda_tegra *hda)
+{
+ static struct snd_device_ops ops = {
+ .dev_free = hda_tegra_dev_free,
+ };
+ struct azx *chip;
+ int err;
+
+ chip = &hda->chip;
+
+ spin_lock_init(&chip->reg_lock);
+ mutex_init(&chip->open_mutex);
+ chip->card = card;
+ chip->ops = hda_ops;
+ chip->irq = -1;
+ chip->driver_caps = driver_caps;
+ chip->driver_type = driver_caps & 0xff;
+ chip->dev_index = 0;
+ INIT_LIST_HEAD(&chip->pcm_list);
+ INIT_LIST_HEAD(&chip->list);
+
+ chip->position_fix[0] = POS_FIX_AUTO;
+ chip->position_fix[1] = POS_FIX_AUTO;
+ chip->codec_probe_mask = -1;
+
+ chip->single_cmd = false;
+ chip->snoop = true;
+
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+ if (err < 0) {
+ dev_err(card->dev, "Error creating device\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id hda_tegra_match[] = {
+ { .compatible = "nvidia,tegra30-hda" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, hda_tegra_match);
+
+static int hda_tegra_probe(struct platform_device *pdev)
+{
+ struct snd_card *card;
+ struct azx *chip;
+ struct hda_tegra *hda;
+ int err;
+ const unsigned int driver_flags = AZX_DCAPS_RIRB_DELAY;
+
+ hda = devm_kzalloc(&pdev->dev, sizeof(*hda), GFP_KERNEL);
+ if (!hda)
+ return -ENOMEM;
+ hda->dev = &pdev->dev;
+ chip = &hda->chip;
+
+ err = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+ THIS_MODULE, 0, &card);
+ if (err < 0) {
+ dev_err(&pdev->dev, "Error creating card!\n");
+ return err;
+ }
+
+ err = hda_tegra_create(card, driver_flags, &hda_tegra_ops, hda);
+ if (err < 0)
+ goto out_free;
+ card->private_data = chip;
+
+ dev_set_drvdata(&pdev->dev, card);
+
+ err = hda_tegra_first_init(chip, pdev);
+ if (err < 0)
+ goto out_free;
+
+ /* create codec instances */
+ err = azx_codec_create(chip, NULL, 0, &power_save);
+ if (err < 0)
+ goto out_free;
+
+ err = azx_codec_configure(chip);
+ if (err < 0)
+ goto out_free;
+
+ /* create PCM streams */
+ err = snd_hda_build_pcms(chip->bus);
+ if (err < 0)
+ goto out_free;
+
+ /* create mixer controls */
+ err = azx_mixer_create(chip);
+ if (err < 0)
+ goto out_free;
+
+ err = snd_card_register(chip->card);
+ if (err < 0)
+ goto out_free;
+
+ chip->running = 1;
+ power_down_all_codecs(chip);
+ hda_tegra_notifier_register(chip);
+
+ return 0;
+
+out_free:
+ snd_card_free(card);
+ return err;
+}
+
+static int hda_tegra_remove(struct platform_device *pdev)
+{
+ return snd_card_free(dev_get_drvdata(&pdev->dev));
+}
+
+static struct platform_driver tegra_platform_hda = {
+ .driver = {
+ .name = "tegra-hda",
+ .pm = &hda_tegra_pm,
+ .of_match_table = hda_tegra_match,
+ },
+ .probe = hda_tegra_probe,
+ .remove = hda_tegra_remove,
+};
+module_platform_driver(tegra_platform_hda);
+
+MODULE_DESCRIPTION("Tegra HDA bus driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/pci/hda/patch_analog.c b/sound/pci/hda/patch_analog.c
index 40ba06eb44af..06275f8807a8 100644
--- a/sound/pci/hda/patch_analog.c
+++ b/sound/pci/hda/patch_analog.c
@@ -332,6 +332,7 @@ static const struct hda_fixup ad1986a_fixups[] = {
static const struct snd_pci_quirk ad1986a_fixup_tbl[] = {
SND_PCI_QUIRK(0x103c, 0x30af, "HP B2800", AD1986A_FIXUP_LAPTOP_IMIC),
+ SND_PCI_QUIRK(0x1043, 0x1447, "ASUS A8JN", AD1986A_FIXUP_EAPD),
SND_PCI_QUIRK_MASK(0x1043, 0xff00, 0x8100, "ASUS P5", AD1986A_FIXUP_3STACK),
SND_PCI_QUIRK_MASK(0x1043, 0xff00, 0x8200, "ASUS M2", AD1986A_FIXUP_3STACK),
SND_PCI_QUIRK(0x10de, 0xcb84, "ASUS A8N-VM", AD1986A_FIXUP_3STACK),
diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c
index 1edbb9c47c2d..3e4417b0ddbe 100644
--- a/sound/pci/hda/patch_hdmi.c
+++ b/sound/pci/hda/patch_hdmi.c
@@ -1127,10 +1127,6 @@ static void hdmi_setup_audio_infoframe(struct hda_codec *codec,
AMP_OUT_UNMUTE);
eld = &per_pin->sink_eld;
- if (!eld->monitor_present) {
- hdmi_set_channel_count(codec, per_pin->cvt_nid, channels);
- return;
- }
if (!non_pcm && per_pin->chmap_set)
ca = hdmi_manual_channel_allocation(channels, per_pin->chmap);
@@ -1598,10 +1594,18 @@ static bool hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
* Re-setup pin and infoframe. This is needed e.g. when
* - sink is first plugged-in (infoframe is not set up if !monitor_present)
* - transcoder can change during stream playback on Haswell
+ * and this can make HW reset converter selection on a pin.
*/
- if (eld->eld_valid && !old_eld_valid && per_pin->setup)
+ if (eld->eld_valid && !old_eld_valid && per_pin->setup) {
+ if (is_haswell_plus(codec) || is_valleyview(codec)) {
+ intel_verify_pin_cvt_connect(codec, per_pin);
+ intel_not_share_assigned_cvt(codec, pin_nid,
+ per_pin->mux_idx);
+ }
+
hdmi_setup_audio_infoframe(codec, per_pin,
per_pin->non_pcm);
+ }
}
if (eld_changed)
@@ -3324,6 +3328,7 @@ static const struct hda_codec_preset snd_hda_preset_hdmi[] = {
{ .id = 0x10de001a, .name = "GPU 1a HDMI/DP", .patch = patch_nvhdmi },
{ .id = 0x10de001b, .name = "GPU 1b HDMI/DP", .patch = patch_nvhdmi },
{ .id = 0x10de001c, .name = "GPU 1c HDMI/DP", .patch = patch_nvhdmi },
+{ .id = 0x10de0028, .name = "Tegra12x HDMI", .patch = patch_nvhdmi },
{ .id = 0x10de0040, .name = "GPU 40 HDMI/DP", .patch = patch_nvhdmi },
{ .id = 0x10de0041, .name = "GPU 41 HDMI/DP", .patch = patch_nvhdmi },
{ .id = 0x10de0042, .name = "GPU 42 HDMI/DP", .patch = patch_nvhdmi },
@@ -3332,6 +3337,7 @@ static const struct hda_codec_preset snd_hda_preset_hdmi[] = {
{ .id = 0x10de0051, .name = "GPU 51 HDMI/DP", .patch = patch_nvhdmi },
{ .id = 0x10de0060, .name = "GPU 60 HDMI/DP", .patch = patch_nvhdmi },
{ .id = 0x10de0067, .name = "MCP67 HDMI", .patch = patch_nvhdmi_2ch },
+{ .id = 0x10de0071, .name = "GPU 71 HDMI/DP", .patch = patch_nvhdmi },
{ .id = 0x10de8001, .name = "MCP73 HDMI", .patch = patch_nvhdmi_2ch },
{ .id = 0x11069f80, .name = "VX900 HDMI/DP", .patch = patch_via_hdmi },
{ .id = 0x11069f81, .name = "VX900 HDMI/DP", .patch = patch_via_hdmi },
@@ -3379,6 +3385,7 @@ MODULE_ALIAS("snd-hda-codec-id:10de0019");
MODULE_ALIAS("snd-hda-codec-id:10de001a");
MODULE_ALIAS("snd-hda-codec-id:10de001b");
MODULE_ALIAS("snd-hda-codec-id:10de001c");
+MODULE_ALIAS("snd-hda-codec-id:10de0028");
MODULE_ALIAS("snd-hda-codec-id:10de0040");
MODULE_ALIAS("snd-hda-codec-id:10de0041");
MODULE_ALIAS("snd-hda-codec-id:10de0042");
@@ -3387,6 +3394,7 @@ MODULE_ALIAS("snd-hda-codec-id:10de0044");
MODULE_ALIAS("snd-hda-codec-id:10de0051");
MODULE_ALIAS("snd-hda-codec-id:10de0060");
MODULE_ALIAS("snd-hda-codec-id:10de0067");
+MODULE_ALIAS("snd-hda-codec-id:10de0071");
MODULE_ALIAS("snd-hda-codec-id:10de8001");
MODULE_ALIAS("snd-hda-codec-id:11069f80");
MODULE_ALIAS("snd-hda-codec-id:11069f81");
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
index 5f7c765391f1..af76995fa966 100644
--- a/sound/pci/hda/patch_realtek.c
+++ b/sound/pci/hda/patch_realtek.c
@@ -929,6 +929,7 @@ struct alc_codec_rename_pci_table {
};
static struct alc_codec_rename_table rename_tbl[] = {
+ { 0x10ec0221, 0xf00f, 0x1003, "ALC231" },
{ 0x10ec0269, 0xfff0, 0x3010, "ALC277" },
{ 0x10ec0269, 0xf0f0, 0x2010, "ALC259" },
{ 0x10ec0269, 0xf0f0, 0x3010, "ALC258" },
@@ -937,6 +938,7 @@ static struct alc_codec_rename_table rename_tbl[] = {
{ 0x10ec0269, 0xffff, 0x6023, "ALC281X" },
{ 0x10ec0269, 0x00f0, 0x0020, "ALC269VC" },
{ 0x10ec0269, 0x00f0, 0x0030, "ALC269VD" },
+ { 0x10ec0662, 0xffff, 0x4020, "ALC656" },
{ 0x10ec0887, 0x00f0, 0x0030, "ALC887-VD" },
{ 0x10ec0888, 0x00f0, 0x0030, "ALC888-VD" },
{ 0x10ec0888, 0xf0f0, 0x3020, "ALC886" },
@@ -951,9 +953,24 @@ static struct alc_codec_rename_pci_table rename_pci_tbl[] = {
{ 0x10ec0280, 0x1028, 0, "ALC3220" },
{ 0x10ec0282, 0x1028, 0, "ALC3221" },
{ 0x10ec0283, 0x1028, 0, "ALC3223" },
+ { 0x10ec0288, 0x1028, 0, "ALC3263" },
{ 0x10ec0292, 0x1028, 0, "ALC3226" },
+ { 0x10ec0293, 0x1028, 0, "ALC3235" },
{ 0x10ec0255, 0x1028, 0, "ALC3234" },
{ 0x10ec0668, 0x1028, 0, "ALC3661" },
+ { 0x10ec0275, 0x1028, 0, "ALC3260" },
+ { 0x10ec0899, 0x1028, 0, "ALC3861" },
+ { 0x10ec0670, 0x1025, 0, "ALC669X" },
+ { 0x10ec0676, 0x1025, 0, "ALC679X" },
+ { 0x10ec0282, 0x1043, 0, "ALC3229" },
+ { 0x10ec0233, 0x1043, 0, "ALC3236" },
+ { 0x10ec0280, 0x103c, 0, "ALC3228" },
+ { 0x10ec0282, 0x103c, 0, "ALC3227" },
+ { 0x10ec0286, 0x103c, 0, "ALC3242" },
+ { 0x10ec0290, 0x103c, 0, "ALC3241" },
+ { 0x10ec0668, 0x103c, 0, "ALC3662" },
+ { 0x10ec0283, 0x17aa, 0, "ALC3239" },
+ { 0x10ec0292, 0x17aa, 0, "ALC3232" },
{ } /* terminator */
};
@@ -1410,6 +1427,7 @@ static const struct snd_pci_quirk alc880_fixup_tbl[] = {
SND_PCI_QUIRK(0x1043, 0x10c3, "ASUS W5A", ALC880_FIXUP_ASUS_W5A),
SND_PCI_QUIRK(0x1043, 0x1964, "ASUS Z71V", ALC880_FIXUP_Z71V),
SND_PCI_QUIRK_VENDOR(0x1043, "ASUS", ALC880_FIXUP_GPIO1),
+ SND_PCI_QUIRK(0x147b, 0x1045, "ABit AA8XE", ALC880_FIXUP_6ST_AUTOMUTE),
SND_PCI_QUIRK(0x1558, 0x5401, "Clevo GPIO2", ALC880_FIXUP_GPIO2),
SND_PCI_QUIRK_VENDOR(0x1558, "Clevo", ALC880_FIXUP_EAPD_COEF),
SND_PCI_QUIRK(0x1584, 0x9050, "Uniwill", ALC880_FIXUP_UNIWILL_DIG),
@@ -1647,12 +1665,10 @@ static const struct hda_fixup alc260_fixups[] = {
[ALC260_FIXUP_COEF] = {
.type = HDA_FIXUP_VERBS,
.v.verbs = (const struct hda_verb[]) {
- { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 },
- { 0x20, AC_VERB_SET_PROC_COEF, 0x3040 },
+ { 0x1a, AC_VERB_SET_COEF_INDEX, 0x07 },
+ { 0x1a, AC_VERB_SET_PROC_COEF, 0x3040 },
{ }
},
- .chained = true,
- .chain_id = ALC260_FIXUP_HP_PIN_0F,
},
[ALC260_FIXUP_GPIO1] = {
.type = HDA_FIXUP_VERBS,
@@ -1667,8 +1683,8 @@ static const struct hda_fixup alc260_fixups[] = {
[ALC260_FIXUP_REPLACER] = {
.type = HDA_FIXUP_VERBS,
.v.verbs = (const struct hda_verb[]) {
- { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 },
- { 0x20, AC_VERB_SET_PROC_COEF, 0x3050 },
+ { 0x1a, AC_VERB_SET_COEF_INDEX, 0x07 },
+ { 0x1a, AC_VERB_SET_PROC_COEF, 0x3050 },
{ }
},
.chained = true,
@@ -3522,6 +3538,7 @@ static void alc_headset_mode_unplugged(struct hda_codec *codec)
/* Direct Drive HP Amp control */
alc_write_coefex_idx(codec, 0x57, 0x03, 0x8aa6);
break;
+ case 0x10ec0233:
case 0x10ec0283:
alc_write_coef_idx(codec, 0x1b, 0x0c0b);
alc_write_coef_idx(codec, 0x45, 0xc429);
@@ -3538,6 +3555,25 @@ static void alc_headset_mode_unplugged(struct hda_codec *codec)
alc_write_coef_idx(codec, 0x18, 0x7308);
alc_write_coef_idx(codec, 0x6b, 0xc429);
break;
+ case 0x10ec0293:
+ /* SET Line1 JD to 0 */
+ val = alc_read_coef_idx(codec, 0x10);
+ alc_write_coef_idx(codec, 0x10, (val & ~(7<<8)) | 6<<8);
+ /* SET charge pump by verb */
+ val = alc_read_coefex_idx(codec, 0x57, 0x05);
+ alc_write_coefex_idx(codec, 0x57, 0x05, (val & ~(1<<15|1<<13)) | 0x0);
+ /* SET EN_OSW to 1 */
+ val = alc_read_coefex_idx(codec, 0x57, 0x03);
+ alc_write_coefex_idx(codec, 0x57, 0x03, (val & ~(1<<10)) | (1<<10) );
+ /* Combo JD gating with LINE1-VREFO */
+ val = alc_read_coef_idx(codec, 0x1a);
+ alc_write_coef_idx(codec, 0x1a, (val & ~(1<<3)) | (1<<3));
+ /* Set to TRS type */
+ alc_write_coef_idx(codec, 0x45, 0xc429);
+ /* Combo Jack auto detect */
+ val = alc_read_coef_idx(codec, 0x4a);
+ alc_write_coef_idx(codec, 0x4a, (val & 0xfff0) | 0x000e);
+ break;
case 0x10ec0668:
alc_write_coef_idx(codec, 0x15, 0x0d40);
alc_write_coef_idx(codec, 0xb7, 0x802b);
@@ -3561,6 +3597,7 @@ static void alc_headset_mode_mic_in(struct hda_codec *codec, hda_nid_t hp_pin,
alc_write_coef_idx(codec, 0x06, 0x6100);
snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50);
break;
+ case 0x10ec0233:
case 0x10ec0283:
alc_write_coef_idx(codec, 0x45, 0xc429);
snd_hda_set_pin_ctl_cache(codec, hp_pin, 0);
@@ -3576,6 +3613,21 @@ static void alc_headset_mode_mic_in(struct hda_codec *codec, hda_nid_t hp_pin,
alc_write_coef_idx(codec, 0x19, 0xa208);
alc_write_coef_idx(codec, 0x2e, 0xacf0);
break;
+ case 0x10ec0293:
+ /* Set to TRS mode */
+ alc_write_coef_idx(codec, 0x45, 0xc429);
+ snd_hda_set_pin_ctl_cache(codec, hp_pin, 0);
+ /* SET charge pump by verb */
+ val = alc_read_coefex_idx(codec, 0x57, 0x05);
+ alc_write_coefex_idx(codec, 0x57, 0x05, (val & ~(1<<15|1<<13)) | (1<<15|1<<13));
+ /* SET EN_OSW to 0 */
+ val = alc_read_coefex_idx(codec, 0x57, 0x03);
+ alc_write_coefex_idx(codec, 0x57, 0x03, (val & ~(1<<10)) | 0x0);
+ /* Combo JD gating without LINE1-VREFO */
+ val = alc_read_coef_idx(codec, 0x1a);
+ alc_write_coef_idx(codec, 0x1a, (val & ~(1<<3)) | 0x0);
+ snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50);
+ break;
case 0x10ec0668:
alc_write_coef_idx(codec, 0x11, 0x0001);
snd_hda_set_pin_ctl_cache(codec, hp_pin, 0);
@@ -3591,6 +3643,8 @@ static void alc_headset_mode_mic_in(struct hda_codec *codec, hda_nid_t hp_pin,
static void alc_headset_mode_default(struct hda_codec *codec)
{
+ int val;
+
switch (codec->vendor_id) {
case 0x10ec0255:
alc_write_coef_idx(codec, 0x45, 0xc089);
@@ -3598,6 +3652,7 @@ static void alc_headset_mode_default(struct hda_codec *codec)
alc_write_coefex_idx(codec, 0x57, 0x03, 0x8ea6);
alc_write_coef_idx(codec, 0x49, 0x0049);
break;
+ case 0x10ec0233:
case 0x10ec0283:
alc_write_coef_idx(codec, 0x06, 0x2100);
alc_write_coef_idx(codec, 0x32, 0x4ea3);
@@ -3608,6 +3663,16 @@ static void alc_headset_mode_default(struct hda_codec *codec)
alc_write_coef_idx(codec, 0x6b, 0xc429);
alc_write_coef_idx(codec, 0x18, 0x7308);
break;
+ case 0x10ec0293:
+ /* Combo Jack auto detect */
+ val = alc_read_coef_idx(codec, 0x4a);
+ alc_write_coef_idx(codec, 0x4a, (val & 0xfff0) | 0x000e);
+ /* Set to TRS type */
+ alc_write_coef_idx(codec, 0x45, 0xC429);
+ /* Combo JD gating without LINE1-VREFO */
+ val = alc_read_coef_idx(codec, 0x1a);
+ alc_write_coef_idx(codec, 0x1a, (val & ~(1<<3)) | 0x0);
+ break;
case 0x10ec0668:
alc_write_coef_idx(codec, 0x11, 0x0041);
alc_write_coef_idx(codec, 0x15, 0x0d40);
@@ -3620,6 +3685,8 @@ static void alc_headset_mode_default(struct hda_codec *codec)
/* Iphone type */
static void alc_headset_mode_ctia(struct hda_codec *codec)
{
+ int val;
+
switch (codec->vendor_id) {
case 0x10ec0255:
/* Set to CTIA type */
@@ -3627,6 +3694,7 @@ static void alc_headset_mode_ctia(struct hda_codec *codec)
alc_write_coef_idx(codec, 0x1b, 0x0c2b);
alc_write_coefex_idx(codec, 0x57, 0x03, 0x8ea6);
break;
+ case 0x10ec0233:
case 0x10ec0283:
alc_write_coef_idx(codec, 0x45, 0xd429);
alc_write_coef_idx(codec, 0x1b, 0x0c2b);
@@ -3637,6 +3705,13 @@ static void alc_headset_mode_ctia(struct hda_codec *codec)
alc_write_coef_idx(codec, 0x76, 0x0008);
alc_write_coef_idx(codec, 0x18, 0x7388);
break;
+ case 0x10ec0293:
+ /* Set to ctia type */
+ alc_write_coef_idx(codec, 0x45, 0xd429);
+ /* SET Line1 JD to 1 */
+ val = alc_read_coef_idx(codec, 0x10);
+ alc_write_coef_idx(codec, 0x10, (val & ~(7<<8)) | 7<<8);
+ break;
case 0x10ec0668:
alc_write_coef_idx(codec, 0x11, 0x0001);
alc_write_coef_idx(codec, 0x15, 0x0d60);
@@ -3649,6 +3724,8 @@ static void alc_headset_mode_ctia(struct hda_codec *codec)
/* Nokia type */
static void alc_headset_mode_omtp(struct hda_codec *codec)
{
+ int val;
+
switch (codec->vendor_id) {
case 0x10ec0255:
/* Set to OMTP Type */
@@ -3656,6 +3733,7 @@ static void alc_headset_mode_omtp(struct hda_codec *codec)
alc_write_coef_idx(codec, 0x1b, 0x0c2b);
alc_write_coefex_idx(codec, 0x57, 0x03, 0x8ea6);
break;
+ case 0x10ec0233:
case 0x10ec0283:
alc_write_coef_idx(codec, 0x45, 0xe429);
alc_write_coef_idx(codec, 0x1b, 0x0c2b);
@@ -3666,6 +3744,13 @@ static void alc_headset_mode_omtp(struct hda_codec *codec)
alc_write_coef_idx(codec, 0x76, 0x0008);
alc_write_coef_idx(codec, 0x18, 0x7388);
break;
+ case 0x10ec0293:
+ /* Set to omtp type */
+ alc_write_coef_idx(codec, 0x45, 0xe429);
+ /* SET Line1 JD to 1 */
+ val = alc_read_coef_idx(codec, 0x10);
+ alc_write_coef_idx(codec, 0x10, (val & ~(7<<8)) | 7<<8);
+ break;
case 0x10ec0668:
alc_write_coef_idx(codec, 0x11, 0x0001);
alc_write_coef_idx(codec, 0x15, 0x0d50);
@@ -3691,6 +3776,7 @@ static void alc_determine_headset_type(struct hda_codec *codec)
val = alc_read_coef_idx(codec, 0x46);
is_ctia = (val & 0x0070) == 0x0070;
break;
+ case 0x10ec0233:
case 0x10ec0283:
alc_write_coef_idx(codec, 0x45, 0xd029);
msleep(300);
@@ -3703,6 +3789,16 @@ static void alc_determine_headset_type(struct hda_codec *codec)
val = alc_read_coef_idx(codec, 0x6c);
is_ctia = (val & 0x001c) == 0x001c;
break;
+ case 0x10ec0293:
+ /* Combo Jack auto detect */
+ val = alc_read_coef_idx(codec, 0x4a);
+ alc_write_coef_idx(codec, 0x4a, (val & 0xfff0) | 0x0008);
+ /* Set to ctia type */
+ alc_write_coef_idx(codec, 0x45, 0xD429);
+ msleep(300);
+ val = alc_read_coef_idx(codec, 0x46);
+ is_ctia = (val & 0x0070) == 0x0070;
+ break;
case 0x10ec0668:
alc_write_coef_idx(codec, 0x11, 0x0001);
alc_write_coef_idx(codec, 0xb7, 0x802b);
@@ -3894,6 +3990,39 @@ static void alc_fixup_no_shutup(struct hda_codec *codec,
}
}
+static void alc_fixup_disable_aamix(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ struct alc_spec *spec = codec->spec;
+ /* Disable AA-loopback as it causes white noise */
+ spec->gen.mixer_nid = 0;
+ }
+}
+
+static unsigned int alc_power_filter_xps13(struct hda_codec *codec,
+ hda_nid_t nid,
+ unsigned int power_state)
+{
+ struct alc_spec *spec = codec->spec;
+
+ /* Avoid pop noises when headphones are plugged in */
+ if (spec->gen.hp_jack_present)
+ if (nid == codec->afg || nid == 0x02)
+ return AC_PWRST_D0;
+ return power_state;
+}
+
+static void alc_fixup_dell_xps13(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PROBE) {
+ struct alc_spec *spec = codec->spec;
+ spec->shutup = alc_no_shutup;
+ codec->power_filter = alc_power_filter_xps13;
+ }
+}
+
static void alc_fixup_headset_mode_alc668(struct hda_codec *codec,
const struct hda_fixup *fix, int action)
{
@@ -4110,12 +4239,14 @@ enum {
ALC269_FIXUP_ASUS_G73JW,
ALC269_FIXUP_LENOVO_EAPD,
ALC275_FIXUP_SONY_HWEQ,
+ ALC275_FIXUP_SONY_DISABLE_AAMIX,
ALC271_FIXUP_DMIC,
ALC269_FIXUP_PCM_44K,
ALC269_FIXUP_STEREO_DMIC,
ALC269_FIXUP_HEADSET_MIC,
ALC269_FIXUP_QUANTA_MUTE,
ALC269_FIXUP_LIFEBOOK,
+ ALC269_FIXUP_LIFEBOOK_EXTMIC,
ALC269_FIXUP_AMIC,
ALC269_FIXUP_DMIC,
ALC269VB_FIXUP_AMIC,
@@ -4159,6 +4290,8 @@ enum {
ALC255_FIXUP_DELL2_MIC_NO_PRESENCE,
ALC255_FIXUP_HEADSET_MODE,
ALC255_FIXUP_HEADSET_MODE_NO_HP_MIC,
+ ALC293_FIXUP_DELL1_MIC_NO_PRESENCE,
+ ALC292_FIXUP_TPT440_DOCK,
};
static const struct hda_fixup alc269_fixups[] = {
@@ -4213,6 +4346,12 @@ static const struct hda_fixup alc269_fixups[] = {
.chained = true,
.chain_id = ALC275_FIXUP_SONY_VAIO_GPIO2
},
+ [ALC275_FIXUP_SONY_DISABLE_AAMIX] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_disable_aamix,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_SONY_VAIO
+ },
[ALC271_FIXUP_DMIC] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc271_fixup_dmic,
@@ -4245,6 +4384,13 @@ static const struct hda_fixup alc269_fixups[] = {
.chained = true,
.chain_id = ALC269_FIXUP_QUANTA_MUTE
},
+ [ALC269_FIXUP_LIFEBOOK_EXTMIC] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x01a1903c }, /* headset mic, with jack detect */
+ { }
+ },
+ },
[ALC269_FIXUP_AMIC] = {
.type = HDA_FIXUP_PINS,
.v.pins = (const struct hda_pintbl[]) {
@@ -4552,6 +4698,26 @@ static const struct hda_fixup alc269_fixups[] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc_fixup_headset_mode_alc255_no_hp_mic,
},
+ [ALC293_FIXUP_DELL1_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x18, 0x01a1913d }, /* use as headphone mic, without its own jack detect */
+ { 0x1a, 0x01a1913c }, /* use as headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE
+ },
+ [ALC292_FIXUP_TPT440_DOCK] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x16, 0x21211010 }, /* dock headphone */
+ { 0x19, 0x21a11010 }, /* dock mic */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_LIMIT_INT_MIC_BOOST
+ },
};
static const struct snd_pci_quirk alc269_fixup_tbl[] = {
@@ -4595,51 +4761,45 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x1028, 0x0606, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1028, 0x0608, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1028, 0x0609, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x060f, "Dell", ALC269_FIXUP_DELL3_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x0610, "Dell", ALC269_FIXUP_DELL3_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1028, 0x0613, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1028, 0x0614, "Dell Inspiron 3135", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1028, 0x0615, "Dell Vostro 5470", ALC290_FIXUP_SUBWOOFER_HSJACK),
SND_PCI_QUIRK(0x1028, 0x0616, "Dell Vostro 5470", ALC290_FIXUP_SUBWOOFER_HSJACK),
- SND_PCI_QUIRK(0x1028, 0x061f, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x0629, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x062c, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x062e, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x0632, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1028, 0x0638, "Dell Inspiron 5439", ALC290_FIXUP_MONO_SPEAKERS_HSJACK),
- SND_PCI_QUIRK(0x1028, 0x063e, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1028, 0x063f, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x0640, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x064d, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x0651, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x0652, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x0653, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x0657, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x0658, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x065f, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x0662, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x0667, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x064a, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x064b, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1028, 0x0668, "Dell", ALC255_FIXUP_DELL2_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1028, 0x0669, "Dell", ALC255_FIXUP_DELL2_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x0674, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x067e, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x067f, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x0684, "Dell", ALC269_FIXUP_DELL2_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1028, 0x15cc, "Dell X5 Precision", ALC269_FIXUP_DELL2_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1028, 0x15cd, "Dell X5 Precision", ALC269_FIXUP_DELL2_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x164a, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1028, 0x164b, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x103c, 0x1586, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC2),
SND_PCI_QUIRK(0x103c, 0x18e6, "HP", ALC269_FIXUP_HP_GPIO_LED),
SND_PCI_QUIRK(0x103c, 0x1973, "HP Pavilion", ALC269_FIXUP_HP_MUTE_LED_MIC1),
SND_PCI_QUIRK(0x103c, 0x1983, "HP Pavilion", ALC269_FIXUP_HP_MUTE_LED_MIC1),
SND_PCI_QUIRK(0x103c, 0x218b, "HP", ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED),
/* ALC282 */
+ SND_PCI_QUIRK(0x103c, 0x220d, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x220e, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
SND_PCI_QUIRK(0x103c, 0x220f, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x2210, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x2211, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x2212, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
SND_PCI_QUIRK(0x103c, 0x2213, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x2214, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
SND_PCI_QUIRK(0x103c, 0x2266, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
SND_PCI_QUIRK(0x103c, 0x2267, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
SND_PCI_QUIRK(0x103c, 0x2268, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
SND_PCI_QUIRK(0x103c, 0x2269, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
SND_PCI_QUIRK(0x103c, 0x226a, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
SND_PCI_QUIRK(0x103c, 0x226b, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x226c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x226d, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x226e, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x226f, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
SND_PCI_QUIRK(0x103c, 0x227a, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
SND_PCI_QUIRK(0x103c, 0x227b, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
SND_PCI_QUIRK(0x103c, 0x229e, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
@@ -4679,6 +4839,10 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x103c, 0x22c8, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
SND_PCI_QUIRK(0x103c, 0x22c3, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
SND_PCI_QUIRK(0x103c, 0x22c4, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x2334, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x2335, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x2336, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
+ SND_PCI_QUIRK(0x103c, 0x2337, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
SND_PCI_QUIRK_VENDOR(0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED),
SND_PCI_QUIRK(0x1043, 0x103f, "ASUS TX300", ALC282_FIXUP_ASUS_TX300),
SND_PCI_QUIRK(0x1043, 0x106d, "Asus K53BE", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
@@ -4699,8 +4863,10 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x104d, 0x9073, "Sony VAIO", ALC275_FIXUP_SONY_VAIO_GPIO2),
SND_PCI_QUIRK(0x104d, 0x907b, "Sony VAIO", ALC275_FIXUP_SONY_HWEQ),
SND_PCI_QUIRK(0x104d, 0x9084, "Sony VAIO", ALC275_FIXUP_SONY_HWEQ),
+ SND_PCI_QUIRK(0x104d, 0x9099, "Sony VAIO S13", ALC275_FIXUP_SONY_DISABLE_AAMIX),
SND_PCI_QUIRK_VENDOR(0x104d, "Sony VAIO", ALC269_FIXUP_SONY_VAIO),
SND_PCI_QUIRK(0x10cf, 0x1475, "Lifebook", ALC269_FIXUP_LIFEBOOK),
+ SND_PCI_QUIRK(0x10cf, 0x1845, "Lifebook U904", ALC269_FIXUP_LIFEBOOK_EXTMIC),
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),
@@ -4712,7 +4878,8 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x17aa, 0x21fb, "Thinkpad T430s", ALC269_FIXUP_LENOVO_DOCK),
SND_PCI_QUIRK(0x17aa, 0x2203, "Thinkpad X230 Tablet", ALC269_FIXUP_LENOVO_DOCK),
SND_PCI_QUIRK(0x17aa, 0x2208, "Thinkpad T431s", ALC269_FIXUP_LENOVO_DOCK),
- SND_PCI_QUIRK(0x17aa, 0x220c, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
+ SND_PCI_QUIRK(0x17aa, 0x220c, "Thinkpad T440s", ALC292_FIXUP_TPT440_DOCK),
+ SND_PCI_QUIRK(0x17aa, 0x220e, "Thinkpad T440p", ALC292_FIXUP_TPT440_DOCK),
SND_PCI_QUIRK(0x17aa, 0x2212, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
SND_PCI_QUIRK(0x17aa, 0x2214, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
SND_PCI_QUIRK(0x17aa, 0x2215, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
@@ -4790,9 +4957,235 @@ static const struct hda_model_fixup alc269_fixup_models[] = {
{.id = ALC269_FIXUP_DELL2_MIC_NO_PRESENCE, .name = "dell-headset-dock"},
{.id = ALC283_FIXUP_CHROME_BOOK, .name = "alc283-dac-wcaps"},
{.id = ALC283_FIXUP_SENSE_COMBO_JACK, .name = "alc283-sense-combo"},
+ {.id = ALC292_FIXUP_TPT440_DOCK, .name = "tpt440-dock"},
{}
};
+static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = {
+ {
+ .codec = 0x10ec0255,
+ .subvendor = 0x1028,
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ .name = "Dell",
+#endif
+ .pins = (const struct hda_pintbl[]) {
+ {0x12, 0x90a60140},
+ {0x14, 0x90170110},
+ {0x17, 0x40000000},
+ {0x18, 0x411111f0},
+ {0x19, 0x411111f0},
+ {0x1a, 0x411111f0},
+ {0x1b, 0x411111f0},
+ {0x1d, 0x40700001},
+ {0x1e, 0x411111f0},
+ {0x21, 0x02211020},
+ },
+ .value = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ },
+ {
+ .codec = 0x10ec0255,
+ .subvendor = 0x1028,
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ .name = "Dell",
+#endif
+ .pins = (const struct hda_pintbl[]) {
+ {0x12, 0x90a60160},
+ {0x14, 0x90170120},
+ {0x17, 0x40000000},
+ {0x18, 0x411111f0},
+ {0x19, 0x411111f0},
+ {0x1a, 0x411111f0},
+ {0x1b, 0x411111f0},
+ {0x1d, 0x40700001},
+ {0x1e, 0x411111f0},
+ {0x21, 0x02211030},
+ },
+ .value = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ },
+ {
+ .codec = 0x10ec0255,
+ .subvendor = 0x1028,
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ .name = "Dell",
+#endif
+ .pins = (const struct hda_pintbl[]) {
+ {0x12, 0x90a60160},
+ {0x14, 0x90170120},
+ {0x17, 0x90170140},
+ {0x18, 0x40000000},
+ {0x19, 0x411111f0},
+ {0x1a, 0x411111f0},
+ {0x1b, 0x411111f0},
+ {0x1d, 0x41163b05},
+ {0x1e, 0x411111f0},
+ {0x21, 0x0321102f},
+ },
+ .value = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ },
+ {
+ .codec = 0x10ec0255,
+ .subvendor = 0x1028,
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ .name = "Dell",
+#endif
+ .pins = (const struct hda_pintbl[]) {
+ {0x12, 0x90a60160},
+ {0x14, 0x90170130},
+ {0x17, 0x40000000},
+ {0x18, 0x411111f0},
+ {0x19, 0x411111f0},
+ {0x1a, 0x411111f0},
+ {0x1b, 0x411111f0},
+ {0x1d, 0x40700001},
+ {0x1e, 0x411111f0},
+ {0x21, 0x02211040},
+ },
+ .value = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ },
+ {
+ .codec = 0x10ec0255,
+ .subvendor = 0x1028,
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ .name = "Dell",
+#endif
+ .pins = (const struct hda_pintbl[]) {
+ {0x12, 0x90a60160},
+ {0x14, 0x90170140},
+ {0x17, 0x40000000},
+ {0x18, 0x411111f0},
+ {0x19, 0x411111f0},
+ {0x1a, 0x411111f0},
+ {0x1b, 0x411111f0},
+ {0x1d, 0x40700001},
+ {0x1e, 0x411111f0},
+ {0x21, 0x02211050},
+ },
+ .value = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ },
+ {
+ .codec = 0x10ec0255,
+ .subvendor = 0x1028,
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ .name = "Dell",
+#endif
+ .pins = (const struct hda_pintbl[]) {
+ {0x12, 0x90a60170},
+ {0x14, 0x90170120},
+ {0x17, 0x40000000},
+ {0x18, 0x411111f0},
+ {0x19, 0x411111f0},
+ {0x1a, 0x411111f0},
+ {0x1b, 0x411111f0},
+ {0x1d, 0x40700001},
+ {0x1e, 0x411111f0},
+ {0x21, 0x02211030},
+ },
+ .value = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ },
+ {
+ .codec = 0x10ec0255,
+ .subvendor = 0x1028,
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ .name = "Dell",
+#endif
+ .pins = (const struct hda_pintbl[]) {
+ {0x12, 0x90a60170},
+ {0x14, 0x90170130},
+ {0x17, 0x40000000},
+ {0x18, 0x411111f0},
+ {0x19, 0x411111f0},
+ {0x1a, 0x411111f0},
+ {0x1b, 0x411111f0},
+ {0x1d, 0x40700001},
+ {0x1e, 0x411111f0},
+ {0x21, 0x02211040},
+ },
+ .value = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
+ },
+ {
+ .codec = 0x10ec0283,
+ .subvendor = 0x1028,
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ .name = "Dell",
+#endif
+ .pins = (const struct hda_pintbl[]) {
+ {0x12, 0x90a60130},
+ {0x14, 0x90170110},
+ {0x17, 0x40020008},
+ {0x18, 0x411111f0},
+ {0x19, 0x411111f0},
+ {0x1a, 0x411111f0},
+ {0x1b, 0x411111f0},
+ {0x1d, 0x40e00001},
+ {0x1e, 0x411111f0},
+ {0x21, 0x0321101f},
+ },
+ .value = ALC269_FIXUP_DELL1_MIC_NO_PRESENCE,
+ },
+ {
+ .codec = 0x10ec0283,
+ .subvendor = 0x1028,
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ .name = "Dell",
+#endif
+ .pins = (const struct hda_pintbl[]) {
+ {0x12, 0x90a60160},
+ {0x14, 0x90170120},
+ {0x17, 0x40000000},
+ {0x18, 0x411111f0},
+ {0x19, 0x411111f0},
+ {0x1a, 0x411111f0},
+ {0x1b, 0x411111f0},
+ {0x1d, 0x40700001},
+ {0x1e, 0x411111f0},
+ {0x21, 0x02211030},
+ },
+ .value = ALC269_FIXUP_DELL1_MIC_NO_PRESENCE,
+ },
+ {
+ .codec = 0x10ec0292,
+ .subvendor = 0x1028,
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ .name = "Dell",
+#endif
+ .pins = (const struct hda_pintbl[]) {
+ {0x12, 0x90a60140},
+ {0x13, 0x411111f0},
+ {0x14, 0x90170110},
+ {0x15, 0x0221401f},
+ {0x16, 0x411111f0},
+ {0x18, 0x411111f0},
+ {0x19, 0x411111f0},
+ {0x1a, 0x411111f0},
+ {0x1b, 0x411111f0},
+ {0x1d, 0x40700001},
+ {0x1e, 0x411111f0},
+ },
+ .value = ALC269_FIXUP_DELL3_MIC_NO_PRESENCE,
+ },
+ {
+ .codec = 0x10ec0293,
+ .subvendor = 0x1028,
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ .name = "Dell",
+#endif
+ .pins = (const struct hda_pintbl[]) {
+ {0x12, 0x40000000},
+ {0x13, 0x90a60140},
+ {0x14, 0x90170110},
+ {0x15, 0x0221401f},
+ {0x16, 0x21014020},
+ {0x18, 0x411111f0},
+ {0x19, 0x21a19030},
+ {0x1a, 0x411111f0},
+ {0x1b, 0x411111f0},
+ {0x1d, 0x40700001},
+ {0x1e, 0x411111f0},
+ },
+ .value = ALC293_FIXUP_DELL1_MIC_NO_PRESENCE,
+ },
+ {}
+};
static void alc269_fill_coef(struct hda_codec *codec)
{
@@ -4854,6 +5247,7 @@ static int patch_alc269(struct hda_codec *codec)
snd_hda_pick_fixup(codec, alc269_fixup_models,
alc269_fixup_tbl, alc269_fixups);
+ snd_hda_pick_pin_fixup(codec, alc269_pin_fixup_tbl, alc269_fixups);
snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
alc_auto_parse_customize_define(codec);
@@ -5310,6 +5704,8 @@ enum {
ALC662_FIXUP_BASS_1A,
ALC662_FIXUP_BASS_CHMAP,
ALC668_FIXUP_AUTO_MUTE,
+ ALC668_FIXUP_DELL_DISABLE_AAMIX,
+ ALC668_FIXUP_DELL_XPS13,
};
static const struct hda_fixup alc662_fixups[] = {
@@ -5476,6 +5872,18 @@ static const struct hda_fixup alc662_fixups[] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc_fixup_inv_dmic_0x12,
},
+ [ALC668_FIXUP_DELL_XPS13] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_dell_xps13,
+ .chained = true,
+ .chain_id = ALC668_FIXUP_DELL_DISABLE_AAMIX
+ },
+ [ALC668_FIXUP_DELL_DISABLE_AAMIX] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_disable_aamix,
+ .chained = true,
+ .chain_id = ALC668_FIXUP_DELL_MIC_NO_PRESENCE
+ },
[ALC668_FIXUP_AUTO_MUTE] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc_fixup_auto_mute_via_amp,
@@ -5536,13 +5944,9 @@ static const struct snd_pci_quirk alc662_fixup_tbl[] = {
SND_PCI_QUIRK(0x1025, 0x038b, "Acer Aspire 8943G", ALC662_FIXUP_ASPIRE),
SND_PCI_QUIRK(0x1028, 0x05d8, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1028, 0x05db, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x060a, "Dell XPS 13", ALC668_FIXUP_DELL_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x0623, "Dell", ALC668_FIXUP_AUTO_MUTE),
- SND_PCI_QUIRK(0x1028, 0x0624, "Dell", ALC668_FIXUP_AUTO_MUTE),
+ SND_PCI_QUIRK(0x1028, 0x060a, "Dell XPS 13", ALC668_FIXUP_DELL_XPS13),
SND_PCI_QUIRK(0x1028, 0x0625, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1028, 0x0626, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1028, 0x0628, "Dell", ALC668_FIXUP_AUTO_MUTE),
- SND_PCI_QUIRK(0x1028, 0x064e, "Dell", ALC668_FIXUP_AUTO_MUTE),
SND_PCI_QUIRK(0x1028, 0x0696, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1028, 0x0698, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x103c, 0x1632, "HP RP5800", ALC662_FIXUP_HP_RP5800),
@@ -5634,6 +6038,94 @@ static const struct hda_model_fixup alc662_fixup_models[] = {
{}
};
+static const struct snd_hda_pin_quirk alc662_pin_fixup_tbl[] = {
+ {
+ .codec = 0x10ec0668,
+ .subvendor = 0x1028,
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ .name = "Dell",
+#endif
+ .pins = (const struct hda_pintbl[]) {
+ {0x12, 0x99a30130},
+ {0x14, 0x90170110},
+ {0x15, 0x0321101f},
+ {0x16, 0x03011020},
+ {0x18, 0x40000008},
+ {0x19, 0x411111f0},
+ {0x1a, 0x411111f0},
+ {0x1b, 0x411111f0},
+ {0x1d, 0x41000001},
+ {0x1e, 0x411111f0},
+ {0x1f, 0x411111f0},
+ },
+ .value = ALC668_FIXUP_AUTO_MUTE,
+ },
+ {
+ .codec = 0x10ec0668,
+ .subvendor = 0x1028,
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ .name = "Dell",
+#endif
+ .pins = (const struct hda_pintbl[]) {
+ {0x12, 0x99a30140},
+ {0x14, 0x90170110},
+ {0x15, 0x0321101f},
+ {0x16, 0x03011020},
+ {0x18, 0x40000008},
+ {0x19, 0x411111f0},
+ {0x1a, 0x411111f0},
+ {0x1b, 0x411111f0},
+ {0x1d, 0x41000001},
+ {0x1e, 0x411111f0},
+ {0x1f, 0x411111f0},
+ },
+ .value = ALC668_FIXUP_AUTO_MUTE,
+ },
+ {
+ .codec = 0x10ec0668,
+ .subvendor = 0x1028,
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ .name = "Dell",
+#endif
+ .pins = (const struct hda_pintbl[]) {
+ {0x12, 0x99a30150},
+ {0x14, 0x90170110},
+ {0x15, 0x0321101f},
+ {0x16, 0x03011020},
+ {0x18, 0x40000008},
+ {0x19, 0x411111f0},
+ {0x1a, 0x411111f0},
+ {0x1b, 0x411111f0},
+ {0x1d, 0x41000001},
+ {0x1e, 0x411111f0},
+ {0x1f, 0x411111f0},
+ },
+ .value = ALC668_FIXUP_AUTO_MUTE,
+ },
+ {
+ .codec = 0x10ec0668,
+ .subvendor = 0x1028,
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ .name = "Dell",
+#endif
+ .pins = (const struct hda_pintbl[]) {
+ {0x12, 0x411111f0},
+ {0x14, 0x90170110},
+ {0x15, 0x0321101f},
+ {0x16, 0x03011020},
+ {0x18, 0x40000008},
+ {0x19, 0x411111f0},
+ {0x1a, 0x411111f0},
+ {0x1b, 0x411111f0},
+ {0x1d, 0x41000001},
+ {0x1e, 0x411111f0},
+ {0x1f, 0x411111f0},
+ },
+ .value = ALC668_FIXUP_AUTO_MUTE,
+ },
+ {}
+};
+
static void alc662_fill_coef(struct hda_codec *codec)
{
int val, coef;
@@ -5683,6 +6175,7 @@ static int patch_alc662(struct hda_codec *codec)
snd_hda_pick_fixup(codec, alc662_fixup_models,
alc662_fixup_tbl, alc662_fixups);
+ snd_hda_pick_pin_fixup(codec, alc662_pin_fixup_tbl, alc662_fixups);
snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
alc_auto_parse_customize_define(codec);
@@ -5771,6 +6264,7 @@ static const struct hda_codec_preset snd_hda_preset_realtek[] = {
{ .id = 0x10ec0221, .name = "ALC221", .patch = patch_alc269 },
{ .id = 0x10ec0231, .name = "ALC231", .patch = patch_alc269 },
{ .id = 0x10ec0233, .name = "ALC233", .patch = patch_alc269 },
+ { .id = 0x10ec0235, .name = "ALC233", .patch = patch_alc269 },
{ .id = 0x10ec0255, .name = "ALC255", .patch = patch_alc269 },
{ .id = 0x10ec0260, .name = "ALC260", .patch = patch_alc260 },
{ .id = 0x10ec0262, .name = "ALC262", .patch = patch_alc262 },
@@ -5804,10 +6298,12 @@ static const struct hda_codec_preset snd_hda_preset_realtek[] = {
.patch = patch_alc662 },
{ .id = 0x10ec0663, .name = "ALC663", .patch = patch_alc662 },
{ .id = 0x10ec0665, .name = "ALC665", .patch = patch_alc662 },
+ { .id = 0x10ec0667, .name = "ALC667", .patch = patch_alc662 },
{ .id = 0x10ec0668, .name = "ALC668", .patch = patch_alc662 },
{ .id = 0x10ec0670, .name = "ALC670", .patch = patch_alc662 },
{ .id = 0x10ec0671, .name = "ALC671", .patch = patch_alc662 },
{ .id = 0x10ec0680, .name = "ALC680", .patch = patch_alc680 },
+ { .id = 0x10ec0867, .name = "ALC891", .patch = patch_alc882 },
{ .id = 0x10ec0880, .name = "ALC880", .patch = patch_alc880 },
{ .id = 0x10ec0882, .name = "ALC882", .patch = patch_alc882 },
{ .id = 0x10ec0883, .name = "ALC883", .patch = patch_alc882 },
diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c
index 75515b494034..7f40a150899c 100644
--- a/sound/pci/hda/patch_sigmatel.c
+++ b/sound/pci/hda/patch_sigmatel.c
@@ -795,7 +795,7 @@ static int find_mute_led_cfg(struct hda_codec *codec, int default_polarity)
}
while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) {
- if (sscanf(dev->name, "HP_Mute_LED_%d_%x",
+ if (sscanf(dev->name, "HP_Mute_LED_%u_%x",
&spec->gpio_led_polarity,
&spec->gpio_led) == 2) {
unsigned int max_gpio;
@@ -808,7 +808,7 @@ static int find_mute_led_cfg(struct hda_codec *codec, int default_polarity)
spec->vref_mute_led_nid = spec->gpio_led;
return 1;
}
- if (sscanf(dev->name, "HP_Mute_LED_%d",
+ if (sscanf(dev->name, "HP_Mute_LED_%u",
&spec->gpio_led_polarity) == 1) {
set_hp_led_gpio(codec);
return 1;
diff --git a/sound/pci/intel8x0.c b/sound/pci/intel8x0.c
index 68340d7df76d..c91860e0a28d 100644
--- a/sound/pci/intel8x0.c
+++ b/sound/pci/intel8x0.c
@@ -2779,7 +2779,7 @@ static void intel8x0_measure_ac97_clock(struct intel8x0 *chip)
unsigned long port;
unsigned long pos, pos1, t;
int civ, timeout = 1000, attempt = 1;
- struct timespec start_time, stop_time;
+ ktime_t start_time, stop_time;
if (chip->ac97_bus->clock != 48000)
return; /* specified in module option */
@@ -2813,7 +2813,7 @@ static void intel8x0_measure_ac97_clock(struct intel8x0 *chip)
iputbyte(chip, port + ICH_REG_OFF_CR, ICH_IOCE);
iputdword(chip, ICHREG(ALI_DMACR), 1 << ichdev->ali_slot);
}
- do_posix_clock_monotonic_gettime(&start_time);
+ start_time = ktime_get();
spin_unlock_irq(&chip->reg_lock);
msleep(50);
spin_lock_irq(&chip->reg_lock);
@@ -2837,7 +2837,7 @@ static void intel8x0_measure_ac97_clock(struct intel8x0 *chip)
pos += ichdev->position;
}
chip->in_measurement = 0;
- do_posix_clock_monotonic_gettime(&stop_time);
+ stop_time = ktime_get();
/* stop */
if (chip->device_type == DEVICE_ALI) {
iputdword(chip, ICHREG(ALI_DMACR), 1 << (ichdev->ali_slot + 16));
@@ -2865,9 +2865,7 @@ static void intel8x0_measure_ac97_clock(struct intel8x0 *chip)
}
pos /= 4;
- t = stop_time.tv_sec - start_time.tv_sec;
- t *= 1000000;
- t += (stop_time.tv_nsec - start_time.tv_nsec) / 1000;
+ t = ktime_us_delta(stop_time, start_time);
dev_info(chip->card->dev,
"%s: measured %lu usecs (%lu samples)\n", __func__, t, pos);
if (t == 0) {
diff --git a/sound/pci/lola/lola_proc.c b/sound/pci/lola/lola_proc.c
index 04df83defc09..c241dc06dd92 100644
--- a/sound/pci/lola/lola_proc.c
+++ b/sound/pci/lola/lola_proc.c
@@ -151,7 +151,7 @@ static void lola_proc_codec_rw_write(struct snd_info_entry *entry,
char line[64];
unsigned int id, verb, data, extdata;
while (!snd_info_get_line(buffer, line, sizeof(line))) {
- if (sscanf(line, "%i %i %i %i", &id, &verb, &data, &extdata) != 4)
+ if (sscanf(line, "%u %u %u %u", &id, &verb, &data, &extdata) != 4)
continue;
lola_codec_read(chip, id, verb, data, extdata,
&chip->debug_res,
diff --git a/sound/pci/lx6464es/lx_core.c b/sound/pci/lx6464es/lx_core.c
index 2d8e95e9fbe5..e8f38e5df10a 100644
--- a/sound/pci/lx6464es/lx_core.c
+++ b/sound/pci/lx6464es/lx_core.c
@@ -24,6 +24,7 @@
/* #define RMH_DEBUG 1 */
+#include <linux/bitops.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/delay.h>
@@ -429,11 +430,6 @@ int lx_dsp_read_async_events(struct lx6464es *chip, u32 *data)
return ret;
}
-#define CSES_TIMEOUT 100 /* microseconds */
-#define CSES_CE 0x0001
-#define CSES_BROADCAST 0x0002
-#define CSES_UPDATE_LDSV 0x0004
-
#define PIPE_INFO_TO_CMD(capture, pipe) \
((u32)((u32)(pipe) | ((capture) ? ID_IS_CAPTURE : 0L)) << ID_OFFSET)
@@ -519,7 +515,6 @@ int lx_buffer_ask(struct lx6464es *chip, u32 pipe, int is_capture,
*r_needed += 1;
}
-#if 0
dev_dbg(chip->card->dev,
"CMD_08_ASK_BUFFERS: needed %d, freed %d\n",
*r_needed, *r_freed);
@@ -530,7 +525,6 @@ int lx_buffer_ask(struct lx6464es *chip, u32 pipe, int is_capture,
chip->rmh.stat[i],
chip->rmh.stat[i] & MASK_DATA_SIZE);
}
-#endif
}
spin_unlock_irqrestore(&chip->msg_lock, flags);
@@ -971,9 +965,9 @@ int lx_level_peaks(struct lx6464es *chip, int is_capture, int channels,
/* interrupt handling */
#define PCX_IRQ_NONE 0
-#define IRQCS_ACTIVE_PCIDB 0x00002000L /* Bit nø 13 */
-#define IRQCS_ENABLE_PCIIRQ 0x00000100L /* Bit nø 08 */
-#define IRQCS_ENABLE_PCIDB 0x00000200L /* Bit nø 09 */
+#define IRQCS_ACTIVE_PCIDB BIT(13)
+#define IRQCS_ENABLE_PCIIRQ BIT(8)
+#define IRQCS_ENABLE_PCIDB BIT(9)
static u32 lx_interrupt_test_ack(struct lx6464es *chip)
{
@@ -1030,25 +1024,21 @@ static int lx_interrupt_handle_async_events(struct lx6464es *chip, u32 irqsrc,
int err;
u32 stat[9]; /* answer from CMD_04_GET_EVENT */
- /* On peut optimiser pour ne pas lire les evenements vides
- * les mots de réponse sont dans l'ordre suivant :
- * Stat[0] mot de status général
- * Stat[1] fin de buffer OUT pF
- * Stat[2] fin de buffer OUT pf
- * Stat[3] fin de buffer IN pF
- * Stat[4] fin de buffer IN pf
- * Stat[5] underrun poid fort
- * Stat[6] underrun poid faible
- * Stat[7] overrun poid fort
- * Stat[8] overrun poid faible
+ /* We can optimize this to not read dumb events.
+ * Answer words are in the following order:
+ * Stat[0] general status
+ * Stat[1] end of buffer OUT pF
+ * Stat[2] end of buffer OUT pf
+ * Stat[3] end of buffer IN pF
+ * Stat[4] end of buffer IN pf
+ * Stat[5] MSB underrun
+ * Stat[6] LSB underrun
+ * Stat[7] MSB overrun
+ * Stat[8] LSB overrun
* */
u64 orun_mask;
u64 urun_mask;
-#if 0
- int has_underrun = (irqsrc & MASK_SYS_STATUS_URUN) ? 1 : 0;
- int has_overrun = (irqsrc & MASK_SYS_STATUS_ORUN) ? 1 : 0;
-#endif
int eb_pending_out = (irqsrc & MASK_SYS_STATUS_EOBO) ? 1 : 0;
int eb_pending_in = (irqsrc & MASK_SYS_STATUS_EOBI) ? 1 : 0;
@@ -1199,9 +1189,8 @@ irqreturn_t lx_interrupt(int irq, void *dev_id)
if (irqsrc & MASK_SYS_STATUS_CMD_DONE)
goto exit;
-#if 0
if (irqsrc & MASK_SYS_STATUS_EOBI)
- dev_dgg(chip->card->dev, "interrupt: EOBI\n");
+ dev_dbg(chip->card->dev, "interrupt: EOBI\n");
if (irqsrc & MASK_SYS_STATUS_EOBO)
dev_dbg(chip->card->dev, "interrupt: EOBO\n");
@@ -1211,7 +1200,6 @@ irqreturn_t lx_interrupt(int irq, void *dev_id)
if (irqsrc & MASK_SYS_STATUS_ORUN)
dev_dbg(chip->card->dev, "interrupt: ORUN\n");
-#endif
if (async_pending) {
u64 notified_in_pipe_mask = 0;
@@ -1238,7 +1226,6 @@ irqreturn_t lx_interrupt(int irq, void *dev_id)
}
if (async_escmd) {
-#if 0
/* backdoor for ethersound commands
*
* for now, we do not need this
@@ -1246,7 +1233,6 @@ irqreturn_t lx_interrupt(int irq, void *dev_id)
* */
dev_dbg(chip->card->dev, "interrupt requests escmd handling\n");
-#endif
}
exit:
diff --git a/sound/soc/atmel/sam9g20_wm8731.c b/sound/soc/atmel/sam9g20_wm8731.c
index 174bd546c08b..bb1149126c54 100644
--- a/sound/soc/atmel/sam9g20_wm8731.c
+++ b/sound/soc/atmel/sam9g20_wm8731.c
@@ -48,7 +48,6 @@
#include <asm/mach-types.h>
#include <mach/hardware.h>
-#include <mach/gpio.h>
#include "../codecs/wm8731.h"
#include "atmel-pcm.h"
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index cbfa1e18f651..0b9571c858f8 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -225,11 +225,11 @@ config SND_SOC_ADAU1373
config SND_SOC_ADAU1701
tristate "Analog Devices ADAU1701 CODEC"
depends on I2C
- select SND_SOC_SIGMADSP
+ select SND_SOC_SIGMADSP_I2C
config SND_SOC_ADAU17X1
tristate
- select SND_SOC_SIGMADSP
+ select SND_SOC_SIGMADSP_REGMAP
config SND_SOC_ADAU1761
tristate
@@ -476,6 +476,14 @@ config SND_SOC_SIGMADSP
tristate
select CRC32
+config SND_SOC_SIGMADSP_I2C
+ tristate
+ select SND_SOC_SIGMADSP
+
+config SND_SOC_SIGMADSP_REGMAP
+ tristate
+ select SND_SOC_SIGMADSP
+
config SND_SOC_SIRF_AUDIO_CODEC
tristate "SiRF SoC internal audio codec"
select REGMAP_MMIO
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index be3377b8d73f..1bd6e1cf6f82 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -77,6 +77,8 @@ snd-soc-sgtl5000-objs := sgtl5000.o
snd-soc-alc5623-objs := alc5623.o
snd-soc-alc5632-objs := alc5632.o
snd-soc-sigmadsp-objs := sigmadsp.o
+snd-soc-sigmadsp-i2c-objs := sigmadsp-i2c.o
+snd-soc-sigmadsp-regmap-objs := sigmadsp-regmap.o
snd-soc-si476x-objs := si476x.o
snd-soc-sirf-audio-codec-objs := sirf-audio-codec.o
snd-soc-sn95031-objs := sn95031.o
@@ -240,6 +242,8 @@ obj-$(CONFIG_SND_SOC_RT5651) += snd-soc-rt5651.o
obj-$(CONFIG_SND_SOC_RT5677) += snd-soc-rt5677.o
obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o
obj-$(CONFIG_SND_SOC_SIGMADSP) += snd-soc-sigmadsp.o
+obj-$(CONFIG_SND_SOC_SIGMADSP_I2C) += snd-soc-sigmadsp-i2c.o
+obj-$(CONFIG_SND_SOC_SIGMADSP_REGMAP) += snd-soc-sigmadsp-regmap.o
obj-$(CONFIG_SND_SOC_SI476X) += snd-soc-si476x.o
obj-$(CONFIG_SND_SOC_SN95031) +=snd-soc-sn95031.o
obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif-rx.o snd-soc-spdif-tx.o
diff --git a/sound/soc/codecs/sigmadsp-i2c.c b/sound/soc/codecs/sigmadsp-i2c.c
new file mode 100644
index 000000000000..246081aae8ca
--- /dev/null
+++ b/sound/soc/codecs/sigmadsp-i2c.c
@@ -0,0 +1,35 @@
+/*
+ * Load Analog Devices SigmaStudio firmware files
+ *
+ * Copyright 2009-2011 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/i2c.h>
+#include <linux/export.h>
+#include <linux/module.h>
+
+#include "sigmadsp.h"
+
+static int sigma_action_write_i2c(void *control_data,
+ const struct sigma_action *sa, size_t len)
+{
+ return i2c_master_send(control_data, (const unsigned char *)&sa->addr,
+ len);
+}
+
+int process_sigma_firmware(struct i2c_client *client, const char *name)
+{
+ struct sigma_firmware ssfw;
+
+ ssfw.control_data = client;
+ ssfw.write = sigma_action_write_i2c;
+
+ return _process_sigma_firmware(&client->dev, &ssfw, name);
+}
+EXPORT_SYMBOL(process_sigma_firmware);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("SigmaDSP I2C firmware loader");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/sigmadsp-regmap.c b/sound/soc/codecs/sigmadsp-regmap.c
new file mode 100644
index 000000000000..f78ed8d2cfb2
--- /dev/null
+++ b/sound/soc/codecs/sigmadsp-regmap.c
@@ -0,0 +1,36 @@
+/*
+ * Load Analog Devices SigmaStudio firmware files
+ *
+ * Copyright 2009-2011 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/regmap.h>
+#include <linux/export.h>
+#include <linux/module.h>
+
+#include "sigmadsp.h"
+
+static int sigma_action_write_regmap(void *control_data,
+ const struct sigma_action *sa, size_t len)
+{
+ return regmap_raw_write(control_data, be16_to_cpu(sa->addr),
+ sa->payload, len - 2);
+}
+
+int process_sigma_firmware_regmap(struct device *dev, struct regmap *regmap,
+ const char *name)
+{
+ struct sigma_firmware ssfw;
+
+ ssfw.control_data = regmap;
+ ssfw.write = sigma_action_write_regmap;
+
+ return _process_sigma_firmware(dev, &ssfw, name);
+}
+EXPORT_SYMBOL(process_sigma_firmware_regmap);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("SigmaDSP regmap firmware loader");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/sigmadsp.c b/sound/soc/codecs/sigmadsp.c
index 4068f2491232..f2de7e049bc6 100644
--- a/sound/soc/codecs/sigmadsp.c
+++ b/sound/soc/codecs/sigmadsp.c
@@ -34,23 +34,6 @@ enum {
SIGMA_ACTION_END,
};
-struct sigma_action {
- u8 instr;
- u8 len_hi;
- __le16 len;
- __be16 addr;
- unsigned char payload[];
-} __packed;
-
-struct sigma_firmware {
- const struct firmware *fw;
- size_t pos;
-
- void *control_data;
- int (*write)(void *control_data, const struct sigma_action *sa,
- size_t len);
-};
-
static inline u32 sigma_action_len(struct sigma_action *sa)
{
return (sa->len_hi << 16) | le16_to_cpu(sa->len);
@@ -138,7 +121,7 @@ process_sigma_actions(struct sigma_firmware *ssfw)
return 0;
}
-static int _process_sigma_firmware(struct device *dev,
+int _process_sigma_firmware(struct device *dev,
struct sigma_firmware *ssfw, const char *name)
{
int ret;
@@ -197,50 +180,6 @@ static int _process_sigma_firmware(struct device *dev,
return ret;
}
-
-#if IS_ENABLED(CONFIG_I2C)
-
-static int sigma_action_write_i2c(void *control_data,
- const struct sigma_action *sa, size_t len)
-{
- return i2c_master_send(control_data, (const unsigned char *)&sa->addr,
- len);
-}
-
-int process_sigma_firmware(struct i2c_client *client, const char *name)
-{
- struct sigma_firmware ssfw;
-
- ssfw.control_data = client;
- ssfw.write = sigma_action_write_i2c;
-
- return _process_sigma_firmware(&client->dev, &ssfw, name);
-}
-EXPORT_SYMBOL(process_sigma_firmware);
-
-#endif
-
-#if IS_ENABLED(CONFIG_REGMAP)
-
-static int sigma_action_write_regmap(void *control_data,
- const struct sigma_action *sa, size_t len)
-{
- return regmap_raw_write(control_data, be16_to_cpu(sa->addr),
- sa->payload, len - 2);
-}
-
-int process_sigma_firmware_regmap(struct device *dev, struct regmap *regmap,
- const char *name)
-{
- struct sigma_firmware ssfw;
-
- ssfw.control_data = regmap;
- ssfw.write = sigma_action_write_regmap;
-
- return _process_sigma_firmware(dev, &ssfw, name);
-}
-EXPORT_SYMBOL(process_sigma_firmware_regmap);
-
-#endif
+EXPORT_SYMBOL_GPL(_process_sigma_firmware);
MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/sigmadsp.h b/sound/soc/codecs/sigmadsp.h
index e439cbd7af7d..c47cd23e9827 100644
--- a/sound/soc/codecs/sigmadsp.h
+++ b/sound/soc/codecs/sigmadsp.h
@@ -12,6 +12,26 @@
#include <linux/device.h>
#include <linux/regmap.h>
+struct sigma_action {
+ u8 instr;
+ u8 len_hi;
+ __le16 len;
+ __be16 addr;
+ unsigned char payload[];
+} __packed;
+
+struct sigma_firmware {
+ const struct firmware *fw;
+ size_t pos;
+
+ void *control_data;
+ int (*write)(void *control_data, const struct sigma_action *sa,
+ size_t len);
+};
+
+int _process_sigma_firmware(struct device *dev,
+ struct sigma_firmware *ssfw, const char *name);
+
struct i2c_client;
extern int process_sigma_firmware(struct i2c_client *client, const char *name);
diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c
index 6bb0ea59284f..a609aafc994d 100644
--- a/sound/soc/fsl/fsl_dma.c
+++ b/sound/soc/fsl/fsl_dma.c
@@ -923,8 +923,8 @@ static int fsl_soc_dma_probe(struct platform_device *pdev)
dma->dai.pcm_free = fsl_dma_free_dma_buffers;
/* Store the SSI-specific information that we need */
- dma->ssi_stx_phys = res.start + offsetof(struct ccsr_ssi, stx0);
- dma->ssi_srx_phys = res.start + offsetof(struct ccsr_ssi, srx0);
+ dma->ssi_stx_phys = res.start + CCSR_SSI_STX0;
+ dma->ssi_srx_phys = res.start + CCSR_SSI_SRX0;
iprop = of_get_property(ssi_np, "fsl,fifo-depth", NULL);
if (iprop)
diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c
index b912d45a2a4c..d7a60614dd21 100644
--- a/sound/soc/fsl/fsl_spdif.c
+++ b/sound/soc/fsl/fsl_spdif.c
@@ -762,7 +762,7 @@ static int fsl_spdif_vbit_get(struct snd_kcontrol *kcontrol,
struct regmap *regmap = spdif_priv->regmap;
u32 val;
- val = regmap_read(regmap, REG_SPDIF_SIS, &val);
+ regmap_read(regmap, REG_SPDIF_SIS, &val);
ucontrol->value.integer.value[0] = (val & INT_VAL_NOGOOD) != 0;
regmap_write(regmap, REG_SPDIF_SIC, INT_VAL_NOGOOD);
@@ -1076,7 +1076,7 @@ static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv,
goto out;
} else if (arate / rate[index] == 1) {
/* A little bigger than expect */
- sub = (arate - rate[index]) * 100000;
+ sub = (u64)(arate - rate[index]) * 100000;
do_div(sub, rate[index]);
if (sub >= savesub)
continue;
@@ -1086,7 +1086,7 @@ static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv,
spdif_priv->txrate[index] = arate;
} else if (rate[index] / arate == 1) {
/* A little smaller than expect */
- sub = (rate[index] - arate) * 100000;
+ sub = (u64)(rate[index] - arate) * 100000;
do_div(sub, rate[index]);
if (sub >= savesub)
continue;
diff --git a/sound/soc/omap/ams-delta.c b/sound/soc/omap/ams-delta.c
index 1f41951d8b7f..0cc41f94de4e 100644
--- a/sound/soc/omap/ams-delta.c
+++ b/sound/soc/omap/ams-delta.c
@@ -527,7 +527,7 @@ static int ams_delta_cx20442_init(struct snd_soc_pcm_runtime *rtd)
return 0;
}
-static int ams_delta_card_remove(struct snd_soc_pcm_runtime *rtd)
+static int ams_delta_card_remove(struct snd_soc_card *card)
{
snd_soc_jack_free_gpios(&ams_delta_hook_switch,
ARRAY_SIZE(ams_delta_hook_switch_gpios),
diff --git a/sound/soc/omap/omap-twl4030.c b/sound/soc/omap/omap-twl4030.c
index b4e282871658..f8a6adc2d81c 100644
--- a/sound/soc/omap/omap-twl4030.c
+++ b/sound/soc/omap/omap-twl4030.c
@@ -231,9 +231,8 @@ static int omap_twl4030_init(struct snd_soc_pcm_runtime *rtd)
return ret;
}
-static int omap_twl4030_card_remove(struct snd_soc_pcm_runtime *rtd)
+static int omap_twl4030_card_remove(struct snd_soc_card *card)
{
- struct snd_soc_card *card = rtd->card;
struct omap_twl4030 *priv = snd_soc_card_get_drvdata(card);
if (priv->jack_detect > 0)
diff --git a/sound/soc/omap/rx51.c b/sound/soc/omap/rx51.c
index 47a10290535b..943922c79f78 100644
--- a/sound/soc/omap/rx51.c
+++ b/sound/soc/omap/rx51.c
@@ -334,7 +334,7 @@ static int rx51_aic34_init(struct snd_soc_pcm_runtime *rtd)
return err;
}
-static int rx51_card_remove(struct snd_soc_pcm_runtime *rtd)
+static int rx51_card_remove(struct snd_soc_card *card)
{
snd_soc_jack_free_gpios(&rx51_av_jack, ARRAY_SIZE(rx51_av_jack_gpios),
rx51_av_jack_gpios);
diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig
index 6acb225ec6fd..2434b6d61675 100644
--- a/sound/soc/pxa/Kconfig
+++ b/sound/soc/pxa/Kconfig
@@ -11,6 +11,7 @@ config SND_PXA2XX_SOC
config SND_MMP_SOC
bool "Soc Audio for Marvell MMP chips"
depends on ARCH_MMP
+ select MMP_SRAM
select SND_SOC_GENERIC_DMAENGINE_PCM
select SND_ARM
help
@@ -40,7 +41,7 @@ config SND_MMP_SOC_SSPA
config SND_PXA2XX_SOC_CORGI
tristate "SoC Audio support for Sharp Zaurus SL-C7x0"
- depends on SND_PXA2XX_SOC && PXA_SHARP_C7xx
+ depends on SND_PXA2XX_SOC && PXA_SHARP_C7xx && I2C
select SND_PXA2XX_SOC_I2S
select SND_SOC_WM8731
help
@@ -49,7 +50,7 @@ config SND_PXA2XX_SOC_CORGI
config SND_PXA2XX_SOC_SPITZ
tristate "SoC Audio support for Sharp Zaurus SL-Cxx00"
- depends on SND_PXA2XX_SOC && PXA_SHARP_Cxx00
+ depends on SND_PXA2XX_SOC && PXA_SHARP_Cxx00 && I2C
select SND_PXA2XX_SOC_I2S
select SND_SOC_WM8750
help
@@ -58,7 +59,7 @@ config SND_PXA2XX_SOC_SPITZ
config SND_PXA2XX_SOC_Z2
tristate "SoC Audio support for Zipit Z2"
- depends on SND_PXA2XX_SOC && MACH_ZIPIT2
+ depends on SND_PXA2XX_SOC && MACH_ZIPIT2 && I2C
select SND_PXA2XX_SOC_I2S
select SND_SOC_WM8750
help
@@ -66,7 +67,7 @@ config SND_PXA2XX_SOC_Z2
config SND_PXA2XX_SOC_POODLE
tristate "SoC Audio support for Poodle"
- depends on SND_PXA2XX_SOC && MACH_POODLE
+ depends on SND_PXA2XX_SOC && MACH_POODLE && I2C
select SND_PXA2XX_SOC_I2S
select SND_SOC_WM8731
help
@@ -181,7 +182,7 @@ config SND_PXA2XX_SOC_HX4700
config SND_PXA2XX_SOC_MAGICIAN
tristate "SoC Audio support for HTC Magician"
- depends on SND_PXA2XX_SOC && MACH_MAGICIAN
+ depends on SND_PXA2XX_SOC && MACH_MAGICIAN && I2C
select SND_PXA2XX_SOC_I2S
select SND_PXA_SOC_SSP
select SND_SOC_UDA1380
diff --git a/sound/soc/pxa/hx4700.c b/sound/soc/pxa/hx4700.c
index 6b81acaffddd..05559a725bec 100644
--- a/sound/soc/pxa/hx4700.c
+++ b/sound/soc/pxa/hx4700.c
@@ -152,7 +152,7 @@ static int hx4700_ak4641_init(struct snd_soc_pcm_runtime *rtd)
return err;
}
-static int hx4700_card_remove(struct snd_soc_pcm_runtime *rtd)
+static int hx4700_card_remove(struct snd_soc_card *card)
{
snd_soc_jack_free_gpios(&hs_jack, 1, &hs_jack_gpio);
diff --git a/sound/soc/samsung/h1940_uda1380.c b/sound/soc/samsung/h1940_uda1380.c
index 720357f11a7f..f2d7980d7ddc 100644
--- a/sound/soc/samsung/h1940_uda1380.c
+++ b/sound/soc/samsung/h1940_uda1380.c
@@ -189,7 +189,7 @@ static int h1940_uda1380_init(struct snd_soc_pcm_runtime *rtd)
return 0;
}
-static int h1940_uda1380_card_remove(struct snd_soc_pcm_runtime *rtd)
+static int h1940_uda1380_card_remove(struct snd_soc_card *card)
{
snd_soc_jack_free_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios),
hp_jack_gpios);
diff --git a/sound/soc/samsung/rx1950_uda1380.c b/sound/soc/samsung/rx1950_uda1380.c
index 192aa9fc102f..37688ebbb2b4 100644
--- a/sound/soc/samsung/rx1950_uda1380.c
+++ b/sound/soc/samsung/rx1950_uda1380.c
@@ -31,7 +31,7 @@
#include "s3c24xx-i2s.h"
static int rx1950_uda1380_init(struct snd_soc_pcm_runtime *rtd);
-static int rx1950_uda1380_card_remove(struct snd_soc_pcm_runtime *rtd);
+static int rx1950_uda1380_card_remove(struct snd_soc_card *card);
static int rx1950_startup(struct snd_pcm_substream *substream);
static int rx1950_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params);
@@ -236,7 +236,7 @@ static int rx1950_uda1380_init(struct snd_soc_pcm_runtime *rtd)
return 0;
}
-static int rx1950_uda1380_card_remove(struct snd_soc_pcm_runtime *rtd)
+static int rx1950_uda1380_card_remove(struct snd_soc_card *card)
{
snd_soc_jack_free_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios),
hp_jack_gpios);
diff --git a/sound/soc/samsung/smartq_wm8987.c b/sound/soc/samsung/smartq_wm8987.c
index 271a904277a1..9b0ffacab790 100644
--- a/sound/soc/samsung/smartq_wm8987.c
+++ b/sound/soc/samsung/smartq_wm8987.c
@@ -182,7 +182,7 @@ static int smartq_wm8987_init(struct snd_soc_pcm_runtime *rtd)
return err;
}
-static int smartq_wm8987_card_remove(struct snd_soc_pcm_runtime *rtd)
+static int smartq_wm8987_card_remove(struct snd_soc_card *card)
{
snd_soc_jack_free_gpios(&smartq_jack, ARRAY_SIZE(smartq_jack_gpios),
smartq_jack_gpios);
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index a74b9bf23d9f..cdc837ed144d 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -2755,7 +2755,7 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol,
unsigned int mask = (1 << fls(max)) - 1;
unsigned int invert = mc->invert;
unsigned int val;
- int connect, change;
+ int connect, change, reg_change = 0;
struct snd_soc_dapm_update update;
int ret = 0;
@@ -2773,20 +2773,23 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol,
mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
change = dapm_kcontrol_set_value(kcontrol, val);
- if (change) {
- if (reg != SND_SOC_NOPM) {
- mask = mask << shift;
- val = val << shift;
-
- if (snd_soc_test_bits(codec, reg, mask, val)) {
- update.kcontrol = kcontrol;
- update.reg = reg;
- update.mask = mask;
- update.val = val;
- card->update = &update;
- }
+ if (reg != SND_SOC_NOPM) {
+ mask = mask << shift;
+ val = val << shift;
+
+ reg_change = snd_soc_test_bits(codec, reg, mask, val);
+ }
+
+ if (change || reg_change) {
+ if (reg_change) {
+ update.kcontrol = kcontrol;
+ update.reg = reg;
+ update.mask = mask;
+ update.val = val;
+ card->update = &update;
}
+ change |= reg_change;
ret = soc_dapm_mixer_update_power(card, kcontrol, connect);
diff --git a/sound/synth/emux/soundfont.c b/sound/synth/emux/soundfont.c
index 1137b85c36e6..78683b2064f7 100644
--- a/sound/synth/emux/soundfont.c
+++ b/sound/synth/emux/soundfont.c
@@ -1021,6 +1021,7 @@ load_guspatch(struct snd_sf_list *sflist, const char __user *data,
data, count);
if (rc < 0) {
sf_sample_delete(sflist, sf, smp);
+ kfree(zone);
return rc;
}
/* memory offset is updated after */
diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig
index e05a86b7c0da..d393153c474f 100644
--- a/sound/usb/Kconfig
+++ b/sound/usb/Kconfig
@@ -147,5 +147,18 @@ config SND_USB_HIFACE
To compile this driver as a module, choose M here: the module
will be called snd-usb-hiface.
+config SND_BCD2000
+ tristate "Behringer BCD2000 MIDI driver"
+ select SND_RAWMIDI
+ help
+ Say Y here to include MIDI support for the Behringer BCD2000 DJ
+ controller.
+
+ Audio support is still work-in-progress at
+ https://github.com/anyc/snd-usb-bcd2000
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-bcd2000.
+
endif # SND_USB
diff --git a/sound/usb/Makefile b/sound/usb/Makefile
index abe668f660d1..2b92f0dcbc4c 100644
--- a/sound/usb/Makefile
+++ b/sound/usb/Makefile
@@ -23,4 +23,4 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o
-obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/
+obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/ bcd2000/
diff --git a/sound/usb/bcd2000/Makefile b/sound/usb/bcd2000/Makefile
new file mode 100644
index 000000000000..f09ccc0af6ff
--- /dev/null
+++ b/sound/usb/bcd2000/Makefile
@@ -0,0 +1,3 @@
+snd-bcd2000-y := bcd2000.o
+
+obj-$(CONFIG_SND_BCD2000) += snd-bcd2000.o \ No newline at end of file
diff --git a/sound/usb/bcd2000/bcd2000.c b/sound/usb/bcd2000/bcd2000.c
new file mode 100644
index 000000000000..820d6ca8c458
--- /dev/null
+++ b/sound/usb/bcd2000/bcd2000.c
@@ -0,0 +1,461 @@
+/*
+ * Behringer BCD2000 driver
+ *
+ * Copyright (C) 2014 Mario Kicherer (dev@kicherer.org)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/bitmap.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+
+#define PREFIX "snd-bcd2000: "
+#define BUFSIZE 64
+
+static struct usb_device_id id_table[] = {
+ { USB_DEVICE(0x1397, 0x00bd) },
+ { },
+};
+
+static unsigned char device_cmd_prefix[] = {0x03, 0x00};
+
+static unsigned char bcd2000_init_sequence[] = {
+ 0x07, 0x00, 0x00, 0x00, 0x78, 0x48, 0x1c, 0x81,
+ 0xc4, 0x00, 0x00, 0x00, 0x5e, 0x53, 0x4a, 0xf7,
+ 0x18, 0xfa, 0x11, 0xff, 0x6c, 0xf3, 0x90, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x18, 0xfa, 0x11, 0xff, 0x14, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xf2, 0x34, 0x4a, 0xf7,
+ 0x18, 0xfa, 0x11, 0xff
+};
+
+struct bcd2000 {
+ struct usb_device *dev;
+ struct snd_card *card;
+ struct usb_interface *intf;
+ int card_index;
+
+ int midi_out_active;
+ struct snd_rawmidi *rmidi;
+ struct snd_rawmidi_substream *midi_receive_substream;
+ struct snd_rawmidi_substream *midi_out_substream;
+
+ unsigned char midi_in_buf[BUFSIZE];
+ unsigned char midi_out_buf[BUFSIZE];
+
+ struct urb *midi_out_urb;
+ struct urb *midi_in_urb;
+
+ struct usb_anchor anchor;
+};
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+
+static DEFINE_MUTEX(devices_mutex);
+DECLARE_BITMAP(devices_used, SNDRV_CARDS);
+static struct usb_driver bcd2000_driver;
+
+#ifdef CONFIG_SND_DEBUG
+static void bcd2000_dump_buffer(const char *prefix, const char *buf, int len)
+{
+ print_hex_dump(KERN_DEBUG, prefix,
+ DUMP_PREFIX_NONE, 16, 1,
+ buf, len, false);
+}
+#else
+static void bcd2000_dump_buffer(const char *prefix, const char *buf, int len) {}
+#endif
+
+static int bcd2000_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+ return 0;
+}
+
+static int bcd2000_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+ return 0;
+}
+
+/* (de)register midi substream from client */
+static void bcd2000_midi_input_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ struct bcd2000 *bcd2k = substream->rmidi->private_data;
+ bcd2k->midi_receive_substream = up ? substream : NULL;
+}
+
+static void bcd2000_midi_handle_input(struct bcd2000 *bcd2k,
+ const unsigned char *buf, unsigned int buf_len)
+{
+ unsigned int payload_length, tocopy;
+ struct snd_rawmidi_substream *midi_receive_substream;
+
+ midi_receive_substream = ACCESS_ONCE(bcd2k->midi_receive_substream);
+ if (!midi_receive_substream)
+ return;
+
+ bcd2000_dump_buffer(PREFIX "received from device: ", buf, buf_len);
+
+ if (buf_len < 2)
+ return;
+
+ payload_length = buf[0];
+
+ /* ignore packets without payload */
+ if (payload_length == 0)
+ return;
+
+ tocopy = min(payload_length, buf_len-1);
+
+ bcd2000_dump_buffer(PREFIX "sending to userspace: ",
+ &buf[1], tocopy);
+
+ snd_rawmidi_receive(midi_receive_substream,
+ &buf[1], tocopy);
+}
+
+static void bcd2000_midi_send(struct bcd2000 *bcd2k)
+{
+ int len, ret;
+ struct snd_rawmidi_substream *midi_out_substream;
+
+ BUILD_BUG_ON(sizeof(device_cmd_prefix) >= BUFSIZE);
+
+ midi_out_substream = ACCESS_ONCE(bcd2k->midi_out_substream);
+ if (!midi_out_substream)
+ return;
+
+ /* copy command prefix bytes */
+ memcpy(bcd2k->midi_out_buf, device_cmd_prefix,
+ sizeof(device_cmd_prefix));
+
+ /*
+ * get MIDI packet and leave space for command prefix
+ * and payload length
+ */
+ len = snd_rawmidi_transmit(midi_out_substream,
+ bcd2k->midi_out_buf + 3, BUFSIZE - 3);
+
+ if (len < 0)
+ dev_err(&bcd2k->dev->dev, "%s: snd_rawmidi_transmit error %d\n",
+ __func__, len);
+
+ if (len <= 0)
+ return;
+
+ /* set payload length */
+ bcd2k->midi_out_buf[2] = len;
+ bcd2k->midi_out_urb->transfer_buffer_length = BUFSIZE;
+
+ bcd2000_dump_buffer(PREFIX "sending to device: ",
+ bcd2k->midi_out_buf, len+3);
+
+ /* send packet to the BCD2000 */
+ ret = usb_submit_urb(bcd2k->midi_out_urb, GFP_ATOMIC);
+ if (ret < 0)
+ dev_err(&bcd2k->dev->dev, PREFIX
+ "%s (%p): usb_submit_urb() failed, ret=%d, len=%d\n",
+ __func__, midi_out_substream, ret, len);
+ else
+ bcd2k->midi_out_active = 1;
+}
+
+static int bcd2000_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+ return 0;
+}
+
+static int bcd2000_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+ struct bcd2000 *bcd2k = substream->rmidi->private_data;
+
+ if (bcd2k->midi_out_active) {
+ usb_kill_urb(bcd2k->midi_out_urb);
+ bcd2k->midi_out_active = 0;
+ }
+
+ return 0;
+}
+
+/* (de)register midi substream from client */
+static void bcd2000_midi_output_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ struct bcd2000 *bcd2k = substream->rmidi->private_data;
+
+ if (up) {
+ bcd2k->midi_out_substream = substream;
+ /* check if there is data userspace wants to send */
+ if (!bcd2k->midi_out_active)
+ bcd2000_midi_send(bcd2k);
+ } else {
+ bcd2k->midi_out_substream = NULL;
+ }
+}
+
+static void bcd2000_output_complete(struct urb *urb)
+{
+ struct bcd2000 *bcd2k = urb->context;
+
+ bcd2k->midi_out_active = 0;
+
+ if (urb->status)
+ dev_warn(&urb->dev->dev,
+ PREFIX "output urb->status: %d\n", urb->status);
+
+ if (urb->status == -ESHUTDOWN)
+ return;
+
+ /* check if there is more data userspace wants to send */
+ bcd2000_midi_send(bcd2k);
+}
+
+static void bcd2000_input_complete(struct urb *urb)
+{
+ int ret;
+ struct bcd2000 *bcd2k = urb->context;
+
+ if (urb->status)
+ dev_warn(&urb->dev->dev,
+ PREFIX "input urb->status: %i\n", urb->status);
+
+ if (!bcd2k || urb->status == -ESHUTDOWN)
+ return;
+
+ if (urb->actual_length > 0)
+ bcd2000_midi_handle_input(bcd2k, urb->transfer_buffer,
+ urb->actual_length);
+
+ /* return URB to device */
+ ret = usb_submit_urb(bcd2k->midi_in_urb, GFP_ATOMIC);
+ if (ret < 0)
+ dev_err(&bcd2k->dev->dev, PREFIX
+ "%s: usb_submit_urb() failed, ret=%d\n",
+ __func__, ret);
+}
+
+static struct snd_rawmidi_ops bcd2000_midi_output = {
+ .open = bcd2000_midi_output_open,
+ .close = bcd2000_midi_output_close,
+ .trigger = bcd2000_midi_output_trigger,
+};
+
+static struct snd_rawmidi_ops bcd2000_midi_input = {
+ .open = bcd2000_midi_input_open,
+ .close = bcd2000_midi_input_close,
+ .trigger = bcd2000_midi_input_trigger,
+};
+
+static void bcd2000_init_device(struct bcd2000 *bcd2k)
+{
+ int ret;
+
+ init_usb_anchor(&bcd2k->anchor);
+ usb_anchor_urb(bcd2k->midi_out_urb, &bcd2k->anchor);
+ usb_anchor_urb(bcd2k->midi_in_urb, &bcd2k->anchor);
+
+ /* copy init sequence into buffer */
+ memcpy(bcd2k->midi_out_buf, bcd2000_init_sequence, 52);
+ bcd2k->midi_out_urb->transfer_buffer_length = 52;
+
+ /* submit sequence */
+ ret = usb_submit_urb(bcd2k->midi_out_urb, GFP_KERNEL);
+ if (ret < 0)
+ dev_err(&bcd2k->dev->dev, PREFIX
+ "%s: usb_submit_urb() out failed, ret=%d: ",
+ __func__, ret);
+ else
+ bcd2k->midi_out_active = 1;
+
+ /* pass URB to device to enable button and controller events */
+ ret = usb_submit_urb(bcd2k->midi_in_urb, GFP_KERNEL);
+ if (ret < 0)
+ dev_err(&bcd2k->dev->dev, PREFIX
+ "%s: usb_submit_urb() in failed, ret=%d: ",
+ __func__, ret);
+
+ /* ensure initialization is finished */
+ usb_wait_anchor_empty_timeout(&bcd2k->anchor, 1000);
+}
+
+static int bcd2000_init_midi(struct bcd2000 *bcd2k)
+{
+ int ret;
+ struct snd_rawmidi *rmidi;
+
+ ret = snd_rawmidi_new(bcd2k->card, bcd2k->card->shortname, 0,
+ 1, /* output */
+ 1, /* input */
+ &rmidi);
+
+ if (ret < 0)
+ return ret;
+
+ strlcpy(rmidi->name, bcd2k->card->shortname, sizeof(rmidi->name));
+
+ rmidi->info_flags = SNDRV_RAWMIDI_INFO_DUPLEX;
+ rmidi->private_data = bcd2k;
+
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+ &bcd2000_midi_output);
+
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+ &bcd2000_midi_input);
+
+ bcd2k->rmidi = rmidi;
+
+ bcd2k->midi_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+ bcd2k->midi_out_urb = usb_alloc_urb(0, GFP_KERNEL);
+
+ if (!bcd2k->midi_in_urb || !bcd2k->midi_out_urb) {
+ dev_err(&bcd2k->dev->dev, PREFIX "usb_alloc_urb failed\n");
+ return -ENOMEM;
+ }
+
+ usb_fill_int_urb(bcd2k->midi_in_urb, bcd2k->dev,
+ usb_rcvintpipe(bcd2k->dev, 0x81),
+ bcd2k->midi_in_buf, BUFSIZE,
+ bcd2000_input_complete, bcd2k, 1);
+
+ usb_fill_int_urb(bcd2k->midi_out_urb, bcd2k->dev,
+ usb_sndintpipe(bcd2k->dev, 0x1),
+ bcd2k->midi_out_buf, BUFSIZE,
+ bcd2000_output_complete, bcd2k, 1);
+
+ bcd2000_init_device(bcd2k);
+
+ return 0;
+}
+
+static void bcd2000_free_usb_related_resources(struct bcd2000 *bcd2k,
+ struct usb_interface *interface)
+{
+ /* usb_kill_urb not necessary, urb is aborted automatically */
+
+ usb_free_urb(bcd2k->midi_out_urb);
+ usb_free_urb(bcd2k->midi_in_urb);
+
+ if (bcd2k->intf) {
+ usb_set_intfdata(bcd2k->intf, NULL);
+ bcd2k->intf = NULL;
+ }
+}
+
+static int bcd2000_probe(struct usb_interface *interface,
+ const struct usb_device_id *usb_id)
+{
+ struct snd_card *card;
+ struct bcd2000 *bcd2k;
+ unsigned int card_index;
+ char usb_path[32];
+ int err;
+
+ mutex_lock(&devices_mutex);
+
+ for (card_index = 0; card_index < SNDRV_CARDS; ++card_index)
+ if (!test_bit(card_index, devices_used))
+ break;
+
+ if (card_index >= SNDRV_CARDS) {
+ mutex_unlock(&devices_mutex);
+ return -ENOENT;
+ }
+
+ err = snd_card_new(&interface->dev, index[card_index], id[card_index],
+ THIS_MODULE, sizeof(*bcd2k), &card);
+ if (err < 0) {
+ mutex_unlock(&devices_mutex);
+ return err;
+ }
+
+ bcd2k = card->private_data;
+ bcd2k->dev = interface_to_usbdev(interface);
+ bcd2k->card = card;
+ bcd2k->card_index = card_index;
+ bcd2k->intf = interface;
+
+ snd_card_set_dev(card, &interface->dev);
+
+ strncpy(card->driver, "snd-bcd2000", sizeof(card->driver));
+ strncpy(card->shortname, "BCD2000", sizeof(card->shortname));
+ usb_make_path(bcd2k->dev, usb_path, sizeof(usb_path));
+ snprintf(bcd2k->card->longname, sizeof(bcd2k->card->longname),
+ "Behringer BCD2000 at %s",
+ usb_path);
+
+ err = bcd2000_init_midi(bcd2k);
+ if (err < 0)
+ goto probe_error;
+
+ err = snd_card_register(card);
+ if (err < 0)
+ goto probe_error;
+
+ usb_set_intfdata(interface, bcd2k);
+ set_bit(card_index, devices_used);
+
+ mutex_unlock(&devices_mutex);
+ return 0;
+
+probe_error:
+ dev_info(&bcd2k->dev->dev, PREFIX "error during probing");
+ bcd2000_free_usb_related_resources(bcd2k, interface);
+ snd_card_free(card);
+ mutex_unlock(&devices_mutex);
+ return err;
+}
+
+static void bcd2000_disconnect(struct usb_interface *interface)
+{
+ struct bcd2000 *bcd2k = usb_get_intfdata(interface);
+
+ if (!bcd2k)
+ return;
+
+ mutex_lock(&devices_mutex);
+
+ /* make sure that userspace cannot create new requests */
+ snd_card_disconnect(bcd2k->card);
+
+ bcd2000_free_usb_related_resources(bcd2k, interface);
+
+ clear_bit(bcd2k->card_index, devices_used);
+
+ snd_card_free_when_closed(bcd2k->card);
+
+ mutex_unlock(&devices_mutex);
+}
+
+static struct usb_driver bcd2000_driver = {
+ .name = "snd-bcd2000",
+ .probe = bcd2000_probe,
+ .disconnect = bcd2000_disconnect,
+ .id_table = id_table,
+};
+
+module_usb_driver(bcd2000_driver);
+
+MODULE_DEVICE_TABLE(usb, id_table);
+MODULE_AUTHOR("Mario Kicherer, dev@kicherer.org");
+MODULE_DESCRIPTION("Behringer BCD2000 driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
index d40a2850e270..0b728d886f0d 100644
--- a/sound/usb/mixer.c
+++ b/sound/usb/mixer.c
@@ -162,7 +162,7 @@ static int check_mapped_selector_name(struct mixer_build *state, int unitid,
{
const struct usbmix_selector_map *p;
- if (! state->selector_map)
+ if (!state->selector_map)
return 0;
for (p = state->selector_map; p->id; p++) {
if (p->id == unitid && index < p->count)
@@ -174,7 +174,8 @@ static int check_mapped_selector_name(struct mixer_build *state, int unitid,
/*
* find an audio control unit with the given unit id
*/
-static void *find_audio_control_unit(struct mixer_build *state, unsigned char unit)
+static void *find_audio_control_unit(struct mixer_build *state,
+ unsigned char unit)
{
/* we just parse the header */
struct uac_feature_unit_descriptor *hdr = NULL;
@@ -194,7 +195,8 @@ static void *find_audio_control_unit(struct mixer_build *state, unsigned char un
/*
* copy a string with the given id
*/
-static int snd_usb_copy_string_desc(struct mixer_build *state, int index, char *buf, int maxlen)
+static int snd_usb_copy_string_desc(struct mixer_build *state,
+ int index, char *buf, int maxlen)
{
int len = usb_string(state->chip->dev, index, buf, maxlen - 1);
buf[len] = 0;
@@ -253,7 +255,7 @@ static int convert_bytes_value(struct usb_mixer_elem_info *cval, int val)
static int get_relative_value(struct usb_mixer_elem_info *cval, int val)
{
- if (! cval->res)
+ if (!cval->res)
cval->res = 1;
if (val < cval->min)
return 0;
@@ -267,7 +269,7 @@ static int get_abs_value(struct usb_mixer_elem_info *cval, int val)
{
if (val < 0)
return cval->min;
- if (! cval->res)
+ if (!cval->res)
cval->res = 1;
val *= cval->res;
val += cval->min;
@@ -281,7 +283,8 @@ static int get_abs_value(struct usb_mixer_elem_info *cval, int val)
* retrieve a mixer value
*/
-static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret)
+static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request,
+ int validx, int *value_ret)
{
struct snd_usb_audio *chip = cval->mixer->chip;
unsigned char buf[2];
@@ -292,6 +295,7 @@ static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, int v
err = snd_usb_autoresume(cval->mixer->chip);
if (err < 0)
return -EIO;
+
down_read(&chip->shutdown_rwsem);
while (timeout-- > 0) {
if (chip->shutdown)
@@ -316,10 +320,11 @@ static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, int v
return err;
}
-static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret)
+static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request,
+ int validx, int *value_ret)
{
struct snd_usb_audio *chip = cval->mixer->chip;
- unsigned char buf[2 + 3*sizeof(__u16)]; /* enough space for one range */
+ unsigned char buf[2 + 3 * sizeof(__u16)]; /* enough space for one range */
unsigned char *val;
int idx = 0, ret, size;
__u8 bRequest;
@@ -339,9 +344,9 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, int v
goto error;
down_read(&chip->shutdown_rwsem);
- if (chip->shutdown)
+ if (chip->shutdown) {
ret = -ENODEV;
- else {
+ } else {
idx = snd_usb_ctrl_intf(chip) | (cval->id << 8);
ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest,
USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
@@ -382,7 +387,8 @@ error:
return 0;
}
-static int get_ctl_value(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret)
+static int get_ctl_value(struct usb_mixer_elem_info *cval, int request,
+ int validx, int *value_ret)
{
validx += cval->idx_off;
@@ -391,7 +397,8 @@ static int get_ctl_value(struct usb_mixer_elem_info *cval, int request, int vali
get_ctl_value_v2(cval, request, validx, value_ret);
}
-static int get_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int *value)
+static int get_cur_ctl_value(struct usb_mixer_elem_info *cval,
+ int validx, int *value)
{
return get_ctl_value(cval, UAC_GET_CUR, validx, value);
}
@@ -400,7 +407,9 @@ static int get_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int *
static inline int get_cur_mix_raw(struct usb_mixer_elem_info *cval,
int channel, int *value)
{
- return get_ctl_value(cval, UAC_GET_CUR, (cval->control << 8) | channel, value);
+ return get_ctl_value(cval, UAC_GET_CUR,
+ (cval->control << 8) | channel,
+ value);
}
static int get_cur_mix_value(struct usb_mixer_elem_info *cval,
@@ -417,7 +426,7 @@ static int get_cur_mix_value(struct usb_mixer_elem_info *cval,
if (!cval->mixer->ignore_ctl_error)
usb_audio_dbg(cval->mixer->chip,
"cannot get current value for control %d ch %d: err = %d\n",
- cval->control, channel, err);
+ cval->control, channel, err);
return err;
}
cval->cached |= 1 << channel;
@@ -425,7 +434,6 @@ static int get_cur_mix_value(struct usb_mixer_elem_info *cval,
return 0;
}
-
/*
* set a mixer value
*/
@@ -474,7 +482,7 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval,
}
}
usb_audio_dbg(chip, "cannot set ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d, data = %#x/%#x\n",
- request, validx, idx, cval->val_type, buf[0], buf[1]);
+ request, validx, idx, cval->val_type, buf[0], buf[1]);
err = -EINVAL;
out:
@@ -483,7 +491,8 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval,
return err;
}
-static int set_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int value)
+static int set_cur_ctl_value(struct usb_mixer_elem_info *cval,
+ int validx, int value)
{
return snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, validx, value);
}
@@ -503,8 +512,9 @@ static int set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel,
return 0;
}
- err = snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, (cval->control << 8) | channel,
- value);
+ err = snd_usb_mixer_set_ctl_value(cval,
+ UAC_SET_CUR, (cval->control << 8) | channel,
+ value);
if (err < 0)
return err;
cval->cached |= 1 << channel;
@@ -541,13 +551,13 @@ static int parse_audio_unit(struct mixer_build *state, int unitid);
* check if the input/output channel routing is enabled on the given bitmap.
* used for mixer unit parser
*/
-static int check_matrix_bitmap(unsigned char *bmap, int ich, int och, int num_outs)
+static int check_matrix_bitmap(unsigned char *bmap,
+ int ich, int och, int num_outs)
{
int idx = ich * num_outs + och;
return bmap[idx >> 3] & (0x80 >> (idx & 7));
}
-
/*
* add an alsa control element
* search and increment the index until an empty slot is found.
@@ -564,7 +574,8 @@ int snd_usb_mixer_add_control(struct usb_mixer_interface *mixer,
while (snd_ctl_find_id(mixer->chip->card, &kctl->id))
kctl->id.index++;
if ((err = snd_ctl_add(mixer->chip->card, kctl)) < 0) {
- usb_audio_dbg(mixer->chip, "cannot add control (err = %d)\n", err);
+ usb_audio_dbg(mixer->chip, "cannot add control (err = %d)\n",
+ err);
return err;
}
cval->elem_id = &kctl->id;
@@ -573,7 +584,6 @@ int snd_usb_mixer_add_control(struct usb_mixer_interface *mixer,
return 0;
}
-
/*
* get a terminal name string
*/
@@ -627,7 +637,8 @@ static int get_term_name(struct mixer_build *state, struct usb_audio_term *iterm
struct iterm_name_combo *names;
if (iterm->name)
- return snd_usb_copy_string_desc(state, iterm->name, name, maxlen);
+ return snd_usb_copy_string_desc(state, iterm->name,
+ name, maxlen);
/* virtual type - not a real terminal */
if (iterm->type >> 16) {
@@ -635,13 +646,17 @@ static int get_term_name(struct mixer_build *state, struct usb_audio_term *iterm
return 0;
switch (iterm->type >> 16) {
case UAC_SELECTOR_UNIT:
- strcpy(name, "Selector"); return 8;
+ strcpy(name, "Selector");
+ return 8;
case UAC1_PROCESSING_UNIT:
- strcpy(name, "Process Unit"); return 12;
+ strcpy(name, "Process Unit");
+ return 12;
case UAC1_EXTENSION_UNIT:
- strcpy(name, "Ext Unit"); return 8;
+ strcpy(name, "Ext Unit");
+ return 8;
case UAC_MIXER_UNIT:
- strcpy(name, "Mixer"); return 5;
+ strcpy(name, "Mixer");
+ return 5;
default:
return sprintf(name, "Unit %d", iterm->id);
}
@@ -649,29 +664,35 @@ static int get_term_name(struct mixer_build *state, struct usb_audio_term *iterm
switch (iterm->type & 0xff00) {
case 0x0100:
- strcpy(name, "PCM"); return 3;
+ strcpy(name, "PCM");
+ return 3;
case 0x0200:
- strcpy(name, "Mic"); return 3;
+ strcpy(name, "Mic");
+ return 3;
case 0x0400:
- strcpy(name, "Headset"); return 7;
+ strcpy(name, "Headset");
+ return 7;
case 0x0500:
- strcpy(name, "Phone"); return 5;
+ strcpy(name, "Phone");
+ return 5;
}
- for (names = iterm_names; names->type; names++)
+ for (names = iterm_names; names->type; names++) {
if (names->type == iterm->type) {
strcpy(name, names->name);
return strlen(names->name);
}
+ }
+
return 0;
}
-
/*
* parse the source unit recursively until it reaches to a terminal
* or a branched unit.
*/
-static int check_input_term(struct mixer_build *state, int id, struct usb_audio_term *term)
+static int check_input_term(struct mixer_build *state, int id,
+ struct usb_audio_term *term)
{
int err;
void *p1;
@@ -766,7 +787,6 @@ static int check_input_term(struct mixer_build *state, int id, struct usb_audio_
return -ENODEV;
}
-
/*
* Feature Unit
*/
@@ -794,7 +814,6 @@ static struct usb_feature_control_info audio_feature_info[] = {
{ "Phase Inverter Control", USB_MIXER_BOOLEAN },
};
-
/* private_free callback */
static void usb_mixer_elem_free(struct snd_kcontrol *kctl)
{
@@ -802,7 +821,6 @@ static void usb_mixer_elem_free(struct snd_kcontrol *kctl)
kctl->private_data = NULL;
}
-
/*
* interface to ALSA control for feature/mixer units
*/
@@ -906,7 +924,6 @@ static void volume_control_quirks(struct usb_mixer_elem_info *cval,
cval->res = 384;
}
break;
-
}
}
@@ -939,21 +956,26 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval,
get_ctl_value(cval, UAC_GET_MIN, (cval->control << 8) | minchn, &cval->min) < 0) {
usb_audio_err(cval->mixer->chip,
"%d:%d: cannot get min/max values for control %d (id %d)\n",
- cval->id, snd_usb_ctrl_intf(cval->mixer->chip), cval->control, cval->id);
+ cval->id, snd_usb_ctrl_intf(cval->mixer->chip),
+ cval->control, cval->id);
return -EINVAL;
}
- if (get_ctl_value(cval, UAC_GET_RES, (cval->control << 8) | minchn, &cval->res) < 0) {
+ if (get_ctl_value(cval, UAC_GET_RES,
+ (cval->control << 8) | minchn,
+ &cval->res) < 0) {
cval->res = 1;
} else {
int last_valid_res = cval->res;
while (cval->res > 1) {
if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES,
- (cval->control << 8) | minchn, cval->res / 2) < 0)
+ (cval->control << 8) | minchn,
+ cval->res / 2) < 0)
break;
cval->res /= 2;
}
- if (get_ctl_value(cval, UAC_GET_RES, (cval->control << 8) | minchn, &cval->res) < 0)
+ if (get_ctl_value(cval, UAC_GET_RES,
+ (cval->control << 8) | minchn, &cval->res) < 0)
cval->res = last_valid_res;
}
if (cval->res == 0)
@@ -1017,7 +1039,8 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval,
#define get_min_max(cval, def) get_min_max_with_quirks(cval, def, NULL)
/* get a feature/mixer unit info */
-static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
{
struct usb_mixer_elem_info *cval = kcontrol->private_data;
@@ -1051,7 +1074,8 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, struct snd_ctl_
}
/* get the current value from feature/mixer unit */
-static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
{
struct usb_mixer_elem_info *cval = kcontrol->private_data;
int c, cnt, val, err;
@@ -1082,7 +1106,8 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, struct snd_ctl_e
}
/* put the current value to feature/mixer unit */
-static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
{
struct usb_mixer_elem_info *cval = kcontrol->private_data;
int c, cnt, val, oval, err;
@@ -1136,22 +1161,25 @@ static struct snd_kcontrol_new usb_feature_unit_ctl_ro = {
.put = NULL,
};
-/* This symbol is exported in order to allow the mixer quirks to
- * hook up to the standard feature unit control mechanism */
+/*
+ * This symbol is exported in order to allow the mixer quirks to
+ * hook up to the standard feature unit control mechanism
+ */
struct snd_kcontrol_new *snd_usb_feature_unit_ctl = &usb_feature_unit_ctl;
/*
* build a feature control
*/
-
static size_t append_ctl_name(struct snd_kcontrol *kctl, const char *str)
{
return strlcat(kctl->id.name, str, sizeof(kctl->id.name));
}
-/* A lot of headsets/headphones have a "Speaker" mixer. Make sure we
- rename it to "Headphone". We determine if something is a headphone
- similar to how udev determines form factor. */
+/*
+ * A lot of headsets/headphones have a "Speaker" mixer. Make sure we
+ * rename it to "Headphone". We determine if something is a headphone
+ * similar to how udev determines form factor.
+ */
static void check_no_speaker_on_headset(struct snd_kcontrol *kctl,
struct snd_card *card)
{
@@ -1201,10 +1229,8 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
return;
cval = kzalloc(sizeof(*cval), GFP_KERNEL);
- if (! cval) {
- usb_audio_err(state->chip, "cannot malloc kcontrol\n");
+ if (!cval)
return;
- }
cval->mixer = state->mixer;
cval->id = unitid;
cval->control = control;
@@ -1222,15 +1248,17 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
cval->ch_readonly = readonly_mask;
}
- /* if all channels in the mask are marked read-only, make the control
+ /*
+ * If all channels in the mask are marked read-only, make the control
* read-only. set_cur_mix_value() will check the mask again and won't
- * issue write commands to read-only channels. */
+ * issue write commands to read-only channels.
+ */
if (cval->channels == readonly_mask)
kctl = snd_ctl_new1(&usb_feature_unit_ctl_ro, cval);
else
kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
- if (! kctl) {
+ if (!kctl) {
usb_audio_err(state->chip, "cannot malloc kcontrol\n");
kfree(cval);
return;
@@ -1239,48 +1267,53 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name));
mapped_name = len != 0;
- if (! len && nameid)
+ if (!len && nameid)
len = snd_usb_copy_string_desc(state, nameid,
kctl->id.name, sizeof(kctl->id.name));
switch (control) {
case UAC_FU_MUTE:
case UAC_FU_VOLUME:
- /* determine the control name. the rule is:
+ /*
+ * determine the control name. the rule is:
* - if a name id is given in descriptor, use it.
* - if the connected input can be determined, then use the name
* of terminal type.
* - if the connected output can be determined, use it.
* - otherwise, anonymous name.
*/
- if (! len) {
- len = get_term_name(state, iterm, kctl->id.name, sizeof(kctl->id.name), 1);
- if (! len)
- len = get_term_name(state, &state->oterm, kctl->id.name, sizeof(kctl->id.name), 1);
- if (! len)
- len = snprintf(kctl->id.name, sizeof(kctl->id.name),
+ if (!len) {
+ len = get_term_name(state, iterm, kctl->id.name,
+ sizeof(kctl->id.name), 1);
+ if (!len)
+ len = get_term_name(state, &state->oterm,
+ kctl->id.name,
+ sizeof(kctl->id.name), 1);
+ if (!len)
+ len = snprintf(kctl->id.name,
+ sizeof(kctl->id.name),
"Feature %d", unitid);
}
if (!mapped_name)
check_no_speaker_on_headset(kctl, state->mixer->chip->card);
- /* determine the stream direction:
+ /*
+ * determine the stream direction:
* if the connected output is USB stream, then it's likely a
* capture stream. otherwise it should be playback (hopefully :)
*/
- if (! mapped_name && ! (state->oterm.type >> 16)) {
- if ((state->oterm.type & 0xff00) == 0x0100) {
+ if (!mapped_name && !(state->oterm.type >> 16)) {
+ if ((state->oterm.type & 0xff00) == 0x0100)
len = append_ctl_name(kctl, " Capture");
- } else {
+ else
len = append_ctl_name(kctl, " Playback");
- }
}
append_ctl_name(kctl, control == UAC_FU_MUTE ?
" Switch" : " Volume");
break;
default:
- if (! len)
+ if (!len)
strlcpy(kctl->id.name, audio_feature_info[control-1].name,
sizeof(kctl->id.name));
break;
@@ -1300,33 +1333,35 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
}
range = (cval->max - cval->min) / cval->res;
- /* Are there devices with volume range more than 255? I use a bit more
+ /*
+ * Are there devices with volume range more than 255? I use a bit more
* to be sure. 384 is a resolution magic number found on Logitech
* devices. It will definitively catch all buggy Logitech devices.
*/
if (range > 384) {
- usb_audio_warn(state->chip, "Warning! Unlikely big "
- "volume range (=%u), cval->res is probably wrong.",
- range);
+ usb_audio_warn(state->chip,
+ "Warning! Unlikely big volume range (=%u), "
+ "cval->res is probably wrong.",
+ range);
usb_audio_warn(state->chip, "[%d] FU [%s] ch = %d, "
- "val = %d/%d/%d", cval->id,
- kctl->id.name, cval->channels,
- cval->min, cval->max, cval->res);
+ "val = %d/%d/%d", cval->id,
+ kctl->id.name, cval->channels,
+ cval->min, cval->max, cval->res);
}
usb_audio_dbg(state->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n",
- cval->id, kctl->id.name, cval->channels, cval->min, cval->max, cval->res);
+ cval->id, kctl->id.name, cval->channels,
+ cval->min, cval->max, cval->res);
snd_usb_mixer_add_control(state->mixer, kctl);
}
-
-
/*
* parse a feature unit
*
* most of controls are defined here.
*/
-static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void *_ftr)
+static int parse_audio_feature_unit(struct mixer_build *state, int unitid,
+ void *_ftr)
{
int channels, i, j;
struct usb_audio_term iterm;
@@ -1400,15 +1435,25 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void
for (i = 0; i < 10; i++) {
unsigned int ch_bits = 0;
for (j = 0; j < channels; j++) {
- unsigned int mask = snd_usb_combine_bytes(bmaControls + csize * (j+1), csize);
+ unsigned int mask;
+
+ mask = snd_usb_combine_bytes(bmaControls +
+ csize * (j+1), csize);
if (mask & (1 << i))
ch_bits |= (1 << j);
}
/* audio class v1 controls are never read-only */
- if (ch_bits & 1) /* the first channel must be set (for ease of programming) */
- build_feature_ctl(state, _ftr, ch_bits, i, &iterm, unitid, 0);
+
+ /*
+ * The first channel must be set
+ * (for ease of programming).
+ */
+ if (ch_bits & 1)
+ build_feature_ctl(state, _ftr, ch_bits, i,
+ &iterm, unitid, 0);
if (master_bits & (1 << i))
- build_feature_ctl(state, _ftr, 0, i, &iterm, unitid, 0);
+ build_feature_ctl(state, _ftr, 0, i, &iterm,
+ unitid, 0);
}
} else { /* UAC_VERSION_2 */
for (i = 0; i < ARRAY_SIZE(audio_feature_info); i++) {
@@ -1416,7 +1461,10 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void
unsigned int ch_read_only = 0;
for (j = 0; j < channels; j++) {
- unsigned int mask = snd_usb_combine_bytes(bmaControls + csize * (j+1), csize);
+ unsigned int mask;
+
+ mask = snd_usb_combine_bytes(bmaControls +
+ csize * (j+1), csize);
if (uac2_control_is_readable(mask, i)) {
ch_bits |= (1 << j);
if (!uac2_control_is_writeable(mask, i))
@@ -1424,12 +1472,22 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void
}
}
- /* NOTE: build_feature_ctl() will mark the control read-only if all channels
- * are marked read-only in the descriptors. Otherwise, the control will be
- * reported as writeable, but the driver will not actually issue a write
- * command for read-only channels */
- if (ch_bits & 1) /* the first channel must be set (for ease of programming) */
- build_feature_ctl(state, _ftr, ch_bits, i, &iterm, unitid, ch_read_only);
+ /*
+ * NOTE: build_feature_ctl() will mark the control
+ * read-only if all channels are marked read-only in
+ * the descriptors. Otherwise, the control will be
+ * reported as writeable, but the driver will not
+ * actually issue a write command for read-only
+ * channels.
+ */
+
+ /*
+ * The first channel must be set
+ * (for ease of programming).
+ */
+ if (ch_bits & 1)
+ build_feature_ctl(state, _ftr, ch_bits, i,
+ &iterm, unitid, ch_read_only);
if (uac2_control_is_readable(master_bits, i))
build_feature_ctl(state, _ftr, 0, i, &iterm, unitid,
!uac2_control_is_writeable(master_bits, i));
@@ -1439,7 +1497,6 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void
return 0;
}
-
/*
* Mixer Unit
*/
@@ -1450,7 +1507,6 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void
* the callbacks are identical with feature unit.
* input channel number (zero based) is given in control field instead.
*/
-
static void build_mixer_unit_ctl(struct mixer_build *state,
struct uac_mixer_unit_descriptor *desc,
int in_pin, int in_ch, int unitid,
@@ -1467,7 +1523,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state,
return;
cval = kzalloc(sizeof(*cval), GFP_KERNEL);
- if (! cval)
+ if (!cval)
return;
cval->mixer = state->mixer;
@@ -1475,7 +1531,9 @@ static void build_mixer_unit_ctl(struct mixer_build *state,
cval->control = in_ch + 1; /* based on 1 */
cval->val_type = USB_MIXER_S16;
for (i = 0; i < num_outs; i++) {
- if (check_matrix_bitmap(uac_mixer_unit_bmControls(desc, state->mixer->protocol), in_ch, i, num_outs)) {
+ __u8 *c = uac_mixer_unit_bmControls(desc, state->mixer->protocol);
+
+ if (check_matrix_bitmap(c, in_ch, i, num_outs)) {
cval->cmask |= (1 << i);
cval->channels++;
}
@@ -1485,7 +1543,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state,
get_min_max(cval, 0);
kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
- if (! kctl) {
+ if (!kctl) {
usb_audio_err(state->chip, "cannot malloc kcontrol\n");
kfree(cval);
return;
@@ -1493,9 +1551,10 @@ static void build_mixer_unit_ctl(struct mixer_build *state,
kctl->private_free = usb_mixer_elem_free;
len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name));
- if (! len)
- len = get_term_name(state, iterm, kctl->id.name, sizeof(kctl->id.name), 0);
- if (! len)
+ if (!len)
+ len = get_term_name(state, iterm, kctl->id.name,
+ sizeof(kctl->id.name), 0);
+ if (!len)
len = sprintf(kctl->id.name, "Mixer Source %d", in_ch + 1);
append_ctl_name(kctl, " Volume");
@@ -1504,24 +1563,28 @@ static void build_mixer_unit_ctl(struct mixer_build *state,
snd_usb_mixer_add_control(state->mixer, kctl);
}
-
/*
* parse a mixer unit
*/
-static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, void *raw_desc)
+static int parse_audio_mixer_unit(struct mixer_build *state, int unitid,
+ void *raw_desc)
{
struct uac_mixer_unit_descriptor *desc = raw_desc;
struct usb_audio_term iterm;
int input_pins, num_ins, num_outs;
int pin, ich, err;
- if (desc->bLength < 11 || ! (input_pins = desc->bNrInPins) || ! (num_outs = uac_mixer_unit_bNrChannels(desc))) {
- usb_audio_err(state->chip, "invalid MIXER UNIT descriptor %d\n", unitid);
+ if (desc->bLength < 11 || !(input_pins = desc->bNrInPins) ||
+ !(num_outs = uac_mixer_unit_bNrChannels(desc))) {
+ usb_audio_err(state->chip,
+ "invalid MIXER UNIT descriptor %d\n",
+ unitid);
return -EINVAL;
}
/* no bmControls field (e.g. Maya44) -> ignore */
if (desc->bLength <= 10 + input_pins) {
- usb_audio_dbg(state->chip, "MU %d has no bmControls field\n", unitid);
+ usb_audio_dbg(state->chip, "MU %d has no bmControls field\n",
+ unitid);
return 0;
}
@@ -1535,12 +1598,14 @@ static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, void *r
if (err < 0)
return err;
num_ins += iterm.channels;
- for (; ich < num_ins; ++ich) {
+ for (; ich < num_ins; ich++) {
int och, ich_has_controls = 0;
- for (och = 0; och < num_outs; ++och) {
- if (check_matrix_bitmap(uac_mixer_unit_bmControls(desc, state->mixer->protocol),
- ich, och, num_outs)) {
+ for (och = 0; och < num_outs; och++) {
+ __u8 *c = uac_mixer_unit_bmControls(desc,
+ state->mixer->protocol);
+
+ if (check_matrix_bitmap(c, ich, och, num_outs)) {
ich_has_controls = 1;
break;
}
@@ -1553,13 +1618,13 @@ static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, void *r
return 0;
}
-
/*
* Processing Unit / Extension Unit
*/
/* get callback for processing/extension unit */
-static int mixer_ctl_procunit_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+static int mixer_ctl_procunit_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
{
struct usb_mixer_elem_info *cval = kcontrol->private_data;
int err, val;
@@ -1577,7 +1642,8 @@ static int mixer_ctl_procunit_get(struct snd_kcontrol *kcontrol, struct snd_ctl_
}
/* put callback for processing/extension unit */
-static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
{
struct usb_mixer_elem_info *cval = kcontrol->private_data;
int val, oval, err;
@@ -1606,7 +1672,6 @@ static struct snd_kcontrol_new mixer_procunit_ctl = {
.put = mixer_ctl_procunit_put,
};
-
/*
* predefined data for processing units
*/
@@ -1697,10 +1762,13 @@ static struct procunit_info extunits[] = {
{ USB_XU_DEVICE_OPTIONS, "AnalogueIn Soft Limit", soft_limit_xu_info },
{ 0 }
};
+
/*
* build a processing/extension unit
*/
-static int build_audio_procunit(struct mixer_build *state, int unitid, void *raw_desc, struct procunit_info *list, char *name)
+static int build_audio_procunit(struct mixer_build *state, int unitid,
+ void *raw_desc, struct procunit_info *list,
+ char *name)
{
struct uac_processing_unit_descriptor *desc = raw_desc;
int num_ins = desc->bNrInPins;
@@ -1733,22 +1801,20 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, void *raw
for (info = list; info && info->type; info++)
if (info->type == type)
break;
- if (! info || ! info->type)
+ if (!info || !info->type)
info = &default_info;
for (valinfo = info->values; valinfo->control; valinfo++) {
__u8 *controls = uac_processing_unit_bmControls(desc, state->mixer->protocol);
- if (! (controls[valinfo->control / 8] & (1 << ((valinfo->control % 8) - 1))))
+ if (!(controls[valinfo->control / 8] & (1 << ((valinfo->control % 8) - 1))))
continue;
map = find_map(state, unitid, valinfo->control);
if (check_ignored_ctl(map))
continue;
cval = kzalloc(sizeof(*cval), GFP_KERNEL);
- if (! cval) {
- usb_audio_err(state->chip, "cannot malloc kcontrol\n");
+ if (!cval)
return -ENOMEM;
- }
cval->mixer = state->mixer;
cval->id = unitid;
cval->control = valinfo->control;
@@ -1765,7 +1831,8 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, void *raw
cval->initialized = 1;
} else {
if (type == USB_XU_CLOCK_RATE) {
- /* E-Mu USB 0404/0202/TrackerPre/0204
+ /*
+ * E-Mu USB 0404/0202/TrackerPre/0204
* samplerate control quirk
*/
cval->min = 0;
@@ -1777,60 +1844,69 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, void *raw
}
kctl = snd_ctl_new1(&mixer_procunit_ctl, cval);
- if (! kctl) {
- usb_audio_err(state->chip, "cannot malloc kcontrol\n");
+ if (!kctl) {
kfree(cval);
return -ENOMEM;
}
kctl->private_free = usb_mixer_elem_free;
- if (check_mapped_name(map, kctl->id.name,
- sizeof(kctl->id.name)))
+ if (check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name))) {
/* nothing */ ;
- else if (info->name)
+ } else if (info->name) {
strlcpy(kctl->id.name, info->name, sizeof(kctl->id.name));
- else {
+ } else {
nameid = uac_processing_unit_iProcessing(desc, state->mixer->protocol);
len = 0;
if (nameid)
- len = snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name));
- if (! len)
+ len = snd_usb_copy_string_desc(state, nameid,
+ kctl->id.name,
+ sizeof(kctl->id.name));
+ if (!len)
strlcpy(kctl->id.name, name, sizeof(kctl->id.name));
}
append_ctl_name(kctl, " ");
append_ctl_name(kctl, valinfo->suffix);
usb_audio_dbg(state->chip,
- "[%d] PU [%s] ch = %d, val = %d/%d\n",
- cval->id, kctl->id.name, cval->channels, cval->min, cval->max);
- if ((err = snd_usb_mixer_add_control(state->mixer, kctl)) < 0)
+ "[%d] PU [%s] ch = %d, val = %d/%d\n",
+ cval->id, kctl->id.name, cval->channels,
+ cval->min, cval->max);
+
+ err = snd_usb_mixer_add_control(state->mixer, kctl);
+ if (err < 0)
return err;
}
return 0;
}
-
-static int parse_audio_processing_unit(struct mixer_build *state, int unitid, void *raw_desc)
+static int parse_audio_processing_unit(struct mixer_build *state, int unitid,
+ void *raw_desc)
{
- return build_audio_procunit(state, unitid, raw_desc, procunits, "Processing Unit");
+ return build_audio_procunit(state, unitid, raw_desc,
+ procunits, "Processing Unit");
}
-static int parse_audio_extension_unit(struct mixer_build *state, int unitid, void *raw_desc)
+static int parse_audio_extension_unit(struct mixer_build *state, int unitid,
+ void *raw_desc)
{
- /* Note that we parse extension units with processing unit descriptors.
- * That's ok as the layout is the same */
- return build_audio_procunit(state, unitid, raw_desc, extunits, "Extension Unit");
+ /*
+ * Note that we parse extension units with processing unit descriptors.
+ * That's ok as the layout is the same.
+ */
+ return build_audio_procunit(state, unitid, raw_desc,
+ extunits, "Extension Unit");
}
-
/*
* Selector Unit
*/
-/* info callback for selector unit
+/*
+ * info callback for selector unit
* use an enumerator type for routing
*/
-static int mixer_ctl_selector_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+static int mixer_ctl_selector_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
{
struct usb_mixer_elem_info *cval = kcontrol->private_data;
const char **itemlist = (const char **)kcontrol->private_value;
@@ -1841,7 +1917,8 @@ static int mixer_ctl_selector_info(struct snd_kcontrol *kcontrol, struct snd_ctl
}
/* get callback for selector unit */
-static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
{
struct usb_mixer_elem_info *cval = kcontrol->private_data;
int val, err;
@@ -1860,7 +1937,8 @@ static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol, struct snd_ctl_
}
/* put callback for selector unit */
-static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
{
struct usb_mixer_elem_info *cval = kcontrol->private_data;
int val, oval, err;
@@ -1889,8 +1967,8 @@ static struct snd_kcontrol_new mixer_selectunit_ctl = {
.put = mixer_ctl_selector_put,
};
-
-/* private free callback.
+/*
+ * private free callback.
* free both private_data and private_value
*/
static void usb_mixer_selector_elem_free(struct snd_kcontrol *kctl)
@@ -1915,7 +1993,8 @@ static void usb_mixer_selector_elem_free(struct snd_kcontrol *kctl)
/*
* parse a selector unit
*/
-static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void *raw_desc)
+static int parse_audio_selector_unit(struct mixer_build *state, int unitid,
+ void *raw_desc)
{
struct uac_selector_unit_descriptor *desc = raw_desc;
unsigned int i, nameid, len;
@@ -1944,10 +2023,8 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void
return 0;
cval = kzalloc(sizeof(*cval), GFP_KERNEL);
- if (! cval) {
- usb_audio_err(state->chip, "cannot malloc kcontrol\n");
+ if (!cval)
return -ENOMEM;
- }
cval->mixer = state->mixer;
cval->id = unitid;
cval->val_type = USB_MIXER_U8;
@@ -1963,8 +2040,7 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void
cval->control = 0;
namelist = kmalloc(sizeof(char *) * desc->bNrInPins, GFP_KERNEL);
- if (! namelist) {
- usb_audio_err(state->chip, "cannot malloc\n");
+ if (!namelist) {
kfree(cval);
return -ENOMEM;
}
@@ -1973,8 +2049,7 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void
struct usb_audio_term iterm;
len = 0;
namelist[i] = kmalloc(MAX_ITEM_NAME_LEN, GFP_KERNEL);
- if (! namelist[i]) {
- usb_audio_err(state->chip, "cannot malloc\n");
+ if (!namelist[i]) {
while (i--)
kfree(namelist[i]);
kfree(namelist);
@@ -1986,7 +2061,7 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void
if (! len && check_input_term(state, desc->baSourceID[i], &iterm) >= 0)
len = get_term_name(state, &iterm, namelist[i], MAX_ITEM_NAME_LEN, 0);
if (! len)
- sprintf(namelist[i], "Input %d", i);
+ sprintf(namelist[i], "Input %u", i);
}
kctl = snd_ctl_new1(&mixer_selectunit_ctl, cval);
@@ -2004,11 +2079,12 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void
if (len)
;
else if (nameid)
- snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name));
+ snd_usb_copy_string_desc(state, nameid, kctl->id.name,
+ sizeof(kctl->id.name));
else {
len = get_term_name(state, &state->oterm,
kctl->id.name, sizeof(kctl->id.name), 0);
- if (! len)
+ if (!len)
strlcpy(kctl->id.name, "USB", sizeof(kctl->id.name));
if (desc->bDescriptorSubtype == UAC2_CLOCK_SELECTOR)
@@ -2027,7 +2103,6 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void
return 0;
}
-
/*
* parse an audio unit recursively
*/
@@ -2125,14 +2200,16 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer)
}
p = NULL;
- while ((p = snd_usb_find_csint_desc(mixer->hostif->extra, mixer->hostif->extralen,
+ while ((p = snd_usb_find_csint_desc(mixer->hostif->extra,
+ mixer->hostif->extralen,
p, UAC_OUTPUT_TERMINAL)) != NULL) {
if (mixer->protocol == UAC_VERSION_1) {
struct uac1_output_terminal_descriptor *desc = p;
if (desc->bLength < sizeof(*desc))
continue; /* invalid descriptor? */
- set_bit(desc->bTerminalID, state.unitbitmap); /* mark terminal ID as visited */
+ /* mark terminal ID as visited */
+ set_bit(desc->bTerminalID, state.unitbitmap);
state.oterm.id = desc->bTerminalID;
state.oterm.type = le16_to_cpu(desc->wTerminalType);
state.oterm.name = desc->iTerminal;
@@ -2144,7 +2221,8 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer)
if (desc->bLength < sizeof(*desc))
continue; /* invalid descriptor? */
- set_bit(desc->bTerminalID, state.unitbitmap); /* mark terminal ID as visited */
+ /* mark terminal ID as visited */
+ set_bit(desc->bTerminalID, state.unitbitmap);
state.oterm.id = desc->bTerminalID;
state.oterm.type = le16_to_cpu(desc->wTerminalType);
state.oterm.name = desc->iTerminal;
@@ -2152,7 +2230,10 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer)
if (err < 0 && err != -EINVAL)
return err;
- /* for UAC2, use the same approach to also add the clock selectors */
+ /*
+ * For UAC2, use the same approach to also add the
+ * clock selectors
+ */
err = parse_audio_unit(&state, desc->bCSourceID);
if (err < 0 && err != -EINVAL)
return err;
@@ -2306,7 +2387,9 @@ static void snd_usb_mixer_interrupt(struct urb *urb)
}
requeue:
- if (ustatus != -ENOENT && ustatus != -ECONNRESET && ustatus != -ESHUTDOWN) {
+ if (ustatus != -ENOENT &&
+ ustatus != -ECONNRESET &&
+ ustatus != -ESHUTDOWN) {
urb->dev = mixer->chip->dev;
usb_submit_urb(urb, GFP_ATOMIC);
}