798 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * bebob_maudio.c - a part of driver for BeBoB based devices4 *5 * Copyright (c) 2013-2014 Takashi Sakamoto6 */7 8#include "./bebob.h"9#include <sound/control.h>10 11/*12 * Just powering on, Firewire 410/Audiophile/1814 and ProjectMix I/O wait to13 * download firmware blob. To enable these devices, drivers should upload14 * firmware blob and send a command to initialize configuration to factory15 * settings when completing uploading. Then these devices generate bus reset16 * and are recognized as new devices with the firmware.17 *18 * But with firmware version 5058 or later, the firmware is stored to flash19 * memory in the device and drivers can tell bootloader to load the firmware20 * by sending a cue. This cue must be sent one time.21 *22 * For streaming, both of output and input streams are needed for Firewire 41023 * and Ozonic. The single stream is OK for the other devices even if the clock24 * source is not SYT-Match (I note no devices use SYT-Match).25 *26 * Without streaming, the devices except for Firewire Audiophile can mix any27 * input and output. For this reason, Audiophile cannot be used as standalone28 * mixer.29 *30 * Firewire 1814 and ProjectMix I/O uses special firmware. It will be freezed31 * when receiving any commands which the firmware can't understand. These32 * devices utilize completely different system to control. It is some33 * write-transaction directly into a certain address. All of addresses for mixer34 * functionality is between 0xffc700700000 to 0xffc70070009c.35 */36 37/* Offset from information register */38#define INFO_OFFSET_SW_DATE 0x2039 40/* Bootloader Protocol Version 1 */41#define MAUDIO_BOOTLOADER_CUE1 0x0000000142/*43 * Initializing configuration to factory settings (= 0x1101), (swapped in line),44 * Command code is zero (= 0x00),45 * the number of operands is zero (= 0x00)(at least significant byte)46 */47#define MAUDIO_BOOTLOADER_CUE2 0x0111000048/* padding */49#define MAUDIO_BOOTLOADER_CUE3 0x0000000050 51#define MAUDIO_SPECIFIC_ADDRESS 0xffc700000000ULL52 53#define METER_OFFSET 0x0060000054 55/* some device has sync info after metering data */56#define METER_SIZE_SPECIAL 84 /* with sync info */57#define METER_SIZE_FW410 76 /* with sync info */58#define METER_SIZE_AUDIOPHILE 60 /* with sync info */59#define METER_SIZE_SOLO 52 /* with sync info */60#define METER_SIZE_OZONIC 4861#define METER_SIZE_NRV10 8062 63/* labels for metering */64#define ANA_IN "Analog In"65#define ANA_OUT "Analog Out"66#define DIG_IN "Digital In"67#define SPDIF_IN "S/PDIF In"68#define ADAT_IN "ADAT In"69#define DIG_OUT "Digital Out"70#define SPDIF_OUT "S/PDIF Out"71#define ADAT_OUT "ADAT Out"72#define STRM_IN "Stream In"73#define AUX_OUT "Aux Out"74#define HP_OUT "HP Out"75/* for NRV */76#define UNKNOWN_METER "Unknown"77 78struct special_params {79 bool is1814;80 unsigned int clk_src;81 unsigned int dig_in_fmt;82 unsigned int dig_out_fmt;83 unsigned int clk_lock;84 struct snd_ctl_elem_id *ctl_id_sync;85};86 87/*88 * For some M-Audio devices, this module just send cue to load firmware. After89 * loading, the device generates bus reset and newly detected.90 *91 * If we make any transactions to load firmware, the operation may failed.92 */93int snd_bebob_maudio_load_firmware(struct fw_unit *unit)94{95 struct fw_device *device = fw_parent_device(unit);96 int err, rcode;97 u64 date;98 __le32 *cues;99 100 /* check date of software used to build */101 err = snd_bebob_read_block(unit, INFO_OFFSET_SW_DATE,102 &date, sizeof(u64));103 if (err < 0)104 return err;105 /*106 * firmware version 5058 or later has date later than "20070401", but107 * 'date' is not null-terminated.108 */109 if (date < 0x3230303730343031LL) {110 dev_err(&unit->device,111 "Use firmware version 5058 or later\n");112 return -ENXIO;113 }114 115 cues = kmalloc_array(3, sizeof(*cues), GFP_KERNEL);116 if (!cues)117 return -ENOMEM;118 119 cues[0] = cpu_to_le32(MAUDIO_BOOTLOADER_CUE1);120 cues[1] = cpu_to_le32(MAUDIO_BOOTLOADER_CUE2);121 cues[2] = cpu_to_le32(MAUDIO_BOOTLOADER_CUE3);122 123 rcode = fw_run_transaction(device->card, TCODE_WRITE_BLOCK_REQUEST,124 device->node_id, device->generation,125 device->max_speed, BEBOB_ADDR_REG_REQ,126 cues, 3 * sizeof(*cues));127 kfree(cues);128 if (rcode != RCODE_COMPLETE) {129 dev_err(&unit->device,130 "Failed to send a cue to load firmware\n");131 err = -EIO;132 }133 134 return err;135}136 137static inline int138get_meter(struct snd_bebob *bebob, void *buf, unsigned int size)139{140 return snd_fw_transaction(bebob->unit, TCODE_READ_BLOCK_REQUEST,141 MAUDIO_SPECIFIC_ADDRESS + METER_OFFSET,142 buf, size, 0);143}144 145static int146check_clk_sync(struct snd_bebob *bebob, unsigned int size, bool *sync)147{148 int err;149 u8 *buf;150 151 buf = kmalloc(size, GFP_KERNEL);152 if (buf == NULL)153 return -ENOMEM;154 155 err = get_meter(bebob, buf, size);156 if (err < 0)157 goto end;158 159 /* if synced, this value is the same as SFC of FDF in CIP header */160 *sync = (buf[size - 2] != 0xff);161end:162 kfree(buf);163 return err;164}165 166/*167 * dig_fmt: 0x00:S/PDIF, 0x01:ADAT168 * clk_lock: 0x00:unlock, 0x01:lock169 */170static int171avc_maudio_set_special_clk(struct snd_bebob *bebob, unsigned int clk_src,172 unsigned int dig_in_fmt, unsigned int dig_out_fmt,173 unsigned int clk_lock)174{175 struct special_params *params = bebob->maudio_special_quirk;176 int err;177 u8 *buf;178 179 if (amdtp_stream_running(&bebob->rx_stream) ||180 amdtp_stream_running(&bebob->tx_stream))181 return -EBUSY;182 183 buf = kmalloc(12, GFP_KERNEL);184 if (buf == NULL)185 return -ENOMEM;186 187 buf[0] = 0x00; /* CONTROL */188 buf[1] = 0xff; /* UNIT */189 buf[2] = 0x00; /* vendor dependent */190 buf[3] = 0x04; /* company ID high */191 buf[4] = 0x00; /* company ID middle */192 buf[5] = 0x04; /* company ID low */193 buf[6] = 0xff & clk_src; /* clock source */194 buf[7] = 0xff & dig_in_fmt; /* input digital format */195 buf[8] = 0xff & dig_out_fmt; /* output digital format */196 buf[9] = 0xff & clk_lock; /* lock these settings */197 buf[10] = 0x00; /* padding */198 buf[11] = 0x00; /* padding */199 200 err = fcp_avc_transaction(bebob->unit, buf, 12, buf, 12,201 BIT(1) | BIT(2) | BIT(3) | BIT(4) |202 BIT(5) | BIT(6) | BIT(7) | BIT(8) |203 BIT(9));204 if ((err > 0) && (err < 10))205 err = -EIO;206 else if (buf[0] == 0x08) /* NOT IMPLEMENTED */207 err = -ENOSYS;208 else if (buf[0] == 0x0a) /* REJECTED */209 err = -EINVAL;210 if (err < 0)211 goto end;212 213 params->clk_src = buf[6];214 params->dig_in_fmt = buf[7];215 params->dig_out_fmt = buf[8];216 params->clk_lock = buf[9];217 218 if (params->ctl_id_sync)219 snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE,220 params->ctl_id_sync);221 222 err = 0;223end:224 kfree(buf);225 return err;226}227static void228special_stream_formation_set(struct snd_bebob *bebob)229{230 static const unsigned int ch_table[2][2][3] = {231 /* AMDTP_OUT_STREAM */232 { { 6, 6, 4 }, /* SPDIF */233 { 12, 8, 4 } }, /* ADAT */234 /* AMDTP_IN_STREAM */235 { { 10, 10, 2 }, /* SPDIF */236 { 16, 12, 2 } } /* ADAT */237 };238 struct special_params *params = bebob->maudio_special_quirk;239 unsigned int i, max;240 241 max = SND_BEBOB_STRM_FMT_ENTRIES - 1;242 if (!params->is1814)243 max -= 2;244 245 for (i = 0; i < max; i++) {246 bebob->tx_stream_formations[i + 1].pcm =247 ch_table[AMDTP_IN_STREAM][params->dig_in_fmt][i / 2];248 bebob->tx_stream_formations[i + 1].midi = 1;249 250 bebob->rx_stream_formations[i + 1].pcm =251 ch_table[AMDTP_OUT_STREAM][params->dig_out_fmt][i / 2];252 bebob->rx_stream_formations[i + 1].midi = 1;253 }254}255 256static int add_special_controls(struct snd_bebob *bebob);257int258snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814)259{260 struct special_params *params;261 int err;262 263 params = devm_kzalloc(&bebob->card->card_dev,264 sizeof(struct special_params), GFP_KERNEL);265 if (!params)266 return -ENOMEM;267 268 mutex_lock(&bebob->mutex);269 270 bebob->maudio_special_quirk = (void *)params;271 params->is1814 = is1814;272 273 /* initialize these parameters because driver is not allowed to ask */274 bebob->rx_stream.context = ERR_PTR(-1);275 bebob->tx_stream.context = ERR_PTR(-1);276 err = avc_maudio_set_special_clk(bebob, 0x03, 0x00, 0x00, 0x00);277 if (err < 0) {278 dev_err(&bebob->unit->device,279 "fail to initialize clock params: %d\n", err);280 goto end;281 }282 283 err = add_special_controls(bebob);284 if (err < 0)285 goto end;286 287 special_stream_formation_set(bebob);288 289 if (params->is1814) {290 bebob->midi_input_ports = 1;291 bebob->midi_output_ports = 1;292 } else {293 bebob->midi_input_ports = 2;294 bebob->midi_output_ports = 2;295 }296end:297 mutex_unlock(&bebob->mutex);298 return err;299}300 301/* Input plug shows actual rate. Output plug is needless for this purpose. */302static int special_get_rate(struct snd_bebob *bebob, unsigned int *rate)303{304 int err, trials;305 306 trials = 0;307 do {308 err = avc_general_get_sig_fmt(bebob->unit, rate,309 AVC_GENERAL_PLUG_DIR_IN, 0);310 } while (err == -EAGAIN && ++trials < 3);311 312 return err;313}314static int special_set_rate(struct snd_bebob *bebob, unsigned int rate)315{316 struct special_params *params = bebob->maudio_special_quirk;317 int err;318 319 err = avc_general_set_sig_fmt(bebob->unit, rate,320 AVC_GENERAL_PLUG_DIR_OUT, 0);321 if (err < 0)322 goto end;323 324 /*325 * Just after changing sampling rate for output, a followed command326 * for input is easy to fail. This is a workaround fot this issue.327 */328 msleep(100);329 330 err = avc_general_set_sig_fmt(bebob->unit, rate,331 AVC_GENERAL_PLUG_DIR_IN, 0);332 if (err < 0)333 goto end;334 335 if (params->ctl_id_sync)336 snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE,337 params->ctl_id_sync);338end:339 return err;340}341 342/* Clock source control for special firmware */343static const enum snd_bebob_clock_type special_clk_types[] = {344 SND_BEBOB_CLOCK_TYPE_INTERNAL, /* With digital mute */345 SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* SPDIF/ADAT */346 SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* Word Clock */347 SND_BEBOB_CLOCK_TYPE_INTERNAL,348};349static int special_clk_get(struct snd_bebob *bebob, unsigned int *id)350{351 struct special_params *params = bebob->maudio_special_quirk;352 *id = params->clk_src;353 return 0;354}355static int special_clk_ctl_info(struct snd_kcontrol *kctl,356 struct snd_ctl_elem_info *einf)357{358 static const char *const special_clk_labels[] = {359 "Internal with Digital Mute",360 "Digital",361 "Word Clock",362 "Internal"363 };364 return snd_ctl_enum_info(einf, 1, ARRAY_SIZE(special_clk_types),365 special_clk_labels);366}367static int special_clk_ctl_get(struct snd_kcontrol *kctl,368 struct snd_ctl_elem_value *uval)369{370 struct snd_bebob *bebob = snd_kcontrol_chip(kctl);371 struct special_params *params = bebob->maudio_special_quirk;372 uval->value.enumerated.item[0] = params->clk_src;373 return 0;374}375static int special_clk_ctl_put(struct snd_kcontrol *kctl,376 struct snd_ctl_elem_value *uval)377{378 struct snd_bebob *bebob = snd_kcontrol_chip(kctl);379 struct special_params *params = bebob->maudio_special_quirk;380 int err, id;381 382 id = uval->value.enumerated.item[0];383 if (id >= ARRAY_SIZE(special_clk_types))384 return -EINVAL;385 386 mutex_lock(&bebob->mutex);387 388 err = avc_maudio_set_special_clk(bebob, id,389 params->dig_in_fmt,390 params->dig_out_fmt,391 params->clk_lock);392 mutex_unlock(&bebob->mutex);393 394 if (err >= 0)395 err = 1;396 397 return err;398}399static const struct snd_kcontrol_new special_clk_ctl = {400 .name = "Clock Source",401 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,402 .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,403 .info = special_clk_ctl_info,404 .get = special_clk_ctl_get,405 .put = special_clk_ctl_put406};407 408/* Clock synchronization control for special firmware */409static int special_sync_ctl_info(struct snd_kcontrol *kctl,410 struct snd_ctl_elem_info *einf)411{412 einf->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;413 einf->count = 1;414 einf->value.integer.min = 0;415 einf->value.integer.max = 1;416 417 return 0;418}419static int special_sync_ctl_get(struct snd_kcontrol *kctl,420 struct snd_ctl_elem_value *uval)421{422 struct snd_bebob *bebob = snd_kcontrol_chip(kctl);423 int err;424 bool synced = 0;425 426 err = check_clk_sync(bebob, METER_SIZE_SPECIAL, &synced);427 if (err >= 0)428 uval->value.integer.value[0] = synced;429 430 return 0;431}432static const struct snd_kcontrol_new special_sync_ctl = {433 .name = "Sync Status",434 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,435 .access = SNDRV_CTL_ELEM_ACCESS_READ,436 .info = special_sync_ctl_info,437 .get = special_sync_ctl_get,438};439 440/* Digital input interface control for special firmware */441static const char *const special_dig_in_iface_labels[] = {442 "S/PDIF Optical", "S/PDIF Coaxial", "ADAT Optical"443};444static int special_dig_in_iface_ctl_info(struct snd_kcontrol *kctl,445 struct snd_ctl_elem_info *einf)446{447 return snd_ctl_enum_info(einf, 1,448 ARRAY_SIZE(special_dig_in_iface_labels),449 special_dig_in_iface_labels);450}451static int special_dig_in_iface_ctl_get(struct snd_kcontrol *kctl,452 struct snd_ctl_elem_value *uval)453{454 struct snd_bebob *bebob = snd_kcontrol_chip(kctl);455 struct special_params *params = bebob->maudio_special_quirk;456 unsigned int dig_in_iface;457 int err, val;458 459 mutex_lock(&bebob->mutex);460 461 err = avc_audio_get_selector(bebob->unit, 0x00, 0x04,462 &dig_in_iface);463 if (err < 0) {464 dev_err(&bebob->unit->device,465 "fail to get digital input interface: %d\n", err);466 goto end;467 }468 469 /* encoded id for user value */470 val = (params->dig_in_fmt << 1) | (dig_in_iface & 0x01);471 472 /* for ADAT Optical */473 if (val > 2)474 val = 2;475 476 uval->value.enumerated.item[0] = val;477end:478 mutex_unlock(&bebob->mutex);479 return err;480}481static int special_dig_in_iface_ctl_set(struct snd_kcontrol *kctl,482 struct snd_ctl_elem_value *uval)483{484 struct snd_bebob *bebob = snd_kcontrol_chip(kctl);485 struct special_params *params = bebob->maudio_special_quirk;486 unsigned int id, dig_in_fmt, dig_in_iface;487 int err;488 489 id = uval->value.enumerated.item[0];490 if (id >= ARRAY_SIZE(special_dig_in_iface_labels))491 return -EINVAL;492 493 /* decode user value */494 dig_in_fmt = (id >> 1) & 0x01;495 dig_in_iface = id & 0x01;496 497 mutex_lock(&bebob->mutex);498 499 err = avc_maudio_set_special_clk(bebob,500 params->clk_src,501 dig_in_fmt,502 params->dig_out_fmt,503 params->clk_lock);504 if (err < 0)505 goto end;506 507 /* For ADAT, optical interface is only available. */508 if (params->dig_in_fmt > 0) {509 err = 1;510 goto end;511 }512 513 /* For S/PDIF, optical/coaxial interfaces are selectable. */514 err = avc_audio_set_selector(bebob->unit, 0x00, 0x04, dig_in_iface);515 if (err < 0)516 dev_err(&bebob->unit->device,517 "fail to set digital input interface: %d\n", err);518 err = 1;519end:520 special_stream_formation_set(bebob);521 mutex_unlock(&bebob->mutex);522 return err;523}524static const struct snd_kcontrol_new special_dig_in_iface_ctl = {525 .name = "Digital Input Interface",526 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,527 .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,528 .info = special_dig_in_iface_ctl_info,529 .get = special_dig_in_iface_ctl_get,530 .put = special_dig_in_iface_ctl_set531};532 533/* Digital output interface control for special firmware */534static const char *const special_dig_out_iface_labels[] = {535 "S/PDIF Optical and Coaxial", "ADAT Optical"536};537static int special_dig_out_iface_ctl_info(struct snd_kcontrol *kctl,538 struct snd_ctl_elem_info *einf)539{540 return snd_ctl_enum_info(einf, 1,541 ARRAY_SIZE(special_dig_out_iface_labels),542 special_dig_out_iface_labels);543}544static int special_dig_out_iface_ctl_get(struct snd_kcontrol *kctl,545 struct snd_ctl_elem_value *uval)546{547 struct snd_bebob *bebob = snd_kcontrol_chip(kctl);548 struct special_params *params = bebob->maudio_special_quirk;549 mutex_lock(&bebob->mutex);550 uval->value.enumerated.item[0] = params->dig_out_fmt;551 mutex_unlock(&bebob->mutex);552 return 0;553}554static int special_dig_out_iface_ctl_set(struct snd_kcontrol *kctl,555 struct snd_ctl_elem_value *uval)556{557 struct snd_bebob *bebob = snd_kcontrol_chip(kctl);558 struct special_params *params = bebob->maudio_special_quirk;559 unsigned int id;560 int err;561 562 id = uval->value.enumerated.item[0];563 if (id >= ARRAY_SIZE(special_dig_out_iface_labels))564 return -EINVAL;565 566 mutex_lock(&bebob->mutex);567 568 err = avc_maudio_set_special_clk(bebob,569 params->clk_src,570 params->dig_in_fmt,571 id, params->clk_lock);572 if (err >= 0) {573 special_stream_formation_set(bebob);574 err = 1;575 }576 577 mutex_unlock(&bebob->mutex);578 return err;579}580static const struct snd_kcontrol_new special_dig_out_iface_ctl = {581 .name = "Digital Output Interface",582 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,583 .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,584 .info = special_dig_out_iface_ctl_info,585 .get = special_dig_out_iface_ctl_get,586 .put = special_dig_out_iface_ctl_set587};588 589static int add_special_controls(struct snd_bebob *bebob)590{591 struct snd_kcontrol *kctl;592 struct special_params *params = bebob->maudio_special_quirk;593 int err;594 595 kctl = snd_ctl_new1(&special_clk_ctl, bebob);596 err = snd_ctl_add(bebob->card, kctl);597 if (err < 0)598 goto end;599 600 kctl = snd_ctl_new1(&special_sync_ctl, bebob);601 err = snd_ctl_add(bebob->card, kctl);602 if (err < 0)603 goto end;604 params->ctl_id_sync = &kctl->id;605 606 kctl = snd_ctl_new1(&special_dig_in_iface_ctl, bebob);607 err = snd_ctl_add(bebob->card, kctl);608 if (err < 0)609 goto end;610 611 kctl = snd_ctl_new1(&special_dig_out_iface_ctl, bebob);612 err = snd_ctl_add(bebob->card, kctl);613end:614 return err;615}616 617/* Hardware metering for special firmware */618static const char *const special_meter_labels[] = {619 ANA_IN, ANA_IN, ANA_IN, ANA_IN,620 SPDIF_IN,621 ADAT_IN, ADAT_IN, ADAT_IN, ADAT_IN,622 ANA_OUT, ANA_OUT,623 SPDIF_OUT,624 ADAT_OUT, ADAT_OUT, ADAT_OUT, ADAT_OUT,625 HP_OUT, HP_OUT,626 AUX_OUT627};628static int629special_meter_get(struct snd_bebob *bebob, u32 *target, unsigned int size)630{631 __be16 *buf;632 unsigned int i, c, channels;633 int err;634 635 channels = ARRAY_SIZE(special_meter_labels) * 2;636 if (size < channels * sizeof(u32))637 return -EINVAL;638 639 /* omit last 4 bytes because it's clock info. */640 buf = kmalloc(METER_SIZE_SPECIAL - 4, GFP_KERNEL);641 if (buf == NULL)642 return -ENOMEM;643 644 err = get_meter(bebob, (void *)buf, METER_SIZE_SPECIAL - 4);645 if (err < 0)646 goto end;647 648 /* Its format is u16 and some channels are unknown. */649 i = 0;650 for (c = 2; c < channels + 2; c++)651 target[i++] = be16_to_cpu(buf[c]) << 16;652end:653 kfree(buf);654 return err;655}656 657/* last 4 bytes are omitted because it's clock info. */658static const char *const fw410_meter_labels[] = {659 ANA_IN, DIG_IN,660 ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, DIG_OUT,661 HP_OUT662};663static const char *const audiophile_meter_labels[] = {664 ANA_IN, DIG_IN,665 ANA_OUT, ANA_OUT, DIG_OUT,666 HP_OUT, AUX_OUT,667};668static const char *const solo_meter_labels[] = {669 ANA_IN, DIG_IN,670 STRM_IN, STRM_IN,671 ANA_OUT, DIG_OUT672};673 674/* no clock info */675static const char *const ozonic_meter_labels[] = {676 ANA_IN, ANA_IN,677 STRM_IN, STRM_IN,678 ANA_OUT, ANA_OUT679};680/* TODO: need testers. these positions are based on authour's assumption */681static const char *const nrv10_meter_labels[] = {682 ANA_IN, ANA_IN, ANA_IN, ANA_IN,683 DIG_IN,684 ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT,685 DIG_IN686};687static int688normal_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size)689{690 const struct snd_bebob_meter_spec *spec = bebob->spec->meter;691 unsigned int c, channels;692 int err;693 694 channels = spec->num * 2;695 if (size < channels * sizeof(u32))696 return -EINVAL;697 698 err = get_meter(bebob, (void *)buf, size);699 if (err < 0)700 goto end;701 702 for (c = 0; c < channels; c++)703 be32_to_cpus(&buf[c]);704 705 /* swap stream channels because inverted */706 if (spec->labels == solo_meter_labels) {707 swap(buf[4], buf[6]);708 swap(buf[5], buf[7]);709 }710end:711 return err;712}713 714/* for special customized devices */715static const struct snd_bebob_rate_spec special_rate_spec = {716 .get = &special_get_rate,717 .set = &special_set_rate,718};719static const struct snd_bebob_clock_spec special_clk_spec = {720 .num = ARRAY_SIZE(special_clk_types),721 .types = special_clk_types,722 .get = &special_clk_get,723};724static const struct snd_bebob_meter_spec special_meter_spec = {725 .num = ARRAY_SIZE(special_meter_labels),726 .labels = special_meter_labels,727 .get = &special_meter_get728};729const struct snd_bebob_spec maudio_special_spec = {730 .clock = &special_clk_spec,731 .rate = &special_rate_spec,732 .meter = &special_meter_spec733};734 735/* Firewire 410 specification */736static const struct snd_bebob_rate_spec usual_rate_spec = {737 .get = &snd_bebob_stream_get_rate,738 .set = &snd_bebob_stream_set_rate,739};740static const struct snd_bebob_meter_spec fw410_meter_spec = {741 .num = ARRAY_SIZE(fw410_meter_labels),742 .labels = fw410_meter_labels,743 .get = &normal_meter_get744};745const struct snd_bebob_spec maudio_fw410_spec = {746 .clock = NULL,747 .rate = &usual_rate_spec,748 .meter = &fw410_meter_spec749};750 751/* Firewire Audiophile specification */752static const struct snd_bebob_meter_spec audiophile_meter_spec = {753 .num = ARRAY_SIZE(audiophile_meter_labels),754 .labels = audiophile_meter_labels,755 .get = &normal_meter_get756};757const struct snd_bebob_spec maudio_audiophile_spec = {758 .clock = NULL,759 .rate = &usual_rate_spec,760 .meter = &audiophile_meter_spec761};762 763/* Firewire Solo specification */764static const struct snd_bebob_meter_spec solo_meter_spec = {765 .num = ARRAY_SIZE(solo_meter_labels),766 .labels = solo_meter_labels,767 .get = &normal_meter_get768};769const struct snd_bebob_spec maudio_solo_spec = {770 .clock = NULL,771 .rate = &usual_rate_spec,772 .meter = &solo_meter_spec773};774 775/* Ozonic specification */776static const struct snd_bebob_meter_spec ozonic_meter_spec = {777 .num = ARRAY_SIZE(ozonic_meter_labels),778 .labels = ozonic_meter_labels,779 .get = &normal_meter_get780};781const struct snd_bebob_spec maudio_ozonic_spec = {782 .clock = NULL,783 .rate = &usual_rate_spec,784 .meter = &ozonic_meter_spec785};786 787/* NRV10 specification */788static const struct snd_bebob_meter_spec nrv10_meter_spec = {789 .num = ARRAY_SIZE(nrv10_meter_labels),790 .labels = nrv10_meter_labels,791 .get = &normal_meter_get792};793const struct snd_bebob_spec maudio_nrv10_spec = {794 .clock = NULL,795 .rate = &usual_rate_spec,796 .meter = &nrv10_meter_spec797};798