Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 1 | /* |
| 2 | * bebob_maudio.c - a part of driver for BeBoB based devices |
| 3 | * |
| 4 | * Copyright (c) 2013-2014 Takashi Sakamoto |
| 5 | * |
| 6 | * Licensed under the terms of the GNU General Public License, version 2. |
| 7 | */ |
| 8 | |
| 9 | #include "./bebob.h" |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 10 | #include <sound/control.h> |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 11 | |
| 12 | /* |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 13 | * Just powering on, Firewire 410/Audiophile/1814 and ProjectMix I/O wait to |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 14 | * download firmware blob. To enable these devices, drivers should upload |
| 15 | * firmware blob and send a command to initialize configuration to factory |
| 16 | * settings when completing uploading. Then these devices generate bus reset |
| 17 | * and are recognized as new devices with the firmware. |
| 18 | * |
Takashi Sakamoto | a2b2a77 | 2014-04-25 22:45:29 +0900 | [diff] [blame] | 19 | * But with firmware version 5058 or later, the firmware is stored to flash |
| 20 | * memory in the device and drivers can tell bootloader to load the firmware |
| 21 | * by sending a cue. This cue must be sent one time. |
| 22 | * |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 23 | * For streaming, both of output and input streams are needed for Firewire 410 |
| 24 | * and Ozonic. The single stream is OK for the other devices even if the clock |
| 25 | * source is not SYT-Match (I note no devices use SYT-Match). |
| 26 | * |
| 27 | * Without streaming, the devices except for Firewire Audiophile can mix any |
| 28 | * input and output. For this reason, Audiophile cannot be used as standalone |
| 29 | * mixer. |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 30 | * |
| 31 | * Firewire 1814 and ProjectMix I/O uses special firmware. It will be freezed |
| 32 | * when receiving any commands which the firmware can't understand. These |
| 33 | * devices utilize completely different system to control. It is some |
| 34 | * write-transaction directly into a certain address. All of addresses for mixer |
| 35 | * functionality is between 0xffc700700000 to 0xffc70070009c. |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 36 | */ |
| 37 | |
Takashi Sakamoto | a2b2a77 | 2014-04-25 22:45:29 +0900 | [diff] [blame] | 38 | /* Offset from information register */ |
| 39 | #define INFO_OFFSET_SW_DATE 0x20 |
| 40 | |
| 41 | /* Bootloader Protocol Version 1 */ |
| 42 | #define MAUDIO_BOOTLOADER_CUE1 0x00000001 |
| 43 | /* |
| 44 | * Initializing configuration to factory settings (= 0x1101), (swapped in line), |
| 45 | * Command code is zero (= 0x00), |
| 46 | * the number of operands is zero (= 0x00)(at least significant byte) |
| 47 | */ |
| 48 | #define MAUDIO_BOOTLOADER_CUE2 0x01110000 |
| 49 | /* padding */ |
| 50 | #define MAUDIO_BOOTLOADER_CUE3 0x00000000 |
| 51 | |
Takashi Sakamoto | 9b5f0ed | 2014-05-28 00:14:42 +0900 | [diff] [blame] | 52 | #define MAUDIO_SPECIFIC_ADDRESS 0xffc700000000ULL |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 53 | |
| 54 | #define METER_OFFSET 0x00600000 |
| 55 | |
| 56 | /* some device has sync info after metering data */ |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 57 | #define METER_SIZE_SPECIAL 84 /* with sync info */ |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 58 | #define METER_SIZE_FW410 76 /* with sync info */ |
| 59 | #define METER_SIZE_AUDIOPHILE 60 /* with sync info */ |
| 60 | #define METER_SIZE_SOLO 52 /* with sync info */ |
| 61 | #define METER_SIZE_OZONIC 48 |
| 62 | #define METER_SIZE_NRV10 80 |
| 63 | |
| 64 | /* labels for metering */ |
| 65 | #define ANA_IN "Analog In" |
| 66 | #define ANA_OUT "Analog Out" |
| 67 | #define DIG_IN "Digital In" |
| 68 | #define SPDIF_IN "S/PDIF In" |
| 69 | #define ADAT_IN "ADAT In" |
| 70 | #define DIG_OUT "Digital Out" |
| 71 | #define SPDIF_OUT "S/PDIF Out" |
| 72 | #define ADAT_OUT "ADAT Out" |
| 73 | #define STRM_IN "Stream In" |
| 74 | #define AUX_OUT "Aux Out" |
| 75 | #define HP_OUT "HP Out" |
| 76 | /* for NRV */ |
| 77 | #define UNKNOWN_METER "Unknown" |
| 78 | |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 79 | struct special_params { |
| 80 | bool is1814; |
| 81 | unsigned int clk_src; |
| 82 | unsigned int dig_in_fmt; |
| 83 | unsigned int dig_out_fmt; |
| 84 | unsigned int clk_lock; |
| 85 | struct snd_ctl_elem_id *ctl_id_sync; |
| 86 | }; |
| 87 | |
Takashi Sakamoto | a2b2a77 | 2014-04-25 22:45:29 +0900 | [diff] [blame] | 88 | /* |
| 89 | * For some M-Audio devices, this module just send cue to load firmware. After |
| 90 | * loading, the device generates bus reset and newly detected. |
| 91 | * |
| 92 | * If we make any transactions to load firmware, the operation may failed. |
| 93 | */ |
| 94 | int snd_bebob_maudio_load_firmware(struct fw_unit *unit) |
| 95 | { |
| 96 | struct fw_device *device = fw_parent_device(unit); |
| 97 | int err, rcode; |
| 98 | u64 date; |
Takashi Sakamoto | a053fc3 | 2015-04-09 01:15:03 +0900 | [diff] [blame] | 99 | __le32 cues[3] = { |
| 100 | cpu_to_le32(MAUDIO_BOOTLOADER_CUE1), |
| 101 | cpu_to_le32(MAUDIO_BOOTLOADER_CUE2), |
| 102 | cpu_to_le32(MAUDIO_BOOTLOADER_CUE3) |
Takashi Sakamoto | a2b2a77 | 2014-04-25 22:45:29 +0900 | [diff] [blame] | 103 | }; |
| 104 | |
| 105 | /* check date of software used to build */ |
| 106 | err = snd_bebob_read_block(unit, INFO_OFFSET_SW_DATE, |
| 107 | &date, sizeof(u64)); |
| 108 | if (err < 0) |
| 109 | goto end; |
| 110 | /* |
| 111 | * firmware version 5058 or later has date later than "20070401", but |
| 112 | * 'date' is not null-terminated. |
| 113 | */ |
Takashi Sakamoto | 9b5f0ed | 2014-05-28 00:14:42 +0900 | [diff] [blame] | 114 | if (date < 0x3230303730343031LL) { |
Takashi Sakamoto | a2b2a77 | 2014-04-25 22:45:29 +0900 | [diff] [blame] | 115 | dev_err(&unit->device, |
| 116 | "Use firmware version 5058 or later\n"); |
| 117 | err = -ENOSYS; |
| 118 | goto end; |
| 119 | } |
| 120 | |
| 121 | rcode = fw_run_transaction(device->card, TCODE_WRITE_BLOCK_REQUEST, |
| 122 | device->node_id, device->generation, |
| 123 | device->max_speed, BEBOB_ADDR_REG_REQ, |
| 124 | cues, sizeof(cues)); |
| 125 | if (rcode != RCODE_COMPLETE) { |
| 126 | dev_err(&unit->device, |
| 127 | "Failed to send a cue to load firmware\n"); |
| 128 | err = -EIO; |
| 129 | } |
| 130 | end: |
| 131 | return err; |
| 132 | } |
| 133 | |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 134 | static inline int |
| 135 | get_meter(struct snd_bebob *bebob, void *buf, unsigned int size) |
| 136 | { |
| 137 | return snd_fw_transaction(bebob->unit, TCODE_READ_BLOCK_REQUEST, |
| 138 | MAUDIO_SPECIFIC_ADDRESS + METER_OFFSET, |
| 139 | buf, size, 0); |
| 140 | } |
| 141 | |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 142 | static int |
| 143 | check_clk_sync(struct snd_bebob *bebob, unsigned int size, bool *sync) |
| 144 | { |
| 145 | int err; |
| 146 | u8 *buf; |
| 147 | |
| 148 | buf = kmalloc(size, GFP_KERNEL); |
| 149 | if (buf == NULL) |
| 150 | return -ENOMEM; |
| 151 | |
| 152 | err = get_meter(bebob, buf, size); |
| 153 | if (err < 0) |
| 154 | goto end; |
| 155 | |
| 156 | /* if synced, this value is the same as SFC of FDF in CIP header */ |
| 157 | *sync = (buf[size - 2] != 0xff); |
| 158 | end: |
| 159 | kfree(buf); |
| 160 | return err; |
| 161 | } |
| 162 | |
| 163 | /* |
| 164 | * dig_fmt: 0x00:S/PDIF, 0x01:ADAT |
| 165 | * clk_lock: 0x00:unlock, 0x01:lock |
| 166 | */ |
| 167 | static int |
| 168 | avc_maudio_set_special_clk(struct snd_bebob *bebob, unsigned int clk_src, |
| 169 | unsigned int dig_in_fmt, unsigned int dig_out_fmt, |
| 170 | unsigned int clk_lock) |
| 171 | { |
| 172 | struct special_params *params = bebob->maudio_special_quirk; |
| 173 | int err; |
| 174 | u8 *buf; |
| 175 | |
| 176 | if (amdtp_stream_running(&bebob->rx_stream) || |
| 177 | amdtp_stream_running(&bebob->tx_stream)) |
| 178 | return -EBUSY; |
| 179 | |
| 180 | buf = kmalloc(12, GFP_KERNEL); |
| 181 | if (buf == NULL) |
| 182 | return -ENOMEM; |
| 183 | |
| 184 | buf[0] = 0x00; /* CONTROL */ |
| 185 | buf[1] = 0xff; /* UNIT */ |
| 186 | buf[2] = 0x00; /* vendor dependent */ |
| 187 | buf[3] = 0x04; /* company ID high */ |
| 188 | buf[4] = 0x00; /* company ID middle */ |
| 189 | buf[5] = 0x04; /* company ID low */ |
| 190 | buf[6] = 0xff & clk_src; /* clock source */ |
| 191 | buf[7] = 0xff & dig_in_fmt; /* input digital format */ |
| 192 | buf[8] = 0xff & dig_out_fmt; /* output digital format */ |
| 193 | buf[9] = 0xff & clk_lock; /* lock these settings */ |
| 194 | buf[10] = 0x00; /* padding */ |
| 195 | buf[11] = 0x00; /* padding */ |
| 196 | |
| 197 | err = fcp_avc_transaction(bebob->unit, buf, 12, buf, 12, |
| 198 | BIT(1) | BIT(2) | BIT(3) | BIT(4) | |
| 199 | BIT(5) | BIT(6) | BIT(7) | BIT(8) | |
| 200 | BIT(9)); |
| 201 | if ((err > 0) && (err < 10)) |
| 202 | err = -EIO; |
| 203 | else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ |
| 204 | err = -ENOSYS; |
| 205 | else if (buf[0] == 0x0a) /* REJECTED */ |
| 206 | err = -EINVAL; |
| 207 | if (err < 0) |
| 208 | goto end; |
| 209 | |
| 210 | params->clk_src = buf[6]; |
| 211 | params->dig_in_fmt = buf[7]; |
| 212 | params->dig_out_fmt = buf[8]; |
| 213 | params->clk_lock = buf[9]; |
| 214 | |
| 215 | if (params->ctl_id_sync) |
| 216 | snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE, |
| 217 | params->ctl_id_sync); |
| 218 | |
| 219 | err = 0; |
| 220 | end: |
| 221 | kfree(buf); |
| 222 | return err; |
| 223 | } |
| 224 | static void |
| 225 | special_stream_formation_set(struct snd_bebob *bebob) |
| 226 | { |
| 227 | static const unsigned int ch_table[2][2][3] = { |
| 228 | /* AMDTP_OUT_STREAM */ |
| 229 | { { 6, 6, 4 }, /* SPDIF */ |
| 230 | { 12, 8, 4 } }, /* ADAT */ |
| 231 | /* AMDTP_IN_STREAM */ |
| 232 | { { 10, 10, 2 }, /* SPDIF */ |
| 233 | { 16, 12, 2 } } /* ADAT */ |
| 234 | }; |
| 235 | struct special_params *params = bebob->maudio_special_quirk; |
| 236 | unsigned int i, max; |
| 237 | |
| 238 | max = SND_BEBOB_STRM_FMT_ENTRIES - 1; |
| 239 | if (!params->is1814) |
| 240 | max -= 2; |
| 241 | |
| 242 | for (i = 0; i < max; i++) { |
| 243 | bebob->tx_stream_formations[i + 1].pcm = |
| 244 | ch_table[AMDTP_IN_STREAM][params->dig_in_fmt][i / 2]; |
| 245 | bebob->tx_stream_formations[i + 1].midi = 1; |
| 246 | |
| 247 | bebob->rx_stream_formations[i + 1].pcm = |
| 248 | ch_table[AMDTP_OUT_STREAM][params->dig_out_fmt][i / 2]; |
| 249 | bebob->rx_stream_formations[i + 1].midi = 1; |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | static int add_special_controls(struct snd_bebob *bebob); |
| 254 | int |
| 255 | snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814) |
| 256 | { |
| 257 | struct special_params *params; |
| 258 | int err; |
| 259 | |
| 260 | params = kzalloc(sizeof(struct special_params), GFP_KERNEL); |
| 261 | if (params == NULL) |
| 262 | return -ENOMEM; |
| 263 | |
| 264 | mutex_lock(&bebob->mutex); |
| 265 | |
| 266 | bebob->maudio_special_quirk = (void *)params; |
| 267 | params->is1814 = is1814; |
| 268 | |
| 269 | /* initialize these parameters because driver is not allowed to ask */ |
| 270 | bebob->rx_stream.context = ERR_PTR(-1); |
| 271 | bebob->tx_stream.context = ERR_PTR(-1); |
| 272 | err = avc_maudio_set_special_clk(bebob, 0x03, 0x00, 0x00, 0x00); |
| 273 | if (err < 0) { |
| 274 | dev_err(&bebob->unit->device, |
| 275 | "fail to initialize clock params: %d\n", err); |
| 276 | goto end; |
| 277 | } |
| 278 | |
| 279 | err = add_special_controls(bebob); |
| 280 | if (err < 0) |
| 281 | goto end; |
| 282 | |
| 283 | special_stream_formation_set(bebob); |
| 284 | |
| 285 | if (params->is1814) { |
| 286 | bebob->midi_input_ports = 1; |
| 287 | bebob->midi_output_ports = 1; |
| 288 | } else { |
| 289 | bebob->midi_input_ports = 2; |
| 290 | bebob->midi_output_ports = 2; |
| 291 | } |
| 292 | end: |
| 293 | if (err < 0) { |
| 294 | kfree(params); |
| 295 | bebob->maudio_special_quirk = NULL; |
| 296 | } |
| 297 | mutex_unlock(&bebob->mutex); |
| 298 | return err; |
| 299 | } |
| 300 | |
| 301 | /* Input plug shows actual rate. Output plug is needless for this purpose. */ |
| 302 | static 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 | } |
| 314 | static 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 command |
| 326 | * 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); |
| 338 | end: |
| 339 | return err; |
| 340 | } |
| 341 | |
| 342 | /* Clock source control for special firmware */ |
Takashi Sakamoto | ba51771 | 2015-06-14 12:49:29 +0900 | [diff] [blame] | 343 | static 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 | }; |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 349 | static 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 | } |
| 355 | static int special_clk_ctl_info(struct snd_kcontrol *kctl, |
| 356 | struct snd_ctl_elem_info *einf) |
| 357 | { |
Takashi Sakamoto | 554d898 | 2015-06-14 12:49:31 +0900 | [diff] [blame] | 358 | static const char *const special_clk_labels[] = { |
| 359 | "Internal with Digital Mute", |
| 360 | "Digital", |
| 361 | "Word Clock", |
| 362 | "Internal" |
| 363 | }; |
Takashi Sakamoto | ba51771 | 2015-06-14 12:49:29 +0900 | [diff] [blame] | 364 | return snd_ctl_enum_info(einf, 1, ARRAY_SIZE(special_clk_types), |
Takashi Iwai | 41be516 | 2014-10-20 18:12:26 +0200 | [diff] [blame] | 365 | special_clk_labels); |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 366 | } |
| 367 | static 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 | } |
| 375 | static 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 | |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 382 | id = uval->value.enumerated.item[0]; |
Takashi Sakamoto | ba51771 | 2015-06-14 12:49:29 +0900 | [diff] [blame] | 383 | if (id >= ARRAY_SIZE(special_clk_types)) |
Takashi Sakamoto | eb12f72 | 2014-07-23 00:02:08 +0900 | [diff] [blame] | 384 | return -EINVAL; |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 385 | |
Takashi Sakamoto | 9014011 | 2014-07-22 23:11:03 +0900 | [diff] [blame] | 386 | mutex_lock(&bebob->mutex); |
| 387 | |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 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 | |
Takashi Sakamoto | f77ac91 | 2014-07-22 23:13:56 +0900 | [diff] [blame] | 394 | if (err >= 0) |
| 395 | err = 1; |
| 396 | |
| 397 | return err; |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 398 | } |
| 399 | static 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_put |
| 406 | }; |
| 407 | |
| 408 | /* Clock synchronization control for special firmware */ |
| 409 | static 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 | } |
| 419 | static 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 | } |
| 432 | static 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 | |
Takashi Sakamoto | 5a0438f | 2014-07-22 23:13:47 +0900 | [diff] [blame] | 440 | /* Digital input interface control for special firmware */ |
Takashi Iwai | e7ced41 | 2014-10-21 08:26:10 +0200 | [diff] [blame] | 441 | static const char *const special_dig_in_iface_labels[] = { |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 442 | "S/PDIF Optical", "S/PDIF Coaxial", "ADAT Optical" |
| 443 | }; |
| 444 | static int special_dig_in_iface_ctl_info(struct snd_kcontrol *kctl, |
| 445 | struct snd_ctl_elem_info *einf) |
| 446 | { |
Takashi Iwai | 41be516 | 2014-10-20 18:12:26 +0200 | [diff] [blame] | 447 | return snd_ctl_enum_info(einf, 1, |
| 448 | ARRAY_SIZE(special_dig_in_iface_labels), |
| 449 | special_dig_in_iface_labels); |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 450 | } |
| 451 | static 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; |
| 477 | end: |
| 478 | mutex_unlock(&bebob->mutex); |
| 479 | return err; |
| 480 | } |
| 481 | static 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 | |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 489 | id = uval->value.enumerated.item[0]; |
Takashi Sakamoto | f77ac91 | 2014-07-22 23:13:56 +0900 | [diff] [blame] | 490 | if (id >= ARRAY_SIZE(special_dig_in_iface_labels)) |
| 491 | return -EINVAL; |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 492 | |
| 493 | /* decode user value */ |
| 494 | dig_in_fmt = (id >> 1) & 0x01; |
| 495 | dig_in_iface = id & 0x01; |
| 496 | |
Takashi Sakamoto | f77ac91 | 2014-07-22 23:13:56 +0900 | [diff] [blame] | 497 | mutex_lock(&bebob->mutex); |
| 498 | |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 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); |
Takashi Sakamoto | 5a0438f | 2014-07-22 23:13:47 +0900 | [diff] [blame] | 504 | if (err < 0) |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 505 | goto end; |
| 506 | |
Takashi Sakamoto | 5a0438f | 2014-07-22 23:13:47 +0900 | [diff] [blame] | 507 | /* For ADAT, optical interface is only available. */ |
Takashi Sakamoto | f77ac91 | 2014-07-22 23:13:56 +0900 | [diff] [blame] | 508 | if (params->dig_in_fmt > 0) { |
| 509 | err = 1; |
Takashi Sakamoto | 5a0438f | 2014-07-22 23:13:47 +0900 | [diff] [blame] | 510 | goto end; |
Takashi Sakamoto | f77ac91 | 2014-07-22 23:13:56 +0900 | [diff] [blame] | 511 | } |
Takashi Sakamoto | 5a0438f | 2014-07-22 23:13:47 +0900 | [diff] [blame] | 512 | |
| 513 | /* For S/PDIF, optical/coaxial interfaces are selectable. */ |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 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); |
Takashi Sakamoto | f77ac91 | 2014-07-22 23:13:56 +0900 | [diff] [blame] | 518 | err = 1; |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 519 | end: |
| 520 | special_stream_formation_set(bebob); |
| 521 | mutex_unlock(&bebob->mutex); |
| 522 | return err; |
| 523 | } |
| 524 | static 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_set |
| 531 | }; |
| 532 | |
Takashi Sakamoto | 5a0438f | 2014-07-22 23:13:47 +0900 | [diff] [blame] | 533 | /* Digital output interface control for special firmware */ |
Takashi Iwai | e7ced41 | 2014-10-21 08:26:10 +0200 | [diff] [blame] | 534 | static const char *const special_dig_out_iface_labels[] = { |
Takashi Sakamoto | 5a0438f | 2014-07-22 23:13:47 +0900 | [diff] [blame] | 535 | "S/PDIF Optical and Coaxial", "ADAT Optical" |
| 536 | }; |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 537 | static int special_dig_out_iface_ctl_info(struct snd_kcontrol *kctl, |
| 538 | struct snd_ctl_elem_info *einf) |
| 539 | { |
Takashi Iwai | 41be516 | 2014-10-20 18:12:26 +0200 | [diff] [blame] | 540 | return snd_ctl_enum_info(einf, 1, |
| 541 | ARRAY_SIZE(special_dig_out_iface_labels), |
| 542 | special_dig_out_iface_labels); |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 543 | } |
| 544 | static 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 | } |
| 554 | static 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 | |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 562 | id = uval->value.enumerated.item[0]; |
Takashi Sakamoto | f77ac91 | 2014-07-22 23:13:56 +0900 | [diff] [blame] | 563 | if (id >= ARRAY_SIZE(special_dig_out_iface_labels)) |
| 564 | return -EINVAL; |
| 565 | |
| 566 | mutex_lock(&bebob->mutex); |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 567 | |
| 568 | err = avc_maudio_set_special_clk(bebob, |
| 569 | params->clk_src, |
| 570 | params->dig_in_fmt, |
| 571 | id, params->clk_lock); |
Takashi Sakamoto | f77ac91 | 2014-07-22 23:13:56 +0900 | [diff] [blame] | 572 | if (err >= 0) { |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 573 | special_stream_formation_set(bebob); |
Takashi Sakamoto | f77ac91 | 2014-07-22 23:13:56 +0900 | [diff] [blame] | 574 | err = 1; |
| 575 | } |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 576 | |
| 577 | mutex_unlock(&bebob->mutex); |
| 578 | return err; |
| 579 | } |
| 580 | static 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_set |
| 587 | }; |
| 588 | |
| 589 | static 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); |
| 613 | end: |
| 614 | return err; |
| 615 | } |
| 616 | |
| 617 | /* Hardware metering for special firmware */ |
Takashi Iwai | e7ced41 | 2014-10-21 08:26:10 +0200 | [diff] [blame] | 618 | static const char *const special_meter_labels[] = { |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 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_OUT |
| 627 | }; |
| 628 | static int |
| 629 | special_meter_get(struct snd_bebob *bebob, u32 *target, unsigned int size) |
| 630 | { |
Takashi Sakamoto | fef586d | 2015-10-18 22:39:52 +0900 | [diff] [blame^] | 631 | __be16 *buf; |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 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; |
| 652 | end: |
| 653 | kfree(buf); |
| 654 | return err; |
| 655 | } |
| 656 | |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 657 | /* last 4 bytes are omitted because it's clock info. */ |
Takashi Iwai | e7ced41 | 2014-10-21 08:26:10 +0200 | [diff] [blame] | 658 | static const char *const fw410_meter_labels[] = { |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 659 | ANA_IN, DIG_IN, |
| 660 | ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, DIG_OUT, |
| 661 | HP_OUT |
| 662 | }; |
Takashi Iwai | e7ced41 | 2014-10-21 08:26:10 +0200 | [diff] [blame] | 663 | static const char *const audiophile_meter_labels[] = { |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 664 | ANA_IN, DIG_IN, |
| 665 | ANA_OUT, ANA_OUT, DIG_OUT, |
| 666 | HP_OUT, AUX_OUT, |
| 667 | }; |
Takashi Iwai | e7ced41 | 2014-10-21 08:26:10 +0200 | [diff] [blame] | 668 | static const char *const solo_meter_labels[] = { |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 669 | ANA_IN, DIG_IN, |
| 670 | STRM_IN, STRM_IN, |
| 671 | ANA_OUT, DIG_OUT |
| 672 | }; |
| 673 | |
| 674 | /* no clock info */ |
Takashi Iwai | e7ced41 | 2014-10-21 08:26:10 +0200 | [diff] [blame] | 675 | static const char *const ozonic_meter_labels[] = { |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 676 | ANA_IN, ANA_IN, |
| 677 | STRM_IN, STRM_IN, |
| 678 | ANA_OUT, ANA_OUT |
| 679 | }; |
| 680 | /* TODO: need testers. these positions are based on authour's assumption */ |
Takashi Iwai | e7ced41 | 2014-10-21 08:26:10 +0200 | [diff] [blame] | 681 | static const char *const nrv10_meter_labels[] = { |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 682 | ANA_IN, ANA_IN, ANA_IN, ANA_IN, |
| 683 | DIG_IN, |
| 684 | ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, |
| 685 | DIG_IN |
| 686 | }; |
| 687 | static int |
| 688 | normal_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size) |
| 689 | { |
Julia Lawall | 6b9866c | 2015-10-11 08:10:55 +0200 | [diff] [blame] | 690 | const struct snd_bebob_meter_spec *spec = bebob->spec->meter; |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 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 | } |
| 710 | end: |
| 711 | return err; |
| 712 | } |
| 713 | |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 714 | /* for special customized devices */ |
Julia Lawall | 6b9866c | 2015-10-11 08:10:55 +0200 | [diff] [blame] | 715 | static const struct snd_bebob_rate_spec special_rate_spec = { |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 716 | .get = &special_get_rate, |
| 717 | .set = &special_set_rate, |
| 718 | }; |
Julia Lawall | 6b9866c | 2015-10-11 08:10:55 +0200 | [diff] [blame] | 719 | static const struct snd_bebob_clock_spec special_clk_spec = { |
Takashi Sakamoto | ba51771 | 2015-06-14 12:49:29 +0900 | [diff] [blame] | 720 | .num = ARRAY_SIZE(special_clk_types), |
Takashi Sakamoto | ba51771 | 2015-06-14 12:49:29 +0900 | [diff] [blame] | 721 | .types = special_clk_types, |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 722 | .get = &special_clk_get, |
| 723 | }; |
Julia Lawall | 6b9866c | 2015-10-11 08:10:55 +0200 | [diff] [blame] | 724 | static const struct snd_bebob_meter_spec special_meter_spec = { |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 725 | .num = ARRAY_SIZE(special_meter_labels), |
| 726 | .labels = special_meter_labels, |
| 727 | .get = &special_meter_get |
| 728 | }; |
Julia Lawall | 6b9866c | 2015-10-11 08:10:55 +0200 | [diff] [blame] | 729 | const struct snd_bebob_spec maudio_special_spec = { |
Takashi Sakamoto | 3149ac4 | 2014-04-25 22:45:26 +0900 | [diff] [blame] | 730 | .clock = &special_clk_spec, |
| 731 | .rate = &special_rate_spec, |
| 732 | .meter = &special_meter_spec |
| 733 | }; |
| 734 | |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 735 | /* Firewire 410 specification */ |
Julia Lawall | 6b9866c | 2015-10-11 08:10:55 +0200 | [diff] [blame] | 736 | static const struct snd_bebob_rate_spec usual_rate_spec = { |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 737 | .get = &snd_bebob_stream_get_rate, |
| 738 | .set = &snd_bebob_stream_set_rate, |
| 739 | }; |
Julia Lawall | 6b9866c | 2015-10-11 08:10:55 +0200 | [diff] [blame] | 740 | static const struct snd_bebob_meter_spec fw410_meter_spec = { |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 741 | .num = ARRAY_SIZE(fw410_meter_labels), |
| 742 | .labels = fw410_meter_labels, |
| 743 | .get = &normal_meter_get |
| 744 | }; |
Julia Lawall | 6b9866c | 2015-10-11 08:10:55 +0200 | [diff] [blame] | 745 | const struct snd_bebob_spec maudio_fw410_spec = { |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 746 | .clock = NULL, |
| 747 | .rate = &usual_rate_spec, |
| 748 | .meter = &fw410_meter_spec |
| 749 | }; |
| 750 | |
| 751 | /* Firewire Audiophile specification */ |
Julia Lawall | 6b9866c | 2015-10-11 08:10:55 +0200 | [diff] [blame] | 752 | static const struct snd_bebob_meter_spec audiophile_meter_spec = { |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 753 | .num = ARRAY_SIZE(audiophile_meter_labels), |
| 754 | .labels = audiophile_meter_labels, |
| 755 | .get = &normal_meter_get |
| 756 | }; |
Julia Lawall | 6b9866c | 2015-10-11 08:10:55 +0200 | [diff] [blame] | 757 | const struct snd_bebob_spec maudio_audiophile_spec = { |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 758 | .clock = NULL, |
| 759 | .rate = &usual_rate_spec, |
| 760 | .meter = &audiophile_meter_spec |
| 761 | }; |
| 762 | |
| 763 | /* Firewire Solo specification */ |
Julia Lawall | 6b9866c | 2015-10-11 08:10:55 +0200 | [diff] [blame] | 764 | static const struct snd_bebob_meter_spec solo_meter_spec = { |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 765 | .num = ARRAY_SIZE(solo_meter_labels), |
| 766 | .labels = solo_meter_labels, |
| 767 | .get = &normal_meter_get |
| 768 | }; |
Julia Lawall | 6b9866c | 2015-10-11 08:10:55 +0200 | [diff] [blame] | 769 | const struct snd_bebob_spec maudio_solo_spec = { |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 770 | .clock = NULL, |
| 771 | .rate = &usual_rate_spec, |
| 772 | .meter = &solo_meter_spec |
| 773 | }; |
| 774 | |
| 775 | /* Ozonic specification */ |
Julia Lawall | 6b9866c | 2015-10-11 08:10:55 +0200 | [diff] [blame] | 776 | static const struct snd_bebob_meter_spec ozonic_meter_spec = { |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 777 | .num = ARRAY_SIZE(ozonic_meter_labels), |
| 778 | .labels = ozonic_meter_labels, |
| 779 | .get = &normal_meter_get |
| 780 | }; |
Julia Lawall | 6b9866c | 2015-10-11 08:10:55 +0200 | [diff] [blame] | 781 | const struct snd_bebob_spec maudio_ozonic_spec = { |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 782 | .clock = NULL, |
| 783 | .rate = &usual_rate_spec, |
| 784 | .meter = &ozonic_meter_spec |
| 785 | }; |
| 786 | |
| 787 | /* NRV10 specification */ |
Julia Lawall | 6b9866c | 2015-10-11 08:10:55 +0200 | [diff] [blame] | 788 | static const struct snd_bebob_meter_spec nrv10_meter_spec = { |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 789 | .num = ARRAY_SIZE(nrv10_meter_labels), |
| 790 | .labels = nrv10_meter_labels, |
| 791 | .get = &normal_meter_get |
| 792 | }; |
Julia Lawall | 6b9866c | 2015-10-11 08:10:55 +0200 | [diff] [blame] | 793 | const struct snd_bebob_spec maudio_nrv10_spec = { |
Takashi Sakamoto | 9076c22 | 2014-04-25 22:45:25 +0900 | [diff] [blame] | 794 | .clock = NULL, |
| 795 | .rate = &usual_rate_spec, |
| 796 | .meter = &nrv10_meter_spec |
| 797 | }; |