162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * oxfw-spkr.c - a part of driver for OXFW970/971 based devices 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) Clemens Ladisch <clemens@ladisch.de> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include "oxfw.h" 962306a36Sopenharmony_ci 1062306a36Sopenharmony_cistruct fw_spkr { 1162306a36Sopenharmony_ci bool mute; 1262306a36Sopenharmony_ci s16 volume[6]; 1362306a36Sopenharmony_ci s16 volume_min; 1462306a36Sopenharmony_ci s16 volume_max; 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci unsigned int mixer_channels; 1762306a36Sopenharmony_ci u8 mute_fb_id; 1862306a36Sopenharmony_ci u8 volume_fb_id; 1962306a36Sopenharmony_ci}; 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cienum control_action { CTL_READ, CTL_WRITE }; 2262306a36Sopenharmony_cienum control_attribute { 2362306a36Sopenharmony_ci CTL_MIN = 0x02, 2462306a36Sopenharmony_ci CTL_MAX = 0x03, 2562306a36Sopenharmony_ci CTL_CURRENT = 0x10, 2662306a36Sopenharmony_ci}; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistatic int avc_audio_feature_mute(struct fw_unit *unit, u8 fb_id, bool *value, 2962306a36Sopenharmony_ci enum control_action action) 3062306a36Sopenharmony_ci{ 3162306a36Sopenharmony_ci u8 *buf; 3262306a36Sopenharmony_ci u8 response_ok; 3362306a36Sopenharmony_ci int err; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci buf = kmalloc(11, GFP_KERNEL); 3662306a36Sopenharmony_ci if (!buf) 3762306a36Sopenharmony_ci return -ENOMEM; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci if (action == CTL_READ) { 4062306a36Sopenharmony_ci buf[0] = 0x01; /* AV/C, STATUS */ 4162306a36Sopenharmony_ci response_ok = 0x0c; /* STABLE */ 4262306a36Sopenharmony_ci } else { 4362306a36Sopenharmony_ci buf[0] = 0x00; /* AV/C, CONTROL */ 4462306a36Sopenharmony_ci response_ok = 0x09; /* ACCEPTED */ 4562306a36Sopenharmony_ci } 4662306a36Sopenharmony_ci buf[1] = 0x08; /* audio unit 0 */ 4762306a36Sopenharmony_ci buf[2] = 0xb8; /* FUNCTION BLOCK */ 4862306a36Sopenharmony_ci buf[3] = 0x81; /* function block type: feature */ 4962306a36Sopenharmony_ci buf[4] = fb_id; /* function block ID */ 5062306a36Sopenharmony_ci buf[5] = 0x10; /* control attribute: current */ 5162306a36Sopenharmony_ci buf[6] = 0x02; /* selector length */ 5262306a36Sopenharmony_ci buf[7] = 0x00; /* audio channel number */ 5362306a36Sopenharmony_ci buf[8] = 0x01; /* control selector: mute */ 5462306a36Sopenharmony_ci buf[9] = 0x01; /* control data length */ 5562306a36Sopenharmony_ci if (action == CTL_READ) 5662306a36Sopenharmony_ci buf[10] = 0xff; 5762306a36Sopenharmony_ci else 5862306a36Sopenharmony_ci buf[10] = *value ? 0x70 : 0x60; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci err = fcp_avc_transaction(unit, buf, 11, buf, 11, 0x3fe); 6162306a36Sopenharmony_ci if (err < 0) 6262306a36Sopenharmony_ci goto error; 6362306a36Sopenharmony_ci if (err < 11) { 6462306a36Sopenharmony_ci dev_err(&unit->device, "short FCP response\n"); 6562306a36Sopenharmony_ci err = -EIO; 6662306a36Sopenharmony_ci goto error; 6762306a36Sopenharmony_ci } 6862306a36Sopenharmony_ci if (buf[0] != response_ok) { 6962306a36Sopenharmony_ci dev_err(&unit->device, "mute command failed\n"); 7062306a36Sopenharmony_ci err = -EIO; 7162306a36Sopenharmony_ci goto error; 7262306a36Sopenharmony_ci } 7362306a36Sopenharmony_ci if (action == CTL_READ) 7462306a36Sopenharmony_ci *value = buf[10] == 0x70; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci err = 0; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cierror: 7962306a36Sopenharmony_ci kfree(buf); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci return err; 8262306a36Sopenharmony_ci} 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistatic int avc_audio_feature_volume(struct fw_unit *unit, u8 fb_id, s16 *value, 8562306a36Sopenharmony_ci unsigned int channel, 8662306a36Sopenharmony_ci enum control_attribute attribute, 8762306a36Sopenharmony_ci enum control_action action) 8862306a36Sopenharmony_ci{ 8962306a36Sopenharmony_ci u8 *buf; 9062306a36Sopenharmony_ci u8 response_ok; 9162306a36Sopenharmony_ci int err; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci buf = kmalloc(12, GFP_KERNEL); 9462306a36Sopenharmony_ci if (!buf) 9562306a36Sopenharmony_ci return -ENOMEM; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci if (action == CTL_READ) { 9862306a36Sopenharmony_ci buf[0] = 0x01; /* AV/C, STATUS */ 9962306a36Sopenharmony_ci response_ok = 0x0c; /* STABLE */ 10062306a36Sopenharmony_ci } else { 10162306a36Sopenharmony_ci buf[0] = 0x00; /* AV/C, CONTROL */ 10262306a36Sopenharmony_ci response_ok = 0x09; /* ACCEPTED */ 10362306a36Sopenharmony_ci } 10462306a36Sopenharmony_ci buf[1] = 0x08; /* audio unit 0 */ 10562306a36Sopenharmony_ci buf[2] = 0xb8; /* FUNCTION BLOCK */ 10662306a36Sopenharmony_ci buf[3] = 0x81; /* function block type: feature */ 10762306a36Sopenharmony_ci buf[4] = fb_id; /* function block ID */ 10862306a36Sopenharmony_ci buf[5] = attribute; /* control attribute */ 10962306a36Sopenharmony_ci buf[6] = 0x02; /* selector length */ 11062306a36Sopenharmony_ci buf[7] = channel; /* audio channel number */ 11162306a36Sopenharmony_ci buf[8] = 0x02; /* control selector: volume */ 11262306a36Sopenharmony_ci buf[9] = 0x02; /* control data length */ 11362306a36Sopenharmony_ci if (action == CTL_READ) { 11462306a36Sopenharmony_ci buf[10] = 0xff; 11562306a36Sopenharmony_ci buf[11] = 0xff; 11662306a36Sopenharmony_ci } else { 11762306a36Sopenharmony_ci buf[10] = *value >> 8; 11862306a36Sopenharmony_ci buf[11] = *value; 11962306a36Sopenharmony_ci } 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci err = fcp_avc_transaction(unit, buf, 12, buf, 12, 0x3fe); 12262306a36Sopenharmony_ci if (err < 0) 12362306a36Sopenharmony_ci goto error; 12462306a36Sopenharmony_ci if (err < 12) { 12562306a36Sopenharmony_ci dev_err(&unit->device, "short FCP response\n"); 12662306a36Sopenharmony_ci err = -EIO; 12762306a36Sopenharmony_ci goto error; 12862306a36Sopenharmony_ci } 12962306a36Sopenharmony_ci if (buf[0] != response_ok) { 13062306a36Sopenharmony_ci dev_err(&unit->device, "volume command failed\n"); 13162306a36Sopenharmony_ci err = -EIO; 13262306a36Sopenharmony_ci goto error; 13362306a36Sopenharmony_ci } 13462306a36Sopenharmony_ci if (action == CTL_READ) 13562306a36Sopenharmony_ci *value = (buf[10] << 8) | buf[11]; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci err = 0; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_cierror: 14062306a36Sopenharmony_ci kfree(buf); 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci return err; 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cistatic int spkr_mute_get(struct snd_kcontrol *control, 14662306a36Sopenharmony_ci struct snd_ctl_elem_value *value) 14762306a36Sopenharmony_ci{ 14862306a36Sopenharmony_ci struct snd_oxfw *oxfw = control->private_data; 14962306a36Sopenharmony_ci struct fw_spkr *spkr = oxfw->spec; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci value->value.integer.value[0] = !spkr->mute; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci return 0; 15462306a36Sopenharmony_ci} 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_cistatic int spkr_mute_put(struct snd_kcontrol *control, 15762306a36Sopenharmony_ci struct snd_ctl_elem_value *value) 15862306a36Sopenharmony_ci{ 15962306a36Sopenharmony_ci struct snd_oxfw *oxfw = control->private_data; 16062306a36Sopenharmony_ci struct fw_spkr *spkr = oxfw->spec; 16162306a36Sopenharmony_ci bool mute; 16262306a36Sopenharmony_ci int err; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci mute = !value->value.integer.value[0]; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci if (mute == spkr->mute) 16762306a36Sopenharmony_ci return 0; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci err = avc_audio_feature_mute(oxfw->unit, spkr->mute_fb_id, &mute, 17062306a36Sopenharmony_ci CTL_WRITE); 17162306a36Sopenharmony_ci if (err < 0) 17262306a36Sopenharmony_ci return err; 17362306a36Sopenharmony_ci spkr->mute = mute; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci return 1; 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_cistatic int spkr_volume_info(struct snd_kcontrol *control, 17962306a36Sopenharmony_ci struct snd_ctl_elem_info *info) 18062306a36Sopenharmony_ci{ 18162306a36Sopenharmony_ci struct snd_oxfw *oxfw = control->private_data; 18262306a36Sopenharmony_ci struct fw_spkr *spkr = oxfw->spec; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; 18562306a36Sopenharmony_ci info->count = spkr->mixer_channels; 18662306a36Sopenharmony_ci info->value.integer.min = spkr->volume_min; 18762306a36Sopenharmony_ci info->value.integer.max = spkr->volume_max; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci return 0; 19062306a36Sopenharmony_ci} 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_cistatic const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 }; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_cistatic int spkr_volume_get(struct snd_kcontrol *control, 19562306a36Sopenharmony_ci struct snd_ctl_elem_value *value) 19662306a36Sopenharmony_ci{ 19762306a36Sopenharmony_ci struct snd_oxfw *oxfw = control->private_data; 19862306a36Sopenharmony_ci struct fw_spkr *spkr = oxfw->spec; 19962306a36Sopenharmony_ci unsigned int i; 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci for (i = 0; i < spkr->mixer_channels; ++i) 20262306a36Sopenharmony_ci value->value.integer.value[channel_map[i]] = spkr->volume[i]; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci return 0; 20562306a36Sopenharmony_ci} 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_cistatic int spkr_volume_put(struct snd_kcontrol *control, 20862306a36Sopenharmony_ci struct snd_ctl_elem_value *value) 20962306a36Sopenharmony_ci{ 21062306a36Sopenharmony_ci struct snd_oxfw *oxfw = control->private_data; 21162306a36Sopenharmony_ci struct fw_spkr *spkr = oxfw->spec; 21262306a36Sopenharmony_ci unsigned int i, changed_channels; 21362306a36Sopenharmony_ci bool equal_values = true; 21462306a36Sopenharmony_ci s16 volume; 21562306a36Sopenharmony_ci int err; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci for (i = 0; i < spkr->mixer_channels; ++i) { 21862306a36Sopenharmony_ci if (value->value.integer.value[i] < spkr->volume_min || 21962306a36Sopenharmony_ci value->value.integer.value[i] > spkr->volume_max) 22062306a36Sopenharmony_ci return -EINVAL; 22162306a36Sopenharmony_ci if (value->value.integer.value[i] != 22262306a36Sopenharmony_ci value->value.integer.value[0]) 22362306a36Sopenharmony_ci equal_values = false; 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci changed_channels = 0; 22762306a36Sopenharmony_ci for (i = 0; i < spkr->mixer_channels; ++i) 22862306a36Sopenharmony_ci if (value->value.integer.value[channel_map[i]] != 22962306a36Sopenharmony_ci spkr->volume[i]) 23062306a36Sopenharmony_ci changed_channels |= 1 << (i + 1); 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci if (equal_values && changed_channels != 0) 23362306a36Sopenharmony_ci changed_channels = 1 << 0; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci for (i = 0; i <= spkr->mixer_channels; ++i) { 23662306a36Sopenharmony_ci volume = value->value.integer.value[channel_map[i ? i - 1 : 0]]; 23762306a36Sopenharmony_ci if (changed_channels & (1 << i)) { 23862306a36Sopenharmony_ci err = avc_audio_feature_volume(oxfw->unit, 23962306a36Sopenharmony_ci spkr->volume_fb_id, &volume, 24062306a36Sopenharmony_ci i, CTL_CURRENT, CTL_WRITE); 24162306a36Sopenharmony_ci if (err < 0) 24262306a36Sopenharmony_ci return err; 24362306a36Sopenharmony_ci } 24462306a36Sopenharmony_ci if (i > 0) 24562306a36Sopenharmony_ci spkr->volume[i - 1] = volume; 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci return changed_channels != 0; 24962306a36Sopenharmony_ci} 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ciint snd_oxfw_add_spkr(struct snd_oxfw *oxfw, bool is_lacie) 25262306a36Sopenharmony_ci{ 25362306a36Sopenharmony_ci static const struct snd_kcontrol_new controls[] = { 25462306a36Sopenharmony_ci { 25562306a36Sopenharmony_ci .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 25662306a36Sopenharmony_ci .name = "PCM Playback Switch", 25762306a36Sopenharmony_ci .info = snd_ctl_boolean_mono_info, 25862306a36Sopenharmony_ci .get = spkr_mute_get, 25962306a36Sopenharmony_ci .put = spkr_mute_put, 26062306a36Sopenharmony_ci }, 26162306a36Sopenharmony_ci { 26262306a36Sopenharmony_ci .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 26362306a36Sopenharmony_ci .name = "PCM Playback Volume", 26462306a36Sopenharmony_ci .info = spkr_volume_info, 26562306a36Sopenharmony_ci .get = spkr_volume_get, 26662306a36Sopenharmony_ci .put = spkr_volume_put, 26762306a36Sopenharmony_ci }, 26862306a36Sopenharmony_ci }; 26962306a36Sopenharmony_ci struct fw_spkr *spkr; 27062306a36Sopenharmony_ci unsigned int i, first_ch; 27162306a36Sopenharmony_ci int err; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci spkr = devm_kzalloc(&oxfw->card->card_dev, sizeof(struct fw_spkr), 27462306a36Sopenharmony_ci GFP_KERNEL); 27562306a36Sopenharmony_ci if (!spkr) 27662306a36Sopenharmony_ci return -ENOMEM; 27762306a36Sopenharmony_ci oxfw->spec = spkr; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci if (is_lacie) { 28062306a36Sopenharmony_ci spkr->mixer_channels = 1; 28162306a36Sopenharmony_ci spkr->mute_fb_id = 0x01; 28262306a36Sopenharmony_ci spkr->volume_fb_id = 0x01; 28362306a36Sopenharmony_ci } else { 28462306a36Sopenharmony_ci spkr->mixer_channels = 6; 28562306a36Sopenharmony_ci spkr->mute_fb_id = 0x01; 28662306a36Sopenharmony_ci spkr->volume_fb_id = 0x02; 28762306a36Sopenharmony_ci } 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, 29062306a36Sopenharmony_ci &spkr->volume_min, 0, CTL_MIN, CTL_READ); 29162306a36Sopenharmony_ci if (err < 0) 29262306a36Sopenharmony_ci return err; 29362306a36Sopenharmony_ci err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, 29462306a36Sopenharmony_ci &spkr->volume_max, 0, CTL_MAX, CTL_READ); 29562306a36Sopenharmony_ci if (err < 0) 29662306a36Sopenharmony_ci return err; 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci err = avc_audio_feature_mute(oxfw->unit, spkr->mute_fb_id, &spkr->mute, 29962306a36Sopenharmony_ci CTL_READ); 30062306a36Sopenharmony_ci if (err < 0) 30162306a36Sopenharmony_ci return err; 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci first_ch = spkr->mixer_channels == 1 ? 0 : 1; 30462306a36Sopenharmony_ci for (i = 0; i < spkr->mixer_channels; ++i) { 30562306a36Sopenharmony_ci err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, 30662306a36Sopenharmony_ci &spkr->volume[i], first_ch + i, 30762306a36Sopenharmony_ci CTL_CURRENT, CTL_READ); 30862306a36Sopenharmony_ci if (err < 0) 30962306a36Sopenharmony_ci return err; 31062306a36Sopenharmony_ci } 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(controls); ++i) { 31362306a36Sopenharmony_ci err = snd_ctl_add(oxfw->card, 31462306a36Sopenharmony_ci snd_ctl_new1(&controls[i], oxfw)); 31562306a36Sopenharmony_ci if (err < 0) 31662306a36Sopenharmony_ci return err; 31762306a36Sopenharmony_ci } 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci return 0; 32062306a36Sopenharmony_ci} 321