18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * oxfw-spkr.c - a part of driver for OXFW970/971 based devices 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) Clemens Ladisch <clemens@ladisch.de> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include "oxfw.h" 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_cistruct fw_spkr { 118c2ecf20Sopenharmony_ci bool mute; 128c2ecf20Sopenharmony_ci s16 volume[6]; 138c2ecf20Sopenharmony_ci s16 volume_min; 148c2ecf20Sopenharmony_ci s16 volume_max; 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci unsigned int mixer_channels; 178c2ecf20Sopenharmony_ci u8 mute_fb_id; 188c2ecf20Sopenharmony_ci u8 volume_fb_id; 198c2ecf20Sopenharmony_ci}; 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_cienum control_action { CTL_READ, CTL_WRITE }; 228c2ecf20Sopenharmony_cienum control_attribute { 238c2ecf20Sopenharmony_ci CTL_MIN = 0x02, 248c2ecf20Sopenharmony_ci CTL_MAX = 0x03, 258c2ecf20Sopenharmony_ci CTL_CURRENT = 0x10, 268c2ecf20Sopenharmony_ci}; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_cistatic int avc_audio_feature_mute(struct fw_unit *unit, u8 fb_id, bool *value, 298c2ecf20Sopenharmony_ci enum control_action action) 308c2ecf20Sopenharmony_ci{ 318c2ecf20Sopenharmony_ci u8 *buf; 328c2ecf20Sopenharmony_ci u8 response_ok; 338c2ecf20Sopenharmony_ci int err; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci buf = kmalloc(11, GFP_KERNEL); 368c2ecf20Sopenharmony_ci if (!buf) 378c2ecf20Sopenharmony_ci return -ENOMEM; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci if (action == CTL_READ) { 408c2ecf20Sopenharmony_ci buf[0] = 0x01; /* AV/C, STATUS */ 418c2ecf20Sopenharmony_ci response_ok = 0x0c; /* STABLE */ 428c2ecf20Sopenharmony_ci } else { 438c2ecf20Sopenharmony_ci buf[0] = 0x00; /* AV/C, CONTROL */ 448c2ecf20Sopenharmony_ci response_ok = 0x09; /* ACCEPTED */ 458c2ecf20Sopenharmony_ci } 468c2ecf20Sopenharmony_ci buf[1] = 0x08; /* audio unit 0 */ 478c2ecf20Sopenharmony_ci buf[2] = 0xb8; /* FUNCTION BLOCK */ 488c2ecf20Sopenharmony_ci buf[3] = 0x81; /* function block type: feature */ 498c2ecf20Sopenharmony_ci buf[4] = fb_id; /* function block ID */ 508c2ecf20Sopenharmony_ci buf[5] = 0x10; /* control attribute: current */ 518c2ecf20Sopenharmony_ci buf[6] = 0x02; /* selector length */ 528c2ecf20Sopenharmony_ci buf[7] = 0x00; /* audio channel number */ 538c2ecf20Sopenharmony_ci buf[8] = 0x01; /* control selector: mute */ 548c2ecf20Sopenharmony_ci buf[9] = 0x01; /* control data length */ 558c2ecf20Sopenharmony_ci if (action == CTL_READ) 568c2ecf20Sopenharmony_ci buf[10] = 0xff; 578c2ecf20Sopenharmony_ci else 588c2ecf20Sopenharmony_ci buf[10] = *value ? 0x70 : 0x60; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci err = fcp_avc_transaction(unit, buf, 11, buf, 11, 0x3fe); 618c2ecf20Sopenharmony_ci if (err < 0) 628c2ecf20Sopenharmony_ci goto error; 638c2ecf20Sopenharmony_ci if (err < 11) { 648c2ecf20Sopenharmony_ci dev_err(&unit->device, "short FCP response\n"); 658c2ecf20Sopenharmony_ci err = -EIO; 668c2ecf20Sopenharmony_ci goto error; 678c2ecf20Sopenharmony_ci } 688c2ecf20Sopenharmony_ci if (buf[0] != response_ok) { 698c2ecf20Sopenharmony_ci dev_err(&unit->device, "mute command failed\n"); 708c2ecf20Sopenharmony_ci err = -EIO; 718c2ecf20Sopenharmony_ci goto error; 728c2ecf20Sopenharmony_ci } 738c2ecf20Sopenharmony_ci if (action == CTL_READ) 748c2ecf20Sopenharmony_ci *value = buf[10] == 0x70; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci err = 0; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_cierror: 798c2ecf20Sopenharmony_ci kfree(buf); 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci return err; 828c2ecf20Sopenharmony_ci} 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cistatic int avc_audio_feature_volume(struct fw_unit *unit, u8 fb_id, s16 *value, 858c2ecf20Sopenharmony_ci unsigned int channel, 868c2ecf20Sopenharmony_ci enum control_attribute attribute, 878c2ecf20Sopenharmony_ci enum control_action action) 888c2ecf20Sopenharmony_ci{ 898c2ecf20Sopenharmony_ci u8 *buf; 908c2ecf20Sopenharmony_ci u8 response_ok; 918c2ecf20Sopenharmony_ci int err; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci buf = kmalloc(12, GFP_KERNEL); 948c2ecf20Sopenharmony_ci if (!buf) 958c2ecf20Sopenharmony_ci return -ENOMEM; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci if (action == CTL_READ) { 988c2ecf20Sopenharmony_ci buf[0] = 0x01; /* AV/C, STATUS */ 998c2ecf20Sopenharmony_ci response_ok = 0x0c; /* STABLE */ 1008c2ecf20Sopenharmony_ci } else { 1018c2ecf20Sopenharmony_ci buf[0] = 0x00; /* AV/C, CONTROL */ 1028c2ecf20Sopenharmony_ci response_ok = 0x09; /* ACCEPTED */ 1038c2ecf20Sopenharmony_ci } 1048c2ecf20Sopenharmony_ci buf[1] = 0x08; /* audio unit 0 */ 1058c2ecf20Sopenharmony_ci buf[2] = 0xb8; /* FUNCTION BLOCK */ 1068c2ecf20Sopenharmony_ci buf[3] = 0x81; /* function block type: feature */ 1078c2ecf20Sopenharmony_ci buf[4] = fb_id; /* function block ID */ 1088c2ecf20Sopenharmony_ci buf[5] = attribute; /* control attribute */ 1098c2ecf20Sopenharmony_ci buf[6] = 0x02; /* selector length */ 1108c2ecf20Sopenharmony_ci buf[7] = channel; /* audio channel number */ 1118c2ecf20Sopenharmony_ci buf[8] = 0x02; /* control selector: volume */ 1128c2ecf20Sopenharmony_ci buf[9] = 0x02; /* control data length */ 1138c2ecf20Sopenharmony_ci if (action == CTL_READ) { 1148c2ecf20Sopenharmony_ci buf[10] = 0xff; 1158c2ecf20Sopenharmony_ci buf[11] = 0xff; 1168c2ecf20Sopenharmony_ci } else { 1178c2ecf20Sopenharmony_ci buf[10] = *value >> 8; 1188c2ecf20Sopenharmony_ci buf[11] = *value; 1198c2ecf20Sopenharmony_ci } 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci err = fcp_avc_transaction(unit, buf, 12, buf, 12, 0x3fe); 1228c2ecf20Sopenharmony_ci if (err < 0) 1238c2ecf20Sopenharmony_ci goto error; 1248c2ecf20Sopenharmony_ci if (err < 12) { 1258c2ecf20Sopenharmony_ci dev_err(&unit->device, "short FCP response\n"); 1268c2ecf20Sopenharmony_ci err = -EIO; 1278c2ecf20Sopenharmony_ci goto error; 1288c2ecf20Sopenharmony_ci } 1298c2ecf20Sopenharmony_ci if (buf[0] != response_ok) { 1308c2ecf20Sopenharmony_ci dev_err(&unit->device, "volume command failed\n"); 1318c2ecf20Sopenharmony_ci err = -EIO; 1328c2ecf20Sopenharmony_ci goto error; 1338c2ecf20Sopenharmony_ci } 1348c2ecf20Sopenharmony_ci if (action == CTL_READ) 1358c2ecf20Sopenharmony_ci *value = (buf[10] << 8) | buf[11]; 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci err = 0; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_cierror: 1408c2ecf20Sopenharmony_ci kfree(buf); 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci return err; 1438c2ecf20Sopenharmony_ci} 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_cistatic int spkr_mute_get(struct snd_kcontrol *control, 1468c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *value) 1478c2ecf20Sopenharmony_ci{ 1488c2ecf20Sopenharmony_ci struct snd_oxfw *oxfw = control->private_data; 1498c2ecf20Sopenharmony_ci struct fw_spkr *spkr = oxfw->spec; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci value->value.integer.value[0] = !spkr->mute; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci return 0; 1548c2ecf20Sopenharmony_ci} 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_cistatic int spkr_mute_put(struct snd_kcontrol *control, 1578c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *value) 1588c2ecf20Sopenharmony_ci{ 1598c2ecf20Sopenharmony_ci struct snd_oxfw *oxfw = control->private_data; 1608c2ecf20Sopenharmony_ci struct fw_spkr *spkr = oxfw->spec; 1618c2ecf20Sopenharmony_ci bool mute; 1628c2ecf20Sopenharmony_ci int err; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci mute = !value->value.integer.value[0]; 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci if (mute == spkr->mute) 1678c2ecf20Sopenharmony_ci return 0; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci err = avc_audio_feature_mute(oxfw->unit, spkr->mute_fb_id, &mute, 1708c2ecf20Sopenharmony_ci CTL_WRITE); 1718c2ecf20Sopenharmony_ci if (err < 0) 1728c2ecf20Sopenharmony_ci return err; 1738c2ecf20Sopenharmony_ci spkr->mute = mute; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci return 1; 1768c2ecf20Sopenharmony_ci} 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_cistatic int spkr_volume_info(struct snd_kcontrol *control, 1798c2ecf20Sopenharmony_ci struct snd_ctl_elem_info *info) 1808c2ecf20Sopenharmony_ci{ 1818c2ecf20Sopenharmony_ci struct snd_oxfw *oxfw = control->private_data; 1828c2ecf20Sopenharmony_ci struct fw_spkr *spkr = oxfw->spec; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; 1858c2ecf20Sopenharmony_ci info->count = spkr->mixer_channels; 1868c2ecf20Sopenharmony_ci info->value.integer.min = spkr->volume_min; 1878c2ecf20Sopenharmony_ci info->value.integer.max = spkr->volume_max; 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci return 0; 1908c2ecf20Sopenharmony_ci} 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_cistatic const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 }; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_cistatic int spkr_volume_get(struct snd_kcontrol *control, 1958c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *value) 1968c2ecf20Sopenharmony_ci{ 1978c2ecf20Sopenharmony_ci struct snd_oxfw *oxfw = control->private_data; 1988c2ecf20Sopenharmony_ci struct fw_spkr *spkr = oxfw->spec; 1998c2ecf20Sopenharmony_ci unsigned int i; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci for (i = 0; i < spkr->mixer_channels; ++i) 2028c2ecf20Sopenharmony_ci value->value.integer.value[channel_map[i]] = spkr->volume[i]; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci return 0; 2058c2ecf20Sopenharmony_ci} 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_cistatic int spkr_volume_put(struct snd_kcontrol *control, 2088c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *value) 2098c2ecf20Sopenharmony_ci{ 2108c2ecf20Sopenharmony_ci struct snd_oxfw *oxfw = control->private_data; 2118c2ecf20Sopenharmony_ci struct fw_spkr *spkr = oxfw->spec; 2128c2ecf20Sopenharmony_ci unsigned int i, changed_channels; 2138c2ecf20Sopenharmony_ci bool equal_values = true; 2148c2ecf20Sopenharmony_ci s16 volume; 2158c2ecf20Sopenharmony_ci int err; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci for (i = 0; i < spkr->mixer_channels; ++i) { 2188c2ecf20Sopenharmony_ci if (value->value.integer.value[i] < spkr->volume_min || 2198c2ecf20Sopenharmony_ci value->value.integer.value[i] > spkr->volume_max) 2208c2ecf20Sopenharmony_ci return -EINVAL; 2218c2ecf20Sopenharmony_ci if (value->value.integer.value[i] != 2228c2ecf20Sopenharmony_ci value->value.integer.value[0]) 2238c2ecf20Sopenharmony_ci equal_values = false; 2248c2ecf20Sopenharmony_ci } 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci changed_channels = 0; 2278c2ecf20Sopenharmony_ci for (i = 0; i < spkr->mixer_channels; ++i) 2288c2ecf20Sopenharmony_ci if (value->value.integer.value[channel_map[i]] != 2298c2ecf20Sopenharmony_ci spkr->volume[i]) 2308c2ecf20Sopenharmony_ci changed_channels |= 1 << (i + 1); 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci if (equal_values && changed_channels != 0) 2338c2ecf20Sopenharmony_ci changed_channels = 1 << 0; 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci for (i = 0; i <= spkr->mixer_channels; ++i) { 2368c2ecf20Sopenharmony_ci volume = value->value.integer.value[channel_map[i ? i - 1 : 0]]; 2378c2ecf20Sopenharmony_ci if (changed_channels & (1 << i)) { 2388c2ecf20Sopenharmony_ci err = avc_audio_feature_volume(oxfw->unit, 2398c2ecf20Sopenharmony_ci spkr->volume_fb_id, &volume, 2408c2ecf20Sopenharmony_ci i, CTL_CURRENT, CTL_WRITE); 2418c2ecf20Sopenharmony_ci if (err < 0) 2428c2ecf20Sopenharmony_ci return err; 2438c2ecf20Sopenharmony_ci } 2448c2ecf20Sopenharmony_ci if (i > 0) 2458c2ecf20Sopenharmony_ci spkr->volume[i - 1] = volume; 2468c2ecf20Sopenharmony_ci } 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci return changed_channels != 0; 2498c2ecf20Sopenharmony_ci} 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ciint snd_oxfw_add_spkr(struct snd_oxfw *oxfw, bool is_lacie) 2528c2ecf20Sopenharmony_ci{ 2538c2ecf20Sopenharmony_ci static const struct snd_kcontrol_new controls[] = { 2548c2ecf20Sopenharmony_ci { 2558c2ecf20Sopenharmony_ci .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 2568c2ecf20Sopenharmony_ci .name = "PCM Playback Switch", 2578c2ecf20Sopenharmony_ci .info = snd_ctl_boolean_mono_info, 2588c2ecf20Sopenharmony_ci .get = spkr_mute_get, 2598c2ecf20Sopenharmony_ci .put = spkr_mute_put, 2608c2ecf20Sopenharmony_ci }, 2618c2ecf20Sopenharmony_ci { 2628c2ecf20Sopenharmony_ci .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 2638c2ecf20Sopenharmony_ci .name = "PCM Playback Volume", 2648c2ecf20Sopenharmony_ci .info = spkr_volume_info, 2658c2ecf20Sopenharmony_ci .get = spkr_volume_get, 2668c2ecf20Sopenharmony_ci .put = spkr_volume_put, 2678c2ecf20Sopenharmony_ci }, 2688c2ecf20Sopenharmony_ci }; 2698c2ecf20Sopenharmony_ci struct fw_spkr *spkr; 2708c2ecf20Sopenharmony_ci unsigned int i, first_ch; 2718c2ecf20Sopenharmony_ci int err; 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci spkr = devm_kzalloc(&oxfw->card->card_dev, sizeof(struct fw_spkr), 2748c2ecf20Sopenharmony_ci GFP_KERNEL); 2758c2ecf20Sopenharmony_ci if (!spkr) 2768c2ecf20Sopenharmony_ci return -ENOMEM; 2778c2ecf20Sopenharmony_ci oxfw->spec = spkr; 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci if (is_lacie) { 2808c2ecf20Sopenharmony_ci spkr->mixer_channels = 1; 2818c2ecf20Sopenharmony_ci spkr->mute_fb_id = 0x01; 2828c2ecf20Sopenharmony_ci spkr->volume_fb_id = 0x01; 2838c2ecf20Sopenharmony_ci } else { 2848c2ecf20Sopenharmony_ci spkr->mixer_channels = 6; 2858c2ecf20Sopenharmony_ci spkr->mute_fb_id = 0x01; 2868c2ecf20Sopenharmony_ci spkr->volume_fb_id = 0x02; 2878c2ecf20Sopenharmony_ci } 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, 2908c2ecf20Sopenharmony_ci &spkr->volume_min, 0, CTL_MIN, CTL_READ); 2918c2ecf20Sopenharmony_ci if (err < 0) 2928c2ecf20Sopenharmony_ci return err; 2938c2ecf20Sopenharmony_ci err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, 2948c2ecf20Sopenharmony_ci &spkr->volume_max, 0, CTL_MAX, CTL_READ); 2958c2ecf20Sopenharmony_ci if (err < 0) 2968c2ecf20Sopenharmony_ci return err; 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ci err = avc_audio_feature_mute(oxfw->unit, spkr->mute_fb_id, &spkr->mute, 2998c2ecf20Sopenharmony_ci CTL_READ); 3008c2ecf20Sopenharmony_ci if (err < 0) 3018c2ecf20Sopenharmony_ci return err; 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci first_ch = spkr->mixer_channels == 1 ? 0 : 1; 3048c2ecf20Sopenharmony_ci for (i = 0; i < spkr->mixer_channels; ++i) { 3058c2ecf20Sopenharmony_ci err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, 3068c2ecf20Sopenharmony_ci &spkr->volume[i], first_ch + i, 3078c2ecf20Sopenharmony_ci CTL_CURRENT, CTL_READ); 3088c2ecf20Sopenharmony_ci if (err < 0) 3098c2ecf20Sopenharmony_ci return err; 3108c2ecf20Sopenharmony_ci } 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(controls); ++i) { 3138c2ecf20Sopenharmony_ci err = snd_ctl_add(oxfw->card, 3148c2ecf20Sopenharmony_ci snd_ctl_new1(&controls[i], oxfw)); 3158c2ecf20Sopenharmony_ci if (err < 0) 3168c2ecf20Sopenharmony_ci return err; 3178c2ecf20Sopenharmony_ci } 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci return 0; 3208c2ecf20Sopenharmony_ci} 321