Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 1 | /* |
Takashi Sakamoto | f3a0e32 | 2015-12-15 23:56:17 +0900 | [diff] [blame] | 2 | * oxfw-spkr.c - a part of driver for OXFW970/971 based devices |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 3 | * |
| 4 | * Copyright (c) Clemens Ladisch <clemens@ladisch.de> |
| 5 | * Licensed under the terms of the GNU General Public License, version 2. |
| 6 | */ |
| 7 | |
| 8 | #include "oxfw.h" |
| 9 | |
Takashi Sakamoto | 40540de | 2015-12-16 20:37:55 +0900 | [diff] [blame] | 10 | struct fw_spkr { |
| 11 | bool mute; |
| 12 | s16 volume[6]; |
| 13 | s16 volume_min; |
| 14 | s16 volume_max; |
Takashi Sakamoto | 3e2f457 | 2015-12-16 20:37:56 +0900 | [diff] [blame] | 15 | |
| 16 | unsigned int mixer_channels; |
| 17 | u8 mute_fb_id; |
| 18 | u8 volume_fb_id; |
Takashi Sakamoto | 40540de | 2015-12-16 20:37:55 +0900 | [diff] [blame] | 19 | }; |
| 20 | |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 21 | enum control_action { CTL_READ, CTL_WRITE }; |
| 22 | enum control_attribute { |
| 23 | CTL_MIN = 0x02, |
| 24 | CTL_MAX = 0x03, |
| 25 | CTL_CURRENT = 0x10, |
| 26 | }; |
| 27 | |
Takashi Sakamoto | eab8e4e | 2015-12-15 23:56:19 +0900 | [diff] [blame] | 28 | static int avc_audio_feature_mute(struct fw_unit *unit, u8 fb_id, bool *value, |
| 29 | enum control_action action) |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 30 | { |
| 31 | u8 *buf; |
| 32 | u8 response_ok; |
| 33 | int err; |
| 34 | |
| 35 | buf = kmalloc(11, GFP_KERNEL); |
| 36 | if (!buf) |
| 37 | return -ENOMEM; |
| 38 | |
| 39 | if (action == CTL_READ) { |
| 40 | buf[0] = 0x01; /* AV/C, STATUS */ |
| 41 | response_ok = 0x0c; /* STABLE */ |
| 42 | } else { |
| 43 | buf[0] = 0x00; /* AV/C, CONTROL */ |
| 44 | response_ok = 0x09; /* ACCEPTED */ |
| 45 | } |
| 46 | buf[1] = 0x08; /* audio unit 0 */ |
| 47 | buf[2] = 0xb8; /* FUNCTION BLOCK */ |
| 48 | buf[3] = 0x81; /* function block type: feature */ |
Takashi Sakamoto | eab8e4e | 2015-12-15 23:56:19 +0900 | [diff] [blame] | 49 | buf[4] = fb_id; /* function block ID */ |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 50 | buf[5] = 0x10; /* control attribute: current */ |
| 51 | buf[6] = 0x02; /* selector length */ |
| 52 | buf[7] = 0x00; /* audio channel number */ |
| 53 | buf[8] = 0x01; /* control selector: mute */ |
| 54 | buf[9] = 0x01; /* control data length */ |
| 55 | if (action == CTL_READ) |
| 56 | buf[10] = 0xff; |
| 57 | else |
| 58 | buf[10] = *value ? 0x70 : 0x60; |
| 59 | |
Takashi Sakamoto | eab8e4e | 2015-12-15 23:56:19 +0900 | [diff] [blame] | 60 | err = fcp_avc_transaction(unit, buf, 11, buf, 11, 0x3fe); |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 61 | if (err < 0) |
| 62 | goto error; |
| 63 | if (err < 11) { |
Takashi Sakamoto | eab8e4e | 2015-12-15 23:56:19 +0900 | [diff] [blame] | 64 | dev_err(&unit->device, "short FCP response\n"); |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 65 | err = -EIO; |
| 66 | goto error; |
| 67 | } |
| 68 | if (buf[0] != response_ok) { |
Takashi Sakamoto | eab8e4e | 2015-12-15 23:56:19 +0900 | [diff] [blame] | 69 | dev_err(&unit->device, "mute command failed\n"); |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 70 | err = -EIO; |
| 71 | goto error; |
| 72 | } |
| 73 | if (action == CTL_READ) |
| 74 | *value = buf[10] == 0x70; |
| 75 | |
| 76 | err = 0; |
| 77 | |
| 78 | error: |
| 79 | kfree(buf); |
| 80 | |
| 81 | return err; |
| 82 | } |
| 83 | |
Takashi Sakamoto | eab8e4e | 2015-12-15 23:56:19 +0900 | [diff] [blame] | 84 | static int avc_audio_feature_volume(struct fw_unit *unit, u8 fb_id, s16 *value, |
| 85 | unsigned int channel, |
| 86 | enum control_attribute attribute, |
| 87 | enum control_action action) |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 88 | { |
| 89 | u8 *buf; |
| 90 | u8 response_ok; |
| 91 | int err; |
| 92 | |
| 93 | buf = kmalloc(12, GFP_KERNEL); |
| 94 | if (!buf) |
| 95 | return -ENOMEM; |
| 96 | |
| 97 | if (action == CTL_READ) { |
| 98 | buf[0] = 0x01; /* AV/C, STATUS */ |
| 99 | response_ok = 0x0c; /* STABLE */ |
| 100 | } else { |
| 101 | buf[0] = 0x00; /* AV/C, CONTROL */ |
| 102 | response_ok = 0x09; /* ACCEPTED */ |
| 103 | } |
| 104 | buf[1] = 0x08; /* audio unit 0 */ |
| 105 | buf[2] = 0xb8; /* FUNCTION BLOCK */ |
| 106 | buf[3] = 0x81; /* function block type: feature */ |
Takashi Sakamoto | eab8e4e | 2015-12-15 23:56:19 +0900 | [diff] [blame] | 107 | buf[4] = fb_id; /* function block ID */ |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 108 | buf[5] = attribute; /* control attribute */ |
| 109 | buf[6] = 0x02; /* selector length */ |
| 110 | buf[7] = channel; /* audio channel number */ |
| 111 | buf[8] = 0x02; /* control selector: volume */ |
| 112 | buf[9] = 0x02; /* control data length */ |
| 113 | if (action == CTL_READ) { |
| 114 | buf[10] = 0xff; |
| 115 | buf[11] = 0xff; |
| 116 | } else { |
| 117 | buf[10] = *value >> 8; |
| 118 | buf[11] = *value; |
| 119 | } |
| 120 | |
Takashi Sakamoto | eab8e4e | 2015-12-15 23:56:19 +0900 | [diff] [blame] | 121 | err = fcp_avc_transaction(unit, buf, 12, buf, 12, 0x3fe); |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 122 | if (err < 0) |
| 123 | goto error; |
| 124 | if (err < 12) { |
Takashi Sakamoto | eab8e4e | 2015-12-15 23:56:19 +0900 | [diff] [blame] | 125 | dev_err(&unit->device, "short FCP response\n"); |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 126 | err = -EIO; |
| 127 | goto error; |
| 128 | } |
| 129 | if (buf[0] != response_ok) { |
Takashi Sakamoto | eab8e4e | 2015-12-15 23:56:19 +0900 | [diff] [blame] | 130 | dev_err(&unit->device, "volume command failed\n"); |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 131 | err = -EIO; |
| 132 | goto error; |
| 133 | } |
| 134 | if (action == CTL_READ) |
| 135 | *value = (buf[10] << 8) | buf[11]; |
| 136 | |
| 137 | err = 0; |
| 138 | |
| 139 | error: |
| 140 | kfree(buf); |
| 141 | |
| 142 | return err; |
| 143 | } |
| 144 | |
Takashi Sakamoto | 29aa09a | 2015-12-15 23:56:18 +0900 | [diff] [blame] | 145 | static int spkr_mute_get(struct snd_kcontrol *control, |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 146 | struct snd_ctl_elem_value *value) |
| 147 | { |
| 148 | struct snd_oxfw *oxfw = control->private_data; |
Takashi Sakamoto | 40540de | 2015-12-16 20:37:55 +0900 | [diff] [blame] | 149 | struct fw_spkr *spkr = oxfw->spec; |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 150 | |
Takashi Sakamoto | 40540de | 2015-12-16 20:37:55 +0900 | [diff] [blame] | 151 | value->value.integer.value[0] = !spkr->mute; |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 152 | |
| 153 | return 0; |
| 154 | } |
| 155 | |
Takashi Sakamoto | 29aa09a | 2015-12-15 23:56:18 +0900 | [diff] [blame] | 156 | static int spkr_mute_put(struct snd_kcontrol *control, |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 157 | struct snd_ctl_elem_value *value) |
| 158 | { |
| 159 | struct snd_oxfw *oxfw = control->private_data; |
Takashi Sakamoto | 40540de | 2015-12-16 20:37:55 +0900 | [diff] [blame] | 160 | struct fw_spkr *spkr = oxfw->spec; |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 161 | bool mute; |
| 162 | int err; |
| 163 | |
| 164 | mute = !value->value.integer.value[0]; |
| 165 | |
Takashi Sakamoto | 40540de | 2015-12-16 20:37:55 +0900 | [diff] [blame] | 166 | if (mute == spkr->mute) |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 167 | return 0; |
| 168 | |
Takashi Sakamoto | 3e2f457 | 2015-12-16 20:37:56 +0900 | [diff] [blame] | 169 | err = avc_audio_feature_mute(oxfw->unit, spkr->mute_fb_id, &mute, |
| 170 | CTL_WRITE); |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 171 | if (err < 0) |
| 172 | return err; |
Takashi Sakamoto | 40540de | 2015-12-16 20:37:55 +0900 | [diff] [blame] | 173 | spkr->mute = mute; |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 174 | |
| 175 | return 1; |
| 176 | } |
| 177 | |
Takashi Sakamoto | 29aa09a | 2015-12-15 23:56:18 +0900 | [diff] [blame] | 178 | static int spkr_volume_info(struct snd_kcontrol *control, |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 179 | struct snd_ctl_elem_info *info) |
| 180 | { |
| 181 | struct snd_oxfw *oxfw = control->private_data; |
Takashi Sakamoto | 40540de | 2015-12-16 20:37:55 +0900 | [diff] [blame] | 182 | struct fw_spkr *spkr = oxfw->spec; |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 183 | |
| 184 | info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; |
Takashi Sakamoto | 3e2f457 | 2015-12-16 20:37:56 +0900 | [diff] [blame] | 185 | info->count = spkr->mixer_channels; |
Takashi Sakamoto | 40540de | 2015-12-16 20:37:55 +0900 | [diff] [blame] | 186 | info->value.integer.min = spkr->volume_min; |
| 187 | info->value.integer.max = spkr->volume_max; |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 188 | |
| 189 | return 0; |
| 190 | } |
| 191 | |
| 192 | static const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 }; |
| 193 | |
Takashi Sakamoto | 29aa09a | 2015-12-15 23:56:18 +0900 | [diff] [blame] | 194 | static int spkr_volume_get(struct snd_kcontrol *control, |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 195 | struct snd_ctl_elem_value *value) |
| 196 | { |
| 197 | struct snd_oxfw *oxfw = control->private_data; |
Takashi Sakamoto | 40540de | 2015-12-16 20:37:55 +0900 | [diff] [blame] | 198 | struct fw_spkr *spkr = oxfw->spec; |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 199 | unsigned int i; |
| 200 | |
Takashi Sakamoto | 3e2f457 | 2015-12-16 20:37:56 +0900 | [diff] [blame] | 201 | for (i = 0; i < spkr->mixer_channels; ++i) |
Takashi Sakamoto | 40540de | 2015-12-16 20:37:55 +0900 | [diff] [blame] | 202 | value->value.integer.value[channel_map[i]] = spkr->volume[i]; |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 203 | |
| 204 | return 0; |
| 205 | } |
| 206 | |
Takashi Sakamoto | 29aa09a | 2015-12-15 23:56:18 +0900 | [diff] [blame] | 207 | static int spkr_volume_put(struct snd_kcontrol *control, |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 208 | struct snd_ctl_elem_value *value) |
| 209 | { |
| 210 | struct snd_oxfw *oxfw = control->private_data; |
Takashi Sakamoto | 40540de | 2015-12-16 20:37:55 +0900 | [diff] [blame] | 211 | struct fw_spkr *spkr = oxfw->spec; |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 212 | unsigned int i, changed_channels; |
| 213 | bool equal_values = true; |
| 214 | s16 volume; |
| 215 | int err; |
| 216 | |
Takashi Sakamoto | 3e2f457 | 2015-12-16 20:37:56 +0900 | [diff] [blame] | 217 | for (i = 0; i < spkr->mixer_channels; ++i) { |
Takashi Sakamoto | 40540de | 2015-12-16 20:37:55 +0900 | [diff] [blame] | 218 | if (value->value.integer.value[i] < spkr->volume_min || |
| 219 | value->value.integer.value[i] > spkr->volume_max) |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 220 | return -EINVAL; |
| 221 | if (value->value.integer.value[i] != |
| 222 | value->value.integer.value[0]) |
| 223 | equal_values = false; |
| 224 | } |
| 225 | |
| 226 | changed_channels = 0; |
Takashi Sakamoto | 3e2f457 | 2015-12-16 20:37:56 +0900 | [diff] [blame] | 227 | for (i = 0; i < spkr->mixer_channels; ++i) |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 228 | if (value->value.integer.value[channel_map[i]] != |
Takashi Sakamoto | 40540de | 2015-12-16 20:37:55 +0900 | [diff] [blame] | 229 | spkr->volume[i]) |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 230 | changed_channels |= 1 << (i + 1); |
| 231 | |
| 232 | if (equal_values && changed_channels != 0) |
| 233 | changed_channels = 1 << 0; |
| 234 | |
Takashi Sakamoto | 3e2f457 | 2015-12-16 20:37:56 +0900 | [diff] [blame] | 235 | for (i = 0; i <= spkr->mixer_channels; ++i) { |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 236 | volume = value->value.integer.value[channel_map[i ? i - 1 : 0]]; |
| 237 | if (changed_channels & (1 << i)) { |
Takashi Sakamoto | eab8e4e | 2015-12-15 23:56:19 +0900 | [diff] [blame] | 238 | err = avc_audio_feature_volume(oxfw->unit, |
Takashi Sakamoto | 3e2f457 | 2015-12-16 20:37:56 +0900 | [diff] [blame] | 239 | spkr->volume_fb_id, &volume, |
Takashi Sakamoto | eab8e4e | 2015-12-15 23:56:19 +0900 | [diff] [blame] | 240 | i, CTL_CURRENT, CTL_WRITE); |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 241 | if (err < 0) |
| 242 | return err; |
| 243 | } |
| 244 | if (i > 0) |
Takashi Sakamoto | 40540de | 2015-12-16 20:37:55 +0900 | [diff] [blame] | 245 | spkr->volume[i - 1] = volume; |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 246 | } |
| 247 | |
| 248 | return changed_channels != 0; |
| 249 | } |
| 250 | |
Takashi Sakamoto | 3e2f457 | 2015-12-16 20:37:56 +0900 | [diff] [blame] | 251 | int snd_oxfw_add_spkr(struct snd_oxfw *oxfw, bool is_lacie) |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 252 | { |
| 253 | static const struct snd_kcontrol_new controls[] = { |
| 254 | { |
| 255 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| 256 | .name = "PCM Playback Switch", |
| 257 | .info = snd_ctl_boolean_mono_info, |
Takashi Sakamoto | 29aa09a | 2015-12-15 23:56:18 +0900 | [diff] [blame] | 258 | .get = spkr_mute_get, |
| 259 | .put = spkr_mute_put, |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 260 | }, |
| 261 | { |
| 262 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| 263 | .name = "PCM Playback Volume", |
Takashi Sakamoto | 29aa09a | 2015-12-15 23:56:18 +0900 | [diff] [blame] | 264 | .info = spkr_volume_info, |
| 265 | .get = spkr_volume_get, |
| 266 | .put = spkr_volume_put, |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 267 | }, |
| 268 | }; |
Takashi Sakamoto | 40540de | 2015-12-16 20:37:55 +0900 | [diff] [blame] | 269 | struct fw_spkr *spkr; |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 270 | unsigned int i, first_ch; |
| 271 | int err; |
| 272 | |
Takashi Sakamoto | 40540de | 2015-12-16 20:37:55 +0900 | [diff] [blame] | 273 | spkr = kzalloc(sizeof(struct fw_spkr), GFP_KERNEL); |
| 274 | if (spkr == NULL) |
| 275 | return -ENOMEM; |
| 276 | oxfw->spec = spkr; |
| 277 | |
Takashi Sakamoto | 3e2f457 | 2015-12-16 20:37:56 +0900 | [diff] [blame] | 278 | if (is_lacie) { |
| 279 | spkr->mixer_channels = 1; |
| 280 | spkr->mute_fb_id = 0x01; |
| 281 | spkr->volume_fb_id = 0x01; |
| 282 | } else { |
| 283 | spkr->mixer_channels = 6; |
| 284 | spkr->mute_fb_id = 0x01; |
| 285 | spkr->volume_fb_id = 0x02; |
| 286 | } |
| 287 | |
| 288 | err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, |
| 289 | &spkr->volume_min, 0, CTL_MIN, CTL_READ); |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 290 | if (err < 0) |
| 291 | return err; |
Takashi Sakamoto | 3e2f457 | 2015-12-16 20:37:56 +0900 | [diff] [blame] | 292 | err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, |
| 293 | &spkr->volume_max, 0, CTL_MAX, CTL_READ); |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 294 | if (err < 0) |
| 295 | return err; |
| 296 | |
Takashi Sakamoto | 3e2f457 | 2015-12-16 20:37:56 +0900 | [diff] [blame] | 297 | err = avc_audio_feature_mute(oxfw->unit, spkr->mute_fb_id, &spkr->mute, |
| 298 | CTL_READ); |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 299 | if (err < 0) |
| 300 | return err; |
| 301 | |
Takashi Sakamoto | 3e2f457 | 2015-12-16 20:37:56 +0900 | [diff] [blame] | 302 | first_ch = spkr->mixer_channels == 1 ? 0 : 1; |
| 303 | for (i = 0; i < spkr->mixer_channels; ++i) { |
| 304 | err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, |
| 305 | &spkr->volume[i], first_ch + i, |
| 306 | CTL_CURRENT, CTL_READ); |
Takashi Sakamoto | 31514bf | 2014-11-29 00:59:29 +0900 | [diff] [blame] | 307 | if (err < 0) |
| 308 | return err; |
| 309 | } |
| 310 | |
| 311 | for (i = 0; i < ARRAY_SIZE(controls); ++i) { |
| 312 | err = snd_ctl_add(oxfw->card, |
| 313 | snd_ctl_new1(&controls[i], oxfw)); |
| 314 | if (err < 0) |
| 315 | return err; |
| 316 | } |
| 317 | |
| 318 | return 0; |
| 319 | } |