[alsa-devel] [PATCH 2/8] add device specific command
o-takashi at sakamocchi.jp
o-takashi at sakamocchi.jp
Sat Jun 1 17:55:52 CEST 2013
From: Takashi Sakamoto <o-takashi at sakamocchi.jp>
Fireworks can be controlled by device specific commands over IEEE1394 TA's
AV/C Digital Interface Command Set. This file add some functions to execute
the command.
Signed-off-by: Takashi Sakamoto <o-takashi at sakamocchi.jp>
---
sound/firewire/fireworks/fireworks_command.c | 535 ++++++++++++++++++++++++++
1 file changed, 535 insertions(+)
create mode 100644 sound/firewire/fireworks/fireworks_command.c
diff --git a/sound/firewire/fireworks/fireworks_command.c b/sound/firewire/fireworks/fireworks_command.c
new file mode 100644
index 0000000..0006be2
--- /dev/null
+++ b/sound/firewire/fireworks/fireworks_command.c
@@ -0,0 +1,535 @@
+/*
+ * fireworks_command.c - driver for Firewire devices from Echo Digital Audio
+ *
+ * Copyright (c) 2013 Takashi Sakamoto <o-takashi at sakmocchi.jp>
+ *
+ *
+ * This driver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2.
+ *
+ * This driver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this driver; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * mostly based on FFADO's souce, which is
+ * Copyright (C) 2005-2008 by Pieter Palmers
+ *
+ */
+
+#include "./fireworks.h"
+
+/*
+ * According to AV/C command specification, vendors can define own command.
+ *
+ * 1394 Trade Association's AV/C Digital Interface Command Set General
+ * Specification 4.2 (September 1, 2004)
+ * 9.6 VENDOR-DEPENDENT commands
+ * (to page 55)
+ * opcode: 0x00
+ * operand[0-2]: company ID
+ * operand[3-]: vendor dependent data
+ *
+ * Echo's Fireworks(TM) utilize this specification.
+ * This module calls it as 'Echo Fireworks Commands' (a.k.a EFC).
+ *
+ * In EFC,
+ * Company ID is always 0x00.
+ * operand[0]: 0x00
+ * operand[1]: 0x00
+ * operand[2]: 0x00
+ *
+ * Two blank operands exists in the beginning of the 'vendor dependent data'
+ * This seems to be for data alignment of 32 bit.
+ * operand[3]: 0x00
+ * operand[4]: 0x00
+ *
+ * Following these operands, EFC substance exists.
+ * At first, 6 data exist. we call these data as 'EFC fields'.
+ * Following to the 6 data, parameters for each commands exists.
+ * Most of parameters are 32 bit. But exception exists according to command.
+ * data[0]: Length of EFC substance.
+ * data[1]: EFC version
+ * data[2]: Sequence number. This is incremented at return value
+ * data[3]: EFC category. If greater than 1,
+ * EFC_CAT_HWINFO return extended fields.
+ * data[4]: EFC command
+ * data[5]: EFC return value in EFC response.
+ * data[6-]: parameters
+ *
+ * As a result, Echo's Fireworks doesn't need generic command sets.
+ */
+
+/*
+ * AV/C parameters for Vendor-Dependent command
+ */
+#define AVC_CTS 0x00
+#define AVC_CTYPE 0x00
+#define AVC_SUBUNIT_TYPE 0x1F
+#define AVC_SUBUNIT_ID 0x07
+#define AVC_OPCODE 0x00
+#define AVC_COMPANY_ID 0x00
+
+struct avc_fields {
+ unsigned short cts:4;
+ unsigned short ctype:4;
+ unsigned short subunit_type:5;
+ unsigned short subunit_id:3;
+ unsigned short opcode:8;
+ unsigned long company_id:24;
+};
+
+/*
+ * EFC command
+ * quadlet parameters following to these fields.
+ */
+struct efc_fields {
+ u32 length;
+ u32 version;
+ u32 seqnum;
+ u32 category;
+ u32 command;
+ u32 retval;
+};
+
+/* for clock source and sampling rate */
+struct efc_clock {
+ u32 source;
+ u32 sampling_rate;
+ u32 index;
+};
+
+/* command categories */
+enum efc_category {
+ EFC_CAT_HWINFO = 0,
+ EFC_CAT_FLASH = 1,
+ EFC_CAT_TRANSPORT = 2,
+ EFC_CAT_HWCTL = 3,
+ EFC_CAT_MIXER_PHYS_OUT = 4,
+ EFC_CAT_MIXER_PHYS_IN = 5,
+ EFC_CAT_MIXER_PLAYBACK = 6,
+ EFC_CAT_MIXER_CAPTURE = 7,
+ EFC_CAT_MIXER_MONITOR = 8,
+ EFC_CAT_IOCONF = 9,
+};
+
+/* hardware info category commands */
+enum efc_cmd_hwinfo {
+ EFC_CMD_HWINFO_GET_CAPS = 0,
+ EFC_CMD_HWINFO_GET_POLLED = 1,
+ EFC_CMD_HWINFO_SET_EFR_ADDRESS = 2,
+ EFC_CMD_HWINFO_READ_SESSION_BLOCK = 3,
+ EFC_CMD_HWINFO_GET_DEBUG_INFO = 4,
+ EFC_CMD_HWINFO_SET_DEBUG_TRACKING = 5
+};
+
+/* flash category commands */
+enum efc_cmd_flash {
+ EFC_CMD_FLASH_ERASE = 0,
+ EFC_CMD_FLASH_READ = 1,
+ EFC_CMD_FLASH_WRITE = 2,
+ EFC_CMD_FLASH_GET_STATUS = 3,
+ EFC_CMD_FLASH_GET_SESSION_BASE = 4,
+ EFC_CMD_FLASH_LOCK = 5
+};
+
+/* hardware control category commands */
+enum efc_cmd_hwctl {
+ EFC_CMD_HWCTL_SET_CLOCK = 0,
+ EFC_CMD_HWCTL_GET_CLOCK = 1,
+ EFC_CMD_HWCTL_BSX_HANDSHAKE = 2,
+ EFC_CMD_HWCTL_CHANGE_FLAGS = 3,
+ EFC_CMD_HWCTL_GET_FLAGS = 4,
+ EFC_CMD_HWCTL_IDENTIFY = 5,
+ EFC_CMD_HWCTL_RECONNECT_PHY = 6
+};
+/* for flags */
+#define EFC_HWCTL_FLAG_MIXER_UNUSABLE 0x00
+#define EFC_HWCTL_FLAG_MIXER_USABLE 0x01
+#define EFC_HWCTL_FLAG_DIGITAL_PRO 0x02
+#define EFC_HWCTL_FLAG_DIGITAL_RAW 0x04
+
+/* I/O config category commands */
+enum efc_cmd_ioconf {
+ EFC_CMD_IOCONF_SET_MIRROR = 0,
+ EFC_CMD_IOCONF_GET_MIRROR = 1,
+ EFC_CMD_IOCONF_SET_DIGITAL_MODE = 2,
+ EFC_CMD_IOCONF_GET_DIGITAL_MODE = 3,
+ EFC_CMD_IOCONF_SET_PHANTOM = 4,
+ EFC_CMD_IOCONF_GET_PHANTOM = 5,
+ EFC_CMD_IOCONF_SET_ISOC_MAP = 6,
+ EFC_CMD_IOCONF_GET_ISOC_MAP = 7,
+};
+
+/* return values in response */
+enum efc_retval {
+ EFC_RETVAL_OK = 0,
+ EFC_RETVAL_BAD = 1,
+ EFC_RETVAL_BAD_COMMAND = 2,
+ EFC_RETVAL_COMM_ERR = 3,
+ EFC_RETVAL_BAD_QUAD_COUNT = 4,
+ EFC_RETVAL_UNSUPPORTED = 5,
+ EFC_RETVAL_1394_TIMEOUT = 6,
+ EFC_RETVAL_DSP_TIMEOUT = 7,
+ EFC_RETVAL_BAD_RATE = 8,
+ EFC_RETVAL_BAD_CLOCK = 9,
+ EFC_RETVAL_BAD_CHANNEL = 10,
+ EFC_RETVAL_BAD_PAN = 11,
+ EFC_RETVAL_FLASH_BUSY = 12,
+ EFC_RETVAL_BAD_MIRROR = 13,
+ EFC_RETVAL_BAD_LED = 14,
+ EFC_RETVAL_BAD_PARAMETER = 15,
+ EFC_RETVAL_INCOMPLETE = 0x80000000
+};
+
+/* for phys_in/phys_out/playback/capture/monitor category commands */
+enum snd_efw_mixer_cmd {
+ SND_EFW_MIXER_SET_GAIN = 0,
+ SND_EFW_MIXER_GET_GAIN = 1,
+ SND_EFW_MIXER_SET_MUTE = 2,
+ SND_EFW_MIXER_GET_MUTE = 3,
+ SND_EFW_MIXER_SET_SOLO = 4,
+ SND_EFW_MIXER_GET_SOLO = 5,
+ SND_EFW_MIXER_SET_PAN = 6,
+ SND_EFW_MIXER_GET_PAN = 7,
+ SND_EFW_MIXER_SET_NOMINAL = 8,
+ SND_EFW_MIXER_GET_NOMINAL = 9
+};
+
+static int
+efc_over_avc(struct snd_efw *efw, unsigned int category,
+ unsigned int command,
+ const u32 *params, unsigned int param_count,
+ void *response, unsigned int response_quadlets)
+{
+ int err;
+
+ unsigned int cmdbuf_bytes;
+ __be32 *cmdbuf;
+ struct efc_fields *efc_fields;
+ u32 sequence_number;
+ unsigned int i;
+
+ /* AV/C fields */
+ struct avc_fields avc_fields = {
+ .cts = AVC_CTS,
+ .ctype = AVC_CTYPE,
+ .subunit_type = AVC_SUBUNIT_TYPE,
+ .subunit_id = AVC_SUBUNIT_ID,
+ .opcode = AVC_OPCODE,
+ .company_id = AVC_COMPANY_ID
+ };
+
+ /* calcurate buffer size*/
+ if (param_count > response_quadlets)
+ cmdbuf_bytes = 32 + param_count * 4;
+ else
+ cmdbuf_bytes = 32 + response_quadlets * 4;
+
+ /* keep buffer */
+ cmdbuf = kzalloc(cmdbuf_bytes, GFP_KERNEL);
+ if (cmdbuf == NULL)
+ return -ENOMEM;
+
+ /* fill AV/C fields */
+ cmdbuf[0] = (avc_fields.cts << 28) |
+ (avc_fields.ctype << 24) |
+ (avc_fields.subunit_type << 19) |
+ (avc_fields.subunit_id << 16) |
+ (avc_fields.opcode << 8) |
+ (avc_fields.company_id >> 16 & 0xFF);
+ cmdbuf[1] = ((avc_fields.company_id >> 8 & 0xFF) << 24) |
+ ((avc_fields.company_id & 0xFF) << 16) |
+ (0x00 << 8) |
+ 0x00;
+
+ /* fill EFC fields */
+ efc_fields = (struct efc_fields *)(cmdbuf + 2);
+ efc_fields->length = sizeof(struct efc_fields) / 4 + param_count;
+ efc_fields->version = 1;
+ efc_fields->category = category;
+ efc_fields->command = command;
+ efc_fields->retval = 0;
+
+ /* sequence number should keep consistency */
+ spin_lock(&efw->lock);
+ efc_fields->seqnum = efw->sequence_number++;
+ sequence_number = efw->sequence_number;
+ spin_unlock(&efw->lock);
+
+ /* fill EFC parameters */
+ for (i = 0; i < param_count; i += 1)
+ cmdbuf[8 + i] = params[i];
+
+ /* for endian-ness */
+ for (i = 0; i < (cmdbuf_bytes / 4); i += 1)
+ cmdbuf[i] = cpu_to_be32(cmdbuf[i]);
+
+ /* if return value is positive, it means return bytes */
+ err = fcp_avc_transaction(efw->unit,
+ cmdbuf, cmdbuf_bytes,
+ cmdbuf, cmdbuf_bytes, 0x00);
+ if (err < 0)
+ goto end;
+
+ /* for endian-ness */
+ for (i = 0; i < (err / 4); i += 1)
+ cmdbuf[i] = cpu_to_be32(cmdbuf[i]);
+
+ /* parse AV/C fields */
+ avc_fields.cts = cmdbuf[0] >> 28;
+ avc_fields.ctype = cmdbuf[0] >> 24 & 0x0F;
+ avc_fields.subunit_type = cmdbuf[0] >> 19 & 0x1F;
+ avc_fields.subunit_id = cmdbuf[0] >> 16 & 0x07;
+ avc_fields.opcode = cmdbuf[0] >> 8 & 0xFF;
+ avc_fields.company_id = ((cmdbuf[0] & 0xFF) << 16) |
+ ((cmdbuf[1] >> 24 & 0xFF) << 8) |
+ (cmdbuf[1] >> 16 & 0xFF);
+
+ /* check AV/C fields */
+ if ((avc_fields.cts != AVC_CTS) ||
+ (avc_fields.ctype != 0x09) || /* ACCEPTED */
+ (avc_fields.subunit_type != AVC_SUBUNIT_TYPE) ||
+ (avc_fields.subunit_id != AVC_SUBUNIT_ID) ||
+ (avc_fields.opcode != AVC_OPCODE) ||
+ (avc_fields.company_id != AVC_COMPANY_ID)) {
+ snd_printk(KERN_INFO "AV/C Failed: 0x%X 0x%X 0x%X 0x%X 0x%X, 0x%X\n",
+ avc_fields.cts, avc_fields.ctype,
+ avc_fields.subunit_type, avc_fields.subunit_id,
+ avc_fields.opcode, avc_fields.company_id);
+ err = -EIO;
+ goto end;
+ }
+
+ /* check EFC response fields */
+ efc_fields = (struct efc_fields *)(cmdbuf + 2);
+ if ((efc_fields->seqnum != sequence_number) |
+ (efc_fields->version < 1) |
+ (efc_fields->category != category) |
+ (efc_fields->command != command) |
+ (efc_fields->retval != EFC_RETVAL_OK)) {
+ snd_printk(KERN_INFO "EFC Failed [%u/%u/%u]: %X\n",
+ efc_fields->version, efc_fields->category,
+ efc_fields->command, efc_fields->retval);
+ err = -EIO;
+ goto end;
+ }
+
+ /* fill response buffer */
+ memset(response, 0, response_quadlets);
+ if (response_quadlets > efc_fields->length)
+ response_quadlets = efc_fields->length;
+ memcpy(response, cmdbuf + 8, response_quadlets * 4);
+
+ err = 0;
+end:
+ kfree(cmdbuf);
+ return err;
+}
+
+int snd_efw_command_identify(struct snd_efw *efw)
+{
+ return efc_over_avc(efw, EFC_CAT_HWCTL,
+ EFC_CMD_HWCTL_IDENTIFY,
+ NULL, 0, NULL, 0);
+}
+
+int snd_efw_command_get_hwinfo(struct snd_efw *efw,
+ struct snd_efw_hwinfo *hwinfo)
+{
+ u32 *tmp;
+ int i;
+ int count;
+ int err = efc_over_avc(efw, EFC_CAT_HWINFO,
+ EFC_CMD_HWINFO_GET_CAPS,
+ NULL, 0, hwinfo, sizeof(*hwinfo) / 4);
+ if (err < 0)
+ goto end;
+
+ /* arrangement for endianness */
+ count = HWINFO_NAME_SIZE_BYTES / 4;
+ tmp = (u32 *)&hwinfo->vendor_name;
+ for (i = 0; i < count; i += 1)
+ tmp[i] = cpu_to_be32(tmp[i]);
+ tmp = (u32 *)&hwinfo->model_name;
+ for (i = 0; i < count; i += 1)
+ tmp[i] = cpu_to_be32(tmp[i]);
+
+ count = sizeof(struct snd_efw_phys_group) * HWINFO_MAX_CAPS_GROUPS / 4;
+ tmp = (u32 *)&hwinfo->out_groups;
+ for (i = 0; i < count; i += 1)
+ tmp[i] = cpu_to_be32(tmp[i]);
+ tmp = (u32 *)&hwinfo->in_groups;
+ for (i = 0; i < count; i += 1)
+ tmp[i] = cpu_to_be32(tmp[i]);
+
+ /* ensure terminated */
+ hwinfo->vendor_name[HWINFO_NAME_SIZE_BYTES - 1] = '\0';
+ hwinfo->model_name[HWINFO_NAME_SIZE_BYTES - 1] = '\0';
+
+ err = 0;
+end:
+ return err;
+}
+
+int snd_efw_command_get_phys_meters(struct snd_efw *efw,
+ struct snd_efw_phys_meters *meters,
+ int len)
+{
+ return efc_over_avc(efw, EFC_CAT_HWINFO,
+ EFC_CMD_HWINFO_GET_POLLED,
+ NULL, 0, meters, len / 4);
+}
+
+static int
+command_get_clock(struct snd_efw *efw, struct efc_clock *clock)
+{
+ return efc_over_avc(efw, EFC_CAT_HWCTL,
+ EFC_CMD_HWCTL_GET_CLOCK,
+ NULL, 0, clock, sizeof(struct efc_clock) / 4);
+}
+
+static int
+command_set_clock(struct snd_efw *efw,
+ int source, int sampling_rate)
+{
+ int err;
+
+ struct efc_clock clock = {0};
+
+ /* check arguments */
+ if ((source < 0) && (sampling_rate < 0)) {
+ err = -EINVAL;
+ goto end;
+ }
+
+ /* get current status */
+ err = command_get_clock(efw, &clock);
+ if (err < 0)
+ goto end;
+
+ /* no need */
+ if ((clock.source == source) &&
+ (clock.sampling_rate == sampling_rate))
+ goto end;
+
+ /* set params */
+ if ((source >= 0) && (clock.source != source))
+ clock.source = source;
+ if ((sampling_rate > 0) && (clock.sampling_rate != sampling_rate))
+ clock.sampling_rate = sampling_rate;
+ clock.index = 0;
+
+ err = efc_over_avc(efw, EFC_CAT_HWCTL,
+ EFC_CMD_HWCTL_SET_CLOCK,
+ (u32 *)&clock, 3, NULL, 0);
+
+ err = 0;
+end:
+ return err;
+}
+
+int snd_efw_command_get_clock_source(struct snd_efw *efw,
+ enum snd_efw_clock_source *source)
+{
+ int err;
+ struct efc_clock clock = {0};
+
+ err = command_get_clock(efw, &clock);
+ if (err >= 0)
+ *source = clock.source;
+
+ return err;
+}
+
+int snd_efw_command_set_clock_source(struct snd_efw *efw,
+ enum snd_efw_clock_source source)
+{
+ return command_set_clock(efw, source, -1);
+}
+
+int snd_efw_command_get_sampling_rate(struct snd_efw *efw,
+ int *sampling_rate)
+{
+ int err;
+ struct efc_clock clock = {0};
+
+ err = command_get_clock(efw, &clock);
+ if (err >= 0)
+ *sampling_rate = clock.sampling_rate;
+
+ return err;
+}
+
+int
+snd_efw_command_set_sampling_rate(struct snd_efw *efw, int sampling_rate)
+{
+ return command_set_clock(efw, -1, sampling_rate);
+}
+
+int snd_efw_command_get_iec60958_format(struct snd_efw *efw,
+ enum snd_efw_iec60958_format *format)
+{
+ int err;
+ u32 flag = {0};
+
+ err = efc_over_avc(efw, EFC_CAT_HWCTL,
+ EFC_CMD_HWCTL_GET_FLAGS,
+ NULL, 0, &flag, 1);
+ if (err >= 0) {
+ if (flag & EFC_HWCTL_FLAG_DIGITAL_PRO)
+ *format = SND_EFW_IEC60958_FORMAT_PROFESSIONAL;
+ else
+ *format = SND_EFW_IEC60958_FORMAT_CONSUMER;
+ }
+
+ return err;
+}
+
+int snd_efw_command_set_iec60958_format(struct snd_efw *efw,
+ enum snd_efw_iec60958_format format)
+{
+ /*
+ * mask[0]: for set
+ * mask[1]: for clear
+ */
+ u32 mask[2] = {0};
+
+ if (format == SND_EFW_IEC60958_FORMAT_PROFESSIONAL)
+ mask[0] = EFC_HWCTL_FLAG_DIGITAL_PRO;
+ else
+ mask[1] = EFC_HWCTL_FLAG_DIGITAL_PRO;
+
+ return efc_over_avc(efw, EFC_CAT_HWCTL,
+ EFC_CMD_HWCTL_CHANGE_FLAGS,
+ (u32 *)mask, 2, NULL, 0);
+}
+
+int snd_efw_command_get_digital_interface(struct snd_efw *efw,
+ enum snd_efw_digital_interface *digital_interface)
+{
+ int err;
+ u32 value = 0;
+
+ err = efc_over_avc(efw, EFC_CAT_IOCONF,
+ EFC_CMD_IOCONF_GET_DIGITAL_MODE,
+ NULL, 0, &value, 1);
+
+ if (err >= 0)
+ *digital_interface = value;
+
+ return err;
+}
+
+int snd_efw_command_set_digital_interface(struct snd_efw *efw,
+ enum snd_efw_digital_interface digital_interface)
+{
+ u32 value = digital_interface;
+
+ return efc_over_avc(efw, EFC_CAT_IOCONF,
+ EFC_CMD_IOCONF_SET_DIGITAL_MODE,
+ &value, 1, NULL, 0);
+}
--
1.7.10.4
More information about the Alsa-devel
mailing list