[alsa-devel] [PATCH 0/7 v3] firewire-tascam: new driver for TASCAM FireWire series
Hi,
This patchset updates a part of my former post:
[alsa-devel] [PATCH 00/25 v2] ALSA: support AMDTP variants http://mailman.alsa-project.org/pipermail/alsa-devel/2015-August/096739.html
This patchset adds support for a part of TASCAM FireWire series. This patchset supports their streaming and PCM functionalities, while their MIDI functionalities are not supported yet. In future work, the functionalities will be supported.
Takashi Sakamoto (7): ALSA: firewire-tascam: add skeleton for TASCAM FireWire series ALSA: firewire-tascam: add a structure for model-dependent parameters. ALSA: firewire-tascam: add proc node to show firmware information ALSA: firewire-tascam: add data block processing layer ALSA: firewire-tascam: add streaming functionality ALSA: firewire-tascam: add PCM functionality ALSA: firewire-tascam: add hwdep interface
include/uapi/sound/asound.h | 3 +- include/uapi/sound/firewire.h | 1 + sound/firewire/Kconfig | 12 + sound/firewire/Makefile | 1 + sound/firewire/tascam/Makefile | 3 + sound/firewire/tascam/amdtp-tascam.c | 243 +++++++++++++++++ sound/firewire/tascam/tascam-hwdep.c | 201 ++++++++++++++ sound/firewire/tascam/tascam-pcm.c | 312 +++++++++++++++++++++ sound/firewire/tascam/tascam-proc.c | 88 ++++++ sound/firewire/tascam/tascam-stream.c | 496 ++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.c | 213 +++++++++++++++ sound/firewire/tascam/tascam.h | 117 ++++++++ 12 files changed, 1689 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/tascam/Makefile create mode 100644 sound/firewire/tascam/amdtp-tascam.c create mode 100644 sound/firewire/tascam/tascam-hwdep.c create mode 100644 sound/firewire/tascam/tascam-pcm.c create mode 100644 sound/firewire/tascam/tascam-proc.c create mode 100644 sound/firewire/tascam/tascam-stream.c create mode 100644 sound/firewire/tascam/tascam.c create mode 100644 sound/firewire/tascam/tascam.h
This commit adds a new driver for TASCAM FireWire series. In this commit, this driver just creates/removes card instance according to bus event. More functionalities will be added in following commits.
TASCAM FireWire series consists of: * PDI 1394P23 for IEEE 1394 PHY layer * PDI 1394L40 for IEEE 1394 LINK layer and IEC 61883 interface * XILINX XC9536XL * XILINX Spartan-II XC2S100 * ATMEL AT91M42800A
Ilya Zimnovich had investigated TASCAM FireWire series in 2011, and discover some features of his FW-1804. You can see a part of his research in FFADO project. http://subversion.ffado.org/wiki/Tascam
A part of my work are based on Ilya's investigation, while this series doesn't support the FW-1804, because of a lack of config ROM information and its protocol detail, especially for PCM channels.
I observed that FW-1884 and FW-1082 don't work properly with 1394 OHCI controller based on VT6315. The controller can actually communicate packets to these models, while these models generate no sounds. It may be due to the PHY/LINK layer issues. Using 1394 OHCI controller produced by the other vendors such as Texas Instruments may work. Or adding another node on the bus.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 11 +++ sound/firewire/Makefile | 1 + sound/firewire/tascam/Makefile | 2 + sound/firewire/tascam/tascam.c | 155 +++++++++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.h | 33 +++++++++ 5 files changed, 202 insertions(+) create mode 100644 sound/firewire/tascam/Makefile create mode 100644 sound/firewire/tascam/tascam.c create mode 100644 sound/firewire/tascam/tascam.h
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index e61445e..bb3f261 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -135,4 +135,15 @@ config SND_FIREWIRE_DIGI00X To compile this driver as a module, choose M here: the module will be called snd-firewire-digi00x.
+config SND_FIREWIRE_TASCAM + tristate "TASCAM FireWire series support" + select SND_FIREWIRE_LIB + help + Say Y here to include support for TASCAM. + * FW-1884 + * FW-1082 + + To compile this driver as a module, choose M here: the module + will be called snd-firewire-tascam. + endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index 5325d15..6ae50f5 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o obj-$(CONFIG_SND_FIREWORKS) += fireworks/ obj-$(CONFIG_SND_BEBOB) += bebob/ obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += digi00x/ +obj-$(CONFIG_SND_FIREWIRE_TASCAM) += tascam/ diff --git a/sound/firewire/tascam/Makefile b/sound/firewire/tascam/Makefile new file mode 100644 index 0000000..627129b --- /dev/null +++ b/sound/firewire/tascam/Makefile @@ -0,0 +1,2 @@ +snd-firewire-tascam-objs := tascam.o +obj-$(CONFIG_SND_FIREWIRE_TASCAM) += snd-firewire-tascam.o diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c new file mode 100644 index 0000000..9f2d2a3 --- /dev/null +++ b/sound/firewire/tascam/tascam.c @@ -0,0 +1,155 @@ +/* + * tascam.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "tascam.h" + +MODULE_DESCRIPTION("TASCAM FireWire series Driver"); +MODULE_AUTHOR("Takashi Sakamoto o-takashi@sakamocchi.jp"); +MODULE_LICENSE("GPL v2"); + +static int check_name(struct snd_tscm *tscm) +{ + struct fw_device *fw_dev = fw_parent_device(tscm->unit); + char vendor[8]; + char model[8]; + __u32 data; + + /* Retrieve model name. */ + data = be32_to_cpu(fw_dev->config_rom[28]); + memcpy(model, &data, 4); + data = be32_to_cpu(fw_dev->config_rom[29]); + memcpy(model + 4, &data, 4); + model[7] = '\0'; + + /* Retrieve vendor name. */ + data = be32_to_cpu(fw_dev->config_rom[23]); + memcpy(vendor, &data, 4); + data = be32_to_cpu(fw_dev->config_rom[24]); + memcpy(vendor + 4, &data, 4); + vendor[7] = '\0'; + + strcpy(tscm->card->driver, "FW-TASCAM"); + strcpy(tscm->card->shortname, model); + strcpy(tscm->card->mixername, model); + snprintf(tscm->card->longname, sizeof(tscm->card->longname), + "%s %s, GUID %08x%08x at %s, S%d", vendor, model, + cpu_to_be32(fw_dev->config_rom[3]), + cpu_to_be32(fw_dev->config_rom[4]), + dev_name(&tscm->unit->device), 100 << fw_dev->max_speed); + + return 0; +} + +static void tscm_card_free(struct snd_card *card) +{ + struct snd_tscm *tscm = card->private_data; + + fw_unit_put(tscm->unit); + + mutex_destroy(&tscm->mutex); +} + +static int snd_tscm_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_card *card; + struct snd_tscm *tscm; + int err; + + /* create card */ + err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, + sizeof(struct snd_tscm), &card); + if (err < 0) + return err; + card->private_free = tscm_card_free; + + /* initialize myself */ + tscm = card->private_data; + tscm->card = card; + tscm->unit = fw_unit_get(unit); + + mutex_init(&tscm->mutex); + + err = check_name(tscm); + if (err < 0) + goto error; + + err = snd_card_register(card); + if (err < 0) + goto error; + + dev_set_drvdata(&unit->device, tscm); + + return err; +error: + snd_card_free(card); + return err; +} + +static void snd_tscm_update(struct fw_unit *unit) +{ + return; +} + +static void snd_tscm_remove(struct fw_unit *unit) +{ + struct snd_tscm *tscm = dev_get_drvdata(&unit->device); + + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(tscm->card); +} + +static const struct ieee1394_device_id snd_tscm_id_table[] = { + /* FW-1082 */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = 0x00022e, + .specifier_id = 0x00022e, + .version = 0x800003, + }, + /* FW-1884 */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = 0x00022e, + .specifier_id = 0x00022e, + .version = 0x800000, + }, + /* FW-1804 mey be supported if IDs are clear. */ + /* FE-08 requires reverse-engineering because it just has faders. */ + {} +}; +MODULE_DEVICE_TABLE(ieee1394, snd_tscm_id_table); + +static struct fw_driver tscm_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "snd-firewire-tascam", + .bus = &fw_bus_type, + }, + .probe = snd_tscm_probe, + .update = snd_tscm_update, + .remove = snd_tscm_remove, + .id_table = snd_tscm_id_table, +}; + +static int __init snd_tscm_init(void) +{ + return driver_register(&tscm_driver.driver); +} + +static void __exit snd_tscm_exit(void) +{ + driver_unregister(&tscm_driver.driver); +} + +module_init(snd_tscm_init); +module_exit(snd_tscm_exit); diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h new file mode 100644 index 0000000..d2f4f67 --- /dev/null +++ b/sound/firewire/tascam/tascam.h @@ -0,0 +1,33 @@ +/* + * tascam.h - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef SOUND_TASCAM_H_INCLUDED +#define SOUND_TASCAM_H_INCLUDED + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/compat.h> + +#include <sound/core.h> +#include <sound/initval.h> + +#include "../lib.h" + +struct snd_tscm { + struct snd_card *card; + struct fw_unit *unit; + + struct mutex mutex; +}; + +#endif
Hi Takashi,
[auto build test results on next-20151001 -- if it's inappropriate base, please ignore]
reproduce: # apt-get install sparse make ARCH=x86_64 allmodconfig make C=1 CF=-D__CHECK_ENDIAN__
sparse warnings: (new ones prefixed by >>)
sound/firewire/tascam/tascam.c:23:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:23:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:23:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:23:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:23:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:23:16: sparse: cast to restricted __be32
sound/firewire/tascam/tascam.c:25:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:25:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:25:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:25:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:25:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:25:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:30:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:30:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:30:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:30:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:30:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:30:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:32:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:32:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:32:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:32:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:32:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:32:16: sparse: cast to restricted __be32
vim +23 sound/firewire/tascam/tascam.c
7 */ 8 9 #include "tascam.h" 10 11 MODULE_DESCRIPTION("TASCAM FireWire series Driver"); 12 MODULE_AUTHOR("Takashi Sakamoto o-takashi@sakamocchi.jp"); 13 MODULE_LICENSE("GPL v2"); 14 15 static int check_name(struct snd_tscm *tscm) 16 { 17 struct fw_device *fw_dev = fw_parent_device(tscm->unit); 18 char vendor[8]; 19 char model[8]; 20 __u32 data; 21 22 /* Retrieve model name. */
23 data = be32_to_cpu(fw_dev->config_rom[28]);
24 memcpy(model, &data, 4); 25 data = be32_to_cpu(fw_dev->config_rom[29]); 26 memcpy(model + 4, &data, 4); 27 model[7] = '\0'; 28 29 /* Retrieve vendor name. */ 30 data = be32_to_cpu(fw_dev->config_rom[23]); 31 memcpy(vendor, &data, 4);
--- 0-DAY kernel test infrastructure Open Source Technology Center https://lists.01.org/pipermail/kbuild-all Intel Corporation
On Thu, 01 Oct 2015 15:54:54 +0200, kbuild test robot wrote:
Hi Takashi,
[auto build test results on next-20151001 -- if it's inappropriate base, please ignore]
reproduce: # apt-get install sparse make ARCH=x86_64 allmodconfig make C=1 CF=-D__CHECK_ENDIAN__
sparse warnings: (new ones prefixed by >>)
sound/firewire/tascam/tascam.c:23:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:23:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:23:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:23:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:23:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:23:16: sparse: cast to restricted __be32
sound/firewire/tascam/tascam.c:25:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:25:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:25:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:25:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:25:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:25:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:30:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:30:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:30:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:30:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:30:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:30:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:32:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:32:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:32:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:32:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:32:16: sparse: cast to restricted __be32 sound/firewire/tascam/tascam.c:32:16: sparse: cast to restricted __be32
vim +23 sound/firewire/tascam/tascam.c
7 */ 8 9 #include "tascam.h" 10 11 MODULE_DESCRIPTION("TASCAM FireWire series Driver"); 12 MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>"); 13 MODULE_LICENSE("GPL v2"); 14 15 static int check_name(struct snd_tscm *tscm) 16 { 17 struct fw_device *fw_dev = fw_parent_device(tscm->unit); 18 char vendor[8]; 19 char model[8]; 20 __u32 data; 21 22 /* Retrieve model name. */
23 data = be32_to_cpu(fw_dev->config_rom[28]);
The code itself looks correct. data is CPU endian. The problem looks rather like that fw_dev->config_rom[] is u32.
Stefan, can it be changed to __be32 instead?
thanks,
Takashi
Takashi Iwai wrote:
kbuild test robot wrote:
sound/firewire/tascam/tascam.c:23:16: sparse: cast to restricted __be32
23 data = be32_to_cpu(fw_dev->config_rom[28]);
The code itself looks correct. data is CPU endian. The problem looks rather like that fw_dev->config_rom[] is u32.
Stefan, can it be changed to __be32 instead?
config_rom[] is CPU endian, too.
Strings should be read with fw_csr_string().
Regards, Clemens
Hi,
On Oct 13 2015 03:47, Clemens Ladisch wrote:
Takashi Iwai wrote:
kbuild test robot wrote:
sound/firewire/tascam/tascam.c:23:16: sparse: cast to restricted __be32
23 data = be32_to_cpu(fw_dev->config_rom[28]);
The code itself looks correct. data is CPU endian. The problem looks rather like that fw_dev->config_rom[] is u32.
Stefan, can it be changed to __be32 instead?
config_rom[] is CPU endian, too.
Strings should be read with fw_csr_string().
The function is designed for IEEE 1212 compliant config ROM. On the other hand, config ROMs of these models are not fully compliant. See:
http://sourceforge.net/p/linux1394/mailman/message/33899800/
Currently, this driver supports just two models. These two models have similar structure in their config ROM, fortunately. Thus, it's reasonable for the driver to get information in hard-coded position of config ROM.
About using 'be32_to_cpu()', the 'config_rom' member actually has 'const u32 *', while in the member caracters in the textual leaf are aligned in big-endian. I selected the simplest way to pick it up.
If it's preferrable to suppress the sparse warnings, I don't mind to replace these codes with the other ways.
Regards
Takashi Sakamoto
On Sat, 03 Oct 2015 04:34:24 +0200, Takashi Sakamoto wrote:
Hi,
On Oct 13 2015 03:47, Clemens Ladisch wrote:
Takashi Iwai wrote:
kbuild test robot wrote:
sound/firewire/tascam/tascam.c:23:16: sparse: cast to restricted __be32
23 data = be32_to_cpu(fw_dev->config_rom[28]);
The code itself looks correct. data is CPU endian. The problem looks rather like that fw_dev->config_rom[] is u32.
Stefan, can it be changed to __be32 instead?
config_rom[] is CPU endian, too.
Strings should be read with fw_csr_string().
The function is designed for IEEE 1212 compliant config ROM. On the other hand, config ROMs of these models are not fully compliant. See:
http://sourceforge.net/p/linux1394/mailman/message/33899800/
Currently, this driver supports just two models. These two models have similar structure in their config ROM, fortunately. Thus, it's reasonable for the driver to get information in hard-coded position of config ROM.
About using 'be32_to_cpu()', the 'config_rom' member actually has 'const u32 *', while in the member caracters in the textual leaf are aligned in big-endian. I selected the simplest way to pick it up.
If it's preferrable to suppress the sparse warnings, I don't mind to replace these codes with the other ways.
Maybe introducing a macro would make things clearer. It's better than open-coding at each place, in anyway.
Takashi
On Oct 03 Takashi Iwai wrote:
On Sat, 03 Oct 2015 04:34:24 +0200, Takashi Sakamoto wrote:
Hi,
On Oct 13 2015 03:47, Clemens Ladisch wrote:
Takashi Iwai wrote:
kbuild test robot wrote:
> sound/firewire/tascam/tascam.c:23:16: sparse: cast to restricted __be32
23 data = be32_to_cpu(fw_dev->config_rom[28]);
The code itself looks correct. data is CPU endian. The problem looks rather like that fw_dev->config_rom[] is u32.
Stefan, can it be changed to __be32 instead?
config_rom[] is CPU endian, too.
Strings should be read with fw_csr_string().
The function is designed for IEEE 1212 compliant config ROM. On the other hand, config ROMs of these models are not fully compliant. See:
http://sourceforge.net/p/linux1394/mailman/message/33899800/
Currently, this driver supports just two models. These two models have similar structure in their config ROM, fortunately. Thus, it's reasonable for the driver to get information in hard-coded position of config ROM.
You mention in the changelog that you support the models FW-1884 and FW-1802, but that there is a third model which you cannot support yet due to lack of information. --- Considering this limited number of models, and if you are confident that there are no firmware revisions with different config ROM offsets, then I agree that using hard-coded offsets is a good way to retrieve the model-dependent information.
About using 'be32_to_cpu()', the 'config_rom' member actually has 'const u32 *', while in the member caracters in the textual leaf are aligned in big-endian. I selected the simplest way to pick it up.
If it's preferrable to suppress the sparse warnings, I don't mind to replace these codes with the other ways.
Maybe introducing a macro would make things clearer. It's better than open-coding at each place, in anyway.
I think it can all be made a lot simpler. Quoting the patch 1/7:
+static const struct ieee1394_device_id snd_tscm_id_table[] = { + /* FW-1082 */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = 0x00022e, + .specifier_id = 0x00022e, + .version = 0x800003, + }, + /* FW-1884 */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = 0x00022e, + .specifier_id = 0x00022e, + .version = 0x800000, + }, + /* FW-1804 mey be supported if IDs are clear. */ + /* FE-08 requires reverse-engineering because it just has faders. */ + {} +};
Since the driver is only matching devices with vendor ID 0x00022e, there is IMO no need to verify the vendor name string in the Configuration ROM.
Do I understand correctly that the model name string needs to be checked because we don't know yet whether the model FW-1804 contains an own "version" or duplicates the "version" of FW-1802 or FW-1884?
In other words, if we knew that FW-1804 had a unique "version", then the function check_name() would not be needed at all.
+static int check_name(struct snd_tscm *tscm) +{ + struct fw_device *fw_dev = fw_parent_device(tscm->unit); + char vendor[8]; + char model[8]; + __u32 data; + + /* Retrieve model name. */ + data = be32_to_cpu(fw_dev->config_rom[28]); + memcpy(model, &data, 4); + data = be32_to_cpu(fw_dev->config_rom[29]); + memcpy(model + 4, &data, 4); + model[7] = '\0';
The config_rom accesses need to be protected by an array bounds check.
The endian conversion actually needs to be the other way around:
__be32 data;
/* * Retrieve model name from a bus_dependent_info leaf at * the fixed ROM offsets [25...29], which is formed like a * textual descriptor leaf in minimal ASCII format. */ data = cpu_to_be32(fw_dev->config_rom[28]); memcpy(model, &data, 4); data = cpu_to_be32(fw_dev->config_rom[29]); memcpy(model + 4, &data, 4); model[7] = '\0';
A mathematically equivalent way to code this can be seen in drivers/firewire/core-device.c::textual_leaf_to_string(): This works for arbitrary ROM offsets and with arbitrary string lengths.
+ /* Retrieve vendor name. */ + data = be32_to_cpu(fw_dev->config_rom[23]); + memcpy(vendor, &data, 4); + data = be32_to_cpu(fw_dev->config_rom[24]); + memcpy(vendor + 4, &data, 4); + vendor[7] = '\0';
As I said, I think this is not needed since the driver does not deal with devices from other vendors in the first place.
+ strcpy(tscm->card->driver, "FW-TASCAM"); + strcpy(tscm->card->shortname, model); + strcpy(tscm->card->mixername, model); + snprintf(tscm->card->longname, sizeof(tscm->card->longname), + "%s %s, GUID %08x%08x at %s, S%d", vendor, model, + cpu_to_be32(fw_dev->config_rom[3]), + cpu_to_be32(fw_dev->config_rom[4]), + dev_name(&tscm->unit->device), 100 << fw_dev->max_speed);
The GUID needs to be printed without endian conversion:
"GUID %08x%08x", fw_dev->config_rom[3], fw_dev->config_rom[4]
+ return 0; +}
The function always returns 0 == no error, therefore it currently fulfills no other purpose than to fill in card->{short,mixer,long}name. It definitely does not actually check anything like the function name suggests. How about something like this:
/* * Retrieve model name from a bus_dependent_info leaf at the fixed ROM * offsets [25...29], which is formed like a textual descriptor leaf in * minimal ASCII format. */ static int check_model_name(struct snd_tscm *tscm) { struct fw_device *fw_dev = fw_parent_device(tscm->unit); u32 rom28, rom29; const char *model;
if (fw_dev->config_rom_length < 30) { dev_err(tscm->unit->device, "unknown Configuration ROM, too short\n"); return -ENODEV; }
rom28 = fw_dev->config_rom[28]; rom29 = fw_dev->config_rom[29];
if (rom28 == 0x46572d31 && rom29 == 0x30383200) { model = "FW-1082"; } else if (rom28 == 0x46572d31 && rom29 == 0x38383400) { model = "FW-1884"; } else { dev_err(tscm->unit->device, "unknown model %04x %04x\n", rom28, rom29); return -ENODEV; }
strcpy(tscm->card->driver, "FW-TASCAM"); strcpy(tscm->card->shortname, model); strcpy(tscm->card->mixername, model); snprintf(tscm->card->longname, sizeof(tscm->card->longname), "TASCAM %s, GUID %08x%08x at %s, S%d", model, fw_dev->config_rom[3], fw_dev->config_rom[4], dev_name(&tscm->unit->device), 100 << fw_dev->max_speed);
return 0; }
Not compile-tested.
Takashi Sakamoto wrote:
23 data = be32_to_cpu(fw_dev->config_rom[28]);
About using 'be32_to_cpu()', the 'config_rom' member actually has 'const u32 *', while in the member caracters in the textual leaf are aligned in big-endian.
Strictly speaking, strings do not have an endianness.
Most data in the actual configuration ROM is organized as big-endian 32- bit values, so the FireWire core converts _all_ words from big endian to CPU endian. The words that are not actually 32-bit values but strings have to be converted back; the correct function for this is cpu_to_be32().
Regards, Clemens
On 2015年10月03日 18:14, Clemens Ladisch wrote:
Takashi Sakamoto wrote:
23 data = be32_to_cpu(fw_dev->config_rom[28]);
About using 'be32_to_cpu()', the 'config_rom' member actually has 'const u32 *', while in the member caracters in the textual leaf are aligned in big-endian.
Strictly speaking, strings do not have an endianness.
Most data in the actual configuration ROM is organized as big-endian 32- bit values, so the FireWire core converts _all_ words from big endian to CPU endian. The words that are not actually 32-bit values but strings have to be converted back; the correct function for this is cpu_to_be32().
Yes. And I forgot a pair of cpu_to_be32/be32_to_cpu is symmetric each other! (I'm really a stupid guy, sigh...)
This patch solves the sparce warnings.
$ git diff diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c index ee2f498..6e1087e 100644 --- a/sound/firewire/tascam/tascam.c +++ b/sound/firewire/tascam/tascam.c @@ -50,19 +50,19 @@ static int check_name(struct snd_tscm *tscm) struct fw_device *fw_dev = fw_parent_device(tscm->unit); char vendor[8]; char model[8]; - __u32 data; + __be32 data;
/* Retrieve model name. */ - data = be32_to_cpu(fw_dev->config_rom[28]); + data = cpu_to_be32(fw_dev->config_rom[28]); memcpy(model, &data, 4); - data = be32_to_cpu(fw_dev->config_rom[29]); + data = cpu_to_be32(fw_dev->config_rom[29]); memcpy(model + 4, &data, 4); model[7] = '\0';
/* Retrieve vendor name. */ - data = be32_to_cpu(fw_dev->config_rom[23]); + data = cpu_to_be32(fw_dev->config_rom[23]); memcpy(vendor, &data, 4); - data = be32_to_cpu(fw_dev->config_rom[24]); + data = cpu_to_be32(fw_dev->config_rom[24]); memcpy(vendor + 4, &data, 4); vendor[7] = '\0';
Iwai-san,
Should I re-post this patchset? Or you kindly merge them with your modification?
Regards
Takashi Sakamoto
On Oct 03 Takashi Sakamoto wrote:
On 2015年10月03日 18:14, Clemens Ladisch wrote:
Most data in the actual configuration ROM is organized as big-endian 32- bit values, so the FireWire core converts _all_ words from big endian to CPU endian. The words that are not actually 32-bit values but strings have to be converted back; the correct function for this is cpu_to_be32().
Yes. And I forgot a pair of cpu_to_be32/be32_to_cpu is symmetric each other! (I'm really a stupid guy, sigh...)
Nah, it's easy to miss that struct fw_device.config_rom ist not identical with the on-the-wire format (on little endian hosts, that is). This has irritated others before. I think this CPU-endian ROM buffer caters to simpler ROM parsing code in the overall picture. Text leaf parsing seems to be the only exception where a big-endian buffer would be preferable, but before your TASCAM driver all of the text leaf parsing was concentrated in core-device.c::textual_leaf_to_string().
Hi Stefan,
On Oct 03 2015 19:54, Stefan Richter wrote:
On Oct 03 Takashi Sakamoto wrote:
On 2015年10月03日 18:14, Clemens Ladisch wrote:
Most data in the actual configuration ROM is organized as big-endian 32- bit values, so the FireWire core converts _all_ words from big endian to CPU endian. The words that are not actually 32-bit values but strings have to be converted back; the correct function for this is cpu_to_be32().
Yes. And I forgot a pair of cpu_to_be32/be32_to_cpu is symmetric each other! (I'm really a stupid guy, sigh...)
Nah, it's easy to miss that struct fw_device.config_rom ist not identical with the on-the-wire format (on little endian hosts, that is). This has irritated others before. I think this CPU-endian ROM buffer caters to simpler ROM parsing code in the overall picture. Text leaf parsing seems to be the only exception where a big-endian buffer would be preferable, but before your TASCAM driver all of the text leaf parsing was concentrated in core-device.c::textual_leaf_to_string().
Yep. I didn't consider about the pair of macros are preprocessed according to CPU-endianness. Anyway this driver require enough implementation to parse the string.
Thanks
Takashi Sakamoto
On Sat, 03 Oct 2015 11:57:34 +0200, Takashi Sakamoto wrote:
On 2015年10月03日 18:14, Clemens Ladisch wrote:
Takashi Sakamoto wrote:
23 data = be32_to_cpu(fw_dev->config_rom[28]);
About using 'be32_to_cpu()', the 'config_rom' member actually has 'const u32 *', while in the member caracters in the textual leaf are aligned in big-endian.
Strictly speaking, strings do not have an endianness.
Most data in the actual configuration ROM is organized as big-endian 32- bit values, so the FireWire core converts _all_ words from big endian to CPU endian. The words that are not actually 32-bit values but strings have to be converted back; the correct function for this is cpu_to_be32().
Yes. And I forgot a pair of cpu_to_be32/be32_to_cpu is symmetric each other! (I'm really a stupid guy, sigh...)
This patch solves the sparce warnings.
$ git diff diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c index ee2f498..6e1087e 100644 --- a/sound/firewire/tascam/tascam.c +++ b/sound/firewire/tascam/tascam.c @@ -50,19 +50,19 @@ static int check_name(struct snd_tscm *tscm) struct fw_device *fw_dev = fw_parent_device(tscm->unit); char vendor[8]; char model[8];
__u32 data;
__be32 data; /* Retrieve model name. */
data = be32_to_cpu(fw_dev->config_rom[28]);
data = cpu_to_be32(fw_dev->config_rom[28]); memcpy(model, &data, 4);
data = be32_to_cpu(fw_dev->config_rom[29]);
data = cpu_to_be32(fw_dev->config_rom[29]); memcpy(model + 4, &data, 4); model[7] = '\0'; /* Retrieve vendor name. */
data = be32_to_cpu(fw_dev->config_rom[23]);
data = cpu_to_be32(fw_dev->config_rom[23]); memcpy(vendor, &data, 4);
data = be32_to_cpu(fw_dev->config_rom[24]);
data = cpu_to_be32(fw_dev->config_rom[24]); memcpy(vendor + 4, &data, 4); vendor[7] = '\0';
Iwai-san,
Should I re-post this patchset? Or you kindly merge them with your modification?
I already merged the latest patchset, so incremental patches are preferred.
thanks,
Takashi
Hi,
On Oct 04 2015 01:07, Takashi Iwai wrote:
On Sat, 03 Oct 2015 11:57:34 +0200, Takashi Sakamoto wrote:
On 2015年10月03日 18:14, Clemens Ladisch wrote:
Takashi Sakamoto wrote:
> 23 data = be32_to_cpu(fw_dev->config_rom[28]);
About using 'be32_to_cpu()', the 'config_rom' member actually has 'const u32 *', while in the member caracters in the textual leaf are aligned in big-endian.
Strictly speaking, strings do not have an endianness.
Most data in the actual configuration ROM is organized as big-endian 32- bit values, so the FireWire core converts _all_ words from big endian to CPU endian. The words that are not actually 32-bit values but strings have to be converted back; the correct function for this is cpu_to_be32().
Yes. And I forgot a pair of cpu_to_be32/be32_to_cpu is symmetric each other! (I'm really a stupid guy, sigh...)
This patch solves the sparce warnings.
$ git diff diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c index ee2f498..6e1087e 100644 --- a/sound/firewire/tascam/tascam.c +++ b/sound/firewire/tascam/tascam.c @@ -50,19 +50,19 @@ static int check_name(struct snd_tscm *tscm) struct fw_device *fw_dev = fw_parent_device(tscm->unit); char vendor[8]; char model[8];
__u32 data;
__be32 data; /* Retrieve model name. */
data = be32_to_cpu(fw_dev->config_rom[28]);
data = cpu_to_be32(fw_dev->config_rom[28]); memcpy(model, &data, 4);
data = be32_to_cpu(fw_dev->config_rom[29]);
data = cpu_to_be32(fw_dev->config_rom[29]); memcpy(model + 4, &data, 4); model[7] = '\0'; /* Retrieve vendor name. */
data = be32_to_cpu(fw_dev->config_rom[23]);
data = cpu_to_be32(fw_dev->config_rom[23]); memcpy(vendor, &data, 4);
data = be32_to_cpu(fw_dev->config_rom[24]);
data = cpu_to_be32(fw_dev->config_rom[24]); memcpy(vendor + 4, &data, 4); vendor[7] = '\0';
Iwai-san,
Should I re-post this patchset? Or you kindly merge them with your modification?
I already merged the latest patchset, so incremental patches are preferred.
OK. Thanks.
Takashi Sakamoto
TASCAM FireWire series doesn't tell drivers their capabilities, thus the drivers should have model-dependent parameters and apply it to detected devices.
This commit adds a structure to represent such parameters.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/tascam/tascam.c | 36 ++++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.h | 13 +++++++++++++ 2 files changed, 49 insertions(+)
diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c index 9f2d2a3..9ac09cb 100644 --- a/sound/firewire/tascam/tascam.c +++ b/sound/firewire/tascam/tascam.c @@ -12,6 +12,39 @@ MODULE_DESCRIPTION("TASCAM FireWire series Driver"); MODULE_AUTHOR("Takashi Sakamoto o-takashi@sakamocchi.jp"); MODULE_LICENSE("GPL v2");
+static struct snd_tscm_spec model_specs[] = { + { + .name = "FW-1884", + .has_adat = true, + .has_spdif = true, + .pcm_capture_analog_channels = 8, + .pcm_playback_analog_channels = 8, + .midi_capture_ports = 4, + .midi_playback_ports = 4, + .is_controller = true, + }, + { + .name = "FW-1804", + .has_adat = true, + .has_spdif = true, + .pcm_capture_analog_channels = 8, + .pcm_playback_analog_channels = 2, + .midi_capture_ports = 2, + .midi_playback_ports = 4, + .is_controller = false, + }, + { + .name = "FW-1082", + .has_adat = false, + .has_spdif = true, + .pcm_capture_analog_channels = 8, + .pcm_playback_analog_channels = 2, + .midi_capture_ports = 2, + .midi_playback_ports = 2, + .is_controller = true, + }, +}; + static int check_name(struct snd_tscm *tscm) { struct fw_device *fw_dev = fw_parent_device(tscm->unit); @@ -72,6 +105,7 @@ static int snd_tscm_probe(struct fw_unit *unit, tscm = card->private_data; tscm->card = card; tscm->unit = fw_unit_get(unit); + tscm->spec = (const struct snd_tscm_spec *)entry->driver_data;
mutex_init(&tscm->mutex);
@@ -113,6 +147,7 @@ static const struct ieee1394_device_id snd_tscm_id_table[] = { .vendor_id = 0x00022e, .specifier_id = 0x00022e, .version = 0x800003, + .driver_data = (kernel_ulong_t)&model_specs[2], }, /* FW-1884 */ { @@ -122,6 +157,7 @@ static const struct ieee1394_device_id snd_tscm_id_table[] = { .vendor_id = 0x00022e, .specifier_id = 0x00022e, .version = 0x800000, + .driver_data = (kernel_ulong_t)&model_specs[0], }, /* FW-1804 mey be supported if IDs are clear. */ /* FE-08 requires reverse-engineering because it just has faders. */ diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index d2f4f67..e12f8b5 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -23,11 +23,24 @@
#include "../lib.h"
+struct snd_tscm_spec { + const char *const name; + bool has_adat; + bool has_spdif; + unsigned int pcm_capture_analog_channels; + unsigned int pcm_playback_analog_channels; + unsigned int midi_capture_ports; + unsigned int midi_playback_ports; + bool is_controller; +}; + struct snd_tscm { struct snd_card *card; struct fw_unit *unit;
struct mutex mutex; + + const struct snd_tscm_spec *spec; };
#endif
TASCAM FireWire series has certain registers for firmware information.
This commit adds proc node to show the information.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/tascam/Makefile | 2 +- sound/firewire/tascam/tascam-proc.c | 88 +++++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.c | 2 + sound/firewire/tascam/tascam.h | 10 +++++ 4 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/tascam/tascam-proc.c
diff --git a/sound/firewire/tascam/Makefile b/sound/firewire/tascam/Makefile index 627129b..1555206 100644 --- a/sound/firewire/tascam/Makefile +++ b/sound/firewire/tascam/Makefile @@ -1,2 +1,2 @@ -snd-firewire-tascam-objs := tascam.o +snd-firewire-tascam-objs := tascam-proc.o tascam.o obj-$(CONFIG_SND_FIREWIRE_TASCAM) += snd-firewire-tascam.o diff --git a/sound/firewire/tascam/tascam-proc.c b/sound/firewire/tascam/tascam-proc.c new file mode 100644 index 0000000..bfd4a4c --- /dev/null +++ b/sound/firewire/tascam/tascam-proc.c @@ -0,0 +1,88 @@ +/* + * tascam-proc.h - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./tascam.h" + +static void proc_read_firmware(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_tscm *tscm = entry->private_data; + __be32 data; + unsigned int reg, fpga, arm, hw; + int err; + + err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_FIRMWARE_REGISTER, + &data, sizeof(data), 0); + if (err < 0) + return; + reg = be32_to_cpu(data); + + err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_FIRMWARE_FPGA, + &data, sizeof(data), 0); + if (err < 0) + return; + fpga = be32_to_cpu(data); + + err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_FIRMWARE_ARM, + &data, sizeof(data), 0); + if (err < 0) + return; + arm = be32_to_cpu(data); + + err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_FIRMWARE_HW, + &data, sizeof(data), 0); + if (err < 0) + return; + hw = be32_to_cpu(data); + + snd_iprintf(buffer, "Register: %d (0x%08x)\n", reg & 0xffff, reg); + snd_iprintf(buffer, "FPGA: %d (0x%08x)\n", fpga & 0xffff, fpga); + snd_iprintf(buffer, "ARM: %d (0x%08x)\n", arm & 0xffff, arm); + snd_iprintf(buffer, "Hardware: %d (0x%08x)\n", hw >> 16, hw); +} + +static void add_node(struct snd_tscm *tscm, struct snd_info_entry *root, + const char *name, + void (*op)(struct snd_info_entry *e, + struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(tscm->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, tscm, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_tscm_proc_init(struct snd_tscm *tscm) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(tscm->card, "firewire", + tscm->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(tscm, root, "firmware", proc_read_firmware); +} diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c index 9ac09cb..d7418c0 100644 --- a/sound/firewire/tascam/tascam.c +++ b/sound/firewire/tascam/tascam.c @@ -113,6 +113,8 @@ static int snd_tscm_probe(struct fw_unit *unit, if (err < 0) goto error;
+ snd_tscm_proc_init(tscm); + err = snd_card_register(card); if (err < 0) goto error; diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index e12f8b5..9ecc550 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -20,6 +20,7 @@
#include <sound/core.h> #include <sound/initval.h> +#include <sound/info.h>
#include "../lib.h"
@@ -43,4 +44,13 @@ struct snd_tscm { const struct snd_tscm_spec *spec; };
+#define TSCM_ADDR_BASE 0xffff00000000ull + +#define TSCM_OFFSET_FIRMWARE_REGISTER 0x0000 +#define TSCM_OFFSET_FIRMWARE_FPGA 0x0004 +#define TSCM_OFFSET_FIRMWARE_ARM 0x0008 +#define TSCM_OFFSET_FIRMWARE_HW 0x000c + +void snd_tscm_proc_init(struct snd_tscm *tscm); + #endif
TASCAM FireWire series uses non-blocking transmission for AMDTP packet streaming, while the format of data blocks is unique.
The CIP headers includes specific value in FMT field and no SYT information.
In transmitted packets, the first data channel represents event counter, and the last data channel has status and control information. The rest has 24bit PCM samples with right padding.
In received packets, all of data channels include 16, 24, 32bit PCM samples. There's no other kind of information.
This commit adds support for this protocol. For convenience, the size of PCM samples in outgoing packet is limited by 16 and 24bit. The status and control information will be supported in future commits.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/tascam/Makefile | 2 +- sound/firewire/tascam/amdtp-tascam.c | 243 +++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.h | 10 ++ 3 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/tascam/amdtp-tascam.c
diff --git a/sound/firewire/tascam/Makefile b/sound/firewire/tascam/Makefile index 1555206..d06c737 100644 --- a/sound/firewire/tascam/Makefile +++ b/sound/firewire/tascam/Makefile @@ -1,2 +1,2 @@ -snd-firewire-tascam-objs := tascam-proc.o tascam.o +snd-firewire-tascam-objs := tascam-proc.o amdtp-tascam.o tascam.o obj-$(CONFIG_SND_FIREWIRE_TASCAM) += snd-firewire-tascam.o diff --git a/sound/firewire/tascam/amdtp-tascam.c b/sound/firewire/tascam/amdtp-tascam.c new file mode 100644 index 0000000..9dd0fcc --- /dev/null +++ b/sound/firewire/tascam/amdtp-tascam.c @@ -0,0 +1,243 @@ +/* + * amdtp-tascam.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <sound/pcm.h> +#include "tascam.h" + +#define AMDTP_FMT_TSCM_TX 0x1e +#define AMDTP_FMT_TSCM_RX 0x3e + +struct amdtp_tscm { + unsigned int pcm_channels; + + void (*transfer_samples)(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames); +}; + +int amdtp_tscm_set_parameters(struct amdtp_stream *s, unsigned int rate) +{ + struct amdtp_tscm *p = s->protocol; + unsigned int data_channels; + + if (amdtp_stream_running(s)) + return -EBUSY; + + data_channels = p->pcm_channels; + + /* Packets in in-stream have extra 2 data channels. */ + if (s->direction == AMDTP_IN_STREAM) + data_channels += 2; + + return amdtp_stream_set_parameters(s, rate, data_channels); +} + +static void write_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_tscm *p = s->protocol; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, i, c; + const u32 *src; + + channels = p->pcm_channels; + src = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + buffer[c] = cpu_to_be32(*src); + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void write_pcm_s16(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_tscm *p = s->protocol; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, i, c; + const u16 *src; + + channels = p->pcm_channels; + src = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + buffer[c] = cpu_to_be32(*src << 16); + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void read_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_tscm *p = s->protocol; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, i, c; + u32 *dst; + + channels = p->pcm_channels; + dst = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + /* The first data channel is for event counter. */ + buffer += 1; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + *dst = be32_to_cpu(buffer[c]); + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_tscm *p = s->protocol; + unsigned int channels, i, c; + + channels = p->pcm_channels; + + for (i = 0; i < data_blocks; ++i) { + for (c = 0; c < channels; ++c) + buffer[c] = 0x00000000; + buffer += s->data_block_quadlets; + } +} + +int amdtp_tscm_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + /* + * Our implementation allows this protocol to deliver 24 bit sample in + * 32bit data channel. + */ + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + + return amdtp_stream_add_pcm_hw_constraints(s, runtime); +} + +void amdtp_tscm_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format) +{ + struct amdtp_tscm *p = s->protocol; + + if (WARN_ON(amdtp_stream_pcm_running(s))) + return; + + switch (format) { + default: + WARN_ON(1); + /* fall through */ + case SNDRV_PCM_FORMAT_S16: + if (s->direction == AMDTP_OUT_STREAM) { + p->transfer_samples = write_pcm_s16; + break; + } + WARN_ON(1); + /* fall through */ + case SNDRV_PCM_FORMAT_S32: + if (s->direction == AMDTP_OUT_STREAM) + p->transfer_samples = write_pcm_s32; + else + p->transfer_samples = read_pcm_s32; + break; + } +} + +static unsigned int process_tx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct amdtp_tscm *p = (struct amdtp_tscm *)s->protocol; + struct snd_pcm_substream *pcm; + + pcm = ACCESS_ONCE(s->pcm); + if (data_blocks > 0 && pcm) + p->transfer_samples(s, pcm, buffer, data_blocks); + + /* A place holder for control messages. */ + + return data_blocks; +} + +static unsigned int process_rx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct amdtp_tscm *p = (struct amdtp_tscm *)s->protocol; + struct snd_pcm_substream *pcm; + + /* This field is not used. */ + *syt = 0x0000; + + pcm = ACCESS_ONCE(s->pcm); + if (pcm) + p->transfer_samples(s, pcm, buffer, data_blocks); + else + write_pcm_silence(s, buffer, data_blocks); + + return data_blocks; +} + +int amdtp_tscm_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, unsigned int pcm_channels) +{ + amdtp_stream_process_data_blocks_t process_data_blocks; + struct amdtp_tscm *p; + unsigned int fmt; + int err; + + if (dir == AMDTP_IN_STREAM) { + fmt = AMDTP_FMT_TSCM_TX; + process_data_blocks = process_tx_data_blocks; + } else { + fmt = AMDTP_FMT_TSCM_RX; + process_data_blocks = process_rx_data_blocks; + } + + err = amdtp_stream_init(s, unit, dir, + CIP_NONBLOCKING | CIP_SKIP_DBC_ZERO_CHECK, fmt, + process_data_blocks, sizeof(struct amdtp_tscm)); + if (err < 0) + return 0; + + /* Use fixed value for FDF field. */ + s->fdf = 0x00; + + /* This protocol uses fixed number of data channels for PCM samples. */ + p = s->protocol; + p->pcm_channels = pcm_channels; + + return 0; +} diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index 9ecc550..195be0b 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -21,8 +21,11 @@ #include <sound/core.h> #include <sound/initval.h> #include <sound/info.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h>
#include "../lib.h" +#include "../amdtp-stream.h"
struct snd_tscm_spec { const char *const name; @@ -51,6 +54,13 @@ struct snd_tscm { #define TSCM_OFFSET_FIRMWARE_ARM 0x0008 #define TSCM_OFFSET_FIRMWARE_HW 0x000c
+int amdtp_tscm_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, unsigned int pcm_channels); +int amdtp_tscm_set_parameters(struct amdtp_stream *s, unsigned int rate); +int amdtp_tscm_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); +void amdtp_tscm_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format); + void snd_tscm_proc_init(struct snd_tscm *tscm);
#endif
This commit adds streaming functionality for both direction. To utilize the sequence of the number of data blocks in packets, full duplex with synchronization is applied.
Besides, TASCAM FireWire series allows drivers to decide which PCM data channels are enabled. For convenience, this driver always enable whole the data channels.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/tascam/Makefile | 3 +- sound/firewire/tascam/tascam-stream.c | 457 ++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.c | 12 +- sound/firewire/tascam/tascam.h | 36 +++ 4 files changed, 506 insertions(+), 2 deletions(-) create mode 100644 sound/firewire/tascam/tascam-stream.c
diff --git a/sound/firewire/tascam/Makefile b/sound/firewire/tascam/Makefile index d06c737..0a1c387 100644 --- a/sound/firewire/tascam/Makefile +++ b/sound/firewire/tascam/Makefile @@ -1,2 +1,3 @@ -snd-firewire-tascam-objs := tascam-proc.o amdtp-tascam.o tascam.o +snd-firewire-tascam-objs := tascam-proc.o amdtp-tascam.o tascam-stream.o \ + tascam.o obj-$(CONFIG_SND_FIREWIRE_TASCAM) += snd-firewire-tascam.o diff --git a/sound/firewire/tascam/tascam-stream.c b/sound/firewire/tascam/tascam-stream.c new file mode 100644 index 0000000..0732f7f --- /dev/null +++ b/sound/firewire/tascam/tascam-stream.c @@ -0,0 +1,457 @@ +/* + * tascam-stream.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/delay.h> +#include "tascam.h" + +#define CALLBACK_TIMEOUT 500 + +static int get_clock(struct snd_tscm *tscm, u32 *data) +{ + __be32 reg; + int err; + + err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS, + ®, sizeof(reg), 0); + if (err >= 0) + *data = be32_to_cpu(reg); + + return err; +} + +static int set_clock(struct snd_tscm *tscm, unsigned int rate, + enum snd_tscm_clock clock) +{ + u32 data; + __be32 reg; + int err; + + err = get_clock(tscm, &data); + if (err < 0) + return err; + data &= 0x0000ffff; + + if (rate > 0) { + data &= 0x000000ff; + /* Base rate. */ + if ((rate % 44100) == 0) { + data |= 0x00000100; + /* Multiplier. */ + if (rate / 44100 == 2) + data |= 0x00008000; + } else if ((rate % 48000) == 0) { + data |= 0x00000200; + /* Multiplier. */ + if (rate / 48000 == 2) + data |= 0x00008000; + } else { + return -EAGAIN; + } + } + + if (clock != INT_MAX) { + data &= 0x0000ff00; + data |= clock + 1; + } + + reg = cpu_to_be32(data); + + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + if (data & 0x00008000) + reg = cpu_to_be32(0x0000001a); + else + reg = cpu_to_be32(0x0000000d); + + return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MULTIPLEX_MODE, + ®, sizeof(reg), 0); +} + +int snd_tscm_stream_get_rate(struct snd_tscm *tscm, unsigned int *rate) +{ + u32 data = 0x0; + unsigned int trials = 0; + int err; + + while (data == 0x0 || trials++ < 5) { + err = get_clock(tscm, &data); + if (err < 0) + return err; + + data = (data & 0xff000000) >> 24; + } + + /* Check base rate. */ + if ((data & 0x0f) == 0x01) + *rate = 44100; + else if ((data & 0x0f) == 0x02) + *rate = 48000; + else + return -EAGAIN; + + /* Check multiplier. */ + if ((data & 0xf0) == 0x80) + *rate *= 2; + else if ((data & 0xf0) != 0x00) + return -EAGAIN; + + return err; +} + +int snd_tscm_stream_get_clock(struct snd_tscm *tscm, enum snd_tscm_clock *clock) +{ + u32 data; + int err; + + err = get_clock(tscm, &data); + if (err < 0) + return err; + + *clock = ((data & 0x00ff0000) >> 16) - 1; + if (*clock < 0 || *clock > SND_TSCM_CLOCK_ADAT) + return -EIO; + + return 0; +} + +static int enable_data_channels(struct snd_tscm *tscm) +{ + __be32 reg; + u32 data; + unsigned int i; + int err; + + data = 0; + for (i = 0; i < tscm->spec->pcm_capture_analog_channels; ++i) + data |= BIT(i); + if (tscm->spec->has_adat) + data |= 0x0000ff00; + if (tscm->spec->has_spdif) + data |= 0x00030000; + + reg = cpu_to_be32(data); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_TX_PCM_CHANNELS, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + data = 0; + for (i = 0; i < tscm->spec->pcm_playback_analog_channels; ++i) + data |= BIT(i); + if (tscm->spec->has_adat) + data |= 0x0000ff00; + if (tscm->spec->has_spdif) + data |= 0x00030000; + + reg = cpu_to_be32(data); + return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_RX_PCM_CHANNELS, + ®, sizeof(reg), 0); +} + +static int set_stream_formats(struct snd_tscm *tscm, unsigned int rate) +{ + __be32 reg; + int err; + + /* Set an option for unknown purpose. */ + reg = cpu_to_be32(0x00200000); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_SET_OPTION, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + err = enable_data_channels(tscm); + if (err < 0) + return err; + + return set_clock(tscm, rate, INT_MAX); +} + +static void finish_session(struct snd_tscm *tscm) +{ + __be32 reg; + + reg = 0; + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING, + ®, sizeof(reg), 0); + + reg = 0; + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON, + ®, sizeof(reg), 0); + +} + +static int begin_session(struct snd_tscm *tscm) +{ + __be32 reg; + int err; + + reg = cpu_to_be32(0x00000001); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + reg = cpu_to_be32(0x00000001); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* Set an option for unknown purpose. */ + reg = cpu_to_be32(0x00002000); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_SET_OPTION, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* Start multiplexing PCM samples on packets. */ + reg = cpu_to_be32(0x00000001); + return snd_fw_transaction(tscm->unit, + TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_ON, + ®, sizeof(reg), 0); +} + +static void release_resources(struct snd_tscm *tscm) +{ + __be32 reg; + + /* Unregister channels. */ + reg = cpu_to_be32(0x00000000); + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_CH, + ®, sizeof(reg), 0); + reg = cpu_to_be32(0x00000000); + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN, + ®, sizeof(reg), 0); + reg = cpu_to_be32(0x00000000); + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_CH, + ®, sizeof(reg), 0); + + /* Release isochronous resources. */ + fw_iso_resources_free(&tscm->tx_resources); + fw_iso_resources_free(&tscm->rx_resources); +} + +static int keep_resources(struct snd_tscm *tscm, unsigned int rate) +{ + __be32 reg; + int err; + + /* Keep resources for in-stream. */ + err = amdtp_tscm_set_parameters(&tscm->tx_stream, rate); + if (err < 0) + return err; + err = fw_iso_resources_allocate(&tscm->tx_resources, + amdtp_stream_get_max_payload(&tscm->tx_stream), + fw_parent_device(tscm->unit)->max_speed); + if (err < 0) + goto error; + + /* Keep resources for out-stream. */ + err = amdtp_tscm_set_parameters(&tscm->rx_stream, rate); + if (err < 0) + return err; + err = fw_iso_resources_allocate(&tscm->rx_resources, + amdtp_stream_get_max_payload(&tscm->rx_stream), + fw_parent_device(tscm->unit)->max_speed); + if (err < 0) + return err; + + /* Register the isochronous channel for transmitting stream. */ + reg = cpu_to_be32(tscm->tx_resources.channel); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_CH, + ®, sizeof(reg), 0); + if (err < 0) + goto error; + + /* Unknown */ + reg = cpu_to_be32(0x00000002); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN, + ®, sizeof(reg), 0); + if (err < 0) + goto error; + + /* Register the isochronous channel for receiving stream. */ + reg = cpu_to_be32(tscm->rx_resources.channel); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_CH, + ®, sizeof(reg), 0); + if (err < 0) + goto error; + + return 0; +error: + release_resources(tscm); + return err; +} + +int snd_tscm_stream_init_duplex(struct snd_tscm *tscm) +{ + unsigned int pcm_channels; + int err; + + /* For out-stream. */ + err = fw_iso_resources_init(&tscm->rx_resources, tscm->unit); + if (err < 0) + return err; + pcm_channels = tscm->spec->pcm_playback_analog_channels; + if (tscm->spec->has_adat) + pcm_channels += 8; + if (tscm->spec->has_spdif) + pcm_channels += 2; + err = amdtp_tscm_init(&tscm->rx_stream, tscm->unit, AMDTP_OUT_STREAM, + pcm_channels); + if (err < 0) + return err; + + /* For in-stream. */ + err = fw_iso_resources_init(&tscm->tx_resources, tscm->unit); + if (err < 0) + return err; + pcm_channels = tscm->spec->pcm_capture_analog_channels; + if (tscm->spec->has_adat) + pcm_channels += 8; + if (tscm->spec->has_spdif) + pcm_channels += 2; + err = amdtp_tscm_init(&tscm->tx_stream, tscm->unit, AMDTP_IN_STREAM, + pcm_channels); + if (err < 0) + amdtp_stream_destroy(&tscm->rx_stream); + + return 0; +} + +/* At bus reset, streaming is stopped and some registers are clear. */ +void snd_tscm_stream_update_duplex(struct snd_tscm *tscm) +{ + amdtp_stream_pcm_abort(&tscm->tx_stream); + amdtp_stream_stop(&tscm->tx_stream); + + amdtp_stream_pcm_abort(&tscm->rx_stream); + amdtp_stream_stop(&tscm->rx_stream); +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm) +{ + amdtp_stream_destroy(&tscm->rx_stream); + amdtp_stream_destroy(&tscm->tx_stream); + + fw_iso_resources_destroy(&tscm->rx_resources); + fw_iso_resources_destroy(&tscm->tx_resources); +} + +int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate) +{ + unsigned int curr_rate; + int err; + + if (tscm->substreams_counter == 0) + return 0; + + err = snd_tscm_stream_get_rate(tscm, &curr_rate); + if (err < 0) + return err; + if (curr_rate != rate || + amdtp_streaming_error(&tscm->tx_stream) || + amdtp_streaming_error(&tscm->rx_stream)) { + finish_session(tscm); + + amdtp_stream_stop(&tscm->tx_stream); + amdtp_stream_stop(&tscm->rx_stream); + + release_resources(tscm); + } + + if (!amdtp_stream_running(&tscm->tx_stream)) { + amdtp_stream_set_sync(CIP_SYNC_TO_DEVICE, + &tscm->tx_stream, &tscm->rx_stream); + err = keep_resources(tscm, rate); + if (err < 0) + goto error; + + err = set_stream_formats(tscm, rate); + if (err < 0) + goto error; + + err = begin_session(tscm); + if (err < 0) + goto error; + + err = amdtp_stream_start(&tscm->tx_stream, + tscm->tx_resources.channel, + fw_parent_device(tscm->unit)->max_speed); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&tscm->tx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; + goto error; + } + } + + if (!amdtp_stream_running(&tscm->rx_stream)) { + err = amdtp_stream_start(&tscm->rx_stream, + tscm->rx_resources.channel, + fw_parent_device(tscm->unit)->max_speed); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&tscm->rx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; + goto error; + } + } + + return 0; +error: + amdtp_stream_stop(&tscm->tx_stream); + amdtp_stream_stop(&tscm->rx_stream); + + finish_session(tscm); + release_resources(tscm); + + return err; +} + +void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm) +{ + if (tscm->substreams_counter > 0) + return; + + amdtp_stream_stop(&tscm->tx_stream); + amdtp_stream_stop(&tscm->rx_stream); + + finish_session(tscm); + release_resources(tscm); +} diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c index d7418c0..c256360 100644 --- a/sound/firewire/tascam/tascam.c +++ b/sound/firewire/tascam/tascam.c @@ -82,6 +82,8 @@ static void tscm_card_free(struct snd_card *card) { struct snd_tscm *tscm = card->private_data;
+ snd_tscm_stream_destroy_duplex(tscm); + fw_unit_put(tscm->unit);
mutex_destroy(&tscm->mutex); @@ -115,6 +117,10 @@ static int snd_tscm_probe(struct fw_unit *unit,
snd_tscm_proc_init(tscm);
+ err = snd_tscm_stream_init_duplex(tscm); + if (err < 0) + goto error; + err = snd_card_register(card); if (err < 0) goto error; @@ -129,7 +135,11 @@ error:
static void snd_tscm_update(struct fw_unit *unit) { - return; + struct snd_tscm *tscm = dev_get_drvdata(&unit->device); + + mutex_lock(&tscm->mutex); + snd_tscm_stream_update_duplex(tscm); + mutex_unlock(&tscm->mutex); }
static void snd_tscm_remove(struct fw_unit *unit) diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index 195be0b..41fe3a9 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -26,6 +26,7 @@
#include "../lib.h" #include "../amdtp-stream.h" +#include "../iso-resources.h"
struct snd_tscm_spec { const char *const name; @@ -45,6 +46,12 @@ struct snd_tscm { struct mutex mutex;
const struct snd_tscm_spec *spec; + + struct fw_iso_resources tx_resources; + struct fw_iso_resources rx_resources; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + unsigned int substreams_counter; };
#define TSCM_ADDR_BASE 0xffff00000000ull @@ -54,6 +61,26 @@ struct snd_tscm { #define TSCM_OFFSET_FIRMWARE_ARM 0x0008 #define TSCM_OFFSET_FIRMWARE_HW 0x000c
+#define TSCM_OFFSET_ISOC_TX_CH 0x0200 +#define TSCM_OFFSET_UNKNOWN 0x0204 +#define TSCM_OFFSET_START_STREAMING 0x0208 +#define TSCM_OFFSET_ISOC_RX_CH 0x020c +#define TSCM_OFFSET_ISOC_RX_ON 0x0210 /* Little conviction. */ +#define TSCM_OFFSET_TX_PCM_CHANNELS 0x0214 +#define TSCM_OFFSET_RX_PCM_CHANNELS 0x0218 +#define TSCM_OFFSET_MULTIPLEX_MODE 0x021c +#define TSCM_OFFSET_ISOC_TX_ON 0x0220 +/* Unknown 0x0224 */ +#define TSCM_OFFSET_CLOCK_STATUS 0x0228 +#define TSCM_OFFSET_SET_OPTION 0x022c + +enum snd_tscm_clock { + SND_TSCM_CLOCK_INTERNAL = 0, + SND_TSCM_CLOCK_WORD = 1, + SND_TSCM_CLOCK_SPDIF = 2, + SND_TSCM_CLOCK_ADAT = 3, +}; + int amdtp_tscm_init(struct amdtp_stream *s, struct fw_unit *unit, enum amdtp_stream_direction dir, unsigned int pcm_channels); int amdtp_tscm_set_parameters(struct amdtp_stream *s, unsigned int rate); @@ -61,6 +88,15 @@ int amdtp_tscm_add_pcm_hw_constraints(struct amdtp_stream *s, struct snd_pcm_runtime *runtime); void amdtp_tscm_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format);
+int snd_tscm_stream_get_rate(struct snd_tscm *tscm, unsigned int *rate); +int snd_tscm_stream_get_clock(struct snd_tscm *tscm, + enum snd_tscm_clock *clock); +int snd_tscm_stream_init_duplex(struct snd_tscm *tscm); +void snd_tscm_stream_update_duplex(struct snd_tscm *tscm); +void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm); +int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate); +void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm); + void snd_tscm_proc_init(struct snd_tscm *tscm);
#endif
This commit adds PCM functionality to transmit/receive PCM samples.
When one of PCM substreams are running or external clock source is selected, current sampling rate is used. Else, the sampling rate is changed as an userspace application requests.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/tascam/Makefile | 2 +- sound/firewire/tascam/tascam-pcm.c | 301 +++++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.c | 4 + sound/firewire/tascam/tascam.h | 2 + 4 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/tascam/tascam-pcm.c
diff --git a/sound/firewire/tascam/Makefile b/sound/firewire/tascam/Makefile index 0a1c387..f075b9b 100644 --- a/sound/firewire/tascam/Makefile +++ b/sound/firewire/tascam/Makefile @@ -1,3 +1,3 @@ snd-firewire-tascam-objs := tascam-proc.o amdtp-tascam.o tascam-stream.o \ - tascam.o + tascam-pcm.o tascam.o obj-$(CONFIG_SND_FIREWIRE_TASCAM) += snd-firewire-tascam.o diff --git a/sound/firewire/tascam/tascam-pcm.c b/sound/firewire/tascam/tascam-pcm.c new file mode 100644 index 0000000..696b371 --- /dev/null +++ b/sound/firewire/tascam/tascam-pcm.c @@ -0,0 +1,301 @@ +/* + * tascam-pcm.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "tascam.h" + +static void set_buffer_params(struct snd_pcm_hardware *hw) +{ + hw->period_bytes_min = 4 * hw->channels_min; + hw->period_bytes_max = hw->period_bytes_min * 2048; + hw->buffer_bytes_max = hw->period_bytes_max * 2; + + hw->periods_min = 2; + hw->periods_max = UINT_MAX; +} + +static int pcm_init_hw_params(struct snd_tscm *tscm, + struct snd_pcm_substream *substream) +{ + static const struct snd_pcm_hardware hardware = { + .info = SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .channels_min = 10, + .channels_max = 18, + }; + struct snd_pcm_runtime *runtime = substream->runtime; + struct amdtp_stream *stream; + unsigned int pcm_channels; + + runtime->hw = hardware; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + stream = &tscm->tx_stream; + pcm_channels = tscm->spec->pcm_capture_analog_channels; + } else { + runtime->hw.formats = + SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S32; + stream = &tscm->rx_stream; + pcm_channels = tscm->spec->pcm_playback_analog_channels; + } + + if (tscm->spec->has_adat) + pcm_channels += 8; + if (tscm->spec->has_spdif) + pcm_channels += 2; + runtime->hw.channels_min = runtime->hw.channels_max = pcm_channels; + + set_buffer_params(&runtime->hw); + + return amdtp_tscm_add_pcm_hw_constraints(stream, runtime); +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + enum snd_tscm_clock clock; + unsigned int rate; + int err; + + err = pcm_init_hw_params(tscm, substream); + if (err < 0) + return err; + + err = snd_tscm_stream_get_clock(tscm, &clock); + if (clock != SND_TSCM_CLOCK_INTERNAL || + amdtp_stream_pcm_running(&tscm->rx_stream) || + amdtp_stream_pcm_running(&tscm->tx_stream)) { + err = snd_tscm_stream_get_rate(tscm, &rate); + if (err < 0) + return err; + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } + + snd_pcm_set_sync(substream); + + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_tscm *tscm = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&tscm->mutex); + tscm->substreams_counter++; + mutex_unlock(&tscm->mutex); + } + + amdtp_tscm_set_pcm_format(&tscm->tx_stream, params_format(hw_params)); + + return 0; +} + +static int pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_tscm *tscm = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&tscm->mutex); + tscm->substreams_counter++; + mutex_unlock(&tscm->mutex); + } + + amdtp_tscm_set_pcm_format(&tscm->rx_stream, params_format(hw_params)); + + return 0; +} + +static int pcm_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + + mutex_lock(&tscm->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + tscm->substreams_counter--; + + snd_tscm_stream_stop_duplex(tscm); + + mutex_unlock(&tscm->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + + mutex_lock(&tscm->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + tscm->substreams_counter--; + + snd_tscm_stream_stop_duplex(tscm); + + mutex_unlock(&tscm->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&tscm->mutex); + + err = snd_tscm_stream_start_duplex(tscm, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&tscm->tx_stream); + + mutex_unlock(&tscm->mutex); + + return err; +} + +static int pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&tscm->mutex); + + err = snd_tscm_stream_start_duplex(tscm, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&tscm->rx_stream); + + mutex_unlock(&tscm->mutex); + + return err; +} + +static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_tscm *tscm = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&tscm->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&tscm->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_tscm *tscm = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&tscm->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&tscm->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_tscm *tscm = sbstrm->private_data; + + return amdtp_stream_pcm_pointer(&tscm->tx_stream); +} + +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_tscm *tscm = sbstrm->private_data; + + return amdtp_stream_pcm_pointer(&tscm->rx_stream); +} + +static struct snd_pcm_ops pcm_capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_capture_hw_params, + .hw_free = pcm_capture_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static struct snd_pcm_ops pcm_playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_playback_hw_params, + .hw_free = pcm_playback_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, +}; + +int snd_tscm_create_pcm_devices(struct snd_tscm *tscm) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(tscm->card, tscm->card->driver, 0, 1, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = tscm; + snprintf(pcm->name, sizeof(pcm->name), + "%s PCM", tscm->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops); + + return 0; +} diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c index c256360..bb4f706 100644 --- a/sound/firewire/tascam/tascam.c +++ b/sound/firewire/tascam/tascam.c @@ -121,6 +121,10 @@ static int snd_tscm_probe(struct fw_unit *unit, if (err < 0) goto error;
+ err = snd_tscm_create_pcm_devices(tscm); + if (err < 0) + goto error; + err = snd_card_register(card); if (err < 0) goto error; diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index 41fe3a9..28c875f 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -99,4 +99,6 @@ void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm);
void snd_tscm_proc_init(struct snd_tscm *tscm);
+int snd_tscm_create_pcm_devices(struct snd_tscm *tscm); + #endif
This commit adds hwdep interface so as the other IEEE 1394 sound devices has.
This interface is designed for mixer/control applications. By using this interface, an 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 | 1 + sound/firewire/Kconfig | 1 + sound/firewire/tascam/Makefile | 2 +- sound/firewire/tascam/tascam-hwdep.c | 201 ++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam-pcm.c | 17 ++- sound/firewire/tascam/tascam-stream.c | 39 +++++++ sound/firewire/tascam/tascam.c | 6 + sound/firewire/tascam/tascam.h | 13 +++ 9 files changed, 278 insertions(+), 5 deletions(-) create mode 100644 sound/firewire/tascam/tascam-hwdep.c
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index aa32913..a82108e 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -101,9 +101,10 @@ enum { SNDRV_HWDEP_IFACE_FW_BEBOB, /* BridgeCo BeBoB based device */ SNDRV_HWDEP_IFACE_FW_OXFW, /* Oxford OXFW970/971 based device */ SNDRV_HWDEP_IFACE_FW_DIGI00X, /* Digidesign Digi 002/003 family */ + SNDRV_HWDEP_IFACE_FW_TASCAM, /* TASCAM FireWire series */
/* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_DIGI00X + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_TASCAM };
struct snd_hwdep_info { diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index deb041c..db79a12 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -64,6 +64,7 @@ union snd_firewire_event { #define SNDRV_FIREWIRE_TYPE_BEBOB 3 #define SNDRV_FIREWIRE_TYPE_OXFW 4 #define SNDRV_FIREWIRE_TYPE_DIGI00X 5 +#define SNDRV_FIREWIRE_TYPE_TASCAM 6 /* RME, MOTU, ... */
struct snd_firewire_get_info { diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index bb3f261..bee0e5f 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -138,6 +138,7 @@ config SND_FIREWIRE_DIGI00X config SND_FIREWIRE_TASCAM tristate "TASCAM FireWire series support" select SND_FIREWIRE_LIB + select SND_HWDEP help Say Y here to include support for TASCAM. * FW-1884 diff --git a/sound/firewire/tascam/Makefile b/sound/firewire/tascam/Makefile index f075b9b..6beefc2 100644 --- a/sound/firewire/tascam/Makefile +++ b/sound/firewire/tascam/Makefile @@ -1,3 +1,3 @@ snd-firewire-tascam-objs := tascam-proc.o amdtp-tascam.o tascam-stream.o \ - tascam-pcm.o tascam.o + tascam-pcm.o tascam-hwdep.o tascam.o obj-$(CONFIG_SND_FIREWIRE_TASCAM) += snd-firewire-tascam.o diff --git a/sound/firewire/tascam/tascam-hwdep.c b/sound/firewire/tascam/tascam-hwdep.c new file mode 100644 index 0000000..131267c --- /dev/null +++ b/sound/firewire/tascam/tascam-hwdep.c @@ -0,0 +1,201 @@ +/* + * tascam-hwdep.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node information + * 2.get notification about starting/stopping stream + * 3.lock/unlock stream + */ + +#include "tascam.h" + +static long hwdep_read_locked(struct snd_tscm *tscm, char __user *buf, + long count) +{ + union snd_firewire_event event; + + memset(&event, 0, sizeof(event)); + + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (tscm->dev_lock_count > 0); + tscm->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_tscm *tscm = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&tscm->lock); + + while (!tscm->dev_lock_changed) { + prepare_to_wait(&tscm->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&tscm->lock); + schedule(); + finish_wait(&tscm->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&tscm->lock); + } + + memset(&event, 0, sizeof(event)); + count = hwdep_read_locked(tscm, buf, count); + spin_unlock_irq(&tscm->lock); + + return count; +} + +static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_tscm *tscm = hwdep->private_data; + unsigned int events; + + poll_wait(file, &tscm->hwdep_wait, wait); + + spin_lock_irq(&tscm->lock); + if (tscm->dev_lock_changed) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&tscm->lock); + + return events; +} + +static int hwdep_get_info(struct snd_tscm *tscm, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(tscm->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_TASCAM; + 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_tscm *tscm) +{ + int err; + + spin_lock_irq(&tscm->lock); + + if (tscm->dev_lock_count == 0) { + tscm->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&tscm->lock); + + return err; +} + +static int hwdep_unlock(struct snd_tscm *tscm) +{ + int err; + + spin_lock_irq(&tscm->lock); + + if (tscm->dev_lock_count == -1) { + tscm->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&tscm->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_tscm *tscm = hwdep->private_data; + + spin_lock_irq(&tscm->lock); + if (tscm->dev_lock_count == -1) + tscm->dev_lock_count = 0; + spin_unlock_irq(&tscm->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_tscm *tscm = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(tscm, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(tscm); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(tscm); + 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_tscm_create_hwdep_device(struct snd_tscm *tscm) +{ + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(tscm->card, "Tascam", 0, &hwdep); + if (err < 0) + return err; + + strcpy(hwdep->name, "Tascam"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_TASCAM; + hwdep->ops = hwdep_ops; + hwdep->private_data = tscm; + hwdep->exclusive = true; + + return err; +} diff --git a/sound/firewire/tascam/tascam-pcm.c b/sound/firewire/tascam/tascam-pcm.c index 696b371..380d3db 100644 --- a/sound/firewire/tascam/tascam-pcm.c +++ b/sound/firewire/tascam/tascam-pcm.c @@ -72,9 +72,13 @@ static int pcm_open(struct snd_pcm_substream *substream) unsigned int rate; int err;
+ err = snd_tscm_stream_lock_try(tscm); + if (err < 0) + goto end; + err = pcm_init_hw_params(tscm, substream); if (err < 0) - return err; + goto err_locked;
err = snd_tscm_stream_get_clock(tscm, &clock); if (clock != SND_TSCM_CLOCK_INTERNAL || @@ -82,18 +86,25 @@ static int pcm_open(struct snd_pcm_substream *substream) amdtp_stream_pcm_running(&tscm->tx_stream)) { err = snd_tscm_stream_get_rate(tscm, &rate); if (err < 0) - return err; + goto err_locked; substream->runtime->hw.rate_min = rate; substream->runtime->hw.rate_max = rate; }
snd_pcm_set_sync(substream); - +end: + return err; +err_locked: + snd_tscm_stream_lock_release(tscm); return err; }
static int pcm_close(struct snd_pcm_substream *substream) { + struct snd_tscm *tscm = substream->private_data; + + snd_tscm_stream_lock_release(tscm); + return 0; }
diff --git a/sound/firewire/tascam/tascam-stream.c b/sound/firewire/tascam/tascam-stream.c index 0732f7f..0e6dd5c6 100644 --- a/sound/firewire/tascam/tascam-stream.c +++ b/sound/firewire/tascam/tascam-stream.c @@ -455,3 +455,42 @@ void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm) finish_session(tscm); release_resources(tscm); } + +void snd_tscm_stream_lock_changed(struct snd_tscm *tscm) +{ + tscm->dev_lock_changed = true; + wake_up(&tscm->hwdep_wait); +} + +int snd_tscm_stream_lock_try(struct snd_tscm *tscm) +{ + int err; + + spin_lock_irq(&tscm->lock); + + /* user land lock this */ + if (tscm->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (tscm->dev_lock_count++ == 0) + snd_tscm_stream_lock_changed(tscm); + err = 0; +end: + spin_unlock_irq(&tscm->lock); + return err; +} + +void snd_tscm_stream_lock_release(struct snd_tscm *tscm) +{ + spin_lock_irq(&tscm->lock); + + if (WARN_ON(tscm->dev_lock_count <= 0)) + goto end; + if (--tscm->dev_lock_count == 0) + snd_tscm_stream_lock_changed(tscm); +end: + spin_unlock_irq(&tscm->lock); +} diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c index bb4f706..ee2f498 100644 --- a/sound/firewire/tascam/tascam.c +++ b/sound/firewire/tascam/tascam.c @@ -110,6 +110,8 @@ static int snd_tscm_probe(struct fw_unit *unit, tscm->spec = (const struct snd_tscm_spec *)entry->driver_data;
mutex_init(&tscm->mutex); + spin_lock_init(&tscm->lock); + init_waitqueue_head(&tscm->hwdep_wait);
err = check_name(tscm); if (err < 0) @@ -125,6 +127,10 @@ static int snd_tscm_probe(struct fw_unit *unit, if (err < 0) goto error;
+ err = snd_tscm_create_hwdep_device(tscm); + if (err < 0) + goto error; + err = snd_card_register(card); if (err < 0) goto error; diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index 28c875f..75a3b9a 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -23,6 +23,8 @@ #include <sound/info.h> #include <sound/pcm.h> #include <sound/pcm_params.h> +#include <sound/firewire.h> +#include <sound/hwdep.h>
#include "../lib.h" #include "../amdtp-stream.h" @@ -44,6 +46,7 @@ struct snd_tscm { struct fw_unit *unit;
struct mutex mutex; + spinlock_t lock;
const struct snd_tscm_spec *spec;
@@ -52,6 +55,10 @@ struct snd_tscm { struct amdtp_stream tx_stream; struct amdtp_stream rx_stream; unsigned int substreams_counter; + + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; };
#define TSCM_ADDR_BASE 0xffff00000000ull @@ -97,8 +104,14 @@ void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm); int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate); void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm);
+void snd_tscm_stream_lock_changed(struct snd_tscm *tscm); +int snd_tscm_stream_lock_try(struct snd_tscm *tscm); +void snd_tscm_stream_lock_release(struct snd_tscm *tscm); + void snd_tscm_proc_init(struct snd_tscm *tscm);
int snd_tscm_create_pcm_devices(struct snd_tscm *tscm);
+int snd_tscm_create_hwdep_device(struct snd_tscm *tscm); + #endif
participants (5)
-
Clemens Ladisch
-
kbuild test robot
-
Stefan Richter
-
Takashi Iwai
-
Takashi Sakamoto