[alsa-devel] [RFC][PATCH 0/8] A new driver for OXFW970/971 based devices
This series of patch is based on my previous series below: http://mailman.alsa-project.org/pipermail/alsa-devel/2013-December/070424.ht...
This series of patch add a new driver, snd-oxfw, for OXFW970/971 based devices. These chipsets are already supported by snd-firewire-speakers but this new driver aim to support recording equipment.
Current supported devices: * Behringer F-Control Audio 202 * Mackie Onyx-i series (former model) * Mackie Onyx Satellite
Devices possible to be supported if identifying IDs: * Mackie, d.2 pro * Mackie, d.4 pro * Mackie, U.420 * Mackie, U.420d * Mackie, Tapco Link.Firewire
I worked with Behringer F-Control Audio 202. So I want someone to test with the other devices, especially about some quirks.
Clemens, would you gime me your opinions about issues below? And if you know something about this chipset, would you share the information?
1.snd-firewire-speakers and snd-oxfw With this series of patches, ALSA has two drivers for the same chipsets. Merging these two drivers is a bit difficult because: - snd-firewire-speakers has control interface but snd-oxfw don't - snd-firewire-speakers don't support in-stream but snd-oxfw supports
2.currently snd-oxfw works with streams in SYT-Match Due to invalid sequence of 'presentation timestamp' in transmitted packets. In detail, please see 4th patch.
3.assumption of channnel formation Some commands may not be implemented. In detail, please see 3rd patch.
4.don't check source of clock All of supported devices (including can be) don't have the functionality to switch source of clock. So I want to omit this.
For issue 3 and 4, Descriptor mechanism in 'Enhancements to the AV/C General Specification 3.0 Version 1.1' may be a best solution but it costs much to implement against the number of available devices. I want to avoid this huge mechanism if possible.
Regards
Takashi Sakamoto
Takashi Sakamoto (8): oxfw: Add skelton for OXFW970/971 based devices oxfw: Read firmware version to name card oxfw: Add some AV/C commands for channel formation of AMDTP stream oxfw: Add connections and streams management oxfw: Add proc interface for debugging purpose oxfw: Add MIDI interface oxfw: Add PCM interface oxfw: Add hwdep interface
include/uapi/sound/asound.h | 3 +- include/uapi/sound/firewire.h | 3 +- sound/firewire/Kconfig | 17 + sound/firewire/Makefile | 1 + sound/firewire/oxfw/Makefile | 3 + sound/firewire/oxfw/oxfw.c | 243 ++++++++++++++ sound/firewire/oxfw/oxfw.h | 140 ++++++++ sound/firewire/oxfw/oxfw_command.c | 161 ++++++++++ sound/firewire/oxfw/oxfw_hwdep.c | 197 ++++++++++++ sound/firewire/oxfw/oxfw_midi.c | 156 +++++++++ sound/firewire/oxfw/oxfw_pcm.c | 421 ++++++++++++++++++++++++ sound/firewire/oxfw/oxfw_proc.c | 61 ++++ sound/firewire/oxfw/oxfw_stream.c | 639 +++++++++++++++++++++++++++++++++++++ 13 files changed, 2043 insertions(+), 2 deletions(-) create mode 100644 sound/firewire/oxfw/Makefile create mode 100644 sound/firewire/oxfw/oxfw.c create mode 100644 sound/firewire/oxfw/oxfw.h create mode 100644 sound/firewire/oxfw/oxfw_command.c create mode 100644 sound/firewire/oxfw/oxfw_hwdep.c create mode 100644 sound/firewire/oxfw/oxfw_midi.c create mode 100644 sound/firewire/oxfw/oxfw_pcm.c create mode 100644 sound/firewire/oxfw/oxfw_proc.c create mode 100644 sound/firewire/oxfw/oxfw_stream.c
This commit add a new driver for BeBoB based devices with no functionality. This driver just create/remove card instance according to callbacks.
OXFW970/971 are chipsets produced by Oxford Semiconductor for Multi-Channel Isochronous Streaming FireWire Audio Controller.
Current supported devices: - Behringer F-Control Audio 202 - Mackie Onyx-i series (former model) - Mackie Onyx Satellite
Devices possible to be supported if identifying IDs: - Mackie, d.2 pro - Mackie, d.4 pro - Mackie, U.420 - Mackie, U.420d - Mackie, Tapco Link.Firewire/
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 17 +++++ sound/firewire/Makefile | 1 + sound/firewire/oxfw/Makefile | 2 + sound/firewire/oxfw/oxfw.c | 162 +++++++++++++++++++++++++++++++++++++++++++ sound/firewire/oxfw/oxfw.h | 42 +++++++++++ 5 files changed, 224 insertions(+) create mode 100644 sound/firewire/oxfw/Makefile create mode 100644 sound/firewire/oxfw/oxfw.c create mode 100644 sound/firewire/oxfw/oxfw.h
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index b2c5a7e..f5a466d 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -117,4 +117,21 @@ config SND_BEBOB To compile this driver as a module, choose M here: the module will be called snd-bebob.
+config SND_OXFW + tristate "Oxford Semiconductor OXFW970/971 support" + select SND_FIREWIRE_LIB + select SND_RAWMIDI + select SND_PCM + select SND_HWDEP + help + Say Y here to include support for FireWire devices based on + Oxford Semiconductor OXFW970/971. SND_FIREWIRE_SPEAKERS also + supports this chipset but this driver supports recording devices: + * Behringer F-Control Audio 202 + * Mackie Onyx-i series (former model) + * Mackie Onyx Satellite + + To compile this driver as a module, choose M here: the module + will be called snd-oxfw. + endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index fad8d49..cbcd6ef 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_SND_ISIGHT) += snd-isight.o obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o obj-$(CONFIG_SND_FIREWORKS) += fireworks/ obj-$(CONFIG_SND_BEBOB) += bebob/ +obj-$(CONFIG_SND_OXFW) += oxfw/ diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile new file mode 100644 index 0000000..9ca49c0 --- /dev/null +++ b/sound/firewire/oxfw/Makefile @@ -0,0 +1,2 @@ +snd-oxfw-objs := oxfw.o +obj-m += snd-oxfw.o diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c new file mode 100644 index 0000000..34be292 --- /dev/null +++ b/sound/firewire/oxfw/oxfw.c @@ -0,0 +1,162 @@ +/* + * oxfw.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2013 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * OXFW970/971 are chipsets produced by Oxford Semiconductor for Multi-Channel + * Isochronous Streaming FireWire Audio Controller. + */ +#include "oxfw.h" + +MODULE_DESCRIPTION("Oxford Semiconductor OXFW970/971 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 OXFW970/971 sound card"); + +static DEFINE_MUTEX(devices_mutex); +static unsigned int devices_used; + +#define VEN_BEHRINGER 0x001564 +#define VEN_LOUD 0x000ff2 + +static void +oxfw_card_free(struct snd_card *card) +{ + struct snd_oxfw *oxfw = card->private_data; + + if (oxfw->card_index >= 0) { + mutex_lock(&devices_mutex); + devices_used &= ~BIT(oxfw->card_index); + mutex_unlock(&devices_mutex); + } + + mutex_destroy(&oxfw->mutex); + + return; +} + +static int +oxfw_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_card *card; + struct snd_oxfw *oxfw; + unsigned int card_index; + int err; + + mutex_lock(&devices_mutex); + + for (card_index = 0; card_index < SNDRV_CARDS; card_index++) { + if (!(devices_used & BIT(card_index)) && enable[card_index]) + break; + } + if (card_index >= SNDRV_CARDS) { + err = -ENOENT; + goto end; + } + + err = snd_card_create(index[card_index], id[card_index], + THIS_MODULE, sizeof(struct snd_oxfw), &card); + if (err < 0) + goto end; + card->private_free = oxfw_card_free; + + oxfw = card->private_data; + oxfw->card = card; + oxfw->device = fw_parent_device(unit); + oxfw->unit = unit; + oxfw->card_index = -1; + mutex_init(&oxfw->mutex); + spin_lock_init(&oxfw->lock); + + snd_card_set_dev(card, &unit->device); + err = snd_card_register(card); + if (err < 0) { + snd_card_free(card); + goto error; + } + dev_set_drvdata(&unit->device, oxfw); + devices_used |= BIT(card_index); + oxfw->card_index = card_index; +end: + mutex_unlock(&devices_mutex); + return err; +error: + snd_card_free(card); + mutex_unlock(&devices_mutex); + return err; +} + +static void +oxfw_update(struct fw_unit *unit) +{ + return; +} + +static void +oxfw_remove(struct fw_unit *unit) +{ + struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device); + + snd_card_disconnect(oxfw->card); + snd_card_free_when_closed(oxfw->card); +} + +static const struct ieee1394_device_id oxfw_id_table[] = { + /* Behringer, F-Control Audio 202 */ + SND_OXFW_DEV_ENTRY(VEN_BEHRINGER, 0x00fc22), + /* Mackie, Onyx-i (former model) */ + SND_OXFW_DEV_ENTRY(VEN_LOUD, 0x081216), + /* Mackie, Onyx Sattelite */ + SND_OXFW_DEV_ENTRY(VEN_LOUD, 0x00200f), + /* IDs are unknown but able to be supported */ + /* Mackie, d.2 pro */ + /* Mackie, d.4 pro */ + /* Mackie, U.420 */ + /* Mackie, U.420d */ + /* Mackie, Tapco Link.Firewire */ + {} +}; +MODULE_DEVICE_TABLE(ieee1394, oxfw_id_table); + +static struct fw_driver oxfw_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = oxfw_probe, + .update = oxfw_update, + .remove = oxfw_remove, + .id_table = oxfw_id_table, +}; + +static int __init +snd_oxfw_init(void) +{ + return driver_register(&oxfw_driver.driver); +} + +static void __exit +snd_oxfw_exit(void) +{ + driver_unregister(&oxfw_driver.driver); + mutex_destroy(&devices_mutex); +} + +module_init(snd_oxfw_init); +module_exit(snd_oxfw_exit); diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h new file mode 100644 index 0000000..3aee9e7 --- /dev/null +++ b/sound/firewire/oxfw/oxfw.h @@ -0,0 +1,42 @@ +/* + * oxford.h - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2013 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef SOUND_OXFW_H_INCLUDED +#define SOUND_OXFW_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> + +struct snd_oxfw { + struct snd_card *card; + struct fw_device *device; + struct fw_unit *unit; + int card_index; + + struct mutex mutex; + spinlock_t lock; +}; + +#define SND_OXFW_DEV_ENTRY(vendor, model) \ +{ \ + .match_flags = IEEE1394_MATCH_VENDOR_ID | \ + IEEE1394_MATCH_MODEL_ID, \ + .vendor_id = vendor, \ + .model_id = model, \ +} + +#endif
There is already an driver for OXFW970/971, snd-firewire-speakers(speakers.c). But this new driver aims to support recording equipment.
It's convinient to apply the same way for card name.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/oxfw.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++ sound/firewire/oxfw/oxfw.h | 2 ++ 2 files changed, 53 insertions(+)
diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index 34be292..e90c53a 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -30,9 +30,56 @@ MODULE_PARM_DESC(enable, "enable OXFW970/971 sound card"); static DEFINE_MUTEX(devices_mutex); static unsigned int devices_used;
+#define OXFW_FIRMWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x50000) + +#define OXFW_HARDWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x90020) +#define OXFW_HARDWARE_ID_OXFW970 0x39443841 +#define OXFW_HARDWARE_ID_OXFW971 0x39373100 + #define VEN_BEHRINGER 0x001564 #define VEN_LOUD 0x000ff2
+static int +name_device(struct snd_oxfw *oxfw, unsigned int vendor_id) +{ + char vendor[24] = {0}; + char model[24] = {0}; + u32 version = 0; + int err; + + /* get vendor name from root directory */ + err = fw_csr_string(oxfw->device->config_rom + 5, CSR_VENDOR, + vendor, sizeof(vendor)); + if (err < 0) + goto end; + + /* get model name from unit directory */ + err = fw_csr_string(oxfw->unit->directory, CSR_MODEL, + model, sizeof(model)); + if (err < 0) + goto end; + + /* 0x970?vvvv or 0x971?vvvv, where vvvv = firmware version */ + err = snd_fw_transaction(oxfw->unit, TCODE_READ_QUADLET_REQUEST, + OXFW_FIRMWARE_ID_ADDRESS, + &version, sizeof(u32), 0); + if (err < 0) + goto end; + be32_to_cpus(&version); + + strcpy(oxfw->card->driver, "OXFW"); + strcpy(oxfw->card->shortname, model); + snprintf(oxfw->card->longname, sizeof(oxfw->card->longname), + "%s %s (%x %04x), GUID %08x%08x at %s, S%d", + vendor, model, version >> 20, version & 0xffff, + oxfw->device->config_rom[3], oxfw->device->config_rom[4], + dev_name(&oxfw->unit->device), + 100 << oxfw->device->max_speed); + strcpy(oxfw->card->mixername, oxfw->card->driver); +end: + return err; +} + static void oxfw_card_free(struct snd_card *card) { @@ -83,6 +130,10 @@ oxfw_probe(struct fw_unit *unit, mutex_init(&oxfw->mutex); spin_lock_init(&oxfw->lock);
+ err = name_device(oxfw, entry->vendor_id); + if (err < 0) + goto error; + snd_card_set_dev(card, &unit->device); err = snd_card_register(card); if (err < 0) { diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index 3aee9e7..e75eb70 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -21,6 +21,8 @@ #include <sound/core.h> #include <sound/initval.h>
+#include "../lib.h" + struct snd_oxfw { struct snd_card *card; struct fw_device *device;
OXFW970/971 may supports AV/C Stream Format Information Specification 1.1 Working Draft (Apr 2005, 1394TA). So this driver uses 'EXTENDED STREAM FORMAT INFORMATION' command.
This command has two subfunctions, SINGLE and LIST. To use SINGLE subfunction, drivers can know current formation of AMDTP stream. To use LIST subfunction, drivers can know all of available formations of AMDTP stream. But there are some devices which don't implement LIST subfunction.
When device don't implement LIST subfunction, this driver assume formation of AMDTP stream. The way of assumption is: 1.getting current formation with SINGLE subfunction 2.checking all of available sampling rate 3.applying current formation for all of available sampling rate
I note that this assumption is too bad when the device don't implement LIST subfunction and different formation for AMDTP streams at every available sampling rates. I believe this is rare.
This driver also uses another assumption for available MIDI ports. If one of formations has MIDI conformant data channel, then this driver assume the device has one MIDI ports. I believe it rare that device has several MIDI ports.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/Makefile | 2 +- sound/firewire/oxfw/oxfw.c | 4 +- sound/firewire/oxfw/oxfw.h | 55 +++++++++ sound/firewire/oxfw/oxfw_command.c | 161 ++++++++++++++++++++++++ sound/firewire/oxfw/oxfw_stream.c | 243 +++++++++++++++++++++++++++++++++++++ 5 files changed, 463 insertions(+), 2 deletions(-) create mode 100644 sound/firewire/oxfw/oxfw_command.c create mode 100644 sound/firewire/oxfw/oxfw_stream.c
diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile index 9ca49c0..5eb3d87 100644 --- a/sound/firewire/oxfw/Makefile +++ b/sound/firewire/oxfw/Makefile @@ -1,2 +1,2 @@ -snd-oxfw-objs := oxfw.o +snd-oxfw-objs := oxfw_command.o oxfw_stream.o oxfw.o obj-m += snd-oxfw.o diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index e90c53a..c030fc0 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -155,7 +155,9 @@ error: static void oxfw_update(struct fw_unit *unit) { - return; + struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device); + + fcp_bus_reset(oxfw->unit); }
static void diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index e75eb70..e82717a 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -22,6 +22,16 @@ #include <sound/initval.h>
#include "../lib.h" +#include "../fcp.h" +#include "../amdtp.h" + +#define SND_OXFW_RATE_TABLE_ENTRIES 7 +struct snd_oxfw_stream_formation { + unsigned int pcm; + unsigned int midi; +}; +/* this is a lookup table for index of stream formations */ +extern const unsigned int snd_oxfw_rate_table[SND_OXFW_RATE_TABLE_ENTRIES];
struct snd_oxfw { struct snd_card *card; @@ -31,8 +41,53 @@ struct snd_oxfw {
struct mutex mutex; spinlock_t lock; + + struct snd_oxfw_stream_formation + tx_stream_formations[SND_OXFW_RATE_TABLE_ENTRIES]; + struct snd_oxfw_stream_formation + rx_stream_formations[SND_OXFW_RATE_TABLE_ENTRIES]; + + unsigned int midi_input_ports; + unsigned int midi_output_ports; };
+/* AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) */ +#define AVC_GENERIC_FRAME_MAXIMUM_BYTES 512 +int avc_stream_get_format(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, + unsigned int eid); +static inline int +avc_stream_get_format_single(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len) +{ + return avc_stream_get_format(unit, dir, pid, buf, len, 0xff); +} +static inline int +avc_stream_get_format_list(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, + unsigned int eid) +{ + return avc_stream_get_format(unit, dir, pid, buf, len, eid); +} + +/* + * AV/C Digital Interface Command Set General Specification 4.2 + * (Sep 2004, 1394TA) + */ +int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short pid); +int snd_oxfw_get_rate(struct snd_oxfw *oxfw, unsigned int *rate, + enum avc_general_plug_dir dir); +int snd_oxfw_set_rate(struct snd_oxfw *oxfw, unsigned int rate, + enum avc_general_plug_dir dir); + +/* for AMDTP streaming */ +int snd_oxfw_stream_discover(struct snd_oxfw *oxfw); + #define SND_OXFW_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/oxfw/oxfw_command.c b/sound/firewire/oxfw/oxfw_command.c new file mode 100644 index 0000000..526518f --- /dev/null +++ b/sound/firewire/oxfw/oxfw_command.c @@ -0,0 +1,161 @@ +/* + * oxfw_command.c - driver for OXFW970/971 based devices + * + * Copyright (c) 2013 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./oxfw.h" + +int avc_stream_get_format(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, + unsigned int eid) +{ + unsigned int subfunc; + int err; + + /* check given buffer */ + if ((buf == NULL) || (*len < 12)) { + err = -EINVAL; + goto end; + } + + if (eid == 0xff) + subfunc = 0xc0; /* SINGLE */ + else + subfunc = 0xc1; /* LIST */ + + buf[0] = 0x01; /* STATUS */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0xbf; /* EXTENDED STREAM FORMAT INFORMATION */ + buf[3] = subfunc; /* SINGLE or LIST */ + buf[4] = dir; /* Plug Direction */ + buf[5] = 0x00; /* Unit */ + buf[6] = 0x00; /* PCR (Isochronous Plug) */ + buf[7] = 0xff & pid; /* Plug ID */ + buf[8] = 0xff; /* Padding */ + buf[9] = 0xff; /* support status in response */ + buf[10] = 0xff & eid; /* entry ID */ + buf[11] = 0xff; /* padding */ + + /* do transaction and check buf[1-7] are the same against command */ + err = fcp_avc_transaction(unit, buf, 12, buf, *len, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7)); + if (err < 0) { + goto end; + /* reach the end of entries */ + } else if (buf[0] == 0x0a) { + err = 0; + *len = 0; + goto end; + } else if (buf[0] != 0x0c) { + err = -EINVAL; + goto end; + /* the content starts at 11th bytes */ + } else if (err < 9) { + err = -EIO; + goto end; + } else if ((subfunc == 0xc1) && (buf[10] != eid)) { + err = -EIO; + goto end; + } + + /* strip */ + memmove(buf, buf + 10, err - 10); + *len = err - 10; + err = 0; +end: + return err; +} + +int avc_general_inquiry_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; + + for (sfc = 0; sfc < CIP_SFC_COUNT; sfc++) { + if (amdtp_rate_table[sfc] == rate) + break; + } + if (sfc == CIP_SFC_COUNT) + return -EINVAL; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x02; /* SPECIFIC INQUIRY */ + 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) + goto end; + + /* check length */ + if (err != 8) { + dev_err(&unit->device, "failed to inquiry sample rate\n"); + err = -EIO; + goto end; + } + + /* return response code */ + err = buf[0]; +end: + kfree(buf); + return err; +} + +int snd_oxfw_get_rate(struct snd_oxfw *oxfw, unsigned int *rate, + enum avc_general_plug_dir dir) +{ + int err; + + err = avc_general_get_sig_fmt(oxfw->unit, rate, dir, 0); + if (err < 0) + goto end; + + /* IMPLEMENTED/STABLE is OK */ + if (err != 0x0c) { + dev_err(&oxfw->unit->device, + "failed to get sampling rate\n"); + err = -EIO; + } +end: + return err; +} + +int snd_oxfw_set_rate(struct snd_oxfw *oxfw, unsigned int rate, + enum avc_general_plug_dir dir) +{ + int err; + + err = avc_general_set_sig_fmt(oxfw->unit, rate, dir, 0); + if (err < 0) + goto end; + + /* ACCEPTED or INTERIM is OK */ + if ((err != 0x0f) && (err != 0x09)) { + dev_err(&oxfw->unit->device, + "failed to set sampling rate\n"); + err = -EIO; + } +end: + return err; +} diff --git a/sound/firewire/oxfw/oxfw_stream.c b/sound/firewire/oxfw/oxfw_stream.c new file mode 100644 index 0000000..60ad700 --- /dev/null +++ b/sound/firewire/oxfw/oxfw_stream.c @@ -0,0 +1,243 @@ +/* + * oxfw_stream.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2013 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./oxfw.h" + +/* + * According to their datasheet: + * OXFW970 32.0/44.1/48.0/96.0 Khz, 4 audio channels I/O + * OXFW971: 32.0/44.1/48.0/88.2/96.0 kHz, 16 audio channels I/O, MIDI I/O + * + * OXFW970 seems not to implement 'LIST' subfunction for 'EXTENDED STREAM + * FORMAT INFORMATION' defined in 'AV/C Stream Format Information + * Specification 1.1 (Apr 2005, 1394TA)'. So this module uses an assumption + * that OXFW970 doesn't change its formation of channels in AMDTP stream. + */ +const unsigned int snd_oxfw_rate_table[SND_OXFW_RATE_TABLE_ENTRIES] = { + [0] = 32000, + [1] = 44100, + [2] = 48000, + [3] = 88200, + [4] = 96000, + [5] = 176400, + [6] = 192000, +}; + +/* + * See Table 5.7 – Sampling frequency for Multi-bit Audio + * at AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) + */ +static const unsigned int avc_stream_rate_table[] = { + [0] = 0x02, + [1] = 0x03, + [2] = 0x04, + [3] = 0x0a, + [4] = 0x05, + [5] = 0x06, + [6] = 0x07, +}; + +/* + * See Table 6.16 - AM824 Stream Format + * Figure 6.19 - format_information field for AM824 Compound + * at AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) + */ +static int +parse_stream_formation(u8 *buf, unsigned int len, + struct snd_oxfw_stream_formation *formation, + unsigned int *index) +{ + unsigned int 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 the sampling rate */ + for (*index = 0; *index < sizeof(avc_stream_rate_table); *index += 1) { + if (buf[2] == avc_stream_rate_table[*index]) + break; + } + if (*index == sizeof(avc_stream_rate_table)) + return -ENOSYS; + + for (e = 0; e < buf[4]; e++) { + channels = buf[5 + e * 2]; + format = buf[6 + e * 2]; + + switch (format) { + /* IEC 60958-3 */ + case 0x00: + /* Multi Bit Linear Audio (Raw) */ + case 0x06: + formation[*index].pcm += channels; + break; + /* MIDI comformant */ + case 0x0d: + formation[*index].midi += channels; + break; + /* Multi Bit Linear Audio (DVD-audio) */ + case 0x07: + /* IEC 61937-3 to 7 */ + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + /* One Bit Audio */ + case 0x08: /* (Plain) Raw */ + case 0x09: /* (Plain) SACD */ + case 0x0a: /* (Encoded) Raw */ + case 0x0b: /* (ENcoded) SACD */ + /* High precision Multi-bit Linear Audio */ + case 0x0c: + /* SMPTE Time-Code conformant */ + case 0x0e: + /* Sample Count */ + case 0x0f: + /* Anciliary Data */ + case 0x10: + /* Synchronization Stream (Stereo Raw audio) */ + case 0x40: + /* Don't care */ + case 0xff: + default: + break; /* not supported */ + } + } + + return 0; +} + +static int +assume_stream_formations(struct snd_oxfw *oxfw, enum avc_general_plug_dir dir, + unsigned int pid, u8 *buf, unsigned int *len, + struct snd_oxfw_stream_formation *formations) +{ + unsigned int i, pcm_channels, midi_channels; + int err; + + /* get formation at current sampling rate */ + err = avc_stream_get_format_single(oxfw->unit, dir, pid, buf, len); + if ((err < 0) || (err == 0x80) /* NOT IMPLEMENTED */) + goto end; + + /* parse and set stream formation */ + err = parse_stream_formation(buf, *len, formations, &i); + if (err < 0) + goto end; + + pcm_channels = formations[i].pcm; + midi_channels = formations[i].midi; + + /* apply the formation for each available sampling rate */ + for (i = 0; i < SND_OXFW_RATE_TABLE_ENTRIES; i++) { + err = avc_general_inquiry_sig_fmt(oxfw->unit, + snd_oxfw_rate_table[i], + dir, pid); + if ((err < 0) || (err == 0x08) /* NOT IMPLEMENTED */) + continue; + + formations[i].pcm = pcm_channels; + formations[i].midi = midi_channels; + } +end: + return err; +} + +static int +fill_stream_formations(struct snd_oxfw *oxfw, enum avc_general_plug_dir dir, + unsigned short pid) +{ + u8 *buf; + struct snd_oxfw_stream_formation *formations; + unsigned int i, len, eid; + int err; + + buf = kmalloc(AVC_GENERIC_FRAME_MAXIMUM_BYTES, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + if (dir == AVC_GENERAL_PLUG_DIR_IN) + formations = oxfw->rx_stream_formations; + else + formations = oxfw->tx_stream_formations; + + /* initialize parameters here because of checking implementation */ + eid = 0; + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + memset(buf, 0, len); + + /* get first entry */ + err = avc_stream_get_format_list(oxfw->unit, dir, 0, buf, &len, eid); + if ((err < 0) || (len < 3)) { + /* LIST subfunction is not implemented */ + err = assume_stream_formations(oxfw, dir, pid, buf, &len, + formations); + goto end; + } + + /* LIST subfunction is implemented */ + do { + /* parse and set stream formation */ + err = parse_stream_formation(buf, len, formations, &i); + if (err < 0) + continue; + + /* get next entry */ + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + memset(buf, 0, len); + err = avc_stream_get_format_list(oxfw->unit, dir, 0, + buf, &len, ++eid); + if ((err < 0) || (len < 3)) + break; + } while (eid < SND_OXFW_RATE_TABLE_ENTRIES); +end: + kfree(buf); + return err; +} + +int snd_oxfw_stream_discover(struct snd_oxfw *oxfw) +{ + u8 plugs[AVC_PLUG_INFO_BUF_COUNT]; + unsigned int i; + int err; + + /* the number of plugs for isoc in/out, ext in/out */ + err = avc_general_get_plug_info(oxfw->unit, 0x1f, 0x07, 0x00, plugs); + if (err < 0) + goto end; + if ((plugs[0] == 0) || (plugs[0] == 0)) { + err = -EIO; + goto end; + } + + /* use oPCR[0] */ + err = fill_stream_formations(oxfw, AVC_GENERAL_PLUG_DIR_OUT, 0); + if (err < 0) + goto end; + + /* use iPCR[0] */ + err = fill_stream_formations(oxfw, AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) + goto end; + + /* if its stream has MIDI conformant data channel, add one MIDI port */ + for (i = 0; i < SND_OXFW_RATE_TABLE_ENTRIES; i++) { + if (oxfw->tx_stream_formations[i].midi > 0) + oxfw->midi_input_ports = 1; + else if (oxfw->rx_stream_formations[i].midi > 0) + oxfw->midi_output_ports = 1; + } +end: + return err; +}
This commit adds management functionality for connections and streams. OXFW970/971 use CMP to manage connections and uses AMDTP for streams.
They transmits AMDTP stream in non-blocking mode. This stream has a quirk for 'presentation timestamp'. The sequence of 'presentation timestamp' is invalid even if header of packet shows 'CIP header with SYT field'. So this driver can't reuse it for out-stream.
In this reason, this driver keep to send correct 'presentation timestamp' in out-stream.
And I note that its fluctuation of 'presentation timestamp' is less when out-stream exists (they receive AMDTP stream) than when no out-stream exists.
no out-stream exists: Index Payload CIP_Header_0 CIP_Header_1 38 14 00020092 900103D1 39 12 00020098 900102FF 40 12 0002009D 9001027F 41 14 000200A2 90010396 42 14 000200A8 900102E8 43 12 000200AE 90010219 44 14 000200B3 90010331 45 12 000200B9 9001025F 46 14 000200BE 90010376 47 12 000200C4 900102A1 00 12 000200C9 9001023E 01 14 000200CE 90010358 02 12 000200D4 90010289 03 16 000200D9 900103A3 04 12 000200E0 900102DD 05 14 000200E5 900103F1 06 12 000200EB 90010335 07 12 000200F0 90010263 08 14 000200F5 9001037C 09 12 000200FB 900102AE
out-stream exists: Index Payload CIP_Header_0 CIP_Header_1 38 12 000200BD 900104A8 39 14 000200C2 900104A8 40 12 000200C8 900104AC 41 14 000200CD 900104A9 42 12 000200D3 900104B1 43 14 000200D8 900104A8 44 12 000200DE 900104AA 45 14 000200E3 900104A9 46 14 000200E9 900104AE 47 12 000200EF 900104A8 00 14 000200F4 900104AD 01 12 000200FA 900104A7 02 14 000200FF 900104A9 03 12 00020005 900104A9 04 14 0002000A 900104B1 05 12 00020010 900104AA 06 14 00020015 900104AD 07 12 0002001B 900104A7 08 14 00020020 900104AC 09 12 00020026 900104A7
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/oxfw.c | 10 ++ sound/firewire/oxfw/oxfw.h | 18 ++ sound/firewire/oxfw/oxfw_stream.c | 357 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 385 insertions(+)
diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index c030fc0..f8320af 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -134,6 +134,14 @@ oxfw_probe(struct fw_unit *unit, if (err < 0) goto error;
+ err = snd_oxfw_stream_discover(oxfw); + if (err < 0) + goto error; + + err = snd_oxfw_stream_init_duplex(oxfw); + if (err < 0) + goto error; + snd_card_set_dev(card, &unit->device); err = snd_card_register(card); if (err < 0) { @@ -158,6 +166,7 @@ oxfw_update(struct fw_unit *unit) struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
fcp_bus_reset(oxfw->unit); + snd_oxfw_stream_update_duplex(oxfw); }
static void @@ -165,6 +174,7 @@ oxfw_remove(struct fw_unit *unit) { struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
+ snd_oxfw_stream_destroy_duplex(oxfw); snd_card_disconnect(oxfw->card); snd_card_free_when_closed(oxfw->card); } diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index e82717a..34ea0c8 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -24,6 +24,9 @@ #include "../lib.h" #include "../fcp.h" #include "../amdtp.h" +#include "../packets-buffer.h" +#include "../iso-resources.h" +#include "../cmp.h"
#define SND_OXFW_RATE_TABLE_ENTRIES 7 struct snd_oxfw_stream_formation { @@ -49,6 +52,11 @@ struct snd_oxfw {
unsigned int midi_input_ports; unsigned int midi_output_ports; + + struct cmp_connection out_conn; + struct amdtp_stream tx_stream; + struct cmp_connection in_conn; + struct amdtp_stream rx_stream; };
/* AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) */ @@ -86,6 +94,16 @@ int snd_oxfw_set_rate(struct snd_oxfw *oxfw, unsigned int rate, enum avc_general_plug_dir dir);
/* for AMDTP streaming */ +int snd_oxfw_stream_get_rate(struct snd_oxfw *oxfw, unsigned int *rate); +int snd_oxfw_stream_set_rate(struct snd_oxfw *oxfw, unsigned int rate); +int snd_oxfw_stream_init_duplex(struct snd_oxfw *oxfw); +int snd_oxfw_stream_start_duplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream, + unsigned int sampling_rate); +int snd_oxfw_stream_stop_duplex(struct snd_oxfw *oxfw); +void snd_oxfw_stream_update_duplex(struct snd_oxfw *oxfw); +void snd_oxfw_stream_destroy_duplex(struct snd_oxfw *oxfw); + int snd_oxfw_stream_discover(struct snd_oxfw *oxfw);
#define SND_OXFW_DEV_ENTRY(vendor, model) \ diff --git a/sound/firewire/oxfw/oxfw_stream.c b/sound/firewire/oxfw/oxfw_stream.c index 60ad700..ad2dd86 100644 --- a/sound/firewire/oxfw/oxfw_stream.c +++ b/sound/firewire/oxfw/oxfw_stream.c @@ -17,6 +17,15 @@ * FORMAT INFORMATION' defined in 'AV/C Stream Format Information * Specification 1.1 (Apr 2005, 1394TA)'. So this module uses an assumption * that OXFW970 doesn't change its formation of channels in AMDTP stream. + * + * They transmit packet following to 'CIP header with SYT field' defined in + * IEC 61883-1. But the sequence of value in SYT field is not compliant. So + * this module doesn't use the value of SYT field in in-packets. Then this + * module performs as a 'master of synchronization'. In this way, this module + * hopes the device to pick up the value of SYT value in out-packet which + * this module transmits. But the device seems not to use it for in-packet + * which the device transmits. Concluding, it doesn't matter whether this + * module perform as a master or slave. */ const unsigned int snd_oxfw_rate_table[SND_OXFW_RATE_TABLE_ENTRIES] = { [0] = 32000, @@ -42,6 +51,354 @@ static const unsigned int avc_stream_rate_table[] = { [6] = 0x07, };
+int snd_oxfw_stream_get_rate(struct snd_oxfw *oxfw, unsigned int *curr_rate) +{ + unsigned int tx_rate, rx_rate; + int err; + + err = snd_oxfw_get_rate(oxfw, &tx_rate, AVC_GENERAL_PLUG_DIR_OUT); + if (err < 0) + goto end; + + err = snd_oxfw_get_rate(oxfw, &rx_rate, AVC_GENERAL_PLUG_DIR_IN); + 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 = snd_oxfw_set_rate(oxfw, rx_rate, AVC_GENERAL_PLUG_DIR_IN); +end: + return err; +} + +int snd_oxfw_stream_set_rate(struct snd_oxfw *oxfw, unsigned int rate) +{ + int err; + + err = snd_oxfw_set_rate(oxfw, rate, AVC_GENERAL_PLUG_DIR_OUT); + if (err < 0) + goto end; + + err = snd_oxfw_set_rate(oxfw, rate, AVC_GENERAL_PLUG_DIR_IN); +end: + return err; +} + +static int +check_connection_used_by_others(struct snd_oxfw *oxfw, + struct amdtp_stream *s, bool *used) +{ + struct cmp_connection *conn; + int err; + + if (s == &oxfw->tx_stream) + conn = &oxfw->out_conn; + else + conn = &oxfw->in_conn; + + err = cmp_connection_check_used(conn, used); + if (err >= 0) + *used = (*used && !amdtp_stream_running(s)); + + return err; +} + +static int +init_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + enum cmp_direction c_dir; + enum amdtp_stream_direction s_dir; + int err; + + if (stream == &oxfw->tx_stream) { + conn = &oxfw->out_conn; + c_dir = CMP_OUTPUT; + s_dir = AMDTP_IN_STREAM; + } else { + conn = &oxfw->in_conn; + c_dir = CMP_INPUT; + s_dir = AMDTP_OUT_STREAM; + } + + err = cmp_connection_init(conn, oxfw->unit, c_dir, 0); + if (err < 0) + goto end; + + err = amdtp_stream_init(stream, oxfw->unit, s_dir, CIP_NONBLOCKING); + if (err < 0) + cmp_connection_destroy(conn); +end: + return err; +} + + +static void +stop_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream) +{ + if (amdtp_stream_running(stream)) + amdtp_stream_stop(stream); + + if (stream == &oxfw->tx_stream) + cmp_connection_break(&oxfw->out_conn); + else + cmp_connection_break(&oxfw->in_conn); + + return; +} + +static int +start_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream, + unsigned int sampling_rate) +{ + struct cmp_connection *conn; + unsigned int i, pcm_channels, midi_ports; + int err; + + /* already running */ + if (amdtp_stream_running(stream)) { + err = 0; + goto end; + } + + for (i = 0; i < sizeof(snd_oxfw_rate_table); i++) { + if (snd_oxfw_rate_table[i] == sampling_rate) + break; + } + if (i == sizeof(snd_oxfw_rate_table)) { + err = -EINVAL; + goto end; + } + + /* set stream formation */ + if (stream == &oxfw->tx_stream) { + conn = &oxfw->out_conn; + pcm_channels = oxfw->tx_stream_formations[i].pcm; + midi_ports = oxfw->tx_stream_formations[i].midi * 8; + } else { + conn = &oxfw->in_conn; + pcm_channels = oxfw->rx_stream_formations[i].pcm; + midi_ports = oxfw->rx_stream_formations[i].midi * 8; + } + 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(oxfw, stream); + + /* wait first callback */ + if (!amdtp_stream_wait_callback(stream)) { + stop_stream(oxfw, stream); + err = -ETIMEDOUT; + } +end: + return err; +} + +static void +update_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + + if (&oxfw->tx_stream == stream) + conn = &oxfw->out_conn; + else + conn = &oxfw->in_conn; + + if (cmp_connection_update(conn) < 0) { + amdtp_stream_pcm_abort(stream); + mutex_lock(&oxfw->mutex); + stop_stream(oxfw, stream); + mutex_unlock(&oxfw->mutex); + return; + } + amdtp_stream_update(stream); +} + +static void +destroy_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream) +{ + stop_stream(oxfw, stream); + + if (stream == &oxfw->tx_stream) + cmp_connection_destroy(&oxfw->out_conn); + else + cmp_connection_destroy(&oxfw->in_conn); +} + +static int +get_roles(struct snd_oxfw *oxfw, enum cip_flags *sync_mode, + struct amdtp_stream **master, struct amdtp_stream **slave) +{ + /* It doesn't matter. So this module perform as a sync master */ + *sync_mode = 0x00; + *master = &oxfw->rx_stream; + *slave = &oxfw->tx_stream; + + return 0; +} + +int snd_oxfw_stream_init_duplex(struct snd_oxfw *oxfw) +{ + int err; + + err = init_stream(oxfw, &oxfw->tx_stream); + if (err < 0) + goto end; + + err = init_stream(oxfw, &oxfw->rx_stream); +end: + return err; +} + +int snd_oxfw_stream_start_duplex(struct snd_oxfw *oxfw, + struct amdtp_stream *request, + unsigned int rate) +{ + struct amdtp_stream *master, *slave; + enum cip_flags sync_mode; + unsigned int curr_rate; + bool slave_flag, used; + int err; + + mutex_lock(&oxfw->mutex); + + err = get_roles(oxfw, &sync_mode, &master, &slave); + if (err < 0) + goto end; + + if ((request == slave) || amdtp_stream_running(slave)) + slave_flag = true; + else + slave_flag = false; + + /* + * Considering JACK/FFADO streaming: + * TODO: This can be removed hwdep functionality becomes popular. + */ + err = check_connection_used_by_others(oxfw, master, &used); + if (err < 0) + goto end; + if (used) { + dev_err(&oxfw->unit->device, + "connections established by others: %d\n", + used); + err = -EBUSY; + goto end; + } + + /* get current rate */ + err = snd_oxfw_stream_get_rate(oxfw, &curr_rate); + if (err < 0) + goto end; + if (rate == 0) + rate = curr_rate; + + /* change sampling rate if needed */ + if (rate != curr_rate) { + /* slave is just for MIDI stream */ + if (amdtp_stream_running(slave) && + !amdtp_stream_pcm_running(slave)) + amdtp_stream_stop(slave); + + /* master is just for MIDI stream */ + if (amdtp_stream_running(master) && + !amdtp_stream_pcm_running(master)) + amdtp_stream_stop(master); + + err = snd_oxfw_stream_set_rate(oxfw, rate); + if (err < 0) + goto end; + } + + /* master should be always running */ + if (!amdtp_stream_running(master)) { + err = start_stream(oxfw, master, rate); + if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to run AMDTP master stream:%d\n", err); + goto end; + } + } + + /* start slave if needed */ + if (slave_flag && !amdtp_stream_running(slave)) { + err = start_stream(oxfw, slave, rate); + if (err < 0) + dev_err(&oxfw->unit->device, + "fail to run AMDTP slave stream:%d\n", err); + } +end: + mutex_unlock(&oxfw->mutex); + return err; +} + +int snd_oxfw_stream_stop_duplex(struct snd_oxfw *oxfw) +{ + struct amdtp_stream *master, *slave; + enum cip_flags sync_mode; + int err; + + mutex_lock(&oxfw->mutex); + + err = get_roles(oxfw, &sync_mode, &master, &slave); + if (err < 0) + goto end; + + if (amdtp_stream_pcm_running(slave) || + amdtp_stream_midi_running(slave)) + goto end; + + stop_stream(oxfw, slave); + + if (amdtp_stream_pcm_running(master) || + amdtp_stream_midi_running(master)) + goto end; + + stop_stream(oxfw, master); +end: + mutex_unlock(&oxfw->mutex); + return err; +} + +void snd_oxfw_stream_update_duplex(struct snd_oxfw *oxfw) +{ + mutex_lock(&oxfw->mutex); + + update_stream(oxfw, &oxfw->rx_stream); + update_stream(oxfw, &oxfw->tx_stream); + + mutex_unlock(&oxfw->mutex); +} + +void snd_oxfw_stream_destroy_duplex(struct snd_oxfw *oxfw) +{ + mutex_lock(&oxfw->mutex); + + if (amdtp_stream_pcm_running(&oxfw->rx_stream)) + amdtp_stream_pcm_abort(&oxfw->rx_stream); + if (amdtp_stream_pcm_running(&oxfw->tx_stream)) + amdtp_stream_pcm_abort(&oxfw->tx_stream); + + destroy_stream(oxfw, &oxfw->rx_stream); + destroy_stream(oxfw, &oxfw->tx_stream); + + mutex_unlock(&oxfw->mutex); +} + /* * See Table 6.16 - AM824 Stream Format * Figure 6.19 - format_information field for AM824 Compound
This commit adds proc interface to get information for debugging. - stream formation - current sampling rate
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/Makefile | 2 +- sound/firewire/oxfw/oxfw.c | 2 ++ sound/firewire/oxfw/oxfw.h | 3 ++ sound/firewire/oxfw/oxfw_proc.c | 61 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/oxfw/oxfw_proc.c
diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile index 5eb3d87..ab7865d 100644 --- a/sound/firewire/oxfw/Makefile +++ b/sound/firewire/oxfw/Makefile @@ -1,2 +1,2 @@ -snd-oxfw-objs := oxfw_command.o oxfw_stream.o oxfw.o +snd-oxfw-objs := oxfw_command.o oxfw_stream.o oxfw_proc.o oxfw.o obj-m += snd-oxfw.o diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index f8320af..8298cba 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -142,6 +142,8 @@ oxfw_probe(struct fw_unit *unit, if (err < 0) goto error;
+ snd_oxfw_proc_init(oxfw); + snd_card_set_dev(card, &unit->device); err = snd_card_register(card); if (err < 0) { diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index 34ea0c8..7c02448e 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -20,6 +20,7 @@
#include <sound/core.h> #include <sound/initval.h> +#include <sound/info.h>
#include "../lib.h" #include "../fcp.h" @@ -106,6 +107,8 @@ void snd_oxfw_stream_destroy_duplex(struct snd_oxfw *oxfw);
int snd_oxfw_stream_discover(struct snd_oxfw *oxfw);
+void snd_oxfw_proc_init(struct snd_oxfw *oxfw); + #define SND_OXFW_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/oxfw/oxfw_proc.c b/sound/firewire/oxfw/oxfw_proc.c new file mode 100644 index 0000000..3c1aa96 --- /dev/null +++ b/sound/firewire/oxfw/oxfw_proc.c @@ -0,0 +1,61 @@ +/* + * oxfw_proc.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2013 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./oxfw.h" + +static void +proc_read_formation(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_oxfw *oxfw = entry->private_data; + struct snd_oxfw_stream_formation *formation; + unsigned int i; + + snd_iprintf(buffer, "Output Stream from device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + formation = oxfw->tx_stream_formations; + for (i = 0; i < SND_OXFW_RATE_TABLE_ENTRIES; i++) { + snd_iprintf(buffer, + "\t%d\t%d\t%d\n", snd_oxfw_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 = oxfw->rx_stream_formations; + for (i = 0; i < SND_OXFW_RATE_TABLE_ENTRIES; i++) { + snd_iprintf(buffer, + "\t%d\t%d\t%d\n", snd_oxfw_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_oxfw *oxfw = entry->private_data; + unsigned int rate; + + if (snd_oxfw_stream_get_rate(oxfw, &rate) < 0) + return; + snd_iprintf(buffer, "Sampling rate: %d\n", rate); +} + +void snd_oxfw_proc_init(struct snd_oxfw *oxfw) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(oxfw->card, "#formation", &entry)) + snd_info_set_text_ops(entry, oxfw, proc_read_formation); + + if (!snd_card_proc_new(oxfw->card, "#clock", &entry)) + snd_info_set_text_ops(entry, oxfw, proc_read_clock); + + return; +}
This commit adds a functionality to capture/playback MIDI messages.
When no AMDTP streams are running, this module starts AMDTP stream at current sampling rate for MIDI stream.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/Makefile | 3 +- sound/firewire/oxfw/oxfw.c | 7 +++ sound/firewire/oxfw/oxfw.h | 3 + sound/firewire/oxfw/oxfw_midi.c | 135 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/oxfw/oxfw_midi.c
diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile index ab7865d..5430ec7 100644 --- a/sound/firewire/oxfw/Makefile +++ b/sound/firewire/oxfw/Makefile @@ -1,2 +1,3 @@ -snd-oxfw-objs := oxfw_command.o oxfw_stream.o oxfw_proc.o oxfw.o +snd-oxfw-objs := oxfw_command.o oxfw_stream.o oxfw_proc.o oxfw_midi.o \ + oxfw.o obj-m += snd-oxfw.o diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index 8298cba..ee817cd 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -144,6 +144,13 @@ oxfw_probe(struct fw_unit *unit,
snd_oxfw_proc_init(oxfw);
+ if ((oxfw->midi_input_ports > 0) || + (oxfw->midi_output_ports > 0)) { + err = snd_oxfw_create_midi_devices(oxfw); + if (err < 0) + goto error; + } + snd_card_set_dev(card, &unit->device); err = snd_card_register(card); if (err < 0) { diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index 7c02448e..523ac40 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -21,6 +21,7 @@ #include <sound/core.h> #include <sound/initval.h> #include <sound/info.h> +#include <sound/rawmidi.h>
#include "../lib.h" #include "../fcp.h" @@ -109,6 +110,8 @@ int snd_oxfw_stream_discover(struct snd_oxfw *oxfw);
void snd_oxfw_proc_init(struct snd_oxfw *oxfw);
+int snd_oxfw_create_midi_devices(struct snd_oxfw *oxfw); + #define SND_OXFW_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/oxfw/oxfw_midi.c b/sound/firewire/oxfw/oxfw_midi.c new file mode 100644 index 0000000..41c14ec --- /dev/null +++ b/sound/firewire/oxfw/oxfw_midi.c @@ -0,0 +1,135 @@ +/* + * oxfw_midi.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2013 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "oxfw.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + struct snd_oxfw *oxfw = substream->rmidi->private_data; + return snd_oxfw_stream_start_duplex(oxfw, &oxfw->tx_stream, 0); +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_oxfw *oxfw = substream->rmidi->private_data; + return snd_oxfw_stream_start_duplex(oxfw, &oxfw->rx_stream, 0); +} + +static int midi_close(struct snd_rawmidi_substream *substream) +{ + struct snd_oxfw *oxfw = substream->rmidi->private_data; + snd_oxfw_stream_stop_duplex(oxfw); + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_oxfw *oxfw = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&oxfw->lock, flags); + + if (up) + amdtp_stream_midi_trigger(&oxfw->tx_stream, + substrm->number, substrm); + else + amdtp_stream_midi_trigger(&oxfw->tx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&oxfw->lock, flags); + + return; +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_oxfw *oxfw = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&oxfw->lock, flags); + + if (up) + amdtp_stream_midi_trigger(&oxfw->rx_stream, + substrm->number, substrm); + else + amdtp_stream_midi_trigger(&oxfw->rx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&oxfw->lock, flags); + + return; +} + +static struct snd_rawmidi_ops midi_capture_ops = { + .open = midi_capture_open, + .close = midi_close, + .trigger = midi_capture_trigger, +}; + +static struct snd_rawmidi_ops midi_playback_ops = { + .open = midi_playback_open, + .close = midi_close, + .trigger = midi_playback_trigger, +}; + +static void set_midi_substream_names(struct snd_oxfw *oxfw, + 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", + oxfw->card->shortname, subs->number + 1); + } +} + +int snd_oxfw_create_midi_devices(struct snd_oxfw *oxfw) +{ + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + /* create midi ports */ + err = snd_rawmidi_new(oxfw->card, oxfw->card->driver, 0, + oxfw->midi_output_ports, oxfw->midi_input_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", oxfw->card->shortname); + rmidi->private_data = oxfw; + + if (oxfw->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(oxfw, str); + } + + if (oxfw->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(oxfw, str); + } + + if ((oxfw->midi_output_ports > 0) && (oxfw->midi_input_ports > 0)) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +}
This commit adds a functionality to capture/playback PCM samples.
Currently this driver has no functionality to check current source of clock. When AMDTP stream is already running for PCM, available sampling rate is limited at current one.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/Makefile | 2 +- sound/firewire/oxfw/oxfw.c | 4 + sound/firewire/oxfw/oxfw.h | 4 + sound/firewire/oxfw/oxfw_pcm.c | 412 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 421 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/oxfw/oxfw_pcm.c
diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile index 5430ec7..d119c06 100644 --- a/sound/firewire/oxfw/Makefile +++ b/sound/firewire/oxfw/Makefile @@ -1,3 +1,3 @@ snd-oxfw-objs := oxfw_command.o oxfw_stream.o oxfw_proc.o oxfw_midi.o \ - oxfw.o + oxfw_pcm.o oxfw.o obj-m += snd-oxfw.o diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index ee817cd..d636463 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -151,6 +151,10 @@ oxfw_probe(struct fw_unit *unit, goto error; }
+ err = snd_oxfw_create_pcm_devices(oxfw); + if (err < 0) + goto error; + snd_card_set_dev(card, &unit->device); err = snd_card_register(card); if (err < 0) { diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index 523ac40..4be43ae 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -22,6 +22,8 @@ #include <sound/initval.h> #include <sound/info.h> #include <sound/rawmidi.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h>
#include "../lib.h" #include "../fcp.h" @@ -112,6 +114,8 @@ void snd_oxfw_proc_init(struct snd_oxfw *oxfw);
int snd_oxfw_create_midi_devices(struct snd_oxfw *oxfw);
+int snd_oxfw_create_pcm_devices(struct snd_oxfw *oxfw); + #define SND_OXFW_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/oxfw/oxfw_pcm.c b/sound/firewire/oxfw/oxfw_pcm.c new file mode 100644 index 0000000..dbd5731 --- /dev/null +++ b/sound/firewire/oxfw/oxfw_pcm.c @@ -0,0 +1,412 @@ +/* + * oxfw_pcm.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2013 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./oxfw.h" + +static int +hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule, + struct snd_oxfw *oxfw, + struct snd_oxfw_stream_formation *formations) +{ + 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_OXFW_RATE_TABLE_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_oxfw_rate_table[i]); + t.max = max(t.max, snd_oxfw_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_oxfw *oxfw, + struct snd_oxfw_stream_formation *formations) +{ + 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_OXFW_RATE_TABLE_ENTRIES; i++) { + /* entry is invalid */ + if (formations[i].pcm == 0) + continue; + + if (!snd_interval_test(r, snd_oxfw_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 inline int +hw_rule_capture_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_oxfw *oxfw = rule->private; + return hw_rule_rate(params, rule, oxfw, + oxfw->tx_stream_formations); +} + +static inline int +hw_rule_playback_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_oxfw *oxfw = rule->private; + return hw_rule_rate(params, rule, oxfw, + oxfw->rx_stream_formations); +} + +static inline int +hw_rule_capture_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_oxfw *oxfw = rule->private; + return hw_rule_channels(params, rule, oxfw, + oxfw->tx_stream_formations); +} + +static inline int +hw_rule_playback_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_oxfw *oxfw = rule->private; + return hw_rule_channels(params, rule, oxfw, + oxfw->rx_stream_formations); +} + +static void +prepare_channels(struct snd_pcm_hardware *hw, + struct snd_oxfw_stream_formation *formations) +{ + unsigned int i; + + for (i = 0; i < SND_OXFW_RATE_TABLE_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); + } + + return; +} + +static void +prepare_rates(struct snd_pcm_hardware *hw, + struct snd_oxfw_stream_formation *formations) +{ + unsigned int i; + + for (i = 0; i < SND_OXFW_RATE_TABLE_ENTRIES; i++) { + /* entry has no PCM channels */ + if (formations[i].pcm == 0) + continue; + + hw->rate_min = min(hw->rate_min, snd_oxfw_rate_table[i]); + hw->rate_max = max(hw->rate_max, snd_oxfw_rate_table[i]); + hw->rates |= snd_pcm_rate_to_rate_bit(snd_oxfw_rate_table[i]); + } + + return; +} + +static int +pcm_init_hw_params(struct snd_oxfw *oxfw, + struct snd_pcm_substream *substream) +{ + int err; + + static const struct snd_pcm_hardware hw = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_FIFO_IN_FRAMES | + SNDRV_PCM_INFO_JOINT_DUPLEX | + /* for Open Sound System compatibility */ + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + /* set up later */ + .rates = 0, + .rate_min = UINT_MAX, + .rate_max = 0, + /* set up later */ + .channels_min = UINT_MAX, + .channels_max = 0, + .buffer_bytes_max = 1024 * 1024 * 1024, + .period_bytes_min = 256, + .period_bytes_max = 1024 * 1024 * 1024 / 2, + .periods_min = 2, + .periods_max = 32, + .fifo_size = 0, + }; + + substream->runtime->hw = hw; + substream->runtime->delay = substream->runtime->hw.fifo_size; + + /* add rule between channels and sampling rate */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + prepare_rates(&substream->runtime->hw, + oxfw->tx_stream_formations); + prepare_channels(&substream->runtime->hw, + oxfw->tx_stream_formations); + substream->runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; + snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_capture_channels, oxfw, + SNDRV_PCM_HW_PARAM_RATE, -1); + snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + hw_rule_capture_rate, oxfw, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + } else { + prepare_rates(&substream->runtime->hw, + oxfw->rx_stream_formations); + prepare_channels(&substream->runtime->hw, + oxfw->rx_stream_formations); + substream->runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS; + snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_playback_channels, oxfw, + SNDRV_PCM_HW_PARAM_RATE, -1); + snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + hw_rule_playback_rate, oxfw, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + } + + /* AM824 in IEC 61883-6 can deliver 24bit data */ + err = snd_pcm_hw_constraint_msbits(substream->runtime, 0, 32, 24); + if (err < 0) + goto end; + + /* + * AMDTP functionality in firewire-lib require periods to be aligned to + * 16 bit, or 24bit inner 32bit. + */ + err = snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); + if (err < 0) + goto end; + + /* time for period constraint */ + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + 500, UINT_MAX); + if (err < 0) + goto end; +end: + return err; +} + +static int +pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + unsigned int sampling_rate; + int err; + + err = pcm_init_hw_params(oxfw, substream); + if (err < 0) + goto end; + + /* + * When any PCM stream are running, the available sampling rate is + * limited at current sampling rate. + */ + if (amdtp_stream_pcm_running(&oxfw->tx_stream) || + amdtp_stream_pcm_running(&oxfw->rx_stream)) { + err = snd_oxfw_stream_get_rate(oxfw, &sampling_rate); + if (err < 0) + goto end; + + substream->runtime->hw.rate_min = sampling_rate; + substream->runtime->hw.rate_max = sampling_rate; + } + + snd_pcm_set_sync(substream); +end: + return err; +} + +static int +pcm_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int +pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} + +static int +pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + + snd_oxfw_stream_stop_duplex(oxfw); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int +pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_oxfw_stream_start_duplex(oxfw, &oxfw->tx_stream, + runtime->rate); + if (err < 0) + goto end; + + amdtp_stream_set_pcm_format(&oxfw->tx_stream, runtime->format); + amdtp_stream_pcm_prepare(&oxfw->tx_stream); +end: + return err; +} +static int +pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_oxfw_stream_start_duplex(oxfw, &oxfw->rx_stream, + runtime->rate); + if (err < 0) + goto end; + + amdtp_stream_set_pcm_format(&oxfw->rx_stream, runtime->format); + amdtp_stream_pcm_prepare(&oxfw->rx_stream); +end: + return err; +} + +static int +pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_oxfw *oxfw = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&oxfw->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&oxfw->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int +pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_oxfw *oxfw = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&oxfw->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&oxfw->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t +pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_oxfw *oxfw = sbstrm->private_data; + return amdtp_stream_pcm_pointer(&oxfw->tx_stream); +} +static snd_pcm_uframes_t +pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_oxfw *oxfw = sbstrm->private_data; + return amdtp_stream_pcm_pointer(&oxfw->rx_stream); +} + +static struct snd_pcm_ops pcm_capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; +static struct snd_pcm_ops pcm_playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_hw_params, + .hw_free = pcm_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_oxfw_create_pcm_devices(struct snd_oxfw *oxfw) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(oxfw->card, oxfw->card->driver, 0, 1, 1, &pcm); + if (err < 0) + goto end; + + pcm->private_data = oxfw; + snprintf(pcm->name, sizeof(pcm->name), + "%s PCM", oxfw->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; +}
This interface is designed for mixer/control application. To use hwdep interface, the application can get information about firewire node, can lock/unlock kernel streaming and can get notification at starting/stopping kernel streaming.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- include/uapi/sound/asound.h | 3 +- include/uapi/sound/firewire.h | 3 +- sound/firewire/oxfw/Makefile | 2 +- sound/firewire/oxfw/oxfw.c | 5 + sound/firewire/oxfw/oxfw.h | 13 +++ sound/firewire/oxfw/oxfw_hwdep.c | 197 ++++++++++++++++++++++++++++++++++++++ sound/firewire/oxfw/oxfw_midi.c | 25 ++++- sound/firewire/oxfw/oxfw_pcm.c | 13 ++- sound/firewire/oxfw/oxfw_stream.c | 39 ++++++++ 9 files changed, 293 insertions(+), 7 deletions(-) create mode 100644 sound/firewire/oxfw/oxfw_hwdep.c
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 2249483..0c1872a 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -96,9 +96,10 @@ enum { SNDRV_HWDEP_IFACE_FW_DICE, /* TC DICE FireWire device */ SNDRV_HWDEP_IFACE_FW_FIREWORKS, /* Echo Audio Fireworks based device */ SNDRV_HWDEP_IFACE_FW_BEBOB, /* BridgeCo BeBoB based device */ + SNDRV_HWDEP_IFACE_FW_OXFW, /* Oxford OXFW970/971 based device */
/* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_BEBOB + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_OXFW };
struct snd_hwdep_info { diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index d69c0b6..1894e32 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -54,7 +54,8 @@ union snd_firewire_event { #define SNDRV_FIREWIRE_TYPE_DICE 1 #define SNDRV_FIREWIRE_TYPE_FIREWORKS 2 #define SNDRV_FIREWIRE_TYPE_BEBOB 3 -/* AV/C, RME, MOTU, ... */ +#define SNDRV_FIREWIRE_TYPE_OXFW 4 +/* RME, MOTU, ... */
struct snd_firewire_get_info { unsigned int type; /* SNDRV_FIREWIRE_TYPE_xxx */ diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile index d119c06..c325245 100644 --- a/sound/firewire/oxfw/Makefile +++ b/sound/firewire/oxfw/Makefile @@ -1,3 +1,3 @@ snd-oxfw-objs := oxfw_command.o oxfw_stream.o oxfw_proc.o oxfw_midi.o \ - oxfw_pcm.o oxfw.o + oxfw_pcm.o oxfw_hwdep.o oxfw.o obj-m += snd-oxfw.o diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index d636463..b09d8ae 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -129,6 +129,7 @@ oxfw_probe(struct fw_unit *unit, oxfw->card_index = -1; mutex_init(&oxfw->mutex); spin_lock_init(&oxfw->lock); + init_waitqueue_head(&oxfw->hwdep_wait);
err = name_device(oxfw, entry->vendor_id); if (err < 0) @@ -155,6 +156,10 @@ oxfw_probe(struct fw_unit *unit, if (err < 0) goto error;
+ err = snd_oxfw_create_hwdep_device(oxfw); + if (err < 0) + goto error; + snd_card_set_dev(card, &unit->device); err = snd_card_register(card); if (err < 0) { diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index 4be43ae..01c9c32 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -24,6 +24,8 @@ #include <sound/rawmidi.h> #include <sound/pcm.h> #include <sound/pcm_params.h> +#include <sound/hwdep.h> +#include <sound/firewire.h>
#include "../lib.h" #include "../fcp.h" @@ -61,6 +63,11 @@ struct snd_oxfw { struct amdtp_stream tx_stream; struct cmp_connection in_conn; struct amdtp_stream rx_stream; + + /* for uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; };
/* AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) */ @@ -110,12 +117,18 @@ void snd_oxfw_stream_destroy_duplex(struct snd_oxfw *oxfw);
int snd_oxfw_stream_discover(struct snd_oxfw *oxfw);
+void snd_oxfw_stream_lock_changed(struct snd_oxfw *oxfw); +int snd_oxfw_stream_lock_try(struct snd_oxfw *oxfw); +void snd_oxfw_stream_lock_release(struct snd_oxfw *oxfw); + void snd_oxfw_proc_init(struct snd_oxfw *oxfw);
int snd_oxfw_create_midi_devices(struct snd_oxfw *oxfw);
int snd_oxfw_create_pcm_devices(struct snd_oxfw *oxfw);
+int snd_oxfw_create_hwdep_device(struct snd_oxfw *oxfw); + #define SND_OXFW_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/oxfw/oxfw_hwdep.c b/sound/firewire/oxfw/oxfw_hwdep.c new file mode 100644 index 0000000..4289626 --- /dev/null +++ b/sound/firewire/oxfw/oxfw_hwdep.c @@ -0,0 +1,197 @@ +/* + * oxfw_hwdep.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2013 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 "oxfw.h" + +static long +hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_oxfw *oxfw = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&oxfw->lock); + + while (!oxfw->dev_lock_changed) { + prepare_to_wait(&oxfw->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&oxfw->lock); + schedule(); + finish_wait(&oxfw->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&oxfw->lock); + } + + memset(&event, 0, sizeof(event)); + if (oxfw->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (oxfw->dev_lock_count > 0); + oxfw->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } + + spin_unlock_irq(&oxfw->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_oxfw *oxfw = hwdep->private_data; + unsigned int events; + + poll_wait(file, &oxfw->hwdep_wait, wait); + + spin_lock_irq(&oxfw->lock); + if (oxfw->dev_lock_changed) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&oxfw->lock); + + return events; +} + +static int +hwdep_get_info(struct snd_oxfw *oxfw, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(oxfw->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_oxfw *oxfw) +{ + int err; + + spin_lock_irq(&oxfw->lock); + + if (oxfw->dev_lock_count == 0) { + oxfw->dev_lock_count = -1; + err = 0; + } else + err = -EBUSY; + + spin_unlock_irq(&oxfw->lock); + + return err; +} + +static int +hwdep_unlock(struct snd_oxfw *oxfw) +{ + int err; + + spin_lock_irq(&oxfw->lock); + + if (oxfw->dev_lock_count == -1) { + oxfw->dev_lock_count = 0; + err = 0; + } else + err = -EBADFD; + + spin_unlock_irq(&oxfw->lock); + + return err; +} + +static int +hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_oxfw *oxfw = hwdep->private_data; + + spin_lock_irq(&oxfw->lock); + if (oxfw->dev_lock_count == -1) + oxfw->dev_lock_count = 0; + spin_unlock_irq(&oxfw->lock); + + return 0; +} + +static int +hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_oxfw *oxfw = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(oxfw, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(oxfw); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(oxfw); + 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_oxfw_create_hwdep_device(struct snd_oxfw *oxfw) +{ + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(oxfw->card, oxfw->card->driver, 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, oxfw->card->driver); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_OXFW; + hwdep->ops = hwdep_ops; + hwdep->private_data = oxfw; + hwdep->exclusive = true; +end: + return err; +} + diff --git a/sound/firewire/oxfw/oxfw_midi.c b/sound/firewire/oxfw/oxfw_midi.c index 41c14ec..d18c195 100644 --- a/sound/firewire/oxfw/oxfw_midi.c +++ b/sound/firewire/oxfw/oxfw_midi.c @@ -11,19 +11,40 @@ static int midi_capture_open(struct snd_rawmidi_substream *substream) { struct snd_oxfw *oxfw = substream->rmidi->private_data; - return snd_oxfw_stream_start_duplex(oxfw, &oxfw->tx_stream, 0); + int err; + + err = snd_oxfw_stream_lock_try(oxfw); + if (err < 0) + goto end; + + err = snd_oxfw_stream_start_duplex(oxfw, &oxfw->tx_stream, 0); + if (err < 0) + snd_oxfw_stream_lock_release(oxfw); +end: + return err; }
static int midi_playback_open(struct snd_rawmidi_substream *substream) { struct snd_oxfw *oxfw = substream->rmidi->private_data; - return snd_oxfw_stream_start_duplex(oxfw, &oxfw->rx_stream, 0); + int err; + + err = snd_oxfw_stream_lock_try(oxfw); + if (err < 0) + goto end; + + err = snd_oxfw_stream_start_duplex(oxfw, &oxfw->rx_stream, 0); + if (err < 0) + snd_oxfw_stream_lock_release(oxfw); +end: + return err; }
static int midi_close(struct snd_rawmidi_substream *substream) { struct snd_oxfw *oxfw = substream->rmidi->private_data; snd_oxfw_stream_stop_duplex(oxfw); + snd_oxfw_stream_lock_release(oxfw); return 0; }
diff --git a/sound/firewire/oxfw/oxfw_pcm.c b/sound/firewire/oxfw/oxfw_pcm.c index dbd5731..b911ddc 100644 --- a/sound/firewire/oxfw/oxfw_pcm.c +++ b/sound/firewire/oxfw/oxfw_pcm.c @@ -236,10 +236,14 @@ pcm_open(struct snd_pcm_substream *substream) unsigned int sampling_rate; int err;
- err = pcm_init_hw_params(oxfw, substream); + err = snd_oxfw_stream_lock_try(oxfw); if (err < 0) goto end;
+ err = pcm_init_hw_params(oxfw, substream); + if (err < 0) + goto err_locked; + /* * When any PCM stream are running, the available sampling rate is * limited at current sampling rate. @@ -248,7 +252,7 @@ pcm_open(struct snd_pcm_substream *substream) amdtp_stream_pcm_running(&oxfw->rx_stream)) { err = snd_oxfw_stream_get_rate(oxfw, &sampling_rate); if (err < 0) - goto end; + goto err_locked;
substream->runtime->hw.rate_min = sampling_rate; substream->runtime->hw.rate_max = sampling_rate; @@ -257,11 +261,16 @@ pcm_open(struct snd_pcm_substream *substream) snd_pcm_set_sync(substream); end: return err; +err_locked: + snd_oxfw_stream_lock_release(oxfw); + return err; }
static int pcm_close(struct snd_pcm_substream *substream) { + struct snd_oxfw *oxfw = substream->private_data; + snd_oxfw_stream_lock_release(oxfw); return 0; }
diff --git a/sound/firewire/oxfw/oxfw_stream.c b/sound/firewire/oxfw/oxfw_stream.c index ad2dd86..5a06fe4 100644 --- a/sound/firewire/oxfw/oxfw_stream.c +++ b/sound/firewire/oxfw/oxfw_stream.c @@ -598,3 +598,42 @@ int snd_oxfw_stream_discover(struct snd_oxfw *oxfw) end: return err; } + +void snd_oxfw_stream_lock_changed(struct snd_oxfw *oxfw) +{ + oxfw->dev_lock_changed = true; + wake_up(&oxfw->hwdep_wait); +} + +int snd_oxfw_stream_lock_try(struct snd_oxfw *oxfw) +{ + int err; + + spin_lock_irq(&oxfw->lock); + + /* user land lock this */ + if (oxfw->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (oxfw->dev_lock_count++ == 0) + snd_oxfw_stream_lock_changed(oxfw); + err = 0; +end: + spin_unlock_irq(&oxfw->lock); + return err; +} + +void snd_oxfw_stream_lock_release(struct snd_oxfw *oxfw) +{ + spin_lock_irq(&oxfw->lock); + + if (WARN_ON(oxfw->dev_lock_count <= 0)) + goto end; + if (--oxfw->dev_lock_count == 0) + snd_oxfw_stream_lock_changed(oxfw); +end: + spin_unlock_irq(&oxfw->lock); +}
At Sun, 5 Jan 2014 20:13:29 +0900, Takashi Sakamoto wrote:
This series of patch is based on my previous series below: http://mailman.alsa-project.org/pipermail/alsa-devel/2013-December/070424.ht...
This series of patch add a new driver, snd-oxfw, for OXFW970/971 based devices. These chipsets are already supported by snd-firewire-speakers but this new driver aim to support recording equipment.
Current supported devices:
- Behringer F-Control Audio 202
- Mackie Onyx-i series (former model)
- Mackie Onyx Satellite
Devices possible to be supported if identifying IDs:
- Mackie, d.2 pro
- Mackie, d.4 pro
- Mackie, U.420
- Mackie, U.420d
- Mackie, Tapco Link.Firewire
I worked with Behringer F-Control Audio 202. So I want someone to test with the other devices, especially about some quirks.
Clemens, would you gime me your opinions about issues below? And if you know something about this chipset, would you share the information?
1.snd-firewire-speakers and snd-oxfw With this series of patches, ALSA has two drivers for the same chipsets. Merging these two drivers is a bit difficult because:
- snd-firewire-speakers has control interface but snd-oxfw don't
- snd-firewire-speakers don't support in-stream but snd-oxfw supports
I don't see why these make things difficult to merge? They sound rather like the functionalities don't conflict.
2.currently snd-oxfw works with streams in SYT-Match Due to invalid sequence of 'presentation timestamp' in transmitted packets. In detail, please see 4th patch.
3.assumption of channnel formation Some commands may not be implemented. In detail, please see 3rd patch.
4.don't check source of clock All of supported devices (including can be) don't have the functionality to switch source of clock. So I want to omit this.
For issue 3 and 4, Descriptor mechanism in 'Enhancements to the AV/C General Specification 3.0 Version 1.1' may be a best solution but it costs much to implement against the number of available devices. I want to avoid this huge mechanism if possible.
Note that since I haven't seen any ack from Clemens, I didn't take previous patches yet. And it's a bit too late for 3.14, we have plenty of time for review and discussion for 3.15.
thanks,
Takashi
Regards
Takashi Sakamoto
Takashi Sakamoto (8): oxfw: Add skelton for OXFW970/971 based devices oxfw: Read firmware version to name card oxfw: Add some AV/C commands for channel formation of AMDTP stream oxfw: Add connections and streams management oxfw: Add proc interface for debugging purpose oxfw: Add MIDI interface oxfw: Add PCM interface oxfw: Add hwdep interface
include/uapi/sound/asound.h | 3 +- include/uapi/sound/firewire.h | 3 +- sound/firewire/Kconfig | 17 + sound/firewire/Makefile | 1 + sound/firewire/oxfw/Makefile | 3 + sound/firewire/oxfw/oxfw.c | 243 ++++++++++++++ sound/firewire/oxfw/oxfw.h | 140 ++++++++ sound/firewire/oxfw/oxfw_command.c | 161 ++++++++++ sound/firewire/oxfw/oxfw_hwdep.c | 197 ++++++++++++ sound/firewire/oxfw/oxfw_midi.c | 156 +++++++++ sound/firewire/oxfw/oxfw_pcm.c | 421 ++++++++++++++++++++++++ sound/firewire/oxfw/oxfw_proc.c | 61 ++++ sound/firewire/oxfw/oxfw_stream.c | 639 +++++++++++++++++++++++++++++++++++++ 13 files changed, 2043 insertions(+), 2 deletions(-) create mode 100644 sound/firewire/oxfw/Makefile create mode 100644 sound/firewire/oxfw/oxfw.c create mode 100644 sound/firewire/oxfw/oxfw.h create mode 100644 sound/firewire/oxfw/oxfw_command.c create mode 100644 sound/firewire/oxfw/oxfw_hwdep.c create mode 100644 sound/firewire/oxfw/oxfw_midi.c create mode 100644 sound/firewire/oxfw/oxfw_pcm.c create mode 100644 sound/firewire/oxfw/oxfw_proc.c create mode 100644 sound/firewire/oxfw/oxfw_stream.c
-- 1.8.3.2
Hi Iwai-san,
1.snd-firewire-speakers and snd-oxfw
I don't see why these make things difficult to merge? They sound rather like the functionalities don't conflict.
I think it better not to touch existed drivers unless they have some bugs.
Actually I can merge two drivers with some selection statements, many changes for card structure and for PCM functionality, many codes for MIDI and hwdep functionaloty. But for snd-firewire-speakers these modifications are needless and not due to bugs.
...On the other hand, I also think it good to merge these two drivers because they support the same chipset. But this idea have a risk to put some new bugs into snd-firewire-speakers.
Note that since I haven't seen any ack from Clemens, I didn't take previous patches yet.
Yes. I've posted my patches with RFC, without merge-request. I describe the reason.
Devices which snd-fireworks, snd-bebob and snd-oxfw supports are categorized in 'GenericAVC' by FFADO. This means the devices are controlled by the same way, AV/C commands. At first, I considered to develop 'snd-genericavc', which perform like OS X implementation. But there were some anxieties for device quirks.
In this reason, I decide to develop them in the same time. As a result, I confirm each chipset/firmware has it own quirk. So now I have a confidence that it's difficult to develop 'snd-genericavc'.
And it's a bit too late for 3.14, we have plenty of time for review
and discussion for 3.15.
Currently I have no will to merge my patches because I need more discussions between ALSA/FFADO developers.
My patches add ALSA to handle devices which FFADO supports for its streaming functionality. It may bring a bit complexity to users. I need to adjust two projects in advance of merge-request.
Thanks
Takashi Sakamoto o-takashi@sakamocchi.jp
At Mon, 06 Jan 2014 17:33:47 +0900, Takashi Sakamoto wrote:
Hi Iwai-san,
1.snd-firewire-speakers and snd-oxfw
I don't see why these make things difficult to merge? They sound rather like the functionalities don't conflict.
I think it better not to touch existed drivers unless they have some bugs.
Actually I can merge two drivers with some selection statements, many changes for card structure and for PCM functionality, many codes for MIDI and hwdep functionaloty. But for snd-firewire-speakers these modifications are needless and not due to bugs.
...On the other hand, I also think it good to merge these two drivers because they support the same chipset. But this idea have a risk to put some new bugs into snd-firewire-speakers.
Yes, having two different drivers for the same hardware leads nothing but confusion, judging from the past experience.
Note that since I haven't seen any ack from Clemens, I didn't take previous patches yet.
Yes. I've posted my patches with RFC, without merge-request. I describe the reason.
OK.
Devices which snd-fireworks, snd-bebob and snd-oxfw supports are categorized in 'GenericAVC' by FFADO. This means the devices are controlled by the same way, AV/C commands. At first, I considered to develop 'snd-genericavc', which perform like OS X implementation. But there were some anxieties for device quirks.
In this reason, I decide to develop them in the same time. As a result, I confirm each chipset/firmware has it own quirk. So now I have a confidence that it's difficult to develop 'snd-genericavc'.
And it's a bit too late for 3.14, we have plenty of time for review
and discussion for 3.15.
Currently I have no will to merge my patches because I need more discussions between ALSA/FFADO developers.
My patches add ALSA to handle devices which FFADO supports for its streaming functionality. It may bring a bit complexity to users. I need to adjust two projects in advance of merge-request.
Fair enough.
thanks,
Takashi
In previous series of patch, I showed an idea to add a new driver for OXFW970/971. This driver can support some recording equipments. http://mailman.alsa-project.org/pipermail/alsa-devel/2014-January/070705.htm...
ALSA already has a driver, firewire-speakers for these chipsets. But it supports PCM playback only. The devices need support for capture/playback of PCM/MIDI.
According to maintainer's advice, I'll add these functionality to firewire-speakers. Additionally, like the other firewire drivers, I'll add hwdep interface.
As a result, this driver can support: * Oxford Semiconductor OXFW970/971. * Griffin Firewave * LaCie Firewire Speakers * Behringer F-Control Audio 202 * Mackie Onyx-i series (former model) * Mackie Onyx Satellite
This driver can also support but IDs are unknown: * Mackie d.2 pro * Mackie d.4 pro * Mackie U.420 * Mackie U.420d * Mackie Tapco Link.Firewire
Takashi Sakamoto (13): speakers: move to its own directory speakers: Split stream functionality to a new file and add a header file speakers: Split PCM functionality to a new file speakers: Split control functionality to a new file speakers: Change the way to name card speakers: Change the way to make PCM rules/constraints speakers: Add proc interface for debugging purpose speakers: Change the way to start stream speakers: Add some AV/C commands to get stream formation and supported sampling rates speakers: Add support for Behringer/Mackie devices speakers: Add support AMDTP in-stream and PCM capture speakers: Add support for capture/playback MIDI messages speakers: Add hwdep interface
include/uapi/sound/asound.h | 3 +- include/uapi/sound/firewire.h | 3 +- sound/firewire/Kconfig | 11 +- sound/firewire/Makefile | 3 +- sound/firewire/speakers.c | 801 ----------------------------- sound/firewire/speakers/Makefile | 3 + sound/firewire/speakers/speakers.c | 272 ++++++++++ sound/firewire/speakers/speakers.h | 140 +++++ sound/firewire/speakers/speakers_command.c | 162 ++++++ sound/firewire/speakers/speakers_control.c | 283 ++++++++++ sound/firewire/speakers/speakers_hwdep.c | 195 +++++++ sound/firewire/speakers/speakers_midi.c | 150 ++++++ sound/firewire/speakers/speakers_pcm.c | 402 +++++++++++++++ sound/firewire/speakers/speakers_proc.c | 60 +++ sound/firewire/speakers/speakers_stream.c | 534 +++++++++++++++++++ 15 files changed, 2214 insertions(+), 808 deletions(-) delete mode 100644 sound/firewire/speakers.c create mode 100644 sound/firewire/speakers/Makefile create mode 100644 sound/firewire/speakers/speakers.c create mode 100644 sound/firewire/speakers/speakers.h create mode 100644 sound/firewire/speakers/speakers_command.c create mode 100644 sound/firewire/speakers/speakers_control.c create mode 100644 sound/firewire/speakers/speakers_hwdep.c create mode 100644 sound/firewire/speakers/speakers_midi.c create mode 100644 sound/firewire/speakers/speakers_pcm.c create mode 100644 sound/firewire/speakers/speakers_proc.c create mode 100644 sound/firewire/speakers/speakers_stream.c
Followed commits add much codes. I'll split them to some files. In this reason, this commit make own directory and move current source to it.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Makefile | 3 +- sound/firewire/speakers.c | 801 ------------------------------------- sound/firewire/speakers/Makefile | 2 + sound/firewire/speakers/speakers.c | 801 +++++++++++++++++++++++++++++++++++++ 4 files changed, 804 insertions(+), 803 deletions(-) delete mode 100644 sound/firewire/speakers.c create mode 100644 sound/firewire/speakers/Makefile create mode 100644 sound/firewire/speakers/speakers.c
diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index fad8d49..e2cd79f 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -1,13 +1,12 @@ snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \ fcp.o cmp.o amdtp.o snd-dice-objs := dice.o -snd-firewire-speakers-objs := speakers.o snd-isight-objs := isight.o snd-scs1x-objs := scs1x.o
obj-$(CONFIG_SND_FIREWIRE_LIB) += snd-firewire-lib.o obj-$(CONFIG_SND_DICE) += snd-dice.o -obj-$(CONFIG_SND_FIREWIRE_SPEAKERS) += snd-firewire-speakers.o +obj-$(CONFIG_SND_FIREWIRE_SPEAKERS) += speakers/ obj-$(CONFIG_SND_ISIGHT) += snd-isight.o obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o obj-$(CONFIG_SND_FIREWORKS) += fireworks/ diff --git a/sound/firewire/speakers.c b/sound/firewire/speakers.c deleted file mode 100644 index abb2eb1..0000000 --- a/sound/firewire/speakers.c +++ /dev/null @@ -1,801 +0,0 @@ -/* - * OXFW970-based speakers driver - * - * Copyright (c) Clemens Ladisch clemens@ladisch.de - * Licensed under the terms of the GNU General Public License, version 2. - */ - -#include <linux/device.h> -#include <linux/firewire.h> -#include <linux/firewire-constants.h> -#include <linux/module.h> -#include <linux/mod_devicetable.h> -#include <linux/mutex.h> -#include <linux/slab.h> -#include <sound/control.h> -#include <sound/core.h> -#include <sound/initval.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include "cmp.h" -#include "fcp.h" -#include "amdtp.h" -#include "lib.h" - -#define OXFORD_FIRMWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x50000) -/* 0x970?vvvv or 0x971?vvvv, where vvvv = firmware version */ - -#define OXFORD_HARDWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x90020) -#define OXFORD_HARDWARE_ID_OXFW970 0x39443841 -#define OXFORD_HARDWARE_ID_OXFW971 0x39373100 - -#define VENDOR_GRIFFIN 0x001292 -#define VENDOR_LACIE 0x00d04b - -#define SPECIFIER_1394TA 0x00a02d -#define VERSION_AVC 0x010001 - -struct device_info { - const char *driver_name; - const char *short_name; - const char *long_name; - int (*pcm_constraints)(struct snd_pcm_runtime *runtime); - unsigned int mixer_channels; - u8 mute_fb_id; - u8 volume_fb_id; -}; - -struct fwspk { - struct snd_card *card; - struct fw_unit *unit; - const struct device_info *device_info; - struct mutex mutex; - struct cmp_connection connection; - struct amdtp_stream stream; - bool mute; - s16 volume[6]; - s16 volume_min; - s16 volume_max; -}; - -MODULE_DESCRIPTION("FireWire speakers driver"); -MODULE_AUTHOR("Clemens Ladisch clemens@ladisch.de"); -MODULE_LICENSE("GPL v2"); - -static int firewave_rate_constraint(struct snd_pcm_hw_params *params, - struct snd_pcm_hw_rule *rule) -{ - static unsigned int stereo_rates[] = { 48000, 96000 }; - struct snd_interval *channels = - hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); - struct snd_interval *rate = - hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); - - /* two channels work only at 48/96 kHz */ - if (snd_interval_max(channels) < 6) - return snd_interval_list(rate, 2, stereo_rates, 0); - return 0; -} - -static int firewave_channels_constraint(struct snd_pcm_hw_params *params, - struct snd_pcm_hw_rule *rule) -{ - static const struct snd_interval all_channels = { .min = 6, .max = 6 }; - struct snd_interval *rate = - hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); - struct snd_interval *channels = - hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); - - /* 32/44.1 kHz work only with all six channels */ - if (snd_interval_max(rate) < 48000) - return snd_interval_refine(channels, &all_channels); - return 0; -} - -static int firewave_constraints(struct snd_pcm_runtime *runtime) -{ - static unsigned int channels_list[] = { 2, 6 }; - static struct snd_pcm_hw_constraint_list channels_list_constraint = { - .count = 2, - .list = channels_list, - }; - int err; - - runtime->hw.rates = SNDRV_PCM_RATE_32000 | - SNDRV_PCM_RATE_44100 | - SNDRV_PCM_RATE_48000 | - SNDRV_PCM_RATE_96000; - runtime->hw.channels_max = 6; - - err = snd_pcm_hw_constraint_list(runtime, 0, - SNDRV_PCM_HW_PARAM_CHANNELS, - &channels_list_constraint); - if (err < 0) - return err; - err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, - firewave_rate_constraint, NULL, - SNDRV_PCM_HW_PARAM_CHANNELS, -1); - if (err < 0) - return err; - err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, - firewave_channels_constraint, NULL, - SNDRV_PCM_HW_PARAM_RATE, -1); - if (err < 0) - return err; - - return 0; -} - -static int lacie_speakers_constraints(struct snd_pcm_runtime *runtime) -{ - runtime->hw.rates = SNDRV_PCM_RATE_32000 | - SNDRV_PCM_RATE_44100 | - SNDRV_PCM_RATE_48000 | - SNDRV_PCM_RATE_88200 | - SNDRV_PCM_RATE_96000; - - return 0; -} - -static int fwspk_open(struct snd_pcm_substream *substream) -{ - static const struct snd_pcm_hardware hardware = { - .info = SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_BATCH | - SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_BLOCK_TRANSFER, - .formats = AMDTP_OUT_PCM_FORMAT_BITS, - .channels_min = 2, - .channels_max = 2, - .buffer_bytes_max = 4 * 1024 * 1024, - .period_bytes_min = 1, - .period_bytes_max = UINT_MAX, - .periods_min = 1, - .periods_max = UINT_MAX, - }; - struct fwspk *fwspk = substream->private_data; - struct snd_pcm_runtime *runtime = substream->runtime; - int err; - - runtime->hw = hardware; - - err = fwspk->device_info->pcm_constraints(runtime); - if (err < 0) - return err; - err = snd_pcm_limit_hw_rates(runtime); - 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); - if (err < 0) - return err; - - return 0; -} - -static int fwspk_close(struct snd_pcm_substream *substream) -{ - return 0; -} - -static void fwspk_stop_stream(struct fwspk *fwspk) -{ - if (amdtp_stream_running(&fwspk->stream)) { - amdtp_stream_stop(&fwspk->stream); - cmp_connection_break(&fwspk->connection); - } -} - -static int fwspk_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *hw_params) -{ - struct fwspk *fwspk = substream->private_data; - int err; - - mutex_lock(&fwspk->mutex); - fwspk_stop_stream(fwspk); - mutex_unlock(&fwspk->mutex); - - err = snd_pcm_lib_alloc_vmalloc_buffer(substream, - params_buffer_bytes(hw_params)); - if (err < 0) - goto error; - - amdtp_stream_set_parameters(&fwspk->stream, - params_rate(hw_params), - params_channels(hw_params), - 0); - - amdtp_stream_set_pcm_format(&fwspk->stream, - params_format(hw_params)); - - err = avc_general_set_sig_fmt(fwspk->unit, params_rate(hw_params), - AVC_GENERAL_PLUG_DIR_IN, 0); - if (err < 0) - goto err_buffer; - if (err != 0x09 /* ACCEPTED */) { - dev_err(&fwspk->unit->device, "failed to set sample rate\n"); - err = -EIO; - goto error; - } - - return 0; - -err_buffer: - snd_pcm_lib_free_vmalloc_buffer(substream); -error: - return err; -} - -static int fwspk_hw_free(struct snd_pcm_substream *substream) -{ - struct fwspk *fwspk = substream->private_data; - - mutex_lock(&fwspk->mutex); - fwspk_stop_stream(fwspk); - mutex_unlock(&fwspk->mutex); - - return snd_pcm_lib_free_vmalloc_buffer(substream); -} - -static int fwspk_prepare(struct snd_pcm_substream *substream) -{ - struct fwspk *fwspk = substream->private_data; - int err; - - mutex_lock(&fwspk->mutex); - - if (amdtp_streaming_error(&fwspk->stream)) - fwspk_stop_stream(fwspk); - - if (!amdtp_stream_running(&fwspk->stream)) { - err = cmp_connection_establish(&fwspk->connection, - amdtp_stream_get_max_payload(&fwspk->stream)); - if (err < 0) - goto err_mutex; - - err = amdtp_stream_start(&fwspk->stream, - fwspk->connection.resources.channel, - fwspk->connection.speed); - if (err < 0) - goto err_connection; - } - - mutex_unlock(&fwspk->mutex); - - amdtp_stream_pcm_prepare(&fwspk->stream); - - return 0; - -err_connection: - cmp_connection_break(&fwspk->connection); -err_mutex: - mutex_unlock(&fwspk->mutex); - - return err; -} - -static int fwspk_trigger(struct snd_pcm_substream *substream, int cmd) -{ - struct fwspk *fwspk = substream->private_data; - struct snd_pcm_substream *pcm; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - pcm = substream; - break; - case SNDRV_PCM_TRIGGER_STOP: - pcm = NULL; - break; - default: - return -EINVAL; - } - amdtp_stream_pcm_trigger(&fwspk->stream, pcm); - return 0; -} - -static snd_pcm_uframes_t fwspk_pointer(struct snd_pcm_substream *substream) -{ - struct fwspk *fwspk = substream->private_data; - - return amdtp_stream_pcm_pointer(&fwspk->stream); -} - -static int fwspk_create_pcm(struct fwspk *fwspk) -{ - static struct snd_pcm_ops ops = { - .open = fwspk_open, - .close = fwspk_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = fwspk_hw_params, - .hw_free = fwspk_hw_free, - .prepare = fwspk_prepare, - .trigger = fwspk_trigger, - .pointer = fwspk_pointer, - .page = snd_pcm_lib_get_vmalloc_page, - .mmap = snd_pcm_lib_mmap_vmalloc, - }; - struct snd_pcm *pcm; - int err; - - err = snd_pcm_new(fwspk->card, "OXFW970", 0, 1, 0, &pcm); - if (err < 0) - return err; - pcm->private_data = fwspk; - strcpy(pcm->name, fwspk->device_info->short_name); - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &ops); - return 0; -} - -enum control_action { CTL_READ, CTL_WRITE }; -enum control_attribute { - CTL_MIN = 0x02, - CTL_MAX = 0x03, - CTL_CURRENT = 0x10, -}; - -static int fwspk_mute_command(struct fwspk *fwspk, bool *value, - enum control_action action) -{ - u8 *buf; - u8 response_ok; - int err; - - buf = kmalloc(11, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - if (action == CTL_READ) { - buf[0] = 0x01; /* AV/C, STATUS */ - response_ok = 0x0c; /* STABLE */ - } else { - buf[0] = 0x00; /* AV/C, CONTROL */ - response_ok = 0x09; /* ACCEPTED */ - } - buf[1] = 0x08; /* audio unit 0 */ - buf[2] = 0xb8; /* FUNCTION BLOCK */ - buf[3] = 0x81; /* function block type: feature */ - buf[4] = fwspk->device_info->mute_fb_id; /* function block ID */ - buf[5] = 0x10; /* control attribute: current */ - buf[6] = 0x02; /* selector length */ - buf[7] = 0x00; /* audio channel number */ - buf[8] = 0x01; /* control selector: mute */ - buf[9] = 0x01; /* control data length */ - if (action == CTL_READ) - buf[10] = 0xff; - else - buf[10] = *value ? 0x70 : 0x60; - - err = fcp_avc_transaction(fwspk->unit, buf, 11, buf, 11, 0x3fe); - if (err < 0) - goto error; - if (err < 11) { - dev_err(&fwspk->unit->device, "short FCP response\n"); - err = -EIO; - goto error; - } - if (buf[0] != response_ok) { - dev_err(&fwspk->unit->device, "mute command failed\n"); - err = -EIO; - goto error; - } - if (action == CTL_READ) - *value = buf[10] == 0x70; - - err = 0; - -error: - kfree(buf); - - return err; -} - -static int fwspk_volume_command(struct fwspk *fwspk, s16 *value, - unsigned int channel, - enum control_attribute attribute, - enum control_action action) -{ - u8 *buf; - u8 response_ok; - int err; - - buf = kmalloc(12, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - if (action == CTL_READ) { - buf[0] = 0x01; /* AV/C, STATUS */ - response_ok = 0x0c; /* STABLE */ - } else { - buf[0] = 0x00; /* AV/C, CONTROL */ - response_ok = 0x09; /* ACCEPTED */ - } - buf[1] = 0x08; /* audio unit 0 */ - buf[2] = 0xb8; /* FUNCTION BLOCK */ - buf[3] = 0x81; /* function block type: feature */ - buf[4] = fwspk->device_info->volume_fb_id; /* function block ID */ - buf[5] = attribute; /* control attribute */ - buf[6] = 0x02; /* selector length */ - buf[7] = channel; /* audio channel number */ - buf[8] = 0x02; /* control selector: volume */ - buf[9] = 0x02; /* control data length */ - if (action == CTL_READ) { - buf[10] = 0xff; - buf[11] = 0xff; - } else { - buf[10] = *value >> 8; - buf[11] = *value; - } - - err = fcp_avc_transaction(fwspk->unit, buf, 12, buf, 12, 0x3fe); - if (err < 0) - goto error; - if (err < 12) { - dev_err(&fwspk->unit->device, "short FCP response\n"); - err = -EIO; - goto error; - } - if (buf[0] != response_ok) { - dev_err(&fwspk->unit->device, "volume command failed\n"); - err = -EIO; - goto error; - } - if (action == CTL_READ) - *value = (buf[10] << 8) | buf[11]; - - err = 0; - -error: - kfree(buf); - - return err; -} - -static int fwspk_mute_get(struct snd_kcontrol *control, - struct snd_ctl_elem_value *value) -{ - struct fwspk *fwspk = control->private_data; - - value->value.integer.value[0] = !fwspk->mute; - - return 0; -} - -static int fwspk_mute_put(struct snd_kcontrol *control, - struct snd_ctl_elem_value *value) -{ - struct fwspk *fwspk = control->private_data; - bool mute; - int err; - - mute = !value->value.integer.value[0]; - - if (mute == fwspk->mute) - return 0; - - err = fwspk_mute_command(fwspk, &mute, CTL_WRITE); - if (err < 0) - return err; - fwspk->mute = mute; - - return 1; -} - -static int fwspk_volume_info(struct snd_kcontrol *control, - struct snd_ctl_elem_info *info) -{ - struct fwspk *fwspk = control->private_data; - - info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - info->count = fwspk->device_info->mixer_channels; - info->value.integer.min = fwspk->volume_min; - info->value.integer.max = fwspk->volume_max; - - return 0; -} - -static const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 }; - -static int fwspk_volume_get(struct snd_kcontrol *control, - struct snd_ctl_elem_value *value) -{ - struct fwspk *fwspk = control->private_data; - unsigned int i; - - for (i = 0; i < fwspk->device_info->mixer_channels; ++i) - value->value.integer.value[channel_map[i]] = fwspk->volume[i]; - - return 0; -} - -static int fwspk_volume_put(struct snd_kcontrol *control, - struct snd_ctl_elem_value *value) -{ - struct fwspk *fwspk = control->private_data; - unsigned int i, changed_channels; - bool equal_values = true; - s16 volume; - int err; - - for (i = 0; i < fwspk->device_info->mixer_channels; ++i) { - if (value->value.integer.value[i] < fwspk->volume_min || - value->value.integer.value[i] > fwspk->volume_max) - return -EINVAL; - if (value->value.integer.value[i] != - value->value.integer.value[0]) - equal_values = false; - } - - changed_channels = 0; - for (i = 0; i < fwspk->device_info->mixer_channels; ++i) - if (value->value.integer.value[channel_map[i]] != - fwspk->volume[i]) - changed_channels |= 1 << (i + 1); - - if (equal_values && changed_channels != 0) - changed_channels = 1 << 0; - - for (i = 0; i <= fwspk->device_info->mixer_channels; ++i) { - volume = value->value.integer.value[channel_map[i ? i - 1 : 0]]; - if (changed_channels & (1 << i)) { - err = fwspk_volume_command(fwspk, &volume, i, - CTL_CURRENT, CTL_WRITE); - if (err < 0) - return err; - } - if (i > 0) - fwspk->volume[i - 1] = volume; - } - - return changed_channels != 0; -} - -static int fwspk_create_mixer(struct fwspk *fwspk) -{ - static const struct snd_kcontrol_new controls[] = { - { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "PCM Playback Switch", - .info = snd_ctl_boolean_mono_info, - .get = fwspk_mute_get, - .put = fwspk_mute_put, - }, - { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "PCM Playback Volume", - .info = fwspk_volume_info, - .get = fwspk_volume_get, - .put = fwspk_volume_put, - }, - }; - unsigned int i, first_ch; - int err; - - err = fwspk_volume_command(fwspk, &fwspk->volume_min, - 0, CTL_MIN, CTL_READ); - if (err < 0) - return err; - err = fwspk_volume_command(fwspk, &fwspk->volume_max, - 0, CTL_MAX, CTL_READ); - if (err < 0) - return err; - - err = fwspk_mute_command(fwspk, &fwspk->mute, CTL_READ); - if (err < 0) - return err; - - first_ch = fwspk->device_info->mixer_channels == 1 ? 0 : 1; - for (i = 0; i < fwspk->device_info->mixer_channels; ++i) { - err = fwspk_volume_command(fwspk, &fwspk->volume[i], - first_ch + i, CTL_CURRENT, CTL_READ); - if (err < 0) - return err; - } - - for (i = 0; i < ARRAY_SIZE(controls); ++i) { - err = snd_ctl_add(fwspk->card, - snd_ctl_new1(&controls[i], fwspk)); - if (err < 0) - return err; - } - - return 0; -} - -static u32 fwspk_read_firmware_version(struct fw_unit *unit) -{ - __be32 data; - int err; - - err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, - OXFORD_FIRMWARE_ID_ADDRESS, &data, 4, 0); - return err >= 0 ? be32_to_cpu(data) : 0; -} - -static void fwspk_card_free(struct snd_card *card) -{ - struct fwspk *fwspk = card->private_data; - - amdtp_stream_destroy(&fwspk->stream); - cmp_connection_destroy(&fwspk->connection); - fw_unit_put(fwspk->unit); - mutex_destroy(&fwspk->mutex); -} - -static int fwspk_probe(struct fw_unit *unit, - const struct ieee1394_device_id *id) -{ - struct fw_device *fw_dev = fw_parent_device(unit); - struct snd_card *card; - struct fwspk *fwspk; - u32 firmware; - int err; - - err = snd_card_create(-1, NULL, THIS_MODULE, sizeof(*fwspk), &card); - if (err < 0) - return err; - snd_card_set_dev(card, &unit->device); - - fwspk = card->private_data; - fwspk->card = card; - mutex_init(&fwspk->mutex); - fwspk->unit = fw_unit_get(unit); - fwspk->device_info = (const struct device_info *)id->driver_data; - - err = cmp_connection_init(&fwspk->connection, unit, CMP_INPUT, 0); - if (err < 0) - goto err_unit; - - err = amdtp_stream_init(&fwspk->stream, unit, AMDTP_OUT_STREAM, - CIP_NONBLOCKING); - if (err < 0) - goto err_connection; - - card->private_free = fwspk_card_free; - - strcpy(card->driver, fwspk->device_info->driver_name); - strcpy(card->shortname, fwspk->device_info->short_name); - firmware = fwspk_read_firmware_version(unit); - snprintf(card->longname, sizeof(card->longname), - "%s (OXFW%x %04x), GUID %08x%08x at %s, S%d", - fwspk->device_info->long_name, - firmware >> 20, firmware & 0xffff, - fw_dev->config_rom[3], fw_dev->config_rom[4], - dev_name(&unit->device), 100 << fw_dev->max_speed); - strcpy(card->mixername, "OXFW970"); - - err = fwspk_create_pcm(fwspk); - if (err < 0) - goto error; - - err = fwspk_create_mixer(fwspk); - if (err < 0) - goto error; - - err = snd_card_register(card); - if (err < 0) - goto error; - - dev_set_drvdata(&unit->device, fwspk); - - return 0; - -err_connection: - cmp_connection_destroy(&fwspk->connection); -err_unit: - fw_unit_put(fwspk->unit); - mutex_destroy(&fwspk->mutex); -error: - snd_card_free(card); - return err; -} - -static void fwspk_bus_reset(struct fw_unit *unit) -{ - struct fwspk *fwspk = dev_get_drvdata(&unit->device); - - fcp_bus_reset(fwspk->unit); - - if (cmp_connection_update(&fwspk->connection) < 0) { - amdtp_stream_pcm_abort(&fwspk->stream); - mutex_lock(&fwspk->mutex); - fwspk_stop_stream(fwspk); - mutex_unlock(&fwspk->mutex); - return; - } - - amdtp_stream_update(&fwspk->stream); -} - -static void fwspk_remove(struct fw_unit *unit) -{ - struct fwspk *fwspk = dev_get_drvdata(&unit->device); - - amdtp_stream_pcm_abort(&fwspk->stream); - snd_card_disconnect(fwspk->card); - - mutex_lock(&fwspk->mutex); - fwspk_stop_stream(fwspk); - mutex_unlock(&fwspk->mutex); - - snd_card_free_when_closed(fwspk->card); -} - -static const struct device_info griffin_firewave = { - .driver_name = "FireWave", - .short_name = "FireWave", - .long_name = "Griffin FireWave Surround", - .pcm_constraints = firewave_constraints, - .mixer_channels = 6, - .mute_fb_id = 0x01, - .volume_fb_id = 0x02, -}; - -static const struct device_info lacie_speakers = { - .driver_name = "FWSpeakers", - .short_name = "FireWire Speakers", - .long_name = "LaCie FireWire Speakers", - .pcm_constraints = lacie_speakers_constraints, - .mixer_channels = 1, - .mute_fb_id = 0x01, - .volume_fb_id = 0x01, -}; - -static const struct ieee1394_device_id fwspk_id_table[] = { - { - .match_flags = IEEE1394_MATCH_VENDOR_ID | - IEEE1394_MATCH_MODEL_ID | - IEEE1394_MATCH_SPECIFIER_ID | - IEEE1394_MATCH_VERSION, - .vendor_id = VENDOR_GRIFFIN, - .model_id = 0x00f970, - .specifier_id = SPECIFIER_1394TA, - .version = VERSION_AVC, - .driver_data = (kernel_ulong_t)&griffin_firewave, - }, - { - .match_flags = IEEE1394_MATCH_VENDOR_ID | - IEEE1394_MATCH_MODEL_ID | - IEEE1394_MATCH_SPECIFIER_ID | - IEEE1394_MATCH_VERSION, - .vendor_id = VENDOR_LACIE, - .model_id = 0x00f970, - .specifier_id = SPECIFIER_1394TA, - .version = VERSION_AVC, - .driver_data = (kernel_ulong_t)&lacie_speakers, - }, - { } -}; -MODULE_DEVICE_TABLE(ieee1394, fwspk_id_table); - -static struct fw_driver fwspk_driver = { - .driver = { - .owner = THIS_MODULE, - .name = KBUILD_MODNAME, - .bus = &fw_bus_type, - }, - .probe = fwspk_probe, - .update = fwspk_bus_reset, - .remove = fwspk_remove, - .id_table = fwspk_id_table, -}; - -static int __init alsa_fwspk_init(void) -{ - return driver_register(&fwspk_driver.driver); -} - -static void __exit alsa_fwspk_exit(void) -{ - driver_unregister(&fwspk_driver.driver); -} - -module_init(alsa_fwspk_init); -module_exit(alsa_fwspk_exit); diff --git a/sound/firewire/speakers/Makefile b/sound/firewire/speakers/Makefile new file mode 100644 index 0000000..3db3c56a --- /dev/null +++ b/sound/firewire/speakers/Makefile @@ -0,0 +1,2 @@ +snd-firewire-speakers-objs := speakers.o +obj-m += snd-firewire-speakers.o diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c new file mode 100644 index 0000000..78eaec1 --- /dev/null +++ b/sound/firewire/speakers/speakers.c @@ -0,0 +1,801 @@ +/* + * OXFW970-based speakers driver + * + * Copyright (c) Clemens Ladisch clemens@ladisch.de + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <sound/control.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include "../cmp.h" +#include "../fcp.h" +#include "../amdtp.h" +#include "../lib.h" + +#define OXFORD_FIRMWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x50000) +/* 0x970?vvvv or 0x971?vvvv, where vvvv = firmware version */ + +#define OXFORD_HARDWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x90020) +#define OXFORD_HARDWARE_ID_OXFW970 0x39443841 +#define OXFORD_HARDWARE_ID_OXFW971 0x39373100 + +#define VENDOR_GRIFFIN 0x001292 +#define VENDOR_LACIE 0x00d04b + +#define SPECIFIER_1394TA 0x00a02d +#define VERSION_AVC 0x010001 + +struct device_info { + const char *driver_name; + const char *short_name; + const char *long_name; + int (*pcm_constraints)(struct snd_pcm_runtime *runtime); + unsigned int mixer_channels; + u8 mute_fb_id; + u8 volume_fb_id; +}; + +struct fwspk { + struct snd_card *card; + struct fw_unit *unit; + const struct device_info *device_info; + struct mutex mutex; + struct cmp_connection connection; + struct amdtp_stream stream; + bool mute; + s16 volume[6]; + s16 volume_min; + s16 volume_max; +}; + +MODULE_DESCRIPTION("FireWire speakers driver"); +MODULE_AUTHOR("Clemens Ladisch clemens@ladisch.de"); +MODULE_LICENSE("GPL v2"); + +static int firewave_rate_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + static unsigned int stereo_rates[] = { 48000, 96000 }; + struct snd_interval *channels = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *rate = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + + /* two channels work only at 48/96 kHz */ + if (snd_interval_max(channels) < 6) + return snd_interval_list(rate, 2, stereo_rates, 0); + return 0; +} + +static int firewave_channels_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + static const struct snd_interval all_channels = { .min = 6, .max = 6 }; + struct snd_interval *rate = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + + /* 32/44.1 kHz work only with all six channels */ + if (snd_interval_max(rate) < 48000) + return snd_interval_refine(channels, &all_channels); + return 0; +} + +static int firewave_constraints(struct snd_pcm_runtime *runtime) +{ + static unsigned int channels_list[] = { 2, 6 }; + static struct snd_pcm_hw_constraint_list channels_list_constraint = { + .count = 2, + .list = channels_list, + }; + int err; + + runtime->hw.rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000; + runtime->hw.channels_max = 6; + + err = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &channels_list_constraint); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + firewave_rate_constraint, NULL, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + firewave_channels_constraint, NULL, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + return 0; +} + +static int lacie_speakers_constraints(struct snd_pcm_runtime *runtime) +{ + runtime->hw.rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000; + + return 0; +} + +static int fwspk_open(struct snd_pcm_substream *substream) +{ + static const struct snd_pcm_hardware hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = AMDTP_OUT_PCM_FORMAT_BITS, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 4 * 1024 * 1024, + .period_bytes_min = 1, + .period_bytes_max = UINT_MAX, + .periods_min = 1, + .periods_max = UINT_MAX, + }; + struct fwspk *fwspk = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + runtime->hw = hardware; + + err = fwspk->device_info->pcm_constraints(runtime); + if (err < 0) + return err; + err = snd_pcm_limit_hw_rates(runtime); + 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); + if (err < 0) + return err; + + return 0; +} + +static int fwspk_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +static void fwspk_stop_stream(struct fwspk *fwspk) +{ + if (amdtp_stream_running(&fwspk->stream)) { + amdtp_stream_stop(&fwspk->stream); + cmp_connection_break(&fwspk->connection); + } +} + +static int fwspk_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct fwspk *fwspk = substream->private_data; + int err; + + mutex_lock(&fwspk->mutex); + fwspk_stop_stream(fwspk); + mutex_unlock(&fwspk->mutex); + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + goto error; + + amdtp_stream_set_parameters(&fwspk->stream, + params_rate(hw_params), + params_channels(hw_params), + 0); + + amdtp_stream_set_pcm_format(&fwspk->stream, + params_format(hw_params)); + + err = avc_general_set_sig_fmt(fwspk->unit, params_rate(hw_params), + AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) + goto err_buffer; + if (err != 0x09 /* ACCEPTED */) { + dev_err(&fwspk->unit->device, "failed to set sample rate\n"); + err = -EIO; + goto error; + } + + return 0; + +err_buffer: + snd_pcm_lib_free_vmalloc_buffer(substream); +error: + return err; +} + +static int fwspk_hw_free(struct snd_pcm_substream *substream) +{ + struct fwspk *fwspk = substream->private_data; + + mutex_lock(&fwspk->mutex); + fwspk_stop_stream(fwspk); + mutex_unlock(&fwspk->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int fwspk_prepare(struct snd_pcm_substream *substream) +{ + struct fwspk *fwspk = substream->private_data; + int err; + + mutex_lock(&fwspk->mutex); + + if (amdtp_streaming_error(&fwspk->stream)) + fwspk_stop_stream(fwspk); + + if (!amdtp_stream_running(&fwspk->stream)) { + err = cmp_connection_establish(&fwspk->connection, + amdtp_stream_get_max_payload(&fwspk->stream)); + if (err < 0) + goto err_mutex; + + err = amdtp_stream_start(&fwspk->stream, + fwspk->connection.resources.channel, + fwspk->connection.speed); + if (err < 0) + goto err_connection; + } + + mutex_unlock(&fwspk->mutex); + + amdtp_stream_pcm_prepare(&fwspk->stream); + + return 0; + +err_connection: + cmp_connection_break(&fwspk->connection); +err_mutex: + mutex_unlock(&fwspk->mutex); + + return err; +} + +static int fwspk_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct fwspk *fwspk = substream->private_data; + struct snd_pcm_substream *pcm; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + pcm = substream; + break; + case SNDRV_PCM_TRIGGER_STOP: + pcm = NULL; + break; + default: + return -EINVAL; + } + amdtp_stream_pcm_trigger(&fwspk->stream, pcm); + return 0; +} + +static snd_pcm_uframes_t fwspk_pointer(struct snd_pcm_substream *substream) +{ + struct fwspk *fwspk = substream->private_data; + + return amdtp_stream_pcm_pointer(&fwspk->stream); +} + +static int fwspk_create_pcm(struct fwspk *fwspk) +{ + static struct snd_pcm_ops ops = { + .open = fwspk_open, + .close = fwspk_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = fwspk_hw_params, + .hw_free = fwspk_hw_free, + .prepare = fwspk_prepare, + .trigger = fwspk_trigger, + .pointer = fwspk_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(fwspk->card, "OXFW970", 0, 1, 0, &pcm); + if (err < 0) + return err; + pcm->private_data = fwspk; + strcpy(pcm->name, fwspk->device_info->short_name); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &ops); + return 0; +} + +enum control_action { CTL_READ, CTL_WRITE }; +enum control_attribute { + CTL_MIN = 0x02, + CTL_MAX = 0x03, + CTL_CURRENT = 0x10, +}; + +static int fwspk_mute_command(struct fwspk *fwspk, bool *value, + enum control_action action) +{ + u8 *buf; + u8 response_ok; + int err; + + buf = kmalloc(11, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (action == CTL_READ) { + buf[0] = 0x01; /* AV/C, STATUS */ + response_ok = 0x0c; /* STABLE */ + } else { + buf[0] = 0x00; /* AV/C, CONTROL */ + response_ok = 0x09; /* ACCEPTED */ + } + buf[1] = 0x08; /* audio unit 0 */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x81; /* function block type: feature */ + buf[4] = fwspk->device_info->mute_fb_id; /* function block ID */ + buf[5] = 0x10; /* control attribute: current */ + buf[6] = 0x02; /* selector length */ + buf[7] = 0x00; /* audio channel number */ + buf[8] = 0x01; /* control selector: mute */ + buf[9] = 0x01; /* control data length */ + if (action == CTL_READ) + buf[10] = 0xff; + else + buf[10] = *value ? 0x70 : 0x60; + + err = fcp_avc_transaction(fwspk->unit, buf, 11, buf, 11, 0x3fe); + if (err < 0) + goto error; + if (err < 11) { + dev_err(&fwspk->unit->device, "short FCP response\n"); + err = -EIO; + goto error; + } + if (buf[0] != response_ok) { + dev_err(&fwspk->unit->device, "mute command failed\n"); + err = -EIO; + goto error; + } + if (action == CTL_READ) + *value = buf[10] == 0x70; + + err = 0; + +error: + kfree(buf); + + return err; +} + +static int fwspk_volume_command(struct fwspk *fwspk, s16 *value, + unsigned int channel, + enum control_attribute attribute, + enum control_action action) +{ + u8 *buf; + u8 response_ok; + int err; + + buf = kmalloc(12, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (action == CTL_READ) { + buf[0] = 0x01; /* AV/C, STATUS */ + response_ok = 0x0c; /* STABLE */ + } else { + buf[0] = 0x00; /* AV/C, CONTROL */ + response_ok = 0x09; /* ACCEPTED */ + } + buf[1] = 0x08; /* audio unit 0 */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x81; /* function block type: feature */ + buf[4] = fwspk->device_info->volume_fb_id; /* function block ID */ + buf[5] = attribute; /* control attribute */ + buf[6] = 0x02; /* selector length */ + buf[7] = channel; /* audio channel number */ + buf[8] = 0x02; /* control selector: volume */ + buf[9] = 0x02; /* control data length */ + if (action == CTL_READ) { + buf[10] = 0xff; + buf[11] = 0xff; + } else { + buf[10] = *value >> 8; + buf[11] = *value; + } + + err = fcp_avc_transaction(fwspk->unit, buf, 12, buf, 12, 0x3fe); + if (err < 0) + goto error; + if (err < 12) { + dev_err(&fwspk->unit->device, "short FCP response\n"); + err = -EIO; + goto error; + } + if (buf[0] != response_ok) { + dev_err(&fwspk->unit->device, "volume command failed\n"); + err = -EIO; + goto error; + } + if (action == CTL_READ) + *value = (buf[10] << 8) | buf[11]; + + err = 0; + +error: + kfree(buf); + + return err; +} + +static int fwspk_mute_get(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct fwspk *fwspk = control->private_data; + + value->value.integer.value[0] = !fwspk->mute; + + return 0; +} + +static int fwspk_mute_put(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct fwspk *fwspk = control->private_data; + bool mute; + int err; + + mute = !value->value.integer.value[0]; + + if (mute == fwspk->mute) + return 0; + + err = fwspk_mute_command(fwspk, &mute, CTL_WRITE); + if (err < 0) + return err; + fwspk->mute = mute; + + return 1; +} + +static int fwspk_volume_info(struct snd_kcontrol *control, + struct snd_ctl_elem_info *info) +{ + struct fwspk *fwspk = control->private_data; + + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = fwspk->device_info->mixer_channels; + info->value.integer.min = fwspk->volume_min; + info->value.integer.max = fwspk->volume_max; + + return 0; +} + +static const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 }; + +static int fwspk_volume_get(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct fwspk *fwspk = control->private_data; + unsigned int i; + + for (i = 0; i < fwspk->device_info->mixer_channels; ++i) + value->value.integer.value[channel_map[i]] = fwspk->volume[i]; + + return 0; +} + +static int fwspk_volume_put(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct fwspk *fwspk = control->private_data; + unsigned int i, changed_channels; + bool equal_values = true; + s16 volume; + int err; + + for (i = 0; i < fwspk->device_info->mixer_channels; ++i) { + if (value->value.integer.value[i] < fwspk->volume_min || + value->value.integer.value[i] > fwspk->volume_max) + return -EINVAL; + if (value->value.integer.value[i] != + value->value.integer.value[0]) + equal_values = false; + } + + changed_channels = 0; + for (i = 0; i < fwspk->device_info->mixer_channels; ++i) + if (value->value.integer.value[channel_map[i]] != + fwspk->volume[i]) + changed_channels |= 1 << (i + 1); + + if (equal_values && changed_channels != 0) + changed_channels = 1 << 0; + + for (i = 0; i <= fwspk->device_info->mixer_channels; ++i) { + volume = value->value.integer.value[channel_map[i ? i - 1 : 0]]; + if (changed_channels & (1 << i)) { + err = fwspk_volume_command(fwspk, &volume, i, + CTL_CURRENT, CTL_WRITE); + if (err < 0) + return err; + } + if (i > 0) + fwspk->volume[i - 1] = volume; + } + + return changed_channels != 0; +} + +static int fwspk_create_mixer(struct fwspk *fwspk) +{ + static const struct snd_kcontrol_new controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = fwspk_mute_get, + .put = fwspk_mute_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .info = fwspk_volume_info, + .get = fwspk_volume_get, + .put = fwspk_volume_put, + }, + }; + unsigned int i, first_ch; + int err; + + err = fwspk_volume_command(fwspk, &fwspk->volume_min, + 0, CTL_MIN, CTL_READ); + if (err < 0) + return err; + err = fwspk_volume_command(fwspk, &fwspk->volume_max, + 0, CTL_MAX, CTL_READ); + if (err < 0) + return err; + + err = fwspk_mute_command(fwspk, &fwspk->mute, CTL_READ); + if (err < 0) + return err; + + first_ch = fwspk->device_info->mixer_channels == 1 ? 0 : 1; + for (i = 0; i < fwspk->device_info->mixer_channels; ++i) { + err = fwspk_volume_command(fwspk, &fwspk->volume[i], + first_ch + i, CTL_CURRENT, CTL_READ); + if (err < 0) + return err; + } + + for (i = 0; i < ARRAY_SIZE(controls); ++i) { + err = snd_ctl_add(fwspk->card, + snd_ctl_new1(&controls[i], fwspk)); + if (err < 0) + return err; + } + + return 0; +} + +static u32 fwspk_read_firmware_version(struct fw_unit *unit) +{ + __be32 data; + int err; + + err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, + OXFORD_FIRMWARE_ID_ADDRESS, &data, 4, 0); + return err >= 0 ? be32_to_cpu(data) : 0; +} + +static void fwspk_card_free(struct snd_card *card) +{ + struct fwspk *fwspk = card->private_data; + + amdtp_stream_destroy(&fwspk->stream); + cmp_connection_destroy(&fwspk->connection); + fw_unit_put(fwspk->unit); + mutex_destroy(&fwspk->mutex); +} + +static int fwspk_probe(struct fw_unit *unit, + const struct ieee1394_device_id *id) +{ + struct fw_device *fw_dev = fw_parent_device(unit); + struct snd_card *card; + struct fwspk *fwspk; + u32 firmware; + int err; + + err = snd_card_create(-1, NULL, THIS_MODULE, sizeof(*fwspk), &card); + if (err < 0) + return err; + snd_card_set_dev(card, &unit->device); + + fwspk = card->private_data; + fwspk->card = card; + mutex_init(&fwspk->mutex); + fwspk->unit = fw_unit_get(unit); + fwspk->device_info = (const struct device_info *)id->driver_data; + + err = cmp_connection_init(&fwspk->connection, unit, CMP_INPUT, 0); + if (err < 0) + goto err_unit; + + err = amdtp_stream_init(&fwspk->stream, unit, AMDTP_OUT_STREAM, + CIP_NONBLOCKING); + if (err < 0) + goto err_connection; + + card->private_free = fwspk_card_free; + + strcpy(card->driver, fwspk->device_info->driver_name); + strcpy(card->shortname, fwspk->device_info->short_name); + firmware = fwspk_read_firmware_version(unit); + snprintf(card->longname, sizeof(card->longname), + "%s (OXFW%x %04x), GUID %08x%08x at %s, S%d", + fwspk->device_info->long_name, + firmware >> 20, firmware & 0xffff, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&unit->device), 100 << fw_dev->max_speed); + strcpy(card->mixername, "OXFW970"); + + err = fwspk_create_pcm(fwspk); + if (err < 0) + goto error; + + err = fwspk_create_mixer(fwspk); + if (err < 0) + goto error; + + err = snd_card_register(card); + if (err < 0) + goto error; + + dev_set_drvdata(&unit->device, fwspk); + + return 0; + +err_connection: + cmp_connection_destroy(&fwspk->connection); +err_unit: + fw_unit_put(fwspk->unit); + mutex_destroy(&fwspk->mutex); +error: + snd_card_free(card); + return err; +} + +static void fwspk_bus_reset(struct fw_unit *unit) +{ + struct fwspk *fwspk = dev_get_drvdata(&unit->device); + + fcp_bus_reset(fwspk->unit); + + if (cmp_connection_update(&fwspk->connection) < 0) { + amdtp_stream_pcm_abort(&fwspk->stream); + mutex_lock(&fwspk->mutex); + fwspk_stop_stream(fwspk); + mutex_unlock(&fwspk->mutex); + return; + } + + amdtp_stream_update(&fwspk->stream); +} + +static void fwspk_remove(struct fw_unit *unit) +{ + struct fwspk *fwspk = dev_get_drvdata(&unit->device); + + amdtp_stream_pcm_abort(&fwspk->stream); + snd_card_disconnect(fwspk->card); + + mutex_lock(&fwspk->mutex); + fwspk_stop_stream(fwspk); + mutex_unlock(&fwspk->mutex); + + snd_card_free_when_closed(fwspk->card); +} + +static const struct device_info griffin_firewave = { + .driver_name = "FireWave", + .short_name = "FireWave", + .long_name = "Griffin FireWave Surround", + .pcm_constraints = firewave_constraints, + .mixer_channels = 6, + .mute_fb_id = 0x01, + .volume_fb_id = 0x02, +}; + +static const struct device_info lacie_speakers = { + .driver_name = "FWSpeakers", + .short_name = "FireWire Speakers", + .long_name = "LaCie FireWire Speakers", + .pcm_constraints = lacie_speakers_constraints, + .mixer_channels = 1, + .mute_fb_id = 0x01, + .volume_fb_id = 0x01, +}; + +static const struct ieee1394_device_id fwspk_id_table[] = { + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = VENDOR_GRIFFIN, + .model_id = 0x00f970, + .specifier_id = SPECIFIER_1394TA, + .version = VERSION_AVC, + .driver_data = (kernel_ulong_t)&griffin_firewave, + }, + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = VENDOR_LACIE, + .model_id = 0x00f970, + .specifier_id = SPECIFIER_1394TA, + .version = VERSION_AVC, + .driver_data = (kernel_ulong_t)&lacie_speakers, + }, + { } +}; +MODULE_DEVICE_TABLE(ieee1394, fwspk_id_table); + +static struct fw_driver fwspk_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = fwspk_probe, + .update = fwspk_bus_reset, + .remove = fwspk_remove, + .id_table = fwspk_id_table, +}; + +static int __init alsa_fwspk_init(void) +{ + return driver_register(&fwspk_driver.driver); +} + +static void __exit alsa_fwspk_exit(void) +{ + driver_unregister(&fwspk_driver.driver); +} + +module_init(alsa_fwspk_init); +module_exit(alsa_fwspk_exit);
To make it easy to work in followed commit.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/speakers/Makefile | 2 +- sound/firewire/speakers/speakers.c | 157 +++++++----------------------- sound/firewire/speakers/speakers.h | 54 ++++++++++ sound/firewire/speakers/speakers_stream.c | 72 ++++++++++++++ 4 files changed, 163 insertions(+), 122 deletions(-) create mode 100644 sound/firewire/speakers/speakers.h create mode 100644 sound/firewire/speakers/speakers_stream.c
diff --git a/sound/firewire/speakers/Makefile b/sound/firewire/speakers/Makefile index 3db3c56a..f46ae1d 100644 --- a/sound/firewire/speakers/Makefile +++ b/sound/firewire/speakers/Makefile @@ -1,2 +1,2 @@ -snd-firewire-speakers-objs := speakers.o +snd-firewire-speakers-objs := speakers_stream.o speakers.o obj-m += snd-firewire-speakers.o diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c index 78eaec1..47d3cc0 100644 --- a/sound/firewire/speakers/speakers.c +++ b/sound/firewire/speakers/speakers.c @@ -1,26 +1,11 @@ /* - * OXFW970-based speakers driver + * speakers.c - a part of OXFW970/971-based speakers driver * * Copyright (c) Clemens Ladisch clemens@ladisch.de * Licensed under the terms of the GNU General Public License, version 2. */
-#include <linux/device.h> -#include <linux/firewire.h> -#include <linux/firewire-constants.h> -#include <linux/module.h> -#include <linux/mod_devicetable.h> -#include <linux/mutex.h> -#include <linux/slab.h> -#include <sound/control.h> -#include <sound/core.h> -#include <sound/initval.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include "../cmp.h" -#include "../fcp.h" -#include "../amdtp.h" -#include "../lib.h" +#include "speakers.h"
#define OXFORD_FIRMWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x50000) /* 0x970?vvvv or 0x971?vvvv, where vvvv = firmware version */ @@ -35,29 +20,6 @@ #define SPECIFIER_1394TA 0x00a02d #define VERSION_AVC 0x010001
-struct device_info { - const char *driver_name; - const char *short_name; - const char *long_name; - int (*pcm_constraints)(struct snd_pcm_runtime *runtime); - unsigned int mixer_channels; - u8 mute_fb_id; - u8 volume_fb_id; -}; - -struct fwspk { - struct snd_card *card; - struct fw_unit *unit; - const struct device_info *device_info; - struct mutex mutex; - struct cmp_connection connection; - struct amdtp_stream stream; - bool mute; - s16 volume[6]; - s16 volume_min; - s16 volume_max; -}; - MODULE_DESCRIPTION("FireWire speakers driver"); MODULE_AUTHOR("Clemens Ladisch clemens@ladisch.de"); MODULE_LICENSE("GPL v2"); @@ -185,14 +147,6 @@ static int fwspk_close(struct snd_pcm_substream *substream) return 0; }
-static void fwspk_stop_stream(struct fwspk *fwspk) -{ - if (amdtp_stream_running(&fwspk->stream)) { - amdtp_stream_stop(&fwspk->stream); - cmp_connection_break(&fwspk->connection); - } -} - static int fwspk_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { @@ -200,7 +154,7 @@ static int fwspk_hw_params(struct snd_pcm_substream *substream, int err;
mutex_lock(&fwspk->mutex); - fwspk_stop_stream(fwspk); + snd_fwspk_stream_stop(fwspk); mutex_unlock(&fwspk->mutex);
err = snd_pcm_lib_alloc_vmalloc_buffer(substream, @@ -208,12 +162,12 @@ static int fwspk_hw_params(struct snd_pcm_substream *substream, if (err < 0) goto error;
- amdtp_stream_set_parameters(&fwspk->stream, + amdtp_stream_set_parameters(&fwspk->rx_stream, params_rate(hw_params), params_channels(hw_params), 0);
- amdtp_stream_set_pcm_format(&fwspk->stream, + amdtp_stream_set_pcm_format(&fwspk->rx_stream, params_format(hw_params));
err = avc_general_set_sig_fmt(fwspk->unit, params_rate(hw_params), @@ -239,7 +193,7 @@ static int fwspk_hw_free(struct snd_pcm_substream *substream) struct fwspk *fwspk = substream->private_data;
mutex_lock(&fwspk->mutex); - fwspk_stop_stream(fwspk); + snd_fwspk_stream_stop(fwspk); mutex_unlock(&fwspk->mutex);
return snd_pcm_lib_free_vmalloc_buffer(substream); @@ -252,33 +206,15 @@ static int fwspk_prepare(struct snd_pcm_substream *substream)
mutex_lock(&fwspk->mutex);
- if (amdtp_streaming_error(&fwspk->stream)) - fwspk_stop_stream(fwspk); + snd_fwspk_stream_stop(fwspk);
- if (!amdtp_stream_running(&fwspk->stream)) { - err = cmp_connection_establish(&fwspk->connection, - amdtp_stream_get_max_payload(&fwspk->stream)); - if (err < 0) - goto err_mutex; - - err = amdtp_stream_start(&fwspk->stream, - fwspk->connection.resources.channel, - fwspk->connection.speed); - if (err < 0) - goto err_connection; - } - - mutex_unlock(&fwspk->mutex); - - amdtp_stream_pcm_prepare(&fwspk->stream); - - return 0; + err = snd_fwspk_stream_start(fwspk); + if (err < 0) + goto end;
-err_connection: - cmp_connection_break(&fwspk->connection); -err_mutex: + amdtp_stream_pcm_prepare(&fwspk->rx_stream); +end: mutex_unlock(&fwspk->mutex); - return err; }
@@ -297,7 +233,7 @@ static int fwspk_trigger(struct snd_pcm_substream *substream, int cmd) default: return -EINVAL; } - amdtp_stream_pcm_trigger(&fwspk->stream, pcm); + amdtp_stream_pcm_trigger(&fwspk->rx_stream, pcm); return 0; }
@@ -305,7 +241,7 @@ static snd_pcm_uframes_t fwspk_pointer(struct snd_pcm_substream *substream) { struct fwspk *fwspk = substream->private_data;
- return amdtp_stream_pcm_pointer(&fwspk->stream); + return amdtp_stream_pcm_pointer(&fwspk->rx_stream); }
static int fwspk_create_pcm(struct fwspk *fwspk) @@ -623,9 +559,11 @@ static void fwspk_card_free(struct snd_card *card) { struct fwspk *fwspk = card->private_data;
- amdtp_stream_destroy(&fwspk->stream); - cmp_connection_destroy(&fwspk->connection); - fw_unit_put(fwspk->unit); + mutex_lock(&fwspk->mutex); + snd_fwspk_stream_destroy(fwspk); + mutex_unlock(&fwspk->mutex); + + fw_unit_put(fwspk->unit); /* dec reference counter */ mutex_destroy(&fwspk->mutex); }
@@ -641,24 +579,13 @@ static int fwspk_probe(struct fw_unit *unit, err = snd_card_create(-1, NULL, THIS_MODULE, sizeof(*fwspk), &card); if (err < 0) return err; - snd_card_set_dev(card, &unit->device);
+ card->private_free = fwspk_card_free; fwspk = card->private_data; fwspk->card = card; - mutex_init(&fwspk->mutex); - fwspk->unit = fw_unit_get(unit); + fwspk->unit = fw_unit_get(unit); /* inc reference counter */ fwspk->device_info = (const struct device_info *)id->driver_data; - - err = cmp_connection_init(&fwspk->connection, unit, CMP_INPUT, 0); - if (err < 0) - goto err_unit; - - err = amdtp_stream_init(&fwspk->stream, unit, AMDTP_OUT_STREAM, - CIP_NONBLOCKING); - if (err < 0) - goto err_connection; - - card->private_free = fwspk_card_free; + mutex_init(&fwspk->mutex);
strcpy(card->driver, fwspk->device_info->driver_name); strcpy(card->shortname, fwspk->device_info->short_name); @@ -671,28 +598,26 @@ static int fwspk_probe(struct fw_unit *unit, dev_name(&unit->device), 100 << fw_dev->max_speed); strcpy(card->mixername, "OXFW970");
+ err = snd_fwspk_stream_init(fwspk); + if (err < 0) + goto err_card; + err = fwspk_create_pcm(fwspk); if (err < 0) - goto error; + goto err_card;
err = fwspk_create_mixer(fwspk); if (err < 0) - goto error; + goto err_card;
+ snd_card_set_dev(card, &unit->device); err = snd_card_register(card); if (err < 0) - goto error; - + goto err_card; dev_set_drvdata(&unit->device, fwspk);
return 0; - -err_connection: - cmp_connection_destroy(&fwspk->connection); -err_unit: - fw_unit_put(fwspk->unit); - mutex_destroy(&fwspk->mutex); -error: +err_card: snd_card_free(card); return err; } @@ -701,30 +626,20 @@ static void fwspk_bus_reset(struct fw_unit *unit) { struct fwspk *fwspk = dev_get_drvdata(&unit->device);
- fcp_bus_reset(fwspk->unit); + mutex_lock(&fwspk->mutex);
- if (cmp_connection_update(&fwspk->connection) < 0) { - amdtp_stream_pcm_abort(&fwspk->stream); - mutex_lock(&fwspk->mutex); - fwspk_stop_stream(fwspk); - mutex_unlock(&fwspk->mutex); - return; - } + fcp_bus_reset(fwspk->unit); + snd_fwspk_stream_update(fwspk);
- amdtp_stream_update(&fwspk->stream); + mutex_unlock(&fwspk->mutex); }
static void fwspk_remove(struct fw_unit *unit) { struct fwspk *fwspk = dev_get_drvdata(&unit->device);
- amdtp_stream_pcm_abort(&fwspk->stream); + snd_fwspk_stream_destroy(fwspk); snd_card_disconnect(fwspk->card); - - mutex_lock(&fwspk->mutex); - fwspk_stop_stream(fwspk); - mutex_unlock(&fwspk->mutex); - snd_card_free_when_closed(fwspk->card); }
diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h new file mode 100644 index 0000000..d322b8b --- /dev/null +++ b/sound/firewire/speakers/speakers.h @@ -0,0 +1,54 @@ +/* + * speakers.h - a part of OXFW970/971-based speakers driver + * + * Copyright (c) Clemens Ladisch clemens@ladisch.de + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/slab.h> + +#include <sound/control.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> + +#include "../cmp.h" +#include "../fcp.h" +#include "../amdtp.h" +#include "../lib.h" + +struct device_info { + const char *driver_name; + const char *short_name; + const char *long_name; + int (*pcm_constraints)(struct snd_pcm_runtime *runtime); + unsigned int mixer_channels; + u8 mute_fb_id; + u8 volume_fb_id; +}; + +struct fwspk { + struct snd_card *card; + struct fw_unit *unit; + const struct device_info *device_info; + struct mutex mutex; + struct cmp_connection in_conn; + struct amdtp_stream rx_stream; + bool mute; + s16 volume[6]; + s16 volume_min; + s16 volume_max; +}; + +int snd_fwspk_stream_init(struct fwspk *fwspk); +int snd_fwspk_stream_start(struct fwspk *fwspk); +void snd_fwspk_stream_stop(struct fwspk *fwspk); +void snd_fwspk_stream_destroy(struct fwspk *fwspk); +void snd_fwspk_stream_update(struct fwspk *fwspk); diff --git a/sound/firewire/speakers/speakers_stream.c b/sound/firewire/speakers/speakers_stream.c new file mode 100644 index 0000000..9cc0ffb --- /dev/null +++ b/sound/firewire/speakers/speakers_stream.c @@ -0,0 +1,72 @@ +/* + * speakers_stream.c - a part of OXFW970/971-based speakers driver + * + * Copyright (c) Takashi Sakamoto o-takashi@sakamocchi.jp + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "speakers.h" + +int snd_fwspk_stream_init(struct fwspk *fwspk) +{ + int err; + + err = cmp_connection_init(&fwspk->in_conn, fwspk->unit, + CMP_INPUT, 0); + if (err < 0) { + fw_unit_put(fwspk->unit); + goto end; + } + + err = amdtp_stream_init(&fwspk->rx_stream, fwspk->unit, + AMDTP_OUT_STREAM, CIP_NONBLOCKING); + if (err < 0) + cmp_connection_destroy(&fwspk->in_conn); +end: + return err; +} + +int snd_fwspk_stream_start(struct fwspk *fwspk) +{ + int err = 0; + + if (amdtp_stream_running(&fwspk->rx_stream)) + goto end; + + err = cmp_connection_establish(&fwspk->in_conn, + amdtp_stream_get_max_payload(&fwspk->rx_stream)); + if (err < 0) + goto end; + + err = amdtp_stream_start(&fwspk->rx_stream, + fwspk->in_conn.resources.channel, + fwspk->in_conn.speed); + if (err < 0) + cmp_connection_break(&fwspk->in_conn); +end: + return err; +} + +void snd_fwspk_stream_stop(struct fwspk *fwspk) +{ + if (amdtp_stream_running(&fwspk->rx_stream)) + amdtp_stream_stop(&fwspk->rx_stream); + + cmp_connection_break(&fwspk->in_conn); +} + +void snd_fwspk_stream_destroy(struct fwspk *fwspk) +{ + amdtp_stream_pcm_abort(&fwspk->rx_stream); + snd_fwspk_stream_stop(fwspk); +} + +void snd_fwspk_stream_update(struct fwspk *fwspk) +{ + if (cmp_connection_update(&fwspk->in_conn) < 0) { + amdtp_stream_pcm_abort(&fwspk->rx_stream); + snd_fwspk_stream_stop(fwspk); + } else { + amdtp_stream_update(&fwspk->rx_stream); + } +}
To make it easy to work in followed commit.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/speakers/Makefile | 2 +- sound/firewire/speakers/speakers.c | 248 +------------------------------ sound/firewire/speakers/speakers.h | 5 + sound/firewire/speakers/speakers_pcm.c | 259 +++++++++++++++++++++++++++++++++ 4 files changed, 266 insertions(+), 248 deletions(-) create mode 100644 sound/firewire/speakers/speakers_pcm.c
diff --git a/sound/firewire/speakers/Makefile b/sound/firewire/speakers/Makefile index f46ae1d..a970ccf 100644 --- a/sound/firewire/speakers/Makefile +++ b/sound/firewire/speakers/Makefile @@ -1,2 +1,2 @@ -snd-firewire-speakers-objs := speakers_stream.o speakers.o +snd-firewire-speakers-objs := speakers_stream.o speakers_pcm.o speakers.o obj-m += snd-firewire-speakers.o diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c index 47d3cc0..20bba46 100644 --- a/sound/firewire/speakers/speakers.c +++ b/sound/firewire/speakers/speakers.c @@ -24,252 +24,6 @@ MODULE_DESCRIPTION("FireWire speakers driver"); MODULE_AUTHOR("Clemens Ladisch clemens@ladisch.de"); MODULE_LICENSE("GPL v2");
-static int firewave_rate_constraint(struct snd_pcm_hw_params *params, - struct snd_pcm_hw_rule *rule) -{ - static unsigned int stereo_rates[] = { 48000, 96000 }; - struct snd_interval *channels = - hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); - struct snd_interval *rate = - hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); - - /* two channels work only at 48/96 kHz */ - if (snd_interval_max(channels) < 6) - return snd_interval_list(rate, 2, stereo_rates, 0); - return 0; -} - -static int firewave_channels_constraint(struct snd_pcm_hw_params *params, - struct snd_pcm_hw_rule *rule) -{ - static const struct snd_interval all_channels = { .min = 6, .max = 6 }; - struct snd_interval *rate = - hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); - struct snd_interval *channels = - hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); - - /* 32/44.1 kHz work only with all six channels */ - if (snd_interval_max(rate) < 48000) - return snd_interval_refine(channels, &all_channels); - return 0; -} - -static int firewave_constraints(struct snd_pcm_runtime *runtime) -{ - static unsigned int channels_list[] = { 2, 6 }; - static struct snd_pcm_hw_constraint_list channels_list_constraint = { - .count = 2, - .list = channels_list, - }; - int err; - - runtime->hw.rates = SNDRV_PCM_RATE_32000 | - SNDRV_PCM_RATE_44100 | - SNDRV_PCM_RATE_48000 | - SNDRV_PCM_RATE_96000; - runtime->hw.channels_max = 6; - - err = snd_pcm_hw_constraint_list(runtime, 0, - SNDRV_PCM_HW_PARAM_CHANNELS, - &channels_list_constraint); - if (err < 0) - return err; - err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, - firewave_rate_constraint, NULL, - SNDRV_PCM_HW_PARAM_CHANNELS, -1); - if (err < 0) - return err; - err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, - firewave_channels_constraint, NULL, - SNDRV_PCM_HW_PARAM_RATE, -1); - if (err < 0) - return err; - - return 0; -} - -static int lacie_speakers_constraints(struct snd_pcm_runtime *runtime) -{ - runtime->hw.rates = SNDRV_PCM_RATE_32000 | - SNDRV_PCM_RATE_44100 | - SNDRV_PCM_RATE_48000 | - SNDRV_PCM_RATE_88200 | - SNDRV_PCM_RATE_96000; - - return 0; -} - -static int fwspk_open(struct snd_pcm_substream *substream) -{ - static const struct snd_pcm_hardware hardware = { - .info = SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_BATCH | - SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_BLOCK_TRANSFER, - .formats = AMDTP_OUT_PCM_FORMAT_BITS, - .channels_min = 2, - .channels_max = 2, - .buffer_bytes_max = 4 * 1024 * 1024, - .period_bytes_min = 1, - .period_bytes_max = UINT_MAX, - .periods_min = 1, - .periods_max = UINT_MAX, - }; - struct fwspk *fwspk = substream->private_data; - struct snd_pcm_runtime *runtime = substream->runtime; - int err; - - runtime->hw = hardware; - - err = fwspk->device_info->pcm_constraints(runtime); - if (err < 0) - return err; - err = snd_pcm_limit_hw_rates(runtime); - 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); - if (err < 0) - return err; - - return 0; -} - -static int fwspk_close(struct snd_pcm_substream *substream) -{ - return 0; -} - -static int fwspk_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *hw_params) -{ - struct fwspk *fwspk = substream->private_data; - int err; - - mutex_lock(&fwspk->mutex); - snd_fwspk_stream_stop(fwspk); - mutex_unlock(&fwspk->mutex); - - err = snd_pcm_lib_alloc_vmalloc_buffer(substream, - params_buffer_bytes(hw_params)); - if (err < 0) - goto error; - - amdtp_stream_set_parameters(&fwspk->rx_stream, - params_rate(hw_params), - params_channels(hw_params), - 0); - - amdtp_stream_set_pcm_format(&fwspk->rx_stream, - params_format(hw_params)); - - err = avc_general_set_sig_fmt(fwspk->unit, params_rate(hw_params), - AVC_GENERAL_PLUG_DIR_IN, 0); - if (err < 0) - goto err_buffer; - if (err != 0x09 /* ACCEPTED */) { - dev_err(&fwspk->unit->device, "failed to set sample rate\n"); - err = -EIO; - goto error; - } - - return 0; - -err_buffer: - snd_pcm_lib_free_vmalloc_buffer(substream); -error: - return err; -} - -static int fwspk_hw_free(struct snd_pcm_substream *substream) -{ - struct fwspk *fwspk = substream->private_data; - - mutex_lock(&fwspk->mutex); - snd_fwspk_stream_stop(fwspk); - mutex_unlock(&fwspk->mutex); - - return snd_pcm_lib_free_vmalloc_buffer(substream); -} - -static int fwspk_prepare(struct snd_pcm_substream *substream) -{ - struct fwspk *fwspk = substream->private_data; - int err; - - mutex_lock(&fwspk->mutex); - - snd_fwspk_stream_stop(fwspk); - - err = snd_fwspk_stream_start(fwspk); - if (err < 0) - goto end; - - amdtp_stream_pcm_prepare(&fwspk->rx_stream); -end: - mutex_unlock(&fwspk->mutex); - return err; -} - -static int fwspk_trigger(struct snd_pcm_substream *substream, int cmd) -{ - struct fwspk *fwspk = substream->private_data; - struct snd_pcm_substream *pcm; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - pcm = substream; - break; - case SNDRV_PCM_TRIGGER_STOP: - pcm = NULL; - break; - default: - return -EINVAL; - } - amdtp_stream_pcm_trigger(&fwspk->rx_stream, pcm); - return 0; -} - -static snd_pcm_uframes_t fwspk_pointer(struct snd_pcm_substream *substream) -{ - struct fwspk *fwspk = substream->private_data; - - return amdtp_stream_pcm_pointer(&fwspk->rx_stream); -} - -static int fwspk_create_pcm(struct fwspk *fwspk) -{ - static struct snd_pcm_ops ops = { - .open = fwspk_open, - .close = fwspk_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = fwspk_hw_params, - .hw_free = fwspk_hw_free, - .prepare = fwspk_prepare, - .trigger = fwspk_trigger, - .pointer = fwspk_pointer, - .page = snd_pcm_lib_get_vmalloc_page, - .mmap = snd_pcm_lib_mmap_vmalloc, - }; - struct snd_pcm *pcm; - int err; - - err = snd_pcm_new(fwspk->card, "OXFW970", 0, 1, 0, &pcm); - if (err < 0) - return err; - pcm->private_data = fwspk; - strcpy(pcm->name, fwspk->device_info->short_name); - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &ops); - return 0; -} - enum control_action { CTL_READ, CTL_WRITE }; enum control_attribute { CTL_MIN = 0x02, @@ -602,7 +356,7 @@ static int fwspk_probe(struct fw_unit *unit, if (err < 0) goto err_card;
- err = fwspk_create_pcm(fwspk); + err = snd_fwspk_create_pcm(fwspk); if (err < 0) goto err_card;
diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h index d322b8b..379196d 100644 --- a/sound/firewire/speakers/speakers.h +++ b/sound/firewire/speakers/speakers.h @@ -52,3 +52,8 @@ int snd_fwspk_stream_start(struct fwspk *fwspk); void snd_fwspk_stream_stop(struct fwspk *fwspk); void snd_fwspk_stream_destroy(struct fwspk *fwspk); void snd_fwspk_stream_update(struct fwspk *fwspk); + +int snd_fwspk_create_pcm(struct fwspk *fwspk); + +int firewave_constraints(struct snd_pcm_runtime *runtime); +int lacie_speakers_constraints(struct snd_pcm_runtime *runtime); diff --git a/sound/firewire/speakers/speakers_pcm.c b/sound/firewire/speakers/speakers_pcm.c new file mode 100644 index 0000000..8fa437f --- /dev/null +++ b/sound/firewire/speakers/speakers_pcm.c @@ -0,0 +1,259 @@ +/* + * speakers_pcm.c - a part of OXFW970/971-based speakers driver + * + * Copyright (c) Clemens Ladisch clemens@ladisch.de + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "speakers.h" + +static int firewave_rate_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + static unsigned int stereo_rates[] = { 48000, 96000 }; + struct snd_interval *channels = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *rate = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + + /* two channels work only at 48/96 kHz */ + if (snd_interval_max(channels) < 6) + return snd_interval_list(rate, 2, stereo_rates, 0); + return 0; +} + +static int firewave_channels_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + static const struct snd_interval all_channels = { .min = 6, .max = 6 }; + struct snd_interval *rate = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + + /* 32/44.1 kHz work only with all six channels */ + if (snd_interval_max(rate) < 48000) + return snd_interval_refine(channels, &all_channels); + return 0; +} + +int firewave_constraints(struct snd_pcm_runtime *runtime) +{ + static unsigned int channels_list[] = { 2, 6 }; + static struct snd_pcm_hw_constraint_list channels_list_constraint = { + .count = 2, + .list = channels_list, + }; + int err; + + runtime->hw.rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000; + runtime->hw.channels_max = 6; + + err = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &channels_list_constraint); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + firewave_rate_constraint, NULL, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + firewave_channels_constraint, NULL, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + return 0; +} + +int lacie_speakers_constraints(struct snd_pcm_runtime *runtime) +{ + runtime->hw.rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000; + + return 0; +} + +static int fwspk_open(struct snd_pcm_substream *substream) +{ + static const struct snd_pcm_hardware hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = AMDTP_OUT_PCM_FORMAT_BITS, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 4 * 1024 * 1024, + .period_bytes_min = 1, + .period_bytes_max = UINT_MAX, + .periods_min = 1, + .periods_max = UINT_MAX, + }; + struct fwspk *fwspk = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + bool used; + int err; + + err = cmp_connection_check_used(&fwspk->in_conn, &used); + if ((err < 0) || used) + goto end; + + runtime->hw = hardware; + + err = fwspk->device_info->pcm_constraints(runtime); + if (err < 0) + goto end; + err = snd_pcm_limit_hw_rates(runtime); + if (err < 0) + goto end; + + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + 5000, UINT_MAX); + if (err < 0) + goto end; + + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + goto end; +end: + return err; +} + +static int fwspk_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int fwspk_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct fwspk *fwspk = substream->private_data; + int err; + + mutex_lock(&fwspk->mutex); + snd_fwspk_stream_stop(fwspk); + mutex_unlock(&fwspk->mutex); + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + goto error; + + amdtp_stream_set_parameters(&fwspk->rx_stream, + params_rate(hw_params), + params_channels(hw_params), + 0); + + amdtp_stream_set_pcm_format(&fwspk->rx_stream, + params_format(hw_params)); + + err = avc_general_set_sig_fmt(fwspk->unit, params_rate(hw_params), + AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) + goto err_buffer; + if (err != 0x09 /* ACCEPTED */) { + dev_err(&fwspk->unit->device, "failed to set sample rate\n"); + err = -EIO; + goto error; + } + + return 0; + +err_buffer: + snd_pcm_lib_free_vmalloc_buffer(substream); +error: + return err; +} + +static int fwspk_hw_free(struct snd_pcm_substream *substream) +{ + struct fwspk *fwspk = substream->private_data; + + mutex_lock(&fwspk->mutex); + snd_fwspk_stream_stop(fwspk); + mutex_unlock(&fwspk->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int fwspk_prepare(struct snd_pcm_substream *substream) +{ + struct fwspk *fwspk = substream->private_data; + int err; + + mutex_lock(&fwspk->mutex); + + snd_fwspk_stream_stop(fwspk); + + err = snd_fwspk_stream_start(fwspk); + if (err < 0) + goto end; + + amdtp_stream_pcm_prepare(&fwspk->rx_stream); +end: + mutex_unlock(&fwspk->mutex); + return err; +} + +static int fwspk_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct fwspk *fwspk = substream->private_data; + struct snd_pcm_substream *pcm; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + pcm = substream; + break; + case SNDRV_PCM_TRIGGER_STOP: + pcm = NULL; + break; + default: + return -EINVAL; + } + amdtp_stream_pcm_trigger(&fwspk->rx_stream, pcm); + return 0; +} + +static snd_pcm_uframes_t fwspk_pointer(struct snd_pcm_substream *substream) +{ + struct fwspk *fwspk = substream->private_data; + + return amdtp_stream_pcm_pointer(&fwspk->rx_stream); +} + +int snd_fwspk_create_pcm(struct fwspk *fwspk) +{ + static struct snd_pcm_ops ops = { + .open = fwspk_open, + .close = fwspk_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = fwspk_hw_params, + .hw_free = fwspk_hw_free, + .prepare = fwspk_prepare, + .trigger = fwspk_trigger, + .pointer = fwspk_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(fwspk->card, fwspk->card->driver, 0, 1, 0, &pcm); + if (err < 0) + return err; + pcm->private_data = fwspk; + strcpy(pcm->name, fwspk->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &ops); + return 0; +}
To make it easy to work in followed commit.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/speakers/Makefile | 3 +- sound/firewire/speakers/speakers.c | 277 +--------------------------- sound/firewire/speakers/speakers.h | 2 + sound/firewire/speakers/speakers_control.c | 283 +++++++++++++++++++++++++++++ 4 files changed, 288 insertions(+), 277 deletions(-) create mode 100644 sound/firewire/speakers/speakers_control.c
diff --git a/sound/firewire/speakers/Makefile b/sound/firewire/speakers/Makefile index a970ccf..8ee55ee 100644 --- a/sound/firewire/speakers/Makefile +++ b/sound/firewire/speakers/Makefile @@ -1,2 +1,3 @@ -snd-firewire-speakers-objs := speakers_stream.o speakers_pcm.o speakers.o +snd-firewire-speakers-objs := speakers_stream.o speakers_pcm.o speakers_control.o \ + speakers.o obj-m += snd-firewire-speakers.o diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c index 20bba46..187377e 100644 --- a/sound/firewire/speakers/speakers.c +++ b/sound/firewire/speakers/speakers.c @@ -24,281 +24,6 @@ MODULE_DESCRIPTION("FireWire speakers driver"); MODULE_AUTHOR("Clemens Ladisch clemens@ladisch.de"); MODULE_LICENSE("GPL v2");
-enum control_action { CTL_READ, CTL_WRITE }; -enum control_attribute { - CTL_MIN = 0x02, - CTL_MAX = 0x03, - CTL_CURRENT = 0x10, -}; - -static int fwspk_mute_command(struct fwspk *fwspk, bool *value, - enum control_action action) -{ - u8 *buf; - u8 response_ok; - int err; - - buf = kmalloc(11, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - if (action == CTL_READ) { - buf[0] = 0x01; /* AV/C, STATUS */ - response_ok = 0x0c; /* STABLE */ - } else { - buf[0] = 0x00; /* AV/C, CONTROL */ - response_ok = 0x09; /* ACCEPTED */ - } - buf[1] = 0x08; /* audio unit 0 */ - buf[2] = 0xb8; /* FUNCTION BLOCK */ - buf[3] = 0x81; /* function block type: feature */ - buf[4] = fwspk->device_info->mute_fb_id; /* function block ID */ - buf[5] = 0x10; /* control attribute: current */ - buf[6] = 0x02; /* selector length */ - buf[7] = 0x00; /* audio channel number */ - buf[8] = 0x01; /* control selector: mute */ - buf[9] = 0x01; /* control data length */ - if (action == CTL_READ) - buf[10] = 0xff; - else - buf[10] = *value ? 0x70 : 0x60; - - err = fcp_avc_transaction(fwspk->unit, buf, 11, buf, 11, 0x3fe); - if (err < 0) - goto error; - if (err < 11) { - dev_err(&fwspk->unit->device, "short FCP response\n"); - err = -EIO; - goto error; - } - if (buf[0] != response_ok) { - dev_err(&fwspk->unit->device, "mute command failed\n"); - err = -EIO; - goto error; - } - if (action == CTL_READ) - *value = buf[10] == 0x70; - - err = 0; - -error: - kfree(buf); - - return err; -} - -static int fwspk_volume_command(struct fwspk *fwspk, s16 *value, - unsigned int channel, - enum control_attribute attribute, - enum control_action action) -{ - u8 *buf; - u8 response_ok; - int err; - - buf = kmalloc(12, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - if (action == CTL_READ) { - buf[0] = 0x01; /* AV/C, STATUS */ - response_ok = 0x0c; /* STABLE */ - } else { - buf[0] = 0x00; /* AV/C, CONTROL */ - response_ok = 0x09; /* ACCEPTED */ - } - buf[1] = 0x08; /* audio unit 0 */ - buf[2] = 0xb8; /* FUNCTION BLOCK */ - buf[3] = 0x81; /* function block type: feature */ - buf[4] = fwspk->device_info->volume_fb_id; /* function block ID */ - buf[5] = attribute; /* control attribute */ - buf[6] = 0x02; /* selector length */ - buf[7] = channel; /* audio channel number */ - buf[8] = 0x02; /* control selector: volume */ - buf[9] = 0x02; /* control data length */ - if (action == CTL_READ) { - buf[10] = 0xff; - buf[11] = 0xff; - } else { - buf[10] = *value >> 8; - buf[11] = *value; - } - - err = fcp_avc_transaction(fwspk->unit, buf, 12, buf, 12, 0x3fe); - if (err < 0) - goto error; - if (err < 12) { - dev_err(&fwspk->unit->device, "short FCP response\n"); - err = -EIO; - goto error; - } - if (buf[0] != response_ok) { - dev_err(&fwspk->unit->device, "volume command failed\n"); - err = -EIO; - goto error; - } - if (action == CTL_READ) - *value = (buf[10] << 8) | buf[11]; - - err = 0; - -error: - kfree(buf); - - return err; -} - -static int fwspk_mute_get(struct snd_kcontrol *control, - struct snd_ctl_elem_value *value) -{ - struct fwspk *fwspk = control->private_data; - - value->value.integer.value[0] = !fwspk->mute; - - return 0; -} - -static int fwspk_mute_put(struct snd_kcontrol *control, - struct snd_ctl_elem_value *value) -{ - struct fwspk *fwspk = control->private_data; - bool mute; - int err; - - mute = !value->value.integer.value[0]; - - if (mute == fwspk->mute) - return 0; - - err = fwspk_mute_command(fwspk, &mute, CTL_WRITE); - if (err < 0) - return err; - fwspk->mute = mute; - - return 1; -} - -static int fwspk_volume_info(struct snd_kcontrol *control, - struct snd_ctl_elem_info *info) -{ - struct fwspk *fwspk = control->private_data; - - info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - info->count = fwspk->device_info->mixer_channels; - info->value.integer.min = fwspk->volume_min; - info->value.integer.max = fwspk->volume_max; - - return 0; -} - -static const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 }; - -static int fwspk_volume_get(struct snd_kcontrol *control, - struct snd_ctl_elem_value *value) -{ - struct fwspk *fwspk = control->private_data; - unsigned int i; - - for (i = 0; i < fwspk->device_info->mixer_channels; ++i) - value->value.integer.value[channel_map[i]] = fwspk->volume[i]; - - return 0; -} - -static int fwspk_volume_put(struct snd_kcontrol *control, - struct snd_ctl_elem_value *value) -{ - struct fwspk *fwspk = control->private_data; - unsigned int i, changed_channels; - bool equal_values = true; - s16 volume; - int err; - - for (i = 0; i < fwspk->device_info->mixer_channels; ++i) { - if (value->value.integer.value[i] < fwspk->volume_min || - value->value.integer.value[i] > fwspk->volume_max) - return -EINVAL; - if (value->value.integer.value[i] != - value->value.integer.value[0]) - equal_values = false; - } - - changed_channels = 0; - for (i = 0; i < fwspk->device_info->mixer_channels; ++i) - if (value->value.integer.value[channel_map[i]] != - fwspk->volume[i]) - changed_channels |= 1 << (i + 1); - - if (equal_values && changed_channels != 0) - changed_channels = 1 << 0; - - for (i = 0; i <= fwspk->device_info->mixer_channels; ++i) { - volume = value->value.integer.value[channel_map[i ? i - 1 : 0]]; - if (changed_channels & (1 << i)) { - err = fwspk_volume_command(fwspk, &volume, i, - CTL_CURRENT, CTL_WRITE); - if (err < 0) - return err; - } - if (i > 0) - fwspk->volume[i - 1] = volume; - } - - return changed_channels != 0; -} - -static int fwspk_create_mixer(struct fwspk *fwspk) -{ - static const struct snd_kcontrol_new controls[] = { - { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "PCM Playback Switch", - .info = snd_ctl_boolean_mono_info, - .get = fwspk_mute_get, - .put = fwspk_mute_put, - }, - { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "PCM Playback Volume", - .info = fwspk_volume_info, - .get = fwspk_volume_get, - .put = fwspk_volume_put, - }, - }; - unsigned int i, first_ch; - int err; - - err = fwspk_volume_command(fwspk, &fwspk->volume_min, - 0, CTL_MIN, CTL_READ); - if (err < 0) - return err; - err = fwspk_volume_command(fwspk, &fwspk->volume_max, - 0, CTL_MAX, CTL_READ); - if (err < 0) - return err; - - err = fwspk_mute_command(fwspk, &fwspk->mute, CTL_READ); - if (err < 0) - return err; - - first_ch = fwspk->device_info->mixer_channels == 1 ? 0 : 1; - for (i = 0; i < fwspk->device_info->mixer_channels; ++i) { - err = fwspk_volume_command(fwspk, &fwspk->volume[i], - first_ch + i, CTL_CURRENT, CTL_READ); - if (err < 0) - return err; - } - - for (i = 0; i < ARRAY_SIZE(controls); ++i) { - err = snd_ctl_add(fwspk->card, - snd_ctl_new1(&controls[i], fwspk)); - if (err < 0) - return err; - } - - return 0; -} - static u32 fwspk_read_firmware_version(struct fw_unit *unit) { __be32 data; @@ -360,7 +85,7 @@ static int fwspk_probe(struct fw_unit *unit, if (err < 0) goto err_card;
- err = fwspk_create_mixer(fwspk); + err = snd_fwspk_create_mixer(fwspk); if (err < 0) goto err_card;
diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h index 379196d..c921714 100644 --- a/sound/firewire/speakers/speakers.h +++ b/sound/firewire/speakers/speakers.h @@ -55,5 +55,7 @@ void snd_fwspk_stream_update(struct fwspk *fwspk);
int snd_fwspk_create_pcm(struct fwspk *fwspk);
+int snd_fwspk_create_mixer(struct fwspk *fwspk); + int firewave_constraints(struct snd_pcm_runtime *runtime); int lacie_speakers_constraints(struct snd_pcm_runtime *runtime); diff --git a/sound/firewire/speakers/speakers_control.c b/sound/firewire/speakers/speakers_control.c new file mode 100644 index 0000000..450632e --- /dev/null +++ b/sound/firewire/speakers/speakers_control.c @@ -0,0 +1,283 @@ +/* + * speakers_control.c - a part of OXFW970/971-based speakers driver + * + * Copyright (c) Clemens Ladisch clemens@ladisch.de + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "speakers.h" + +enum control_action { CTL_READ, CTL_WRITE }; +enum control_attribute { + CTL_MIN = 0x02, + CTL_MAX = 0x03, + CTL_CURRENT = 0x10, +}; + +static int fwspk_mute_command(struct fwspk *fwspk, bool *value, + enum control_action action) +{ + u8 *buf; + u8 response_ok; + int err; + + buf = kmalloc(11, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (action == CTL_READ) { + buf[0] = 0x01; /* AV/C, STATUS */ + response_ok = 0x0c; /* STABLE */ + } else { + buf[0] = 0x00; /* AV/C, CONTROL */ + response_ok = 0x09; /* ACCEPTED */ + } + buf[1] = 0x08; /* audio unit 0 */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x81; /* function block type: feature */ + buf[4] = fwspk->device_info->mute_fb_id; /* function block ID */ + buf[5] = 0x10; /* control attribute: current */ + buf[6] = 0x02; /* selector length */ + buf[7] = 0x00; /* audio channel number */ + buf[8] = 0x01; /* control selector: mute */ + buf[9] = 0x01; /* control data length */ + if (action == CTL_READ) + buf[10] = 0xff; + else + buf[10] = *value ? 0x70 : 0x60; + + err = fcp_avc_transaction(fwspk->unit, buf, 11, buf, 11, 0x3fe); + if (err < 0) + goto error; + if (err < 11) { + dev_err(&fwspk->unit->device, "short FCP response\n"); + err = -EIO; + goto error; + } + if (buf[0] != response_ok) { + dev_err(&fwspk->unit->device, "mute command failed\n"); + err = -EIO; + goto error; + } + if (action == CTL_READ) + *value = buf[10] == 0x70; + + err = 0; + +error: + kfree(buf); + + return err; +} + +static int fwspk_volume_command(struct fwspk *fwspk, s16 *value, + unsigned int channel, + enum control_attribute attribute, + enum control_action action) +{ + u8 *buf; + u8 response_ok; + int err; + + buf = kmalloc(12, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (action == CTL_READ) { + buf[0] = 0x01; /* AV/C, STATUS */ + response_ok = 0x0c; /* STABLE */ + } else { + buf[0] = 0x00; /* AV/C, CONTROL */ + response_ok = 0x09; /* ACCEPTED */ + } + buf[1] = 0x08; /* audio unit 0 */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x81; /* function block type: feature */ + buf[4] = fwspk->device_info->volume_fb_id; /* function block ID */ + buf[5] = attribute; /* control attribute */ + buf[6] = 0x02; /* selector length */ + buf[7] = channel; /* audio channel number */ + buf[8] = 0x02; /* control selector: volume */ + buf[9] = 0x02; /* control data length */ + if (action == CTL_READ) { + buf[10] = 0xff; + buf[11] = 0xff; + } else { + buf[10] = *value >> 8; + buf[11] = *value; + } + + err = fcp_avc_transaction(fwspk->unit, buf, 12, buf, 12, 0x3fe); + if (err < 0) + goto error; + if (err < 12) { + dev_err(&fwspk->unit->device, "short FCP response\n"); + err = -EIO; + goto error; + } + if (buf[0] != response_ok) { + dev_err(&fwspk->unit->device, "volume command failed\n"); + err = -EIO; + goto error; + } + if (action == CTL_READ) + *value = (buf[10] << 8) | buf[11]; + + err = 0; + +error: + kfree(buf); + + return err; +} + +static int fwspk_mute_get(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct fwspk *fwspk = control->private_data; + + value->value.integer.value[0] = !fwspk->mute; + + return 0; +} + +static int fwspk_mute_put(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct fwspk *fwspk = control->private_data; + bool mute; + int err; + + mute = !value->value.integer.value[0]; + + if (mute == fwspk->mute) + return 0; + + err = fwspk_mute_command(fwspk, &mute, CTL_WRITE); + if (err < 0) + return err; + fwspk->mute = mute; + + return 1; +} + +static int fwspk_volume_info(struct snd_kcontrol *control, + struct snd_ctl_elem_info *info) +{ + struct fwspk *fwspk = control->private_data; + + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = fwspk->device_info->mixer_channels; + info->value.integer.min = fwspk->volume_min; + info->value.integer.max = fwspk->volume_max; + + return 0; +} + +static const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 }; + +static int fwspk_volume_get(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct fwspk *fwspk = control->private_data; + unsigned int i; + + for (i = 0; i < fwspk->device_info->mixer_channels; ++i) + value->value.integer.value[channel_map[i]] = fwspk->volume[i]; + + return 0; +} + +static int fwspk_volume_put(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct fwspk *fwspk = control->private_data; + unsigned int i, changed_channels; + bool equal_values = true; + s16 volume; + int err; + + for (i = 0; i < fwspk->device_info->mixer_channels; ++i) { + if (value->value.integer.value[i] < fwspk->volume_min || + value->value.integer.value[i] > fwspk->volume_max) + return -EINVAL; + if (value->value.integer.value[i] != + value->value.integer.value[0]) + equal_values = false; + } + + changed_channels = 0; + for (i = 0; i < fwspk->device_info->mixer_channels; ++i) + if (value->value.integer.value[channel_map[i]] != + fwspk->volume[i]) + changed_channels |= 1 << (i + 1); + + if (equal_values && changed_channels != 0) + changed_channels = 1 << 0; + + for (i = 0; i <= fwspk->device_info->mixer_channels; ++i) { + volume = value->value.integer.value[channel_map[i ? i - 1 : 0]]; + if (changed_channels & (1 << i)) { + err = fwspk_volume_command(fwspk, &volume, i, + CTL_CURRENT, CTL_WRITE); + if (err < 0) + return err; + } + if (i > 0) + fwspk->volume[i - 1] = volume; + } + + return changed_channels != 0; +} + +int snd_fwspk_create_mixer(struct fwspk *fwspk) +{ + static const struct snd_kcontrol_new controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = fwspk_mute_get, + .put = fwspk_mute_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .info = fwspk_volume_info, + .get = fwspk_volume_get, + .put = fwspk_volume_put, + }, + }; + unsigned int i, first_ch; + int err; + + err = fwspk_volume_command(fwspk, &fwspk->volume_min, + 0, CTL_MIN, CTL_READ); + if (err < 0) + return err; + err = fwspk_volume_command(fwspk, &fwspk->volume_max, + 0, CTL_MAX, CTL_READ); + if (err < 0) + return err; + + err = fwspk_mute_command(fwspk, &fwspk->mute, CTL_READ); + if (err < 0) + return err; + + first_ch = fwspk->device_info->mixer_channels == 1 ? 0 : 1; + for (i = 0; i < fwspk->device_info->mixer_channels; ++i) { + err = fwspk_volume_command(fwspk, &fwspk->volume[i], + first_ch + i, CTL_CURRENT, CTL_READ); + if (err < 0) + return err; + } + + for (i = 0; i < ARRAY_SIZE(controls); ++i) { + err = snd_ctl_add(fwspk->card, + snd_ctl_new1(&controls[i], fwspk)); + if (err < 0) + return err; + } + + return 0; +}
'struct snd_card' has four members for name, 'driver', 'shortname', 'longname' and 'mixername'. The 'driver' is used to detect card configuration file but the others are not used for important functionality. So I name them with the information from config ROM.
This commit adds 'name_card()' function and change the way to name card. The 'driver' is still given by 'struct device_info'. The 'shortname' and 'mixername' is given by 'model' and the 'longname' is given by 'vendor' and 'model'.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/speakers/speakers.c | 59 ++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 21 deletions(-)
diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c index 187377e..1b5556b 100644 --- a/sound/firewire/speakers/speakers.c +++ b/sound/firewire/speakers/speakers.c @@ -24,14 +24,44 @@ MODULE_DESCRIPTION("FireWire speakers driver"); MODULE_AUTHOR("Clemens Ladisch clemens@ladisch.de"); MODULE_LICENSE("GPL v2");
-static u32 fwspk_read_firmware_version(struct fw_unit *unit) +static int name_card(struct fwspk *fwspk) { - __be32 data; + struct fw_device *fw_dev = fw_parent_device(fwspk->unit); + char vendor[24] = {0}; + char model[24] = {0}; + u32 firmware; int err;
- err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, - OXFORD_FIRMWARE_ID_ADDRESS, &data, 4, 0); - return err >= 0 ? be32_to_cpu(data) : 0; + /* 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(fwspk->unit->directory, CSR_MODEL, + model, sizeof(model)); + if (err < 0) + goto end; + + err = snd_fw_transaction(fwspk->unit, TCODE_READ_QUADLET_REQUEST, + OXFORD_FIRMWARE_ID_ADDRESS, &firmware, 4, 0); + if (err < 0) + goto end; + be32_to_cpus(&firmware); + + strcpy(fwspk->card->driver, fwspk->device_info->driver_name); + strcpy(fwspk->card->shortname, model); + + snprintf(fwspk->card->longname, sizeof(fwspk->card->longname), + "%s %s (OXFW%x %04x), GUID %08x%08x at %s, S%d", + vendor, model, firmware >> 20, firmware & 0xffff, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&fwspk->unit->device), 100 << fw_dev->max_speed); + + strcpy(fwspk->card->mixername, fwspk->card->shortname); +end: + return err; }
static void fwspk_card_free(struct snd_card *card) @@ -49,10 +79,8 @@ static void fwspk_card_free(struct snd_card *card) static int fwspk_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) { - struct fw_device *fw_dev = fw_parent_device(unit); struct snd_card *card; struct fwspk *fwspk; - u32 firmware; int err;
err = snd_card_create(-1, NULL, THIS_MODULE, sizeof(*fwspk), &card); @@ -66,16 +94,9 @@ static int fwspk_probe(struct fw_unit *unit, fwspk->device_info = (const struct device_info *)id->driver_data; mutex_init(&fwspk->mutex);
- strcpy(card->driver, fwspk->device_info->driver_name); - strcpy(card->shortname, fwspk->device_info->short_name); - firmware = fwspk_read_firmware_version(unit); - snprintf(card->longname, sizeof(card->longname), - "%s (OXFW%x %04x), GUID %08x%08x at %s, S%d", - fwspk->device_info->long_name, - firmware >> 20, firmware & 0xffff, - fw_dev->config_rom[3], fw_dev->config_rom[4], - dev_name(&unit->device), 100 << fw_dev->max_speed); - strcpy(card->mixername, "OXFW970"); + err = name_card(fwspk); + if (err < 0) + goto err_card;
err = snd_fwspk_stream_init(fwspk); if (err < 0) @@ -124,8 +145,6 @@ static void fwspk_remove(struct fw_unit *unit)
static const struct device_info griffin_firewave = { .driver_name = "FireWave", - .short_name = "FireWave", - .long_name = "Griffin FireWave Surround", .pcm_constraints = firewave_constraints, .mixer_channels = 6, .mute_fb_id = 0x01, @@ -134,8 +153,6 @@ static const struct device_info griffin_firewave = {
static const struct device_info lacie_speakers = { .driver_name = "FWSpeakers", - .short_name = "FireWire Speakers", - .long_name = "LaCie FireWire Speakers", .pcm_constraints = lacie_speakers_constraints, .mixer_channels = 1, .mute_fb_id = 0x01,
For PCM rules/constrains, drivers need to know stream formation at each supported sampling rates in advance. This commit adds 'struct fwspk_stream_formation' to save formation information.
According to datasheet of OXFW970/971, they support 32.0kHz to 196.0kHz. So this commit adds 'rx_stream_formations' member to 'struct fwspk'. It's an array with 7 elements. Each element corresponds to each sampling rate.
With these data structure, this commit changes the way to make PCM rules/constraints.
I don't know whether Griffin/LaCie devices supports SINGLE/LIST subfunction of 'AV/C Stream Format Information' command. So this commit adds codes to generate formations for them.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/speakers/speakers.c | 37 +++--- sound/firewire/speakers/speakers.h | 16 ++- sound/firewire/speakers/speakers_pcm.c | 203 +++++++++++++++++++----------- sound/firewire/speakers/speakers_stream.c | 36 ++++++ 4 files changed, 198 insertions(+), 94 deletions(-)
diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c index 1b5556b..e07ed5f 100644 --- a/sound/firewire/speakers/speakers.c +++ b/sound/firewire/speakers/speakers.c @@ -24,6 +24,20 @@ MODULE_DESCRIPTION("FireWire speakers driver"); MODULE_AUTHOR("Clemens Ladisch clemens@ladisch.de"); MODULE_LICENSE("GPL v2");
+static const struct device_info griffin_firewave = { + .driver_name = "FireWave", + .mixer_channels = 6, + .mute_fb_id = 0x01, + .volume_fb_id = 0x02, +}; + +static const struct device_info lacie_speakers = { + .driver_name = "FWSpeakers", + .mixer_channels = 1, + .mute_fb_id = 0x01, + .volume_fb_id = 0x01, +}; + static int name_card(struct fwspk *fwspk) { struct fw_device *fw_dev = fw_parent_device(fwspk->unit); @@ -94,6 +108,13 @@ static int fwspk_probe(struct fw_unit *unit, fwspk->device_info = (const struct device_info *)id->driver_data; mutex_init(&fwspk->mutex);
+ if (fwspk->device_info == &griffin_firewave) + err = firewave_stream_discover(fwspk); + else + err = lacie_speakers_stream_discover(fwspk); + if (err < 0) + goto err_card; + err = name_card(fwspk); if (err < 0) goto err_card; @@ -143,22 +164,6 @@ static void fwspk_remove(struct fw_unit *unit) snd_card_free_when_closed(fwspk->card); }
-static const struct device_info griffin_firewave = { - .driver_name = "FireWave", - .pcm_constraints = firewave_constraints, - .mixer_channels = 6, - .mute_fb_id = 0x01, - .volume_fb_id = 0x02, -}; - -static const struct device_info lacie_speakers = { - .driver_name = "FWSpeakers", - .pcm_constraints = lacie_speakers_constraints, - .mixer_channels = 1, - .mute_fb_id = 0x01, - .volume_fb_id = 0x01, -}; - static const struct ieee1394_device_id fwspk_id_table[] = { { .match_flags = IEEE1394_MATCH_VENDOR_ID | diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h index c921714..d3029f0 100644 --- a/sound/firewire/speakers/speakers.h +++ b/sound/firewire/speakers/speakers.h @@ -28,19 +28,28 @@ struct device_info { const char *driver_name; const char *short_name; const char *long_name; - int (*pcm_constraints)(struct snd_pcm_runtime *runtime); unsigned int mixer_channels; u8 mute_fb_id; u8 volume_fb_id; };
+#define FWSPK_STREAM_TABLE_ENTRIES 7 +struct fwspk_stream_formation { + unsigned int pcm; + unsigned int midi; +}; +extern const unsigned int fwspk_rate_table[FWSPK_STREAM_TABLE_ENTRIES]; struct fwspk { struct snd_card *card; struct fw_unit *unit; const struct device_info *device_info; struct mutex mutex; + + struct fwspk_stream_formation + rx_stream_formations[FWSPK_STREAM_TABLE_ENTRIES]; struct cmp_connection in_conn; struct amdtp_stream rx_stream; + bool mute; s16 volume[6]; s16 volume_min; @@ -53,9 +62,10 @@ void snd_fwspk_stream_stop(struct fwspk *fwspk); void snd_fwspk_stream_destroy(struct fwspk *fwspk); void snd_fwspk_stream_update(struct fwspk *fwspk);
+int firewave_stream_discover(struct fwspk *fwspk); +int lacie_speakers_stream_discover(struct fwspk *fwspk); + int snd_fwspk_create_pcm(struct fwspk *fwspk);
int snd_fwspk_create_mixer(struct fwspk *fwspk);
-int firewave_constraints(struct snd_pcm_runtime *runtime); -int lacie_speakers_constraints(struct snd_pcm_runtime *runtime); diff --git a/sound/firewire/speakers/speakers_pcm.c b/sound/firewire/speakers/speakers_pcm.c index 8fa437f..cc4b610 100644 --- a/sound/firewire/speakers/speakers_pcm.c +++ b/sound/firewire/speakers/speakers_pcm.c @@ -7,92 +7,132 @@
#include "speakers.h"
-static int firewave_rate_constraint(struct snd_pcm_hw_params *params, - struct snd_pcm_hw_rule *rule) +static int hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule, + struct fwspk *fwspk, + struct fwspk_stream_formation *formations) { - static unsigned int stereo_rates[] = { 48000, 96000 }; - struct snd_interval *channels = - hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); - struct snd_interval *rate = - hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); - - /* two channels work only at 48/96 kHz */ - if (snd_interval_max(channels) < 6) - return snd_interval_list(rate, 2, stereo_rates, 0); - return 0; + 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 < FWSPK_STREAM_TABLE_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, fwspk_rate_table[i]); + t.max = max(t.max, fwspk_rate_table[i]); + + } + return snd_interval_refine(r, &t); }
-static int firewave_channels_constraint(struct snd_pcm_hw_params *params, +static int hw_rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule, + struct fwspk *fwspk, + struct fwspk_stream_formation *formations) +{ + 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 < FWSPK_STREAM_TABLE_ENTRIES; i++) { + /* entry is invalid */ + if (formations[i].pcm == 0) + continue; + + if (!snd_interval_test(r, fwspk_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 inline int hw_rule_playback_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { - static const struct snd_interval all_channels = { .min = 6, .max = 6 }; - struct snd_interval *rate = - hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); - struct snd_interval *channels = - hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); - - /* 32/44.1 kHz work only with all six channels */ - if (snd_interval_max(rate) < 48000) - return snd_interval_refine(channels, &all_channels); - return 0; + struct fwspk *fwspk = rule->private; + return hw_rule_rate(params, rule, fwspk, + fwspk->rx_stream_formations); }
-int firewave_constraints(struct snd_pcm_runtime *runtime) +static inline int hw_rule_playback_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) { - static unsigned int channels_list[] = { 2, 6 }; - static struct snd_pcm_hw_constraint_list channels_list_constraint = { - .count = 2, - .list = channels_list, - }; - int err; + struct fwspk *fwspk = rule->private; + return hw_rule_channels(params, rule, fwspk, + fwspk->rx_stream_formations); +}
- runtime->hw.rates = SNDRV_PCM_RATE_32000 | - SNDRV_PCM_RATE_44100 | - SNDRV_PCM_RATE_48000 | - SNDRV_PCM_RATE_96000; - runtime->hw.channels_max = 6; +static void prepare_channels(struct snd_pcm_hardware *hw, + struct fwspk_stream_formation *formations) +{ + unsigned int i;
- err = snd_pcm_hw_constraint_list(runtime, 0, - SNDRV_PCM_HW_PARAM_CHANNELS, - &channels_list_constraint); - if (err < 0) - return err; - err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, - firewave_rate_constraint, NULL, - SNDRV_PCM_HW_PARAM_CHANNELS, -1); - if (err < 0) - return err; - err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, - firewave_channels_constraint, NULL, - SNDRV_PCM_HW_PARAM_RATE, -1); - if (err < 0) - return err; + for (i = 0; i < FWSPK_STREAM_TABLE_ENTRIES; i++) { + /* entry has no PCM channels */ + if (formations[i].pcm == 0) + continue;
- return 0; + hw->channels_min = min(hw->channels_min, formations[i].pcm); + hw->channels_max = max(hw->channels_max, formations[i].pcm); + } + + return; }
-int lacie_speakers_constraints(struct snd_pcm_runtime *runtime) +static void prepare_rates(struct snd_pcm_hardware *hw, + struct fwspk_stream_formation *formations) { - runtime->hw.rates = SNDRV_PCM_RATE_32000 | - SNDRV_PCM_RATE_44100 | - SNDRV_PCM_RATE_48000 | - SNDRV_PCM_RATE_88200 | - SNDRV_PCM_RATE_96000; + unsigned int i;
- return 0; + for (i = 0; i < FWSPK_STREAM_TABLE_ENTRIES; i++) { + /* entry has no PCM channels */ + if (formations[i].pcm == 0) + continue; + + hw->rate_min = min(hw->rate_min, fwspk_rate_table[i]); + hw->rate_max = max(hw->rate_max, fwspk_rate_table[i]); + hw->rates |= snd_pcm_rate_to_rate_bit(fwspk_rate_table[i]); + } + + return; }
-static int fwspk_open(struct snd_pcm_substream *substream) +int fwspk_open(struct snd_pcm_substream *substream) { - static const struct snd_pcm_hardware hardware = { + static const struct snd_pcm_hardware hw = { .info = SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH | SNDRV_PCM_INFO_INTERLEAVED | + /* for Open Sound System compatibility */ + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BLOCK_TRANSFER, - .formats = AMDTP_OUT_PCM_FORMAT_BITS, - .channels_min = 2, - .channels_max = 2, + /* set up later */ + .rates = 0, + .rate_min = UINT_MAX, + .rate_max = 0, + /* set up later */ + .channels_min = UINT_MAX, + .channels_max = 0, .buffer_bytes_max = 4 * 1024 * 1024, .period_bytes_min = 1, .period_bytes_max = UINT_MAX, @@ -108,24 +148,37 @@ static int fwspk_open(struct snd_pcm_substream *substream) if ((err < 0) || used) goto end;
- runtime->hw = hardware; + runtime->hw = hw;
- err = fwspk->device_info->pcm_constraints(runtime); - if (err < 0) - goto end; - err = snd_pcm_limit_hw_rates(runtime); - if (err < 0) - goto end; + /* add rule between channels and sampling rate */ + prepare_rates(&runtime->hw, fwspk->rx_stream_formations); + prepare_channels(&runtime->hw, fwspk->rx_stream_formations); + runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS; + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_playback_channels, fwspk, + SNDRV_PCM_HW_PARAM_RATE, -1); + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_playback_rate, fwspk, + SNDRV_PCM_HW_PARAM_CHANNELS, -1);
- err = snd_pcm_hw_constraint_minmax(runtime, - SNDRV_PCM_HW_PARAM_PERIOD_TIME, - 5000, UINT_MAX); + /* AM824 in IEC 61883-6 can deliver 24bit data */ + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); if (err < 0) goto end;
- err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + /* + * AMDTP functionality in firewire-lib require periods to be aligned to + * 16 bit, or 24bit inner 32bit. + */ + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); if (err < 0) goto end; + + /* time for period constraint */ + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + 5000, UINT_MAX); end: return err; } diff --git a/sound/firewire/speakers/speakers_stream.c b/sound/firewire/speakers/speakers_stream.c index 9cc0ffb..17d83e4 100644 --- a/sound/firewire/speakers/speakers_stream.c +++ b/sound/firewire/speakers/speakers_stream.c @@ -7,6 +7,21 @@
#include "speakers.h"
+/* + * According to their datasheet: + * OXFW970: 32.0/44.1/48.0/96.0 Khz, 8 audio channels I/O + * OXFW971: 32.0/44.1/48.0/88.2/96.0/192.0 kHz, 16 audio channels I/O, MIDI I/O + */ +const unsigned int fwspk_rate_table[FWSPK_STREAM_TABLE_ENTRIES] = { + [0] = 32000, + [1] = 44100, + [2] = 48000, + [3] = 88200, + [4] = 96000, + [5] = 176400, + [6] = 192000, +}; + int snd_fwspk_stream_init(struct fwspk *fwspk) { int err; @@ -70,3 +85,24 @@ void snd_fwspk_stream_update(struct fwspk *fwspk) amdtp_stream_update(&fwspk->rx_stream); } } + +int firewave_stream_discover(struct fwspk *fwspk) +{ + fwspk->rx_stream_formations[2].pcm = 6; + fwspk->rx_stream_formations[3].pcm = 6; + fwspk->rx_stream_formations[4].pcm = 2; + fwspk->rx_stream_formations[6].pcm = 2; + + return 0; +} + +int lacie_speakers_stream_discover(struct fwspk *fwspk) +{ + fwspk->rx_stream_formations[2].pcm = 2; + fwspk->rx_stream_formations[3].pcm = 2; + fwspk->rx_stream_formations[4].pcm = 2; + fwspk->rx_stream_formations[5].pcm = 2; + fwspk->rx_stream_formations[6].pcm = 2; + + return 0; +}
On Jan 11 Takashi Sakamoto wrote:
I don't know whether Griffin/LaCie devices supports SINGLE/LIST subfunction of 'AV/C Stream Format Information' command. So this commit adds codes to generate formations for them.
I have got both devices. I could perform specific tests with them if you want and give me instructions. It can be a very slow process though because I have got almost no spare time, at least throughout January and February.
Hi Stefan,
I have got both devices. I could perform specific tests with them if you want and give me instructions. It can be a very slow process though because I have got almost no spare time, at least throughout January and February.
Thank you. Please confirm these items below with Clemens' jujuutils when you have a free time:
1.They have both iPCR/oPCR. $ ./firewire-request /dev/fw1 read 0xfffff0000900 (oMPR) $ ./firewire-request /dev/fw1 read 0xfffff0000980 (iMPR) If they have, I can apply the same way to change sampling rate for all of models.
2.They can respond to 'SINGLE' subfunction of 'AV/C Stream Format Information' command. $ ./firewire-request /dev/fw1 fcp 0x01ffbfc000000000ffffffff response: 000: 0c ff bf c0 00 00 00 00 ff 00 90 40 04 02 01 02 ...........@.... response: 010: 06 This is an example of Behringer F-Control A 202(OXFW970).
3.They can respond to 'LIST' subfunction of 'AV/C Stream Format Information' command. $ ./firewire-request /dev/fw1 fcp 0x01ffbfc100000000ffff01ff I have no devices which supports this command. I guess OXFW971 supports this.
It's better to receive your results till the middle of February.
I have a plan to post all of my patches with many fixments in the end of January. This may be my last RFCs. Then I recall for testing to some mailing lists like LAD. If everything goes well, I hope to post merge-request in the end of February.
Thanks
Takashi Sakamoto o-takashi@sakamocchi.jp
On Jan 12 Takashi Sakamoto wrote:
Hi Stefan,
I have got both devices. I could perform specific tests with them if you want and give me instructions. It can be a very slow process though because I have got almost no spare time, at least throughout January and February.
Thank you. Please confirm these items below with Clemens' jujuutils when you have a free time:
Thanks for these straightforward instructions. Here are the results.
1.They have both iPCR/oPCR. $ ./firewire-request /dev/fw1 read 0xfffff0000900 (oMPR) $ ./firewire-request /dev/fw1 read 0xfffff0000980 (iMPR) If they have, I can apply the same way to change sampling rate for all of models.
LaCie Speakers: --------------- $ firewire-request /dev/fw7 read 0xfffff0000900 result: 80ff0000 $ firewire-request /dev/fw7 read 0xfffff0000980 result: 80ff0001
Griffin FireWave: ----------------- $ firewire-request /dev/fw8 read 0xfffff0000900 result: 80ff0000 $ firewire-request /dev/fw8 read 0xfffff0000980 result: 80ff0001
2.They can respond to 'SINGLE' subfunction of 'AV/C Stream Format Information' command. $ ./firewire-request /dev/fw1 fcp 0x01ffbfc000000000ffffffff response: 000: 0c ff bf c0 00 00 00 00 ff 00 90 40 04 02 01 02 ...........@.... response: 010: 06 This is an example of Behringer F-Control A 202(OXFW970).
LaCie Speakers: --------------- $ firewire-request /dev/fw7 fcp 0x01ffbfc000000000ffffffff response: 000: 0c ff bf c0 00 00 00 00 ff 00 90 40 04 02 01 02 ...........@.... response: 010: 06 .
Griffin FireWave: ----------------- $ firewire-request /dev/fw8 fcp 0x01ffbfc000000000ffffffff response: 000: 0c ff bf c0 00 00 00 00 ff 00 90 40 04 02 01 06 ...........@.... response: 010: 06 .
3.They can respond to 'LIST' subfunction of 'AV/C Stream Format Information' command. $ ./firewire-request /dev/fw1 fcp 0x01ffbfc100000000ffff01ff I have no devices which supports this command. I guess OXFW971 supports this.
LaCie Speakers: --------------- $ firewire-request /dev/fw7 fcp 0x01ffbfc100000000ffff01ff response: 000: 0c ff bf c1 00 00 00 00 ff 00 01 90 40 03 02 01 ............@... response: 010: 02 06 ..
Griffin FireWave: ----------------- $ firewire-request /dev/fw8 fcp 0x01ffbfc100000000ffff01ff response: 000: 0c ff bf c1 00 00 00 00 ff 00 01 90 40 02 02 01 ............@... response: 010: 06 06 ..
Hi Stefan,
Thanks to get these logs. It's really helpful to me.
1.They have both iPCR/oPCR.
They have no oPCRs, against my guess.
I wrote snd-oxfw with an assumption that devices have iPCR/oPCR. So I must write some patches for my last RFC:
http://mailman.alsa-project.org/pipermail/alsa-devel/2014-January/071820.htm...
2.They can respond to 'SINGLE' subfunction of 'AV/C Stream Format Information' command.
OK. They supports this subfunction.
3.They can respond to 'LIST' subfunction of 'AV/C Stream Format Information' command.
OK. They supports this subfunction. The snd-oxfw can detect channnels/rates for these devices instead of hard-coded parameters.
Thanks
Takashi Sakamoto o-takashi@sakamocchi.jp
On Feb 01 Takashi Sakamoto wrote:
Hi Stefan,
Thanks to get these logs. It's really helpful to me.
1.They have both iPCR/oPCR.
They have no oPCRs, against my guess.
I wrote snd-oxfw with an assumption that devices have iPCR/oPCR. So I must write some patches for my last RFC:
Neither LaCie FireWire Speakers nor Griffin FireWave have physical recording inputs. It is therefore sensible that they present OUTPUT_MASTER_PLUG.output_plugs == 0.
By the way, while LaCie FireWire Speakers is a stereo playback device, Griffin FireWave is a 6-channel playback device with six physical outputs (three 3.5 mm stereo receptacles). It also contains a "Dolby™ Digital decoder and a Dolby Pro Logic II adaptive matrix surround decoder" according to the user's manual. I have no idea how much of that is implemented in the FireWave itself and how much is done in the FireWave driver for OS X.
Apple' OS X built-in FireWire audio driver presents the FireWave as a output-only device without any control besides the global joint volume control. I don't know whether it works as a 6-channel device with the stock driver; it evidently does work at least as a 2-channel device with stereo sound. In order to use Dolby features on OS X, a custom driver from Griffin needs to be installed. I haven't tried that driver myself yet, but I have heard from somebody else that this driver emits vendor-specific (FCP?) requests to the FireWave to set up 5.1 playback.
I do have a Mac which dual-boots to Apple OS X, and a PCILynx card which I can put into a Linux box, so I could attempt to analyze those vendor- specific requests that enable 5.1 output. Though currently, live seems too short to bother.
Just in case it tells anything useful, here are some parts of alsa-info's output (from alsa-info.sh version 0.4.63 and kernel 3.13-rc5 without your patches; I haven't tried your patches yet):
!!Soundcards recognised by ALSA !!----------------------------- [...] 2 [FireWave ]: FireWave - FireWave Griffin FireWave Surround (OXFW970 0104), GUID 0012920600520991 at fw6.0, S400 3 [Speakers ]: FWSpeakers - FireWire Speakers LaCie FireWire Speakers (OXFW970 0105), GUID 00d04b21010212c8 at fw7.0, S400
!!Aplay/Arecord output !!--------------------
APLAY
**** List of PLAYBACK Hardware Devices **** [...] card 2: FireWave [FireWave], device 0: OXFW970 [FireWave] Subdevices: 1/1 Subdevice #0: subdevice #0 card 3: Speakers [FireWire Speakers], device 0: OXFW970 [FireWire Speakers] Subdevices: 1/1 Subdevice #0: subdevice #0
ARECORD
**** List of CAPTURE Hardware Devices **** [...of course nothing from cards 2 and 3....]
!!Amixer output !!------------- [...] !!-------Mixer controls for card 2 [FireWave]
Card hw:2 'FireWave'/'Griffin FireWave Surround (OXFW970 0104), GUID 0012920600520991 at fw6.0, S400' Mixer name : 'OXFW970' Components : '' Controls : 2 Simple ctrls : 1 Simple mixer control 'PCM',0 Capabilities: pvolume pswitch pswitch-joined Playback channels: Front Left - Front Right - Rear Left - Rear Right - Front Center - Woofer Limits: Playback -32768 - 0 Mono: Front Left: Playback 0 [100%] [on] Front Right: Playback 0 [100%] [on] Rear Left: Playback 0 [100%] [on] Rear Right: Playback 0 [100%] [on] Front Center: Playback 0 [100%] [on] Woofer: Playback 0 [100%] [on]
!!-------Mixer controls for card 3 [Speakers]
Card hw:3 'Speakers'/'LaCie FireWire Speakers (OXFW970 0105), GUID 00d04b21010212c8 at fw7.0, S400' Mixer name : 'OXFW970' Components : '' Controls : 2 Simple ctrls : 1 Simple mixer control 'PCM',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined Playback channels: Mono Limits: Playback -11538 - 0 Mono: Playback -3137 [73%] [on]
!!Alsactl output !!-------------- [...] state.FireWave { control.1 { iface MIXER name 'PCM Playback Switch' value true comment { access 'read write' type BOOLEAN count 1 } } control.2 { iface MIXER name 'PCM Playback Volume' value.0 0 value.1 0 value.2 0 value.3 0 value.4 0 value.5 0 comment { access 'read write' type INTEGER count 6 range '-32768 - 0' } } } state.Speakers { control.1 { iface MIXER name 'PCM Playback Switch' value true comment { access 'read write' type BOOLEAN count 1 } } control.2 { iface MIXER name 'PCM Playback Volume' value -3137 comment { access 'read write' type INTEGER count 1 range '-11538 - 0' } } }
Stefan,
Neither LaCie FireWire Speakers nor Griffin FireWave have physical recording inputs. It is therefore sensible that they present OUTPUT_MASTER_PLUG.output_plugs == 0.
In this case for Lacie/Griffin, this reasoning is true.
But it's not always true.
I did adhere to infrastructure for non 'SYT-Match' when I wrote snd-oxfw.
I haven't tried your patches yet):
The snd-oxfw with these patches cannot handle Lacie/Griffin. It fails in probe() function. It's my fault. I've update these patches and push them into my repository. Please use my repository when you test.
Regards
Takashi Sakamoto
The previous commit adds stream formation. This commit adds proc interface to get information about stream: - stream formation - current sampling rate
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/speakers/Makefile | 2 +- sound/firewire/speakers/speakers.c | 2 ++ sound/firewire/speakers/speakers.h | 2 ++ sound/firewire/speakers/speakers_proc.c | 54 +++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/speakers/speakers_proc.c
diff --git a/sound/firewire/speakers/Makefile b/sound/firewire/speakers/Makefile index 8ee55ee..8936816 100644 --- a/sound/firewire/speakers/Makefile +++ b/sound/firewire/speakers/Makefile @@ -1,3 +1,3 @@ snd-firewire-speakers-objs := speakers_stream.o speakers_pcm.o speakers_control.o \ - speakers.o + speakers_proc.o speakers.o obj-m += snd-firewire-speakers.o diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c index e07ed5f..8c3bf4a 100644 --- a/sound/firewire/speakers/speakers.c +++ b/sound/firewire/speakers/speakers.c @@ -131,6 +131,8 @@ static int fwspk_probe(struct fw_unit *unit, if (err < 0) goto err_card;
+ snd_fwspk_proc_init(fwspk); + snd_card_set_dev(card, &unit->device); err = snd_card_register(card); if (err < 0) diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h index d3029f0..977db20 100644 --- a/sound/firewire/speakers/speakers.h +++ b/sound/firewire/speakers/speakers.h @@ -18,6 +18,7 @@ #include <sound/initval.h> #include <sound/pcm.h> #include <sound/pcm_params.h> +#include <sound/info.h>
#include "../cmp.h" #include "../fcp.h" @@ -69,3 +70,4 @@ int snd_fwspk_create_pcm(struct fwspk *fwspk);
int snd_fwspk_create_mixer(struct fwspk *fwspk);
+void snd_fwspk_proc_init(struct fwspk *fwspk); diff --git a/sound/firewire/speakers/speakers_proc.c b/sound/firewire/speakers/speakers_proc.c new file mode 100644 index 0000000..9721e52 --- /dev/null +++ b/sound/firewire/speakers/speakers_proc.c @@ -0,0 +1,54 @@ +/* + * speakers_proc.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2013 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./speakers.h" + +static void +proc_read_formation(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct fwspk *fwspk = entry->private_data; + struct fwspk_stream_formation *formation; + unsigned int i; + + snd_iprintf(buffer, "Input Stream to device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + formation = fwspk->rx_stream_formations; + for (i = 0; i < FWSPK_STREAM_TABLE_ENTRIES; i++) { + snd_iprintf(buffer, + "\t%d\t%d\t%d\n", fwspk_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 fwspk *fwspk = entry->private_data; + unsigned int rate; + int err; + + err = avc_general_get_sig_fmt(fwspk->unit, &rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + if ((err < 0) && (err == 0x09)) + snd_iprintf(buffer, "Sampling rate: %d\n", rate); +} + +void snd_fwspk_proc_init(struct fwspk *fwspk) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(fwspk->card, "#formation", &entry)) + snd_info_set_text_ops(entry, fwspk, proc_read_formation); + + if (!snd_card_proc_new(fwspk->card, "#clock", &entry)) + snd_info_set_text_ops(entry, fwspk, proc_read_clock); + + return; +}
In past commit, driver can keep formations for each sampling rate. So its stream functionality can decide its formation when given sampling rate.
But PCM functionality still decide it. This commit move some codes to decide stream formation from PCM functionality to stream functionality.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/speakers/speakers.h | 2 +- sound/firewire/speakers/speakers_pcm.c | 49 +++-------------------- sound/firewire/speakers/speakers_stream.c | 66 +++++++++++++++++++++++++++++-- 3 files changed, 68 insertions(+), 49 deletions(-)
diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h index 977db20..90c65f4 100644 --- a/sound/firewire/speakers/speakers.h +++ b/sound/firewire/speakers/speakers.h @@ -58,7 +58,7 @@ struct fwspk { };
int snd_fwspk_stream_init(struct fwspk *fwspk); -int snd_fwspk_stream_start(struct fwspk *fwspk); +int snd_fwspk_stream_start(struct fwspk *fwspk, unsigned int rate); void snd_fwspk_stream_stop(struct fwspk *fwspk); void snd_fwspk_stream_destroy(struct fwspk *fwspk); void snd_fwspk_stream_update(struct fwspk *fwspk); diff --git a/sound/firewire/speakers/speakers_pcm.c b/sound/firewire/speakers/speakers_pcm.c index cc4b610..2258ddf 100644 --- a/sound/firewire/speakers/speakers_pcm.c +++ b/sound/firewire/speakers/speakers_pcm.c @@ -141,13 +141,8 @@ int fwspk_open(struct snd_pcm_substream *substream) }; struct fwspk *fwspk = substream->private_data; struct snd_pcm_runtime *runtime = substream->runtime; - bool used; int err;
- err = cmp_connection_check_used(&fwspk->in_conn, &used); - if ((err < 0) || used) - goto end; - runtime->hw = hw;
/* add rule between channels and sampling rate */ @@ -191,42 +186,8 @@ static int fwspk_close(struct snd_pcm_substream *substream) static int fwspk_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { - struct fwspk *fwspk = substream->private_data; - int err; - - mutex_lock(&fwspk->mutex); - snd_fwspk_stream_stop(fwspk); - mutex_unlock(&fwspk->mutex); - - err = snd_pcm_lib_alloc_vmalloc_buffer(substream, - params_buffer_bytes(hw_params)); - if (err < 0) - goto error; - - amdtp_stream_set_parameters(&fwspk->rx_stream, - params_rate(hw_params), - params_channels(hw_params), - 0); - - amdtp_stream_set_pcm_format(&fwspk->rx_stream, - params_format(hw_params)); - - err = avc_general_set_sig_fmt(fwspk->unit, params_rate(hw_params), - AVC_GENERAL_PLUG_DIR_IN, 0); - if (err < 0) - goto err_buffer; - if (err != 0x09 /* ACCEPTED */) { - dev_err(&fwspk->unit->device, "failed to set sample rate\n"); - err = -EIO; - goto error; - } - - return 0; - -err_buffer: - snd_pcm_lib_free_vmalloc_buffer(substream); -error: - return err; + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); }
static int fwspk_hw_free(struct snd_pcm_substream *substream) @@ -243,16 +204,16 @@ static int fwspk_hw_free(struct snd_pcm_substream *substream) static int fwspk_prepare(struct snd_pcm_substream *substream) { struct fwspk *fwspk = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; int err;
mutex_lock(&fwspk->mutex);
- snd_fwspk_stream_stop(fwspk); - - err = snd_fwspk_stream_start(fwspk); + err = snd_fwspk_stream_start(fwspk, runtime->rate); if (err < 0) goto end;
+ amdtp_stream_set_pcm_format(&fwspk->rx_stream, runtime->format); amdtp_stream_pcm_prepare(&fwspk->rx_stream); end: mutex_unlock(&fwspk->mutex); diff --git a/sound/firewire/speakers/speakers_stream.c b/sound/firewire/speakers/speakers_stream.c index 17d83e4..ef2ac9d 100644 --- a/sound/firewire/speakers/speakers_stream.c +++ b/sound/firewire/speakers/speakers_stream.c @@ -41,18 +41,76 @@ end: return err; }
-int snd_fwspk_stream_start(struct fwspk *fwspk) +int snd_fwspk_stream_start(struct fwspk *fwspk, unsigned int rate) { - int err = 0; + unsigned int i, curr_rate, pcm_channels, midi_ports; + bool used; + int err;
- if (amdtp_stream_running(&fwspk->rx_stream)) + /* already start or not */ + if (amdtp_stream_running(&fwspk->rx_stream)) { + err = 0; + goto end; + } + + /* check other's connection */ + err = cmp_connection_check_used(&fwspk->in_conn, &used); + if (err < 0) + goto end; + else if (used) { + err = -EBUSY; + goto end; + }; + + /* arrange sampling rate */ + err = avc_general_get_sig_fmt(fwspk->unit, &curr_rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) goto end; + if (err != 0x0c /* IMPLEMENTED/STABLE */) { + dev_err(&fwspk->unit->device, + "failed to get sample rate\n"); + err = -EIO; + goto end; + } + if (curr_rate != rate) { + err = avc_general_set_sig_fmt(fwspk->unit, rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) + goto end; + if (err != 0x09 /* ACCEPTED */) { + dev_err(&fwspk->unit->device, + "failed to set sample rate\n"); + err = -EIO; + goto end; + } + } + + /* set stream formation */ + for (i = 0; i < FWSPK_STREAM_TABLE_ENTRIES; i++) { + if (fwspk_rate_table[i] == rate) + break; + } + if (i == FWSPK_STREAM_TABLE_ENTRIES) { + err = -EINVAL; + goto end; + } + pcm_channels = fwspk->rx_stream_formations[i].pcm; + midi_ports = fwspk->rx_stream_formations[i].midi; + if ((pcm_channels == 0) && (midi_ports == 0)) { + err = -EINVAL; + goto end; + } + amdtp_stream_set_parameters(&fwspk->rx_stream, rate, + pcm_channels, midi_ports);
+ /* establish connection */ err = cmp_connection_establish(&fwspk->in_conn, - amdtp_stream_get_max_payload(&fwspk->rx_stream)); + amdtp_stream_get_max_payload(&fwspk->rx_stream)); if (err < 0) goto end;
+ /* start stream */ err = amdtp_stream_start(&fwspk->rx_stream, fwspk->in_conn.resources.channel, fwspk->in_conn.speed);
OXFW970/971 may supports AV/C Stream Format Information Specification 1.1 Working Draft (Apr 2005, 1394TA). So this driver uses 'EXTENDED STREAM FORMAT INFORMATION' command.
This command has two subfunctions, 'SINGLE' and 'LIST'. Drivers can use 'SINGLE' subfunction to know current formation of AMDTP stream, Drivers can use 'LIST' subfunction to know an available formation of AMDTP stream in a certain sampling rate. Followed commit adds parser for returned information.
This commit adds 'inquiry' version of 'PLUG SIGNAL FORMAT' command. When device doesn't support 'LIST' subfunction of 'AV/C Stream Format Information' command, drivers can use 'inquiry' command to get supported sampling rates.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/speakers/Makefile | 2 +- sound/firewire/speakers/speakers.h | 30 +++++++ sound/firewire/speakers/speakers_command.c | 122 +++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/speakers/speakers_command.c
diff --git a/sound/firewire/speakers/Makefile b/sound/firewire/speakers/Makefile index 8936816..86b2b6c 100644 --- a/sound/firewire/speakers/Makefile +++ b/sound/firewire/speakers/Makefile @@ -1,3 +1,3 @@ -snd-firewire-speakers-objs := speakers_stream.o speakers_pcm.o speakers_control.o \ +snd-firewire-speakers-objs := speakers_command.o speakers_stream.o speakers_pcm.o speakers_control.o \ speakers_proc.o speakers.o obj-m += snd-firewire-speakers.o diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h index 90c65f4..386e267 100644 --- a/sound/firewire/speakers/speakers.h +++ b/sound/firewire/speakers/speakers.h @@ -57,6 +57,36 @@ struct fwspk { s16 volume_max; };
+/* AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) */ +#define AVC_GENERIC_FRAME_MAXIMUM_BYTES 512 +int avc_stream_get_format(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, + unsigned int eid); +static inline int +avc_stream_get_format_single(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len) +{ + return avc_stream_get_format(unit, dir, pid, buf, len, 0xff); +} +static inline int +avc_stream_get_format_list(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, + unsigned int eid) +{ + return avc_stream_get_format(unit, dir, pid, buf, len, eid); +} + +/* + * AV/C Digital Interface Command Set General Specification 4.2 + * (Sep 2004, 1394TA) + */ +int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short pid); + int snd_fwspk_stream_init(struct fwspk *fwspk); int snd_fwspk_stream_start(struct fwspk *fwspk, unsigned int rate); void snd_fwspk_stream_stop(struct fwspk *fwspk); diff --git a/sound/firewire/speakers/speakers_command.c b/sound/firewire/speakers/speakers_command.c new file mode 100644 index 0000000..a13bc9e --- /dev/null +++ b/sound/firewire/speakers/speakers_command.c @@ -0,0 +1,122 @@ +/* + * speakers_command.c - part of OXFW970/971-based speakers driver + * + * Copyright (c) Takashi Sakamoto o-takashi@sakamocchi.jp + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "speakers.h" + +int avc_stream_get_format(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, + unsigned int eid) +{ + unsigned int subfunc; + int err; + + /* check given buffer */ + if ((buf == NULL) || (*len < 12)) { + err = -EINVAL; + goto end; + } + + if (eid == 0xff) + subfunc = 0xc0; /* SINGLE */ + else + subfunc = 0xc1; /* LIST */ + + buf[0] = 0x01; /* STATUS */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0xbf; /* EXTENDED STREAM FORMAT INFORMATION */ + buf[3] = subfunc; /* SINGLE or LIST */ + buf[4] = dir; /* Plug Direction */ + buf[5] = 0x00; /* Unit */ + buf[6] = 0x00; /* PCR (Isochronous Plug) */ + buf[7] = 0xff & pid; /* Plug ID */ + buf[8] = 0xff; /* Padding */ + buf[9] = 0xff; /* support status in response */ + buf[10] = 0xff & eid; /* entry ID */ + buf[11] = 0xff; /* padding */ + + /* do transaction and check buf[1-7] are the same against command */ + err = fcp_avc_transaction(unit, buf, 12, buf, *len, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7)); + if (err < 0) { + goto end; + /* reach the end of entries */ + } else if (buf[0] == 0x0a) { + err = 0; + *len = 0; + goto end; + } else if (buf[0] != 0x0c) { + err = -EINVAL; + goto end; + /* the content starts at 11th bytes */ + } else if (err < 9) { + err = -EIO; + goto end; + } else if ((subfunc == 0xc1) && (buf[10] != eid)) { + err = -EIO; + goto end; + } + + /* strip */ + memmove(buf, buf + 10, err - 10); + *len = err - 10; + err = 0; +end: + return err; +} + +int avc_general_inquiry_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; + + for (sfc = 0; sfc < CIP_SFC_COUNT; sfc++) { + if (amdtp_rate_table[sfc] == rate) + break; + } + if (sfc == CIP_SFC_COUNT) + return -EINVAL; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x02; /* SPECIFIC INQUIRY */ + 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) + goto end; + + /* check length */ + if (err != 8) { + dev_err(&unit->device, "failed to inquiry sample rate\n"); + err = -EIO; + goto end; + } + + /* return response code */ + err = buf[0]; +end: + kfree(buf); + return err; +}
FFADO project have already identified that some devices produced by Behringer/Mackie are based on OXFW970/971.
They support 'AV/C Stream Format Information' command. But some of them don't implement 'LIST' subfunction. So this commit uses an assumption that 'if they don't implement it, they don't change formation of stream for each sampling rate'.
With this assumption, this driver generate formations for the devices by: 1.getting current formation by SINGLE subfunction 2.getting supported sampling rates 3.applying current formation for all of supported sampling rates
Behringer/Mackie devices support capture/playback of PCM-samples and some of them supports capture/playback of MIDI-messages. Followed commits will add these functionalities.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 11 +- sound/firewire/speakers/speakers.c | 48 +++++++- sound/firewire/speakers/speakers.h | 1 + sound/firewire/speakers/speakers_stream.c | 197 ++++++++++++++++++++++++++++++ 4 files changed, 249 insertions(+), 8 deletions(-)
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index b2c5a7e..1cf371f 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -27,12 +27,17 @@ config SND_DICE will be called snd-dice.
config SND_FIREWIRE_SPEAKERS - tristate "FireWire speakers" + tristate "Oxford OXFW970/971 chipset support" select SND_PCM select SND_FIREWIRE_LIB help - Say Y here to include support for the Griffin FireWave Surround - and the LaCie FireWire Speakers. + Say Y here to include support for Firewire devices based on + Oxford Semiconductor OXFW970/971. + * Griffin Firewave + * LaCie Firewire Speakers + * Behringer F-Control Audio 202 + * Mackie Onyx-i series (former model) + * Mackie Onyx Satellite
To compile this driver as a module, choose M here: the module will be called snd-firewire-speakers. diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c index 8c3bf4a..e68046c 100644 --- a/sound/firewire/speakers/speakers.c +++ b/sound/firewire/speakers/speakers.c @@ -16,6 +16,8 @@
#define VENDOR_GRIFFIN 0x001292 #define VENDOR_LACIE 0x00d04b +#define VEN_BEHRINGER 0x001564 +#define VEN_LOUD 0x000ff2
#define SPECIFIER_1394TA 0x00a02d #define VERSION_AVC 0x010001 @@ -64,7 +66,12 @@ static int name_card(struct fwspk *fwspk) goto end; be32_to_cpus(&firmware);
- strcpy(fwspk->card->driver, fwspk->device_info->driver_name); + /* to apply card definitions */ + if (fwspk->device_info) + strcpy(fwspk->card->driver, fwspk->device_info->driver_name); + else + strcpy(fwspk->card->driver, "OXFW"); + strcpy(fwspk->card->shortname, model);
snprintf(fwspk->card->longname, sizeof(fwspk->card->longname), @@ -110,8 +117,10 @@ static int fwspk_probe(struct fw_unit *unit,
if (fwspk->device_info == &griffin_firewave) err = firewave_stream_discover(fwspk); - else + else if (fwspk->device_info == &lacie_speakers) err = lacie_speakers_stream_discover(fwspk); + else + err = snd_fwspk_stream_discover(fwspk); if (err < 0) goto err_card;
@@ -127,9 +136,11 @@ static int fwspk_probe(struct fw_unit *unit, if (err < 0) goto err_card;
- err = snd_fwspk_create_mixer(fwspk); - if (err < 0) - goto err_card; + if (fwspk->device_info) { + err = snd_fwspk_create_mixer(fwspk); + if (err < 0) + goto err_card; + }
snd_fwspk_proc_init(fwspk);
@@ -189,6 +200,33 @@ static const struct ieee1394_device_id fwspk_id_table[] = { .version = VERSION_AVC, .driver_data = (kernel_ulong_t)&lacie_speakers, }, + /* Behringer,F-Control Audio 202 */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = VEN_BEHRINGER, + .model_id = 0x00fc22, + }, + /* Mackie, Onyx-i series (former models) */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = VEN_LOUD, + .model_id = 0x081216, + }, + /* Mackie, Onyx Satellite */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = VEN_LOUD, + .model_id = 0x00200f, + }, + /* IDs are unknown but able to be supported */ + /* Mackie(Loud), d.2 pro */ + /* Mackie(Loud), d.4 pro */ + /* Mackie(Loud), U.420 */ + /* Mackie(Loud), U.420d */ + /* Mackie(Loud), Tapco Link.Firewire */ { } }; MODULE_DEVICE_TABLE(ieee1394, fwspk_id_table); diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h index 386e267..017d972 100644 --- a/sound/firewire/speakers/speakers.h +++ b/sound/firewire/speakers/speakers.h @@ -95,6 +95,7 @@ void snd_fwspk_stream_update(struct fwspk *fwspk);
int firewave_stream_discover(struct fwspk *fwspk); int lacie_speakers_stream_discover(struct fwspk *fwspk); +int snd_fwspk_stream_discover(struct fwspk *fwspk);
int snd_fwspk_create_pcm(struct fwspk *fwspk);
diff --git a/sound/firewire/speakers/speakers_stream.c b/sound/firewire/speakers/speakers_stream.c index ef2ac9d..591e6bf 100644 --- a/sound/firewire/speakers/speakers_stream.c +++ b/sound/firewire/speakers/speakers_stream.c @@ -22,6 +22,20 @@ const unsigned int fwspk_rate_table[FWSPK_STREAM_TABLE_ENTRIES] = { [6] = 192000, };
+/* + * See Table 5.7 – Sampling frequency for Multi-bit Audio + * at AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) + */ +static const unsigned int avc_stream_rate_table[] = { + [0] = 0x02, + [1] = 0x03, + [2] = 0x04, + [3] = 0x0a, + [4] = 0x05, + [5] = 0x06, + [6] = 0x07, +}; + int snd_fwspk_stream_init(struct fwspk *fwspk) { int err; @@ -164,3 +178,186 @@ int lacie_speakers_stream_discover(struct fwspk *fwspk)
return 0; } + +/* + * See Table 6.16 - AM824 Stream Format + * Figure 6.19 - format_information field for AM824 Compound + * at AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) + */ +static int +parse_stream_formation(u8 *buf, unsigned int len, + struct fwspk_stream_formation *formation, + unsigned int *index) +{ + unsigned int 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 the sampling rate */ + for (*index = 0; *index < sizeof(avc_stream_rate_table); *index += 1) { + if (buf[2] == avc_stream_rate_table[*index]) + break; + } + if (*index == sizeof(avc_stream_rate_table)) + return -ENOSYS; + + for (e = 0; e < buf[4]; e++) { + channels = buf[5 + e * 2]; + format = buf[6 + e * 2]; + + switch (format) { + /* IEC 60958-3 */ + case 0x00: + /* Multi Bit Linear Audio (Raw) */ + case 0x06: + formation[*index].pcm += channels; + break; + /* MIDI comformant */ + case 0x0d: + formation[*index].midi += channels; + break; + /* Multi Bit Linear Audio (DVD-audio) */ + case 0x07: + /* IEC 61937-3 to 7 */ + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + /* One Bit Audio */ + case 0x08: /* (Plain) Raw */ + case 0x09: /* (Plain) SACD */ + case 0x0a: /* (Encoded) Raw */ + case 0x0b: /* (ENcoded) SACD */ + /* High precision Multi-bit Linear Audio */ + case 0x0c: + /* SMPTE Time-Code conformant */ + case 0x0e: + /* Sample Count */ + case 0x0f: + /* Anciliary Data */ + case 0x10: + /* Synchronization Stream (Stereo Raw audio) */ + case 0x40: + /* Don't care */ + case 0xff: + default: + break; /* not supported */ + } + } + + return 0; +} + +static int +assume_stream_formations(struct fwspk *fwspk, enum avc_general_plug_dir dir, + unsigned int pid, u8 *buf, unsigned int *len, + struct fwspk_stream_formation *formations) +{ + unsigned int i, pcm_channels, midi_channels; + int err; + + /* get formation at current sampling rate */ + err = avc_stream_get_format_single(fwspk->unit, dir, pid, buf, len); + if ((err < 0) || (err == 0x80) /* NOT IMPLEMENTED */) + goto end; + + /* parse and set stream formation */ + err = parse_stream_formation(buf, *len, formations, &i); + if (err < 0) + goto end; + + pcm_channels = formations[i].pcm; + midi_channels = formations[i].midi; + + /* apply the formation for each available sampling rate */ + for (i = 0; i < FWSPK_STREAM_TABLE_ENTRIES; i++) { + err = avc_general_inquiry_sig_fmt(fwspk->unit, + fwspk_rate_table[i], + dir, pid); + if ((err < 0) || (err == 0x08) /* NOT IMPLEMENTED */) + continue; + + formations[i].pcm = pcm_channels; + formations[i].midi = midi_channels; + } +end: + return err; +} + +static int +fill_stream_formations(struct fwspk *fwspk, enum avc_general_plug_dir dir, + unsigned short pid) +{ + u8 *buf; + struct fwspk_stream_formation *formations; + unsigned int i, len, eid; + int err; + + buf = kmalloc(AVC_GENERIC_FRAME_MAXIMUM_BYTES, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + formations = fwspk->rx_stream_formations; + + /* initialize parameters here because of checking implementation */ + eid = 0; + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + memset(buf, 0, len); + + /* get first entry */ + err = avc_stream_get_format_list(fwspk->unit, dir, 0, buf, &len, eid); + if ((err < 0) || (len < 3)) { + /* LIST subfunction is not implemented */ + err = assume_stream_formations(fwspk, dir, pid, buf, &len, + formations); + goto end; + } + + /* LIST subfunction is implemented */ + do { + /* parse and set stream formation */ + err = parse_stream_formation(buf, len, formations, &i); + if (err < 0) + continue; + + /* get next entry */ + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + memset(buf, 0, len); + err = avc_stream_get_format_list(fwspk->unit, dir, 0, + buf, &len, ++eid); + if ((err < 0) || (len < 3)) + break; + } while (eid < FWSPK_STREAM_TABLE_ENTRIES); +end: + kfree(buf); + return err; +} + +int snd_fwspk_stream_discover(struct fwspk *fwspk) +{ + u8 plugs[AVC_PLUG_INFO_BUF_COUNT]; + int err; + + /* the number of plugs for isoc in/out, ext in/out */ + err = avc_general_get_plug_info(fwspk->unit, 0x1f, 0x07, 0x00, plugs); + if (err < 0) + goto end; + if ((plugs[0] == 0) || (plugs[0] == 0)) { + err = -EIO; + goto end; + } + + /* use iPCR[0] */ + err = fill_stream_formations(fwspk, AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) + goto end; +end: + return err; +}
Previous commit adds support for some devices which can capture PCM samples. They transmits AMDTP stream in non-blocking mode. This stream has a quirk for 'presentation timestamp'.
The sequence of 'presentation timestamp' is invalid even if header of packet shows 'CIP header with SYT field'. So this driver can't reuse it for out stream.
In this reason, this driver handles both streams separately.
For your information, I note the sequence of CIP headers in stream transmitted by Behringer F-Control Audio 202:
When this driver gives no out-stream: Index Payload CIP_Header_0 CIP_Header_1 38 14 00020092 900103D1 39 12 00020098 900102FF 40 12 0002009D 9001027F 41 14 000200A2 90010396 42 14 000200A8 900102E8 43 12 000200AE 90010219 44 14 000200B3 90010331 45 12 000200B9 9001025F 46 14 000200BE 90010376 47 12 000200C4 900102A1 00 12 000200C9 9001023E 01 14 000200CE 90010358 02 12 000200D4 90010289 03 16 000200D9 900103A3 04 12 000200E0 900102DD 05 14 000200E5 900103F1 06 12 000200EB 90010335 07 12 000200F0 90010263 08 14 000200F5 9001037C 09 12 000200FB 900102AE
When this driver gives out-stream: Index Payload CIP_Header_0 CIP_Header_1 38 12 000200BD 900104A8 39 14 000200C2 900104A8 40 12 000200C8 900104AC 41 14 000200CD 900104A9 42 12 000200D3 900104B1 43 14 000200D8 900104A8 44 12 000200DE 900104AA 45 14 000200E3 900104A9 46 14 000200E9 900104AE 47 12 000200EF 900104A8 00 14 000200F4 900104AD 01 12 000200FA 900104A7 02 14 000200FF 900104A9 03 12 00020005 900104A9 04 14 0002000A 900104B1 05 12 00020010 900104AA 06 14 00020015 900104AD 07 12 0002001B 900104A7 08 14 00020020 900104AC 09 12 00020026 900104A7
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/speakers/speakers.c | 9 +- sound/firewire/speakers/speakers.h | 26 ++++- sound/firewire/speakers/speakers_command.c | 40 +++++++ sound/firewire/speakers/speakers_pcm.c | 179 +++++++++++++++++++++++++---- sound/firewire/speakers/speakers_proc.c | 14 ++- sound/firewire/speakers/speakers_stream.c | 171 +++++++++++++++++++++------ 6 files changed, 365 insertions(+), 74 deletions(-)
diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c index e68046c..76afce9 100644 --- a/sound/firewire/speakers/speakers.c +++ b/sound/firewire/speakers/speakers.c @@ -90,7 +90,8 @@ static void fwspk_card_free(struct snd_card *card) struct fwspk *fwspk = card->private_data;
mutex_lock(&fwspk->mutex); - snd_fwspk_stream_destroy(fwspk); + snd_fwspk_stream_destroy(fwspk, &fwspk->rx_stream); + snd_fwspk_stream_destroy(fwspk, &fwspk->tx_stream); mutex_unlock(&fwspk->mutex);
fw_unit_put(fwspk->unit); /* dec reference counter */ @@ -163,7 +164,8 @@ static void fwspk_bus_reset(struct fw_unit *unit) mutex_lock(&fwspk->mutex);
fcp_bus_reset(fwspk->unit); - snd_fwspk_stream_update(fwspk); + snd_fwspk_stream_update(fwspk, &fwspk->rx_stream); + snd_fwspk_stream_update(fwspk, &fwspk->tx_stream);
mutex_unlock(&fwspk->mutex); } @@ -172,7 +174,8 @@ static void fwspk_remove(struct fw_unit *unit) { struct fwspk *fwspk = dev_get_drvdata(&unit->device);
- snd_fwspk_stream_destroy(fwspk); + snd_fwspk_stream_destroy(fwspk, &fwspk->rx_stream); + snd_fwspk_stream_destroy(fwspk, &fwspk->tx_stream); snd_card_disconnect(fwspk->card); snd_card_free_when_closed(fwspk->card); } diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h index 017d972..5d6c8e8 100644 --- a/sound/firewire/speakers/speakers.h +++ b/sound/firewire/speakers/speakers.h @@ -47,8 +47,12 @@ struct fwspk { struct mutex mutex;
struct fwspk_stream_formation + tx_stream_formations[FWSPK_STREAM_TABLE_ENTRIES]; + struct fwspk_stream_formation rx_stream_formations[FWSPK_STREAM_TABLE_ENTRIES]; + struct cmp_connection out_conn; struct cmp_connection in_conn; + struct amdtp_stream tx_stream; struct amdtp_stream rx_stream;
bool mute; @@ -87,11 +91,23 @@ int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate, enum avc_general_plug_dir dir, unsigned short pid);
-int snd_fwspk_stream_init(struct fwspk *fwspk); -int snd_fwspk_stream_start(struct fwspk *fwspk, unsigned int rate); -void snd_fwspk_stream_stop(struct fwspk *fwspk); -void snd_fwspk_stream_destroy(struct fwspk *fwspk); -void snd_fwspk_stream_update(struct fwspk *fwspk); +int snd_fwspk_command_set_rate(struct fwspk *fwspk, + enum avc_general_plug_dir dir, + unsigned int rate); +int snd_fwspk_command_get_rate(struct fwspk *fwspk, + enum avc_general_plug_dir dir, + unsigned int *rate); + +int snd_fwspk_stream_get_rate(struct fwspk *fwspk, unsigned int *rate); +int snd_fwspk_stream_set_rate(struct fwspk *fwspk, unsigned int rate); + +int snd_fwspk_stream_start(struct fwspk *fwspk, + struct amdtp_stream *stream, unsigned int rate); +void snd_fwspk_stream_stop(struct fwspk *fwspk, struct amdtp_stream *stream); +void snd_fwspk_stream_destroy(struct fwspk *fwspk, struct amdtp_stream *stream); +void snd_fwspk_stream_update(struct fwspk *fwspk, struct amdtp_stream *stream); + +int snd_fwspk_streams_init(struct fwspk *fwspk);
int firewave_stream_discover(struct fwspk *fwspk); int lacie_speakers_stream_discover(struct fwspk *fwspk); diff --git a/sound/firewire/speakers/speakers_command.c b/sound/firewire/speakers/speakers_command.c index a13bc9e..274683e 100644 --- a/sound/firewire/speakers/speakers_command.c +++ b/sound/firewire/speakers/speakers_command.c @@ -120,3 +120,43 @@ end: kfree(buf); return err; } + +int snd_fwspk_command_set_rate(struct fwspk *fwspk, + enum avc_general_plug_dir dir, + unsigned int rate) +{ + int err; + + err = avc_general_set_sig_fmt(fwspk->unit, rate, dir, 0); + if (err < 0) + goto end; + + /* ACCEPTED or INTERIM is OK */ + if ((err != 0x0f) && (err != 0x09)) { + dev_err(&fwspk->unit->device, + "failed to set sampling rate\n"); + err = -EIO; + } +end: + return err; +} + +int snd_fwspk_command_get_rate(struct fwspk *fwspk, + enum avc_general_plug_dir dir, + unsigned int *rate) +{ + int err; + + err = avc_general_get_sig_fmt(fwspk->unit, rate, dir, 0); + if (err < 0) + goto end; + + /* IMPLEMENTED/STABLE is OK */ + if (err != 0x0c) { + dev_err(&fwspk->unit->device, + "failed to get sampling rate\n"); + err = -EIO; + } +end: + return err; +} diff --git a/sound/firewire/speakers/speakers_pcm.c b/sound/firewire/speakers/speakers_pcm.c index 2258ddf..da777d2 100644 --- a/sound/firewire/speakers/speakers_pcm.c +++ b/sound/firewire/speakers/speakers_pcm.c @@ -66,6 +66,13 @@ static int hw_rule_channels(struct snd_pcm_hw_params *params, return snd_interval_refine(c, &t); }
+static inline int hw_rule_capture_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct fwspk *fwspk = rule->private; + return hw_rule_rate(params, rule, fwspk, + fwspk->tx_stream_formations); +} static inline int hw_rule_playback_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { @@ -74,6 +81,14 @@ static inline int hw_rule_playback_rate(struct snd_pcm_hw_params *params, fwspk->rx_stream_formations); }
+static inline int hw_rule_capture_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct fwspk *fwspk = rule->private; + return hw_rule_channels(params, rule, fwspk, + fwspk->tx_stream_formations); +} + static inline int hw_rule_playback_channels(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { @@ -117,12 +132,14 @@ static void prepare_rates(struct snd_pcm_hardware *hw, return; }
-int fwspk_open(struct snd_pcm_substream *substream) +static int init_hw_params(struct fwspk *fwspk, + struct snd_pcm_substream *substream) { static const struct snd_pcm_hardware hw = { .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BATCH | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | /* for Open Sound System compatibility */ SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BLOCK_TRANSFER, @@ -139,22 +156,33 @@ int fwspk_open(struct snd_pcm_substream *substream) .periods_min = 1, .periods_max = UINT_MAX, }; - struct fwspk *fwspk = substream->private_data; struct snd_pcm_runtime *runtime = substream->runtime; int err;
runtime->hw = hw;
/* add rule between channels and sampling rate */ - prepare_rates(&runtime->hw, fwspk->rx_stream_formations); - prepare_channels(&runtime->hw, fwspk->rx_stream_formations); - runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS; - snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, - hw_rule_playback_channels, fwspk, - SNDRV_PCM_HW_PARAM_RATE, -1); - snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, - hw_rule_playback_rate, fwspk, - SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + prepare_rates(&runtime->hw, fwspk->tx_stream_formations); + prepare_channels(&runtime->hw, fwspk->tx_stream_formations); + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_capture_channels, fwspk, + SNDRV_PCM_HW_PARAM_RATE, -1); + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_capture_rate, fwspk, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + } else { + prepare_rates(&runtime->hw, fwspk->rx_stream_formations); + prepare_channels(&runtime->hw, fwspk->rx_stream_formations); + runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS; + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_playback_channels, fwspk, + SNDRV_PCM_HW_PARAM_RATE, -1); + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_playback_rate, fwspk, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + }
/* AM824 in IEC 61883-6 can deliver 24bit data */ err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); @@ -178,6 +206,34 @@ end: return err; }
+static int fwspk_open(struct snd_pcm_substream *substream) +{ + struct fwspk *fwspk = substream->private_data; + unsigned int rate; + int err; + + err = init_hw_params(fwspk, substream); + if (err < 0) + goto end; + + /* + * When any PCM stream are already running, the available sampling rate + * is limited at current value. + */ + if (amdtp_stream_pcm_running(&fwspk->tx_stream) || + amdtp_stream_pcm_running(&fwspk->rx_stream)) { + err = snd_fwspk_stream_get_rate(fwspk, &rate); + if (err < 0) + goto end; + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } + + snd_pcm_set_sync(substream); +end: + return err; +} + static int fwspk_close(struct snd_pcm_substream *substream) { return 0; @@ -190,18 +246,46 @@ static int fwspk_hw_params(struct snd_pcm_substream *substream, params_buffer_bytes(hw_params)); }
-static int fwspk_hw_free(struct snd_pcm_substream *substream) +static int fwspk_hw_free_capture(struct snd_pcm_substream *substream) { struct fwspk *fwspk = substream->private_data;
mutex_lock(&fwspk->mutex); - snd_fwspk_stream_stop(fwspk); + snd_fwspk_stream_stop(fwspk, &fwspk->tx_stream); mutex_unlock(&fwspk->mutex);
return snd_pcm_lib_free_vmalloc_buffer(substream); } +static int fwspk_hw_free_playback(struct snd_pcm_substream *substream) +{ + struct fwspk *fwspk = substream->private_data; + + mutex_lock(&fwspk->mutex); + snd_fwspk_stream_stop(fwspk, &fwspk->rx_stream); + mutex_unlock(&fwspk->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int fwspk_prepare_capture(struct snd_pcm_substream *substream) +{ + struct fwspk *fwspk = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err;
-static int fwspk_prepare(struct snd_pcm_substream *substream) + mutex_lock(&fwspk->mutex); + + err = snd_fwspk_stream_start(fwspk, &fwspk->tx_stream, runtime->rate); + if (err < 0) + goto end; + + amdtp_stream_set_pcm_format(&fwspk->tx_stream, runtime->format); + amdtp_stream_pcm_prepare(&fwspk->tx_stream); +end: + mutex_unlock(&fwspk->mutex); + return err; +} +static int fwspk_prepare_playback(struct snd_pcm_substream *substream) { struct fwspk *fwspk = substream->private_data; struct snd_pcm_runtime *runtime = substream->runtime; @@ -209,7 +293,7 @@ static int fwspk_prepare(struct snd_pcm_substream *substream)
mutex_lock(&fwspk->mutex);
- err = snd_fwspk_stream_start(fwspk, runtime->rate); + err = snd_fwspk_stream_start(fwspk, &fwspk->rx_stream, runtime->rate); if (err < 0) goto end;
@@ -220,7 +304,25 @@ end: return err; }
-static int fwspk_trigger(struct snd_pcm_substream *substream, int cmd) +static int fwspk_trigger_capture(struct snd_pcm_substream *substream, int cmd) +{ + struct fwspk *fwspk = substream->private_data; + struct snd_pcm_substream *pcm; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + pcm = substream; + break; + case SNDRV_PCM_TRIGGER_STOP: + pcm = NULL; + break; + default: + return -EINVAL; + } + amdtp_stream_pcm_trigger(&fwspk->tx_stream, pcm); + return 0; +} +static int fwspk_trigger_playback(struct snd_pcm_substream *substream, int cmd) { struct fwspk *fwspk = substream->private_data; struct snd_pcm_substream *pcm; @@ -239,35 +341,62 @@ static int fwspk_trigger(struct snd_pcm_substream *substream, int cmd) return 0; }
-static snd_pcm_uframes_t fwspk_pointer(struct snd_pcm_substream *substream) +static snd_pcm_uframes_t fwspk_pointer_capture(struct snd_pcm_substream *sbstm) { - struct fwspk *fwspk = substream->private_data; + struct fwspk *fwspk = sbstm->private_data; + + return amdtp_stream_pcm_pointer(&fwspk->tx_stream); +} +static snd_pcm_uframes_t fwspk_pointer_playback(struct snd_pcm_substream *sbstm) +{ + struct fwspk *fwspk = sbstm->private_data;
return amdtp_stream_pcm_pointer(&fwspk->rx_stream); }
int snd_fwspk_create_pcm(struct fwspk *fwspk) { - static struct snd_pcm_ops ops = { + static struct snd_pcm_ops capture_ops = { .open = fwspk_open, .close = fwspk_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = fwspk_hw_params, - .hw_free = fwspk_hw_free, - .prepare = fwspk_prepare, - .trigger = fwspk_trigger, - .pointer = fwspk_pointer, + .hw_free = fwspk_hw_free_capture, + .prepare = fwspk_prepare_capture, + .trigger = fwspk_trigger_capture, + .pointer = fwspk_pointer_capture, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, + }; + static struct snd_pcm_ops playback_ops = { + .open = fwspk_open, + .close = fwspk_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = fwspk_hw_params, + .hw_free = fwspk_hw_free_playback, + .prepare = fwspk_prepare_playback, + .trigger = fwspk_trigger_playback, + .pointer = fwspk_pointer_playback, .page = snd_pcm_lib_get_vmalloc_page, .mmap = snd_pcm_lib_mmap_vmalloc, }; struct snd_pcm *pcm; + unsigned int cap = 0; int err;
- err = snd_pcm_new(fwspk->card, fwspk->card->driver, 0, 1, 0, &pcm); + /* 44.1kHz is the most popular */ + if (fwspk->tx_stream_formations[1].pcm > 0) + cap = 1; + + err = snd_pcm_new(fwspk->card, fwspk->card->driver, 0, 1, cap, &pcm); if (err < 0) return err; + pcm->private_data = fwspk; strcpy(pcm->name, fwspk->card->shortname); - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + if (cap > 0) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); + return 0; } diff --git a/sound/firewire/speakers/speakers_proc.c b/sound/firewire/speakers/speakers_proc.c index 9721e52..2865ce2 100644 --- a/sound/firewire/speakers/speakers_proc.c +++ b/sound/firewire/speakers/speakers_proc.c @@ -16,6 +16,15 @@ proc_read_formation(struct snd_info_entry *entry, struct fwspk_stream_formation *formation; unsigned int i;
+ snd_iprintf(buffer, "Output Stream from device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + formation = fwspk->tx_stream_formations; + for (i = 0; i < FWSPK_STREAM_TABLE_ENTRIES; i++) { + snd_iprintf(buffer, + "\t%d\t%d\t%d\n", fwspk_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 = fwspk->rx_stream_formations; @@ -32,11 +41,8 @@ proc_read_clock(struct snd_info_entry *entry, { struct fwspk *fwspk = entry->private_data; unsigned int rate; - int err;
- err = avc_general_get_sig_fmt(fwspk->unit, &rate, - AVC_GENERAL_PLUG_DIR_IN, 0); - if ((err < 0) && (err == 0x09)) + if (snd_fwspk_stream_get_rate(fwspk, &rate) >= 0) snd_iprintf(buffer, "Sampling rate: %d\n", rate); }
diff --git a/sound/firewire/speakers/speakers_stream.c b/sound/firewire/speakers/speakers_stream.c index 591e6bf..b02b03c 100644 --- a/sound/firewire/speakers/speakers_stream.c +++ b/sound/firewire/speakers/speakers_stream.c @@ -36,39 +36,101 @@ static const unsigned int avc_stream_rate_table[] = { [6] = 0x07, };
-int snd_fwspk_stream_init(struct fwspk *fwspk) +int snd_fwspk_stream_get_rate(struct fwspk *fwspk, unsigned int *rate) { + unsigned int tx_rate, rx_rate; int err;
- err = cmp_connection_init(&fwspk->in_conn, fwspk->unit, - CMP_INPUT, 0); + err = snd_fwspk_command_get_rate(fwspk, + AVC_GENERAL_PLUG_DIR_OUT, &tx_rate); + if (err < 0) + goto end; + + err = snd_fwspk_command_get_rate(fwspk, + AVC_GENERAL_PLUG_DIR_IN, &rx_rate); + if (err < 0) + goto end; + + *rate = rx_rate; + if (rx_rate == tx_rate) + goto end; + + /* synchronize receive stream rate to transmit stream rate */ + err = snd_fwspk_command_set_rate(fwspk, + AVC_GENERAL_PLUG_DIR_IN, rx_rate); +end: + return err; +} + +int snd_fwspk_stream_set_rate(struct fwspk *fwspk, unsigned int rate) +{ + int err; + + err = snd_fwspk_command_set_rate(fwspk, AVC_GENERAL_PLUG_DIR_OUT, rate); + if (err < 0) + goto end; + + err = snd_fwspk_command_set_rate(fwspk, AVC_GENERAL_PLUG_DIR_IN, rate); +end: + return err; +} + +static int stream_init(struct fwspk *fwspk, struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + enum cmp_direction c_dir; + enum amdtp_stream_direction s_dir; + int err; + + if (stream == &fwspk->tx_stream) { + conn = &fwspk->out_conn; + c_dir = CMP_OUTPUT; + s_dir = AMDTP_IN_STREAM; + } else { + conn = &fwspk->in_conn; + c_dir = CMP_INPUT; + s_dir = AMDTP_OUT_STREAM; + } + + err = cmp_connection_init(conn, fwspk->unit, c_dir, 0); if (err < 0) { fw_unit_put(fwspk->unit); goto end; }
- err = amdtp_stream_init(&fwspk->rx_stream, fwspk->unit, - AMDTP_OUT_STREAM, CIP_NONBLOCKING); + err = amdtp_stream_init(stream, fwspk->unit, s_dir, CIP_NONBLOCKING); if (err < 0) - cmp_connection_destroy(&fwspk->in_conn); + cmp_connection_destroy(conn); end: return err; }
-int snd_fwspk_stream_start(struct fwspk *fwspk, unsigned int rate) +int snd_fwspk_stream_start(struct fwspk *fwspk, + struct amdtp_stream *stream, + unsigned int rate) { unsigned int i, curr_rate, pcm_channels, midi_ports; + struct cmp_connection *conn; + enum avc_general_plug_dir dir; bool used; int err;
/* already start or not */ - if (amdtp_stream_running(&fwspk->rx_stream)) { + if (amdtp_stream_running(stream)) { err = 0; goto end; }
+ if (stream == &fwspk->tx_stream) { + conn = &fwspk->out_conn; + dir = AVC_GENERAL_PLUG_DIR_OUT; + } else { + conn = &fwspk->in_conn; + dir = AVC_GENERAL_PLUG_DIR_IN; + } + /* check other's connection */ - err = cmp_connection_check_used(&fwspk->in_conn, &used); + err = cmp_connection_check_used(conn, &used); if (err < 0) goto end; else if (used) { @@ -77,8 +139,7 @@ int snd_fwspk_stream_start(struct fwspk *fwspk, unsigned int rate) };
/* arrange sampling rate */ - err = avc_general_get_sig_fmt(fwspk->unit, &curr_rate, - AVC_GENERAL_PLUG_DIR_IN, 0); + err = avc_general_get_sig_fmt(fwspk->unit, &curr_rate, dir, 0); if (err < 0) goto end; if (err != 0x0c /* IMPLEMENTED/STABLE */) { @@ -88,8 +149,7 @@ int snd_fwspk_stream_start(struct fwspk *fwspk, unsigned int rate) goto end; } if (curr_rate != rate) { - err = avc_general_set_sig_fmt(fwspk->unit, rate, - AVC_GENERAL_PLUG_DIR_IN, 0); + err = avc_general_set_sig_fmt(fwspk->unit, rate, dir, 0); if (err < 0) goto end; if (err != 0x09 /* ACCEPTED */) { @@ -109,52 +169,66 @@ int snd_fwspk_stream_start(struct fwspk *fwspk, unsigned int rate) err = -EINVAL; goto end; } - pcm_channels = fwspk->rx_stream_formations[i].pcm; - midi_ports = fwspk->rx_stream_formations[i].midi; - if ((pcm_channels == 0) && (midi_ports == 0)) { + if (stream == &fwspk->tx_stream) { + pcm_channels = fwspk->tx_stream_formations[i].pcm; + midi_ports = fwspk->tx_stream_formations[i].midi; + } else { + pcm_channels = fwspk->rx_stream_formations[i].pcm; + midi_ports = fwspk->rx_stream_formations[i].midi; + } + if (pcm_channels == 0) { err = -EINVAL; goto end; } - amdtp_stream_set_parameters(&fwspk->rx_stream, rate, - pcm_channels, midi_ports); + amdtp_stream_set_parameters(stream, rate, pcm_channels, midi_ports);
/* establish connection */ - err = cmp_connection_establish(&fwspk->in_conn, - amdtp_stream_get_max_payload(&fwspk->rx_stream)); + err = cmp_connection_establish(conn, + amdtp_stream_get_max_payload(stream)); if (err < 0) goto end;
/* start stream */ - err = amdtp_stream_start(&fwspk->rx_stream, - fwspk->in_conn.resources.channel, - fwspk->in_conn.speed); + err = amdtp_stream_start(stream, + conn->resources.channel, + conn->speed); if (err < 0) - cmp_connection_break(&fwspk->in_conn); + cmp_connection_break(conn); end: return err; }
-void snd_fwspk_stream_stop(struct fwspk *fwspk) +void snd_fwspk_stream_stop(struct fwspk *fwspk, struct amdtp_stream *stream) { - if (amdtp_stream_running(&fwspk->rx_stream)) - amdtp_stream_stop(&fwspk->rx_stream); + if (amdtp_stream_running(stream)) + amdtp_stream_stop(stream);
- cmp_connection_break(&fwspk->in_conn); + if (stream == &fwspk->tx_stream) + cmp_connection_break(&fwspk->out_conn); + else + cmp_connection_break(&fwspk->in_conn); }
-void snd_fwspk_stream_destroy(struct fwspk *fwspk) +void snd_fwspk_stream_destroy(struct fwspk *fwspk, struct amdtp_stream *stream) { - amdtp_stream_pcm_abort(&fwspk->rx_stream); - snd_fwspk_stream_stop(fwspk); + amdtp_stream_pcm_abort(stream); + snd_fwspk_stream_stop(fwspk, stream); }
-void snd_fwspk_stream_update(struct fwspk *fwspk) +void snd_fwspk_stream_update(struct fwspk *fwspk, struct amdtp_stream *stream) { - if (cmp_connection_update(&fwspk->in_conn) < 0) { - amdtp_stream_pcm_abort(&fwspk->rx_stream); - snd_fwspk_stream_stop(fwspk); + struct cmp_connection *conn; + + if (stream == &fwspk->tx_stream) + conn = &fwspk->out_conn; + else + conn = &fwspk->in_conn; + + if (cmp_connection_update(conn) < 0) { + amdtp_stream_pcm_abort(stream); + snd_fwspk_stream_stop(fwspk, stream); } else { - amdtp_stream_update(&fwspk->rx_stream); + amdtp_stream_update(stream); } }
@@ -304,7 +378,10 @@ fill_stream_formations(struct fwspk *fwspk, enum avc_general_plug_dir dir, if (buf == NULL) return -ENOMEM;
- formations = fwspk->rx_stream_formations; + if (dir == AVC_GENERAL_PLUG_DIR_OUT) + formations = fwspk->tx_stream_formations; + else + formations = fwspk->rx_stream_formations;
/* initialize parameters here because of checking implementation */ eid = 0; @@ -354,6 +431,11 @@ int snd_fwspk_stream_discover(struct fwspk *fwspk) goto end; }
+ /* use oPCR[0] */ + err = fill_stream_formations(fwspk, AVC_GENERAL_PLUG_DIR_OUT, 0); + if (err < 0) + goto end; + /* use iPCR[0] */ err = fill_stream_formations(fwspk, AVC_GENERAL_PLUG_DIR_IN, 0); if (err < 0) @@ -361,3 +443,18 @@ int snd_fwspk_stream_discover(struct fwspk *fwspk) end: return err; } + +int snd_fwspk_streams_init(struct fwspk *fwspk) +{ + int err; + + err = stream_init(fwspk, &fwspk->rx_stream); + if (err < 0) + goto end; + + /* 44.1kHz is the most popular */ + if (fwspk->tx_stream_formations[1].pcm > 0) + err = stream_init(fwspk, &fwspk->tx_stream); +end: + return err; +}
I assume that 'if device formations of stream have MIDI conformant data channels, the device has one MIDI port'. With this assumption, the driver create MIDI devices.
When no streams have already started, MIDI functionality starts stream with current sampling rate.
When MIDI functionality have already starts some streams and PCM functionality is going to start streams at different sampling rate, this driver stops streams once and changes sampling rate, then restarts streams.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/speakers/Makefile | 2 +- sound/firewire/speakers/speakers.c | 9 +- sound/firewire/speakers/speakers.h | 7 ++ sound/firewire/speakers/speakers_midi.c | 150 ++++++++++++++++++++++++++++++ sound/firewire/speakers/speakers_stream.c | 35 +++++++ 5 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 sound/firewire/speakers/speakers_midi.c
diff --git a/sound/firewire/speakers/Makefile b/sound/firewire/speakers/Makefile index 86b2b6c..d98d4f7 100644 --- a/sound/firewire/speakers/Makefile +++ b/sound/firewire/speakers/Makefile @@ -1,3 +1,3 @@ snd-firewire-speakers-objs := speakers_command.o speakers_stream.o speakers_pcm.o speakers_control.o \ - speakers_proc.o speakers.o + speakers_proc.o speakers_midi.o speakers.o obj-m += snd-firewire-speakers.o diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c index 76afce9..fd16e59 100644 --- a/sound/firewire/speakers/speakers.c +++ b/sound/firewire/speakers/speakers.c @@ -115,6 +115,7 @@ static int fwspk_probe(struct fw_unit *unit, fwspk->unit = fw_unit_get(unit); /* inc reference counter */ fwspk->device_info = (const struct device_info *)id->driver_data; mutex_init(&fwspk->mutex); + spin_lock_init(&fwspk->lock);
if (fwspk->device_info == &griffin_firewave) err = firewave_stream_discover(fwspk); @@ -129,7 +130,7 @@ static int fwspk_probe(struct fw_unit *unit, if (err < 0) goto err_card;
- err = snd_fwspk_stream_init(fwspk); + err = snd_fwspk_streams_init(fwspk); if (err < 0) goto err_card;
@@ -145,6 +146,12 @@ static int fwspk_probe(struct fw_unit *unit,
snd_fwspk_proc_init(fwspk);
+ if ((fwspk->midi_input_ports > 0) || (fwspk->midi_output_ports > 0)) { + err = snd_fwspk_create_midi(fwspk); + if (err < 0) + goto err_card; + } + snd_card_set_dev(card, &unit->device); err = snd_card_register(card); if (err < 0) diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h index 5d6c8e8..2c54946 100644 --- a/sound/firewire/speakers/speakers.h +++ b/sound/firewire/speakers/speakers.h @@ -19,6 +19,7 @@ #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/info.h> +#include <sound/rawmidi.h>
#include "../cmp.h" #include "../fcp.h" @@ -45,6 +46,7 @@ struct fwspk { struct fw_unit *unit; const struct device_info *device_info; struct mutex mutex; + spinlock_t lock;
struct fwspk_stream_formation tx_stream_formations[FWSPK_STREAM_TABLE_ENTRIES]; @@ -55,6 +57,9 @@ struct fwspk { struct amdtp_stream tx_stream; struct amdtp_stream rx_stream;
+ unsigned int midi_input_ports; + unsigned int midi_output_ports; + bool mute; s16 volume[6]; s16 volume_min; @@ -118,3 +123,5 @@ int snd_fwspk_create_pcm(struct fwspk *fwspk); int snd_fwspk_create_mixer(struct fwspk *fwspk);
void snd_fwspk_proc_init(struct fwspk *fwspk); + +int snd_fwspk_create_midi(struct fwspk *fwspk); diff --git a/sound/firewire/speakers/speakers_midi.c b/sound/firewire/speakers/speakers_midi.c new file mode 100644 index 0000000..83067a1 --- /dev/null +++ b/sound/firewire/speakers/speakers_midi.c @@ -0,0 +1,150 @@ +/* + * speakers_midi.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2013 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "speakers.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + struct fwspk *fwspk = substream->rmidi->private_data; + + return snd_fwspk_stream_start(fwspk, &fwspk->tx_stream, 0); +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct fwspk *fwspk = substream->rmidi->private_data; + + return snd_fwspk_stream_start(fwspk, &fwspk->rx_stream, 0); +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + struct fwspk *fwspk = substream->rmidi->private_data; + + if (amdtp_stream_running(&fwspk->rx_stream) && + !amdtp_stream_pcm_running(&fwspk->tx_stream)) + snd_fwspk_stream_stop(fwspk, &fwspk->tx_stream); + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct fwspk *fwspk = substream->rmidi->private_data; + + if (amdtp_stream_running(&fwspk->rx_stream) && + !amdtp_stream_pcm_running(&fwspk->rx_stream)) + snd_fwspk_stream_stop(fwspk, &fwspk->rx_stream); + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct fwspk *fwspk = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&fwspk->lock, flags); + + if (up) + amdtp_stream_midi_trigger(&fwspk->tx_stream, + substrm->number, substrm); + else + amdtp_stream_midi_trigger(&fwspk->tx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&fwspk->lock, flags); + + return; +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct fwspk *fwspk = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&fwspk->lock, flags); + + if (up) + amdtp_stream_midi_trigger(&fwspk->rx_stream, + substrm->number, substrm); + else + amdtp_stream_midi_trigger(&fwspk->rx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&fwspk->lock, flags); + + return; +} + +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 fwspk *fwspk, + 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", + fwspk->card->shortname, subs->number + 1); + } +} + +int snd_fwspk_create_midi(struct fwspk *fwspk) +{ + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + /* create midi ports */ + err = snd_rawmidi_new(fwspk->card, fwspk->card->driver, 0, + fwspk->midi_output_ports, fwspk->midi_input_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", fwspk->card->shortname); + rmidi->private_data = fwspk; + + if (fwspk->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(fwspk, str); + } + + if (fwspk->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(fwspk, str); + } + + if ((fwspk->midi_output_ports > 0) && (fwspk->midi_input_ports > 0)) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/speakers/speakers_stream.c b/sound/firewire/speakers/speakers_stream.c index b02b03c..2649f02 100644 --- a/sound/firewire/speakers/speakers_stream.c +++ b/sound/firewire/speakers/speakers_stream.c @@ -110,6 +110,7 @@ int snd_fwspk_stream_start(struct fwspk *fwspk, unsigned int rate) { unsigned int i, curr_rate, pcm_channels, midi_ports; + struct amdtp_stream *opposite = NULL; struct cmp_connection *conn; enum avc_general_plug_dir dir; bool used; @@ -148,7 +149,21 @@ int snd_fwspk_stream_start(struct fwspk *fwspk, err = -EIO; goto end; } + if (rate == 0) + rate = curr_rate; if (curr_rate != rate) { + /* in case that AMDTP streams includes just MIDI */ + if (amdtp_stream_running(&fwspk->tx_stream)) { + snd_fwspk_stream_stop(fwspk, &fwspk->tx_stream); + if (stream != &fwspk->tx_stream) + opposite = &fwspk->tx_stream; + } + if (amdtp_stream_running(&fwspk->rx_stream)) { + snd_fwspk_stream_stop(fwspk, &fwspk->rx_stream); + if (stream != &fwspk->rx_stream) + opposite = &fwspk->rx_stream; + } + err = avc_general_set_sig_fmt(fwspk->unit, rate, dir, 0); if (err < 0) goto end; @@ -158,6 +173,17 @@ int snd_fwspk_stream_start(struct fwspk *fwspk, err = -EIO; goto end; } + + /* + * NOTE: + * No matter for recursive call because this opposite stream + * will start at current sampling rate. + */ + if (opposite) { + err = snd_fwspk_stream_start(fwspk, opposite, 0); + if (err < 0) + goto end; + } }
/* set stream formation */ @@ -420,6 +446,7 @@ end: int snd_fwspk_stream_discover(struct fwspk *fwspk) { u8 plugs[AVC_PLUG_INFO_BUF_COUNT]; + unsigned int i; int err;
/* the number of plugs for isoc in/out, ext in/out */ @@ -440,6 +467,14 @@ int snd_fwspk_stream_discover(struct fwspk *fwspk) err = fill_stream_formations(fwspk, AVC_GENERAL_PLUG_DIR_IN, 0); if (err < 0) goto end; + + /* if its stream has MIDI conformant data channel, add one MIDI port */ + for (i = 0; i < FWSPK_STREAM_TABLE_ENTRIES; i++) { + if (fwspk->tx_stream_formations[i].midi > 0) + fwspk->midi_input_ports = 1; + else if (fwspk->rx_stream_formations[i].midi > 0) + fwspk->midi_output_ports = 1; + } end: return err; }
This interface is designed for mixer/control application. To use hwdep interface, the application can get information about firewire node, can lock/unlock kernel streaming and can get notification at starting/stopping kernel streaming.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- include/uapi/sound/asound.h | 3 +- include/uapi/sound/firewire.h | 3 +- sound/firewire/speakers/Makefile | 2 +- sound/firewire/speakers/speakers.c | 5 + sound/firewire/speakers/speakers.h | 13 ++ sound/firewire/speakers/speakers_hwdep.c | 195 ++++++++++++++++++++++++++++++ sound/firewire/speakers/speakers_stream.c | 39 ++++++ 7 files changed, 257 insertions(+), 3 deletions(-) create mode 100644 sound/firewire/speakers/speakers_hwdep.c
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 2249483..5d6fa42 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -96,9 +96,10 @@ enum { SNDRV_HWDEP_IFACE_FW_DICE, /* TC DICE FireWire device */ SNDRV_HWDEP_IFACE_FW_FIREWORKS, /* Echo Audio Fireworks based device */ SNDRV_HWDEP_IFACE_FW_BEBOB, /* BridgeCo BeBoB based device */ + SNDRV_HWDEP_IFACE_FW_FWSPK, /* Oxford FW970/971 based devices */
/* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_BEBOB + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_FWSPK };
struct snd_hwdep_info { diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index d69c0b6..147349e 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -54,7 +54,8 @@ union snd_firewire_event { #define SNDRV_FIREWIRE_TYPE_DICE 1 #define SNDRV_FIREWIRE_TYPE_FIREWORKS 2 #define SNDRV_FIREWIRE_TYPE_BEBOB 3 -/* AV/C, RME, MOTU, ... */ +#define SNDRV_FIREWIRE_TYPE_FWSPK 4 +/* RME, MOTU, ... */
struct snd_firewire_get_info { unsigned int type; /* SNDRV_FIREWIRE_TYPE_xxx */ diff --git a/sound/firewire/speakers/Makefile b/sound/firewire/speakers/Makefile index d98d4f7..b7b1e45 100644 --- a/sound/firewire/speakers/Makefile +++ b/sound/firewire/speakers/Makefile @@ -1,3 +1,3 @@ snd-firewire-speakers-objs := speakers_command.o speakers_stream.o speakers_pcm.o speakers_control.o \ - speakers_proc.o speakers_midi.o speakers.o + speakers_proc.o speakers_midi.o speakers_hwdep.o speakers.o obj-m += snd-firewire-speakers.o diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c index fd16e59..4eff180 100644 --- a/sound/firewire/speakers/speakers.c +++ b/sound/firewire/speakers/speakers.c @@ -116,6 +116,7 @@ static int fwspk_probe(struct fw_unit *unit, fwspk->device_info = (const struct device_info *)id->driver_data; mutex_init(&fwspk->mutex); spin_lock_init(&fwspk->lock); + init_waitqueue_head(&fwspk->hwdep_wait);
if (fwspk->device_info == &griffin_firewave) err = firewave_stream_discover(fwspk); @@ -152,6 +153,10 @@ static int fwspk_probe(struct fw_unit *unit, goto err_card; }
+ err = snd_fwspk_create_hwdep(fwspk); + if (err < 0) + goto err_card; + snd_card_set_dev(card, &unit->device); err = snd_card_register(card); if (err < 0) diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h index 2c54946..73eecb0 100644 --- a/sound/firewire/speakers/speakers.h +++ b/sound/firewire/speakers/speakers.h @@ -12,6 +12,7 @@ #include <linux/mod_devicetable.h> #include <linux/mutex.h> #include <linux/slab.h> +#include <linux/compat.h>
#include <sound/control.h> #include <sound/core.h> @@ -20,6 +21,8 @@ #include <sound/pcm_params.h> #include <sound/info.h> #include <sound/rawmidi.h> +#include <sound/firewire.h> +#include <sound/hwdep.h>
#include "../cmp.h" #include "../fcp.h" @@ -64,6 +67,10 @@ struct fwspk { s16 volume[6]; s16 volume_min; s16 volume_max; + + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; };
/* AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) */ @@ -118,6 +125,10 @@ int firewave_stream_discover(struct fwspk *fwspk); int lacie_speakers_stream_discover(struct fwspk *fwspk); int snd_fwspk_stream_discover(struct fwspk *fwspk);
+void snd_fwspk_stream_lock_changed(struct fwspk *fwspk); +int snd_fwspk_stream_lock_try(struct fwspk *fwspk); +void snd_fwspk_stream_lock_release(struct fwspk *fwspk); + int snd_fwspk_create_pcm(struct fwspk *fwspk);
int snd_fwspk_create_mixer(struct fwspk *fwspk); @@ -125,3 +136,5 @@ int snd_fwspk_create_mixer(struct fwspk *fwspk); void snd_fwspk_proc_init(struct fwspk *fwspk);
int snd_fwspk_create_midi(struct fwspk *fwspk); + +int snd_fwspk_create_hwdep(struct fwspk *fwspk); diff --git a/sound/firewire/speakers/speakers_hwdep.c b/sound/firewire/speakers/speakers_hwdep.c new file mode 100644 index 0000000..eae7e184 --- /dev/null +++ b/sound/firewire/speakers/speakers_hwdep.c @@ -0,0 +1,195 @@ +/* + * speakers_hwdep.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2013 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 "speakers.h" + +static long +hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct fwspk *fwspk = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&fwspk->lock); + + while (!fwspk->dev_lock_changed) { + prepare_to_wait(&fwspk->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&fwspk->lock); + schedule(); + finish_wait(&fwspk->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&fwspk->lock); + } + + memset(&event, 0, sizeof(event)); + if (fwspk->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (fwspk->dev_lock_count > 0); + fwspk->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } + + spin_unlock_irq(&fwspk->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 fwspk *fwspk = hwdep->private_data; + unsigned int events; + + poll_wait(file, &fwspk->hwdep_wait, wait); + + spin_lock_irq(&fwspk->lock); + if (fwspk->dev_lock_changed) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&fwspk->lock); + + return events; +} + +static int +hwdep_get_info(struct fwspk *fwspk, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(fwspk->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_FWSPK; + 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 fwspk *fwspk) +{ + int err; + + spin_lock_irq(&fwspk->lock); + + if (fwspk->dev_lock_count == 0) { + fwspk->dev_lock_count = -1; + err = 0; + } else + err = -EBUSY; + + spin_unlock_irq(&fwspk->lock); + + return err; +} + +static int +hwdep_unlock(struct fwspk *fwspk) +{ + int err; + + spin_lock_irq(&fwspk->lock); + + if (fwspk->dev_lock_count == -1) { + fwspk->dev_lock_count = 0; + err = 0; + } else + err = -EBADFD; + + spin_unlock_irq(&fwspk->lock); + + return err; +} + +static int +hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct fwspk *fwspk = hwdep->private_data; + + spin_lock_irq(&fwspk->lock); + if (fwspk->dev_lock_count == -1) + fwspk->dev_lock_count = 0; + spin_unlock_irq(&fwspk->lock); + + return 0; +} + +static int +hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct fwspk *fwspk = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(fwspk, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(fwspk); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(fwspk); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int +hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +int snd_fwspk_create_hwdep(struct fwspk *fwspk) +{ + static const struct snd_hwdep_ops hwdep_ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(fwspk->card, fwspk->card->driver, 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, fwspk->card->driver); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_FWSPK; + hwdep->ops = hwdep_ops; + hwdep->private_data = fwspk; + hwdep->exclusive = true; +end: + return err; +} diff --git a/sound/firewire/speakers/speakers_stream.c b/sound/firewire/speakers/speakers_stream.c index 2649f02..cc91160 100644 --- a/sound/firewire/speakers/speakers_stream.c +++ b/sound/firewire/speakers/speakers_stream.c @@ -493,3 +493,42 @@ int snd_fwspk_streams_init(struct fwspk *fwspk) end: return err; } + +void snd_fwspk_stream_lock_changed(struct fwspk *fwspk) +{ + fwspk->dev_lock_changed = true; + wake_up(&fwspk->hwdep_wait); +} + +int snd_fwspk_stream_lock_try(struct fwspk *fwspk) +{ + int err; + + spin_lock_irq(&fwspk->lock); + + /* user land lock this */ + if (fwspk->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (fwspk->dev_lock_count++ == 0) + snd_fwspk_stream_lock_changed(fwspk); + err = 0; +end: + spin_unlock_irq(&fwspk->lock); + return err; +} + +void snd_fwspk_stream_lock_release(struct fwspk *fwspk) +{ + spin_lock_irq(&fwspk->lock); + + if (WARN_ON(fwspk->dev_lock_count <= 0)) + goto end; + if (--fwspk->dev_lock_count == 0) + snd_fwspk_stream_lock_changed(fwspk); +end: + spin_unlock_irq(&fwspk->lock); +}
Takashi Sakamoto wrote:
In previous series of patch, I showed an idea to add a new driver for OXFW970/971. This driver can support some recording equipments. http://mailman.alsa-project.org/pipermail/alsa-devel/2014-January/070705.htm...
ALSA already has a driver, firewire-speakers for these chipsets. But it supports PCM playback only. The devices need support for capture/playback of PCM/MIDI.
According to maintainer's advice, I'll add these functionality to firewire-speakers.
That driver is called "firewire-speakers" because it is for playback- only devices.
But there is exactly one known user, so I'd guess it would be okay to rename it.
(And I really have to review all these patches some day ...)
Regards, Clemens
At Fri, 10 Jan 2014 16:45:10 +0100, Clemens Ladisch wrote:
Takashi Sakamoto wrote:
In previous series of patch, I showed an idea to add a new driver for OXFW970/971. This driver can support some recording equipments. http://mailman.alsa-project.org/pipermail/alsa-devel/2014-January/070705.htm...
ALSA already has a driver, firewire-speakers for these chipsets. But it supports PCM playback only. The devices need support for capture/playback of PCM/MIDI.
According to maintainer's advice, I'll add these functionality to firewire-speakers.
That driver is called "firewire-speakers" because it is for playback- only devices.
But there is exactly one known user, so I'd guess it would be okay to rename it.
Yeah, module names aren't that important. And you can put MODULE_ALIAS("snd-firewire-speakers"), if any.
(And I really have to review all these patches some day ...)
Yes, pretty please :)
Takashi
Hi Iwai-san,
(Jan 11 2014 00:57), Takashi Iwai wrote:
Yeah, module names aren't that important. And you can put MODULE_ALIAS("snd-firewire-speakers"), if any.
OK. I decide to rename the driver to 'snd-oxfw, and add the alias.
Thanks
Takashi Sakamoto o-takashi@sakamocchi.jp
On Jan 11 Takashi Sakamoto wrote:
(Jan 11 2014 00:57), Takashi Iwai wrote:
Yeah, module names aren't that important. And you can put MODULE_ALIAS("snd-firewire-speakers"), if any.
OK. I decide to rename the driver to 'snd-oxfw, and add the alias.
I agree that it should be renamed, and 'snd-oxfw' looks good to me. The module alias wouldn't even be strictly necessary for a driver like this (probably not present in initrd images etc.), but it doesn't hurt to include it.
The constants which patch 13/13 adds to include/uapi/sound/{asound,firewire}.h should of course be named accordingly then.
Hi Stefan,
The constants which patch 13/13 adds to include/uapi/sound/{asound,firewire}.h should of course be named accordingly then.
Yes. I forgot to rename it...
And I'm really sorry to maintainer but this patch (for hwdep) includes more bugs. I just added hwdep functionality but forgot to add codes to use it in PCM/MIDI functionality.
The codes are almost the same as the other drivers (snd-fireworks/snd-bebob) have. So in this time I hope you consider about the items which I mentioned in the first post. http://mailman.alsa-project.org/pipermail/alsa-devel/2014-January/070705.htm...
Thanks
Takashi Sakamoto o-takashi@sakamocchi.jp
Hi Clemens,
(Jan 11 2013 00:45), Clemens Ladisch wrote:
That driver is called "firewire-speakers" because it is for playback- only devices.
But there is exactly one known user, so I'd guess it would be okay to rename it.
I've found the characters in firmware binary blob in general update tools produced by Oxford Semiconductor. So I guessed this is the reason of the name for this driver.
If possible, I really want to rename it because it's too long to write ;) But I think renaming driver is a bit delicate issue.
For this issue, I need comments from the other maintainers. I guess I can do it if Iwai-san agrees.
Thanks
Takashi Sakamoto
participants (4)
-
Clemens Ladisch
-
Stefan Richter
-
Takashi Iwai
-
Takashi Sakamoto