From f3b827e0b1841f4cfc18436e09f4f269f3be908e Mon Sep 17 00:00:00 2001 From: Bhumika Goyal Date: Mon, 20 Feb 2017 00:18:09 +0530 Subject: ALSA: pci: constify snd_kcontrol_new structures Declare snd_kcontrol_new structures as const as they are only passed as an argument to the function snd_ctl_new1. This argument is of type const, so snd_kcontrol_new structures having the same property can be made const too. Done using Coccinelle: @r1 disable optional_qualifier @ identifier i; position p; @@ static struct snd_kcontrol_new i@p = {...}; @ok1@ identifier r1.i; position p; expression e1; @@ snd_ctl_new1(&i@p,e1) @bad@ position p!={r1.p,ok1.p}; identifier r1.i; @@ i@p @depends on !bad disable optional_qualifier@ identifier r1.i; @@ +const struct snd_kcontrol_new i; Signed-off-by: Bhumika Goyal Signed-off-by: Takashi Iwai --- sound/pci/au88x0/au88x0_a3d.c | 2 +- sound/pci/au88x0/au88x0_eq.c | 6 +++--- sound/pci/au88x0/au88x0_pcm.c | 2 +- sound/pci/aw2/aw2-alsa.c | 2 +- sound/pci/bt87x.c | 6 +++--- sound/pci/ca0106/ca0106_mixer.c | 4 ++-- sound/pci/cmipci.c | 6 +++--- sound/pci/cs4281.c | 4 ++-- sound/pci/echoaudio/echoaudio.c | 26 +++++++++++++------------- sound/pci/emu10k1/emu10k1x.c | 6 +++--- sound/pci/emu10k1/emumixer.c | 30 +++++++++++++++--------------- sound/pci/emu10k1/emupcm.c | 2 +- sound/pci/ens1370.c | 4 ++-- sound/pci/hda/hda_codec.c | 4 ++-- sound/pci/hda/patch_hdmi.c | 2 +- sound/pci/ice1712/delta.c | 2 +- sound/pci/ice1712/ews.c | 4 ++-- sound/pci/ice1712/ice1712.c | 30 +++++++++++++++--------------- sound/pci/ice1712/ice1724.c | 20 ++++++++++---------- sound/pci/lola/lola_mixer.c | 2 +- sound/pci/lx6464es/lx6464es.c | 2 +- sound/pci/mixart/mixart_mixer.c | 6 +++--- sound/pci/pcxhr/pcxhr_mix22.c | 6 +++--- sound/pci/pcxhr/pcxhr_mixer.c | 22 +++++++++++----------- sound/pci/trident/trident_main.c | 22 +++++++++++----------- sound/pci/via82xx.c | 6 +++--- sound/pci/vx222/vx222_ops.c | 4 ++-- sound/pci/ymfpci/ymfpci_main.c | 14 +++++++------- 28 files changed, 123 insertions(+), 123 deletions(-) (limited to 'sound') diff --git a/sound/pci/au88x0/au88x0_a3d.c b/sound/pci/au88x0/au88x0_a3d.c index ab0f87312911..7a4558a70fb9 100644 --- a/sound/pci/au88x0/au88x0_a3d.c +++ b/sound/pci/au88x0/au88x0_a3d.c @@ -846,7 +846,7 @@ snd_vortex_a3d_filter_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new vortex_a3d_kcontrol = { +static const struct snd_kcontrol_new vortex_a3d_kcontrol = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "Playback PCM advanced processing", .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, diff --git a/sound/pci/au88x0/au88x0_eq.c b/sound/pci/au88x0/au88x0_eq.c index 9585c5c63b96..b566b44e4da7 100644 --- a/sound/pci/au88x0/au88x0_eq.c +++ b/sound/pci/au88x0/au88x0_eq.c @@ -757,7 +757,7 @@ snd_vortex_eqtoggle_put(struct snd_kcontrol *kcontrol, return 1; /* Allways changes */ } -static struct snd_kcontrol_new vortex_eqtoggle_kcontrol = { +static const struct snd_kcontrol_new vortex_eqtoggle_kcontrol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "EQ Enable", .index = 0, @@ -815,7 +815,7 @@ snd_vortex_eq_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucon return changed; } -static struct snd_kcontrol_new vortex_eq_kcontrol = { +static const struct snd_kcontrol_new vortex_eq_kcontrol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = " .", .index = 0, @@ -855,7 +855,7 @@ snd_vortex_peaks_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u return 0; } -static struct snd_kcontrol_new vortex_levels_kcontrol = { +static const struct snd_kcontrol_new vortex_levels_kcontrol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "EQ Peaks", .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, diff --git a/sound/pci/au88x0/au88x0_pcm.c b/sound/pci/au88x0/au88x0_pcm.c index df5741a78fd2..335979a753fe 100644 --- a/sound/pci/au88x0/au88x0_pcm.c +++ b/sound/pci/au88x0/au88x0_pcm.c @@ -601,7 +601,7 @@ static int snd_vortex_pcm_vol_put(struct snd_kcontrol *kcontrol, static const DECLARE_TLV_DB_MINMAX(vortex_pcm_vol_db_scale, -9600, 2400); -static struct snd_kcontrol_new snd_vortex_pcm_vol = { +static const struct snd_kcontrol_new snd_vortex_pcm_vol = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "PCM Playback Volume", .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | diff --git a/sound/pci/aw2/aw2-alsa.c b/sound/pci/aw2/aw2-alsa.c index 57bbb87d0c62..8356180bfe0e 100644 --- a/sound/pci/aw2/aw2-alsa.c +++ b/sound/pci/aw2/aw2-alsa.c @@ -202,7 +202,7 @@ static const struct snd_pcm_ops snd_aw2_capture_ops = { .pointer = snd_aw2_pcm_pointer_capture, }; -static struct snd_kcontrol_new aw2_control = { +static const struct snd_kcontrol_new aw2_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Capture Route", .index = 0, diff --git a/sound/pci/bt87x.c b/sound/pci/bt87x.c index f2c0709d7441..099efb046b1c 100644 --- a/sound/pci/bt87x.c +++ b/sound/pci/bt87x.c @@ -598,7 +598,7 @@ static int snd_bt87x_capture_volume_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_bt87x_capture_volume = { +static const struct snd_kcontrol_new snd_bt87x_capture_volume = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture Volume", .info = snd_bt87x_capture_volume_info, @@ -634,7 +634,7 @@ static int snd_bt87x_capture_boost_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_bt87x_capture_boost = { +static const struct snd_kcontrol_new snd_bt87x_capture_boost = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture Boost", .info = snd_bt87x_capture_boost_info, @@ -676,7 +676,7 @@ static int snd_bt87x_capture_source_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_bt87x_capture_source = { +static const struct snd_kcontrol_new snd_bt87x_capture_source = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture Source", .info = snd_bt87x_capture_source_info, diff --git a/sound/pci/ca0106/ca0106_mixer.c b/sound/pci/ca0106/ca0106_mixer.c index 025805cba779..b4d3415331f6 100644 --- a/sound/pci/ca0106/ca0106_mixer.c +++ b/sound/pci/ca0106/ca0106_mixer.c @@ -301,7 +301,7 @@ static int snd_ca0106_capture_mic_line_in_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ca0106_capture_mic_line_in = +static const struct snd_kcontrol_new snd_ca0106_capture_mic_line_in = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Shared Mic/Line in Capture Switch", @@ -310,7 +310,7 @@ static struct snd_kcontrol_new snd_ca0106_capture_mic_line_in = .put = snd_ca0106_capture_mic_line_in_put }; -static struct snd_kcontrol_new snd_ca0106_capture_line_in_side_out = +static const struct snd_kcontrol_new snd_ca0106_capture_line_in_side_out = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Shared Line in/Side out Capture Switch", diff --git a/sound/pci/cmipci.c b/sound/pci/cmipci.c index aeedc270ed9b..227c9d3802b8 100644 --- a/sound/pci/cmipci.c +++ b/sound/pci/cmipci.c @@ -1045,7 +1045,7 @@ static int snd_cmipci_spdif_default_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_cmipci_spdif_default = +static const struct snd_kcontrol_new snd_cmipci_spdif_default = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), @@ -1072,7 +1072,7 @@ static int snd_cmipci_spdif_mask_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_cmipci_spdif_mask = +static const struct snd_kcontrol_new snd_cmipci_spdif_mask = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1119,7 +1119,7 @@ static int snd_cmipci_spdif_stream_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_cmipci_spdif_stream = +static const struct snd_kcontrol_new snd_cmipci_spdif_stream = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, diff --git a/sound/pci/cs4281.c b/sound/pci/cs4281.c index fa7c51684dd2..f870697aca67 100644 --- a/sound/pci/cs4281.c +++ b/sound/pci/cs4281.c @@ -1055,7 +1055,7 @@ static int snd_cs4281_put_volume(struct snd_kcontrol *kcontrol, static const DECLARE_TLV_DB_SCALE(db_scale_dsp, -4650, 150, 0); -static struct snd_kcontrol_new snd_cs4281_fm_vol = +static const struct snd_kcontrol_new snd_cs4281_fm_vol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Synth Playback Volume", @@ -1066,7 +1066,7 @@ static struct snd_kcontrol_new snd_cs4281_fm_vol = .tlv = { .p = db_scale_dsp }, }; -static struct snd_kcontrol_new snd_cs4281_pcm_vol = +static const struct snd_kcontrol_new snd_cs4281_pcm_vol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Stream Playback Volume", diff --git a/sound/pci/echoaudio/echoaudio.c b/sound/pci/echoaudio/echoaudio.c index 937071760bc4..d15ecf9febbf 100644 --- a/sound/pci/echoaudio/echoaudio.c +++ b/sound/pci/echoaudio/echoaudio.c @@ -1039,7 +1039,7 @@ static int snd_echo_output_gain_put(struct snd_kcontrol *kcontrol, #ifdef ECHOCARD_HAS_LINE_OUT_GAIN /* On the Mia this one controls the line-out volume */ -static struct snd_kcontrol_new snd_echo_line_output_gain = { +static const struct snd_kcontrol_new snd_echo_line_output_gain = { .name = "Line Playback Volume", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | @@ -1050,7 +1050,7 @@ static struct snd_kcontrol_new snd_echo_line_output_gain = { .tlv = {.p = db_scale_output_gain}, }; #else -static struct snd_kcontrol_new snd_echo_pcm_output_gain = { +static const struct snd_kcontrol_new snd_echo_pcm_output_gain = { .name = "PCM Playback Volume", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, @@ -1120,7 +1120,7 @@ static int snd_echo_input_gain_put(struct snd_kcontrol *kcontrol, static const DECLARE_TLV_DB_SCALE(db_scale_input_gain, -2500, 50, 0); -static struct snd_kcontrol_new snd_echo_line_input_gain = { +static const struct snd_kcontrol_new snd_echo_line_input_gain = { .name = "Line Capture Volume", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, @@ -1184,7 +1184,7 @@ static int snd_echo_output_nominal_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_echo_output_nominal_level = { +static const struct snd_kcontrol_new snd_echo_output_nominal_level = { .name = "Line Playback Switch (-10dBV)", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .info = snd_echo_output_nominal_info, @@ -1250,7 +1250,7 @@ static int snd_echo_input_nominal_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_echo_intput_nominal_level = { +static const struct snd_kcontrol_new snd_echo_intput_nominal_level = { .name = "Line Capture Switch (-10dBV)", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .info = snd_echo_input_nominal_info, @@ -1477,7 +1477,7 @@ static int snd_echo_digital_mode_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_echo_digital_mode_switch = { +static const struct snd_kcontrol_new snd_echo_digital_mode_switch = { .name = "Digital mode Switch", .iface = SNDRV_CTL_ELEM_IFACE_CARD, .info = snd_echo_digital_mode_info, @@ -1527,7 +1527,7 @@ static int snd_echo_spdif_mode_put(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_echo_spdif_mode_switch = { +static const struct snd_kcontrol_new snd_echo_spdif_mode_switch = { .name = "S/PDIF mode Switch", .iface = SNDRV_CTL_ELEM_IFACE_CARD, .info = snd_echo_spdif_mode_info, @@ -1600,7 +1600,7 @@ static int snd_echo_clock_source_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_echo_clock_source_switch = { +static const struct snd_kcontrol_new snd_echo_clock_source_switch = { .name = "Sample Clock Source", .iface = SNDRV_CTL_ELEM_IFACE_PCM, .info = snd_echo_clock_source_info, @@ -1643,7 +1643,7 @@ static int snd_echo_phantom_power_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_echo_phantom_power_switch = { +static const struct snd_kcontrol_new snd_echo_phantom_power_switch = { .name = "Phantom power Switch", .iface = SNDRV_CTL_ELEM_IFACE_CARD, .info = snd_echo_phantom_power_info, @@ -1686,7 +1686,7 @@ static int snd_echo_automute_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_echo_automute_switch = { +static const struct snd_kcontrol_new snd_echo_automute_switch = { .name = "Digital Capture Switch (automute)", .iface = SNDRV_CTL_ELEM_IFACE_CARD, .info = snd_echo_automute_info, @@ -1713,7 +1713,7 @@ static int snd_echo_vumeters_switch_put(struct snd_kcontrol *kcontrol, return 1; } -static struct snd_kcontrol_new snd_echo_vumeters_switch = { +static const struct snd_kcontrol_new snd_echo_vumeters_switch = { .name = "VU-meters Switch", .iface = SNDRV_CTL_ELEM_IFACE_CARD, .access = SNDRV_CTL_ELEM_ACCESS_WRITE, @@ -1751,7 +1751,7 @@ static int snd_echo_vumeters_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_echo_vumeters = { +static const struct snd_kcontrol_new snd_echo_vumeters = { .name = "VU-meters", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READ | @@ -1804,7 +1804,7 @@ static int snd_echo_channels_info_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_echo_channels_info = { +static const struct snd_kcontrol_new snd_echo_channels_info = { .name = "Channels info", .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, diff --git a/sound/pci/emu10k1/emu10k1x.c b/sound/pci/emu10k1/emu10k1x.c index 32842734ada6..77a4413f4564 100644 --- a/sound/pci/emu10k1/emu10k1x.c +++ b/sound/pci/emu10k1/emu10k1x.c @@ -1112,7 +1112,7 @@ static int snd_emu10k1x_shared_spdif_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1x_shared_spdif = +static const struct snd_kcontrol_new snd_emu10k1x_shared_spdif = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Analog/Digital Output Jack", @@ -1171,7 +1171,7 @@ static int snd_emu10k1x_spdif_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1x_spdif_mask_control = +static const struct snd_kcontrol_new snd_emu10k1x_spdif_mask_control = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1181,7 +1181,7 @@ static struct snd_kcontrol_new snd_emu10k1x_spdif_mask_control = .get = snd_emu10k1x_spdif_get_mask }; -static struct snd_kcontrol_new snd_emu10k1x_spdif_control = +static const struct snd_kcontrol_new snd_emu10k1x_spdif_control = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 076b117009c5..b2219a73c17c 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -795,7 +795,7 @@ static int snd_emu1010_internal_clock_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu1010_internal_clock = +static const struct snd_kcontrol_new snd_emu1010_internal_clock = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .iface = SNDRV_CTL_ELEM_IFACE_MIXER, @@ -847,7 +847,7 @@ static int snd_emu1010_optical_out_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu1010_optical_out = { +static const struct snd_kcontrol_new snd_emu1010_optical_out = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Optical Output Mode", @@ -898,7 +898,7 @@ static int snd_emu1010_optical_in_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu1010_optical_in = { +static const struct snd_kcontrol_new snd_emu1010_optical_in = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Optical Input Mode", @@ -978,7 +978,7 @@ static int snd_audigy_i2c_capture_source_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_audigy_i2c_capture_source = +static const struct snd_kcontrol_new snd_audigy_i2c_capture_source = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture Source", @@ -1177,7 +1177,7 @@ static int snd_emu10k1_spdif_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_spdif_mask_control = +static const struct snd_kcontrol_new snd_emu10k1_spdif_mask_control = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1187,7 +1187,7 @@ static struct snd_kcontrol_new snd_emu10k1_spdif_mask_control = .get = snd_emu10k1_spdif_get_mask }; -static struct snd_kcontrol_new snd_emu10k1_spdif_control = +static const struct snd_kcontrol_new snd_emu10k1_spdif_control = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), @@ -1293,7 +1293,7 @@ static int snd_emu10k1_send_routing_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_send_routing_control = +static const struct snd_kcontrol_new snd_emu10k1_send_routing_control = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1364,7 +1364,7 @@ static int snd_emu10k1_send_volume_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_send_volume_control = +static const struct snd_kcontrol_new snd_emu10k1_send_volume_control = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1429,7 +1429,7 @@ static int snd_emu10k1_attn_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_attn_control = +static const struct snd_kcontrol_new snd_emu10k1_attn_control = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1501,7 +1501,7 @@ static int snd_emu10k1_efx_send_routing_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_efx_send_routing_control = +static const struct snd_kcontrol_new snd_emu10k1_efx_send_routing_control = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1568,7 +1568,7 @@ static int snd_emu10k1_efx_send_volume_put(struct snd_kcontrol *kcontrol, } -static struct snd_kcontrol_new snd_emu10k1_efx_send_volume_control = +static const struct snd_kcontrol_new snd_emu10k1_efx_send_volume_control = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1626,7 +1626,7 @@ static int snd_emu10k1_efx_attn_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_efx_attn_control = +static const struct snd_kcontrol_new snd_emu10k1_efx_attn_control = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1691,7 +1691,7 @@ static int snd_emu10k1_shared_spdif_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_shared_spdif = +static const struct snd_kcontrol_new snd_emu10k1_shared_spdif = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "SB Live Analog/Digital Output Jack", @@ -1700,7 +1700,7 @@ static struct snd_kcontrol_new snd_emu10k1_shared_spdif = .put = snd_emu10k1_shared_spdif_put }; -static struct snd_kcontrol_new snd_audigy_shared_spdif = +static const struct snd_kcontrol_new snd_audigy_shared_spdif = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Audigy Analog/Digital Output Jack", @@ -1738,7 +1738,7 @@ static int snd_audigy_capture_boost_put(struct snd_kcontrol *kcontrol, return snd_ac97_update(emu->ac97, AC97_REC_GAIN, val); } -static struct snd_kcontrol_new snd_audigy_capture_boost = +static const struct snd_kcontrol_new snd_audigy_capture_boost = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Mic Extra Boost", diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 37be1e14d756..ef1cf530c929 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -1542,7 +1542,7 @@ static int snd_emu10k1_pcm_efx_voices_mask_put(struct snd_kcontrol *kcontrol, st return change; } -static struct snd_kcontrol_new snd_emu10k1_pcm_efx_voices_mask = { +static const struct snd_kcontrol_new snd_emu10k1_pcm_efx_voices_mask = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "Captured FX8010 Outputs", .info = snd_emu10k1_pcm_efx_voices_mask_info, diff --git a/sound/pci/ens1370.c b/sound/pci/ens1370.c index 164adad91650..5d10349d11ce 100644 --- a/sound/pci/ens1370.c +++ b/sound/pci/ens1370.c @@ -1530,7 +1530,7 @@ static int snd_es1373_rear_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ens1373_rear = +static const struct snd_kcontrol_new snd_ens1373_rear = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "AC97 2ch->4ch Copy Switch", @@ -1575,7 +1575,7 @@ static int snd_es1373_line_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_ens1373_line = +static const struct snd_kcontrol_new snd_ens1373_line = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Line In->Rear Out Switch", diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 9913be8532ab..bf491eb58b24 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -1897,7 +1897,7 @@ static int vmaster_mute_mode_put(struct snd_kcontrol *kcontrol, return 1; } -static struct snd_kcontrol_new vmaster_mute_mode = { +static const struct snd_kcontrol_new vmaster_mute_mode = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Mute-LED Mode", .info = vmaster_mute_mode_info, @@ -2637,7 +2637,7 @@ static int spdif_share_sw_put(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new spdif_share_sw = { +static const struct snd_kcontrol_new spdif_share_sw = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "IEC958 Default PCM Playback Switch", .info = snd_ctl_boolean_mono_info, diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index 3fc201c3b95a..3ce872c26436 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -354,7 +354,7 @@ static int hdmi_eld_ctl_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new eld_bytes_ctl = { +static const struct snd_kcontrol_new eld_bytes_ctl = { .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "ELD", diff --git a/sound/pci/ice1712/delta.c b/sound/pci/ice1712/delta.c index 3bfdc78cbc5f..da5f37b7fdd0 100644 --- a/sound/pci/ice1712/delta.c +++ b/sound/pci/ice1712/delta.c @@ -432,7 +432,7 @@ static int snd_ice1712_delta1010lt_wordclock_status_get(struct snd_kcontrol *kco return 0; } -static struct snd_kcontrol_new snd_ice1712_delta1010lt_wordclock_status = +static const struct snd_kcontrol_new snd_ice1712_delta1010lt_wordclock_status = { .access = (SNDRV_CTL_ELEM_ACCESS_READ), .iface = SNDRV_CTL_ELEM_IFACE_MIXER, diff --git a/sound/pci/ice1712/ews.c b/sound/pci/ice1712/ews.c index 5cb587cf360e..ec07136fc288 100644 --- a/sound/pci/ice1712/ews.c +++ b/sound/pci/ice1712/ews.c @@ -719,7 +719,7 @@ static int snd_ice1712_ews88mt_input_sense_put(struct snd_kcontrol *kcontrol, st return ndata != data; } -static struct snd_kcontrol_new snd_ice1712_ews88mt_input_sense = { +static const struct snd_kcontrol_new snd_ice1712_ews88mt_input_sense = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Input Sensitivity Switch", .info = snd_ice1712_ewx_io_sense_info, @@ -728,7 +728,7 @@ static struct snd_kcontrol_new snd_ice1712_ews88mt_input_sense = { .count = 8, }; -static struct snd_kcontrol_new snd_ice1712_ews88mt_output_sense = { +static const struct snd_kcontrol_new snd_ice1712_ews88mt_output_sense = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Output Sensitivity Switch", .info = snd_ice1712_ewx_io_sense_info, diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c index b4aa4c1370a8..1d8612cabb9e 100644 --- a/sound/pci/ice1712/ice1712.c +++ b/sound/pci/ice1712/ice1712.c @@ -279,7 +279,7 @@ static int snd_ice1712_digmix_route_ac97_put(struct snd_kcontrol *kcontrol, stru return val != nval; } -static struct snd_kcontrol_new snd_ice1712_mixer_digmix_route_ac97 = { +static const struct snd_kcontrol_new snd_ice1712_mixer_digmix_route_ac97 = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Digital Mixer To AC97", .info = snd_ice1712_digmix_route_ac97_info, @@ -1410,7 +1410,7 @@ static struct snd_kcontrol_new snd_ice1712_multi_capture_analog_switch = { .private_value = 10, }; -static struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_switch = { +static const struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = SNDRV_CTL_NAME_IEC958("Multi ", CAPTURE, SWITCH), .info = snd_ice1712_pro_mixer_switch_info, @@ -1432,7 +1432,7 @@ static struct snd_kcontrol_new snd_ice1712_multi_capture_analog_volume = { .tlv = { .p = db_scale_playback } }; -static struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_volume = { +static const struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_volume = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = SNDRV_CTL_NAME_IEC958("Multi ", CAPTURE, VOLUME), .info = snd_ice1712_pro_mixer_volume_info, @@ -1630,7 +1630,7 @@ static int snd_ice1712_eeprom_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ice1712_eeprom = { +static const struct snd_kcontrol_new snd_ice1712_eeprom = { .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = "ICE1712 EEPROM", .access = SNDRV_CTL_ELEM_ACCESS_READ, @@ -1666,7 +1666,7 @@ static int snd_ice1712_spdif_default_put(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ice1712_spdif_default = +static const struct snd_kcontrol_new snd_ice1712_spdif_default = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), @@ -1717,7 +1717,7 @@ static int snd_ice1712_spdif_maskp_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ice1712_spdif_maskc = +static const struct snd_kcontrol_new snd_ice1712_spdif_maskc = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1726,7 +1726,7 @@ static struct snd_kcontrol_new snd_ice1712_spdif_maskc = .get = snd_ice1712_spdif_maskc_get, }; -static struct snd_kcontrol_new snd_ice1712_spdif_maskp = +static const struct snd_kcontrol_new snd_ice1712_spdif_maskp = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1753,7 +1753,7 @@ static int snd_ice1712_spdif_stream_put(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ice1712_spdif_stream = +static const struct snd_kcontrol_new snd_ice1712_spdif_stream = { .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE), @@ -1878,7 +1878,7 @@ static int snd_ice1712_pro_internal_clock_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ice1712_pro_internal_clock = { +static const struct snd_kcontrol_new snd_ice1712_pro_internal_clock = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Internal Clock", .info = snd_ice1712_pro_internal_clock_info, @@ -1943,7 +1943,7 @@ static int snd_ice1712_pro_internal_clock_default_put(struct snd_kcontrol *kcont return change; } -static struct snd_kcontrol_new snd_ice1712_pro_internal_clock_default = { +static const struct snd_kcontrol_new snd_ice1712_pro_internal_clock_default = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Internal Clock Default", .info = snd_ice1712_pro_internal_clock_default_info, @@ -1974,7 +1974,7 @@ static int snd_ice1712_pro_rate_locking_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ice1712_pro_rate_locking = { +static const struct snd_kcontrol_new snd_ice1712_pro_rate_locking = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Rate Locking", .info = snd_ice1712_pro_rate_locking_info, @@ -2005,7 +2005,7 @@ static int snd_ice1712_pro_rate_reset_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ice1712_pro_rate_reset = { +static const struct snd_kcontrol_new snd_ice1712_pro_rate_reset = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Rate Reset", .info = snd_ice1712_pro_rate_reset_info, @@ -2173,7 +2173,7 @@ static struct snd_kcontrol_new snd_ice1712_mixer_pro_analog_route = { .put = snd_ice1712_pro_route_analog_put, }; -static struct snd_kcontrol_new snd_ice1712_mixer_pro_spdif_route = { +static const struct snd_kcontrol_new snd_ice1712_mixer_pro_spdif_route = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, NONE) "Route", .info = snd_ice1712_pro_route_info, @@ -2215,7 +2215,7 @@ static int snd_ice1712_pro_volume_rate_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ice1712_mixer_pro_volume_rate = { +static const struct snd_kcontrol_new snd_ice1712_mixer_pro_volume_rate = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Volume Rate", .info = snd_ice1712_pro_volume_rate_info, @@ -2248,7 +2248,7 @@ static int snd_ice1712_pro_peak_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ice1712_mixer_pro_peak = { +static const struct snd_kcontrol_new snd_ice1712_mixer_pro_peak = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "Multi Track Peak", .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, diff --git a/sound/pci/ice1712/ice1724.c b/sound/pci/ice1712/ice1724.c index 842744e7a139..9cd6e55c0642 100644 --- a/sound/pci/ice1712/ice1724.c +++ b/sound/pci/ice1712/ice1724.c @@ -1598,7 +1598,7 @@ static int snd_vt1724_eeprom_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_vt1724_eeprom = { +static const struct snd_kcontrol_new snd_vt1724_eeprom = { .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = "ICE1724 EEPROM", .access = SNDRV_CTL_ELEM_ACCESS_READ, @@ -1711,7 +1711,7 @@ static int snd_vt1724_spdif_default_put(struct snd_kcontrol *kcontrol, return val != old; } -static struct snd_kcontrol_new snd_vt1724_spdif_default = +static const struct snd_kcontrol_new snd_vt1724_spdif_default = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), @@ -1743,7 +1743,7 @@ static int snd_vt1724_spdif_maskp_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_vt1724_spdif_maskc = +static const struct snd_kcontrol_new snd_vt1724_spdif_maskc = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1752,7 +1752,7 @@ static struct snd_kcontrol_new snd_vt1724_spdif_maskc = .get = snd_vt1724_spdif_maskc_get, }; -static struct snd_kcontrol_new snd_vt1724_spdif_maskp = +static const struct snd_kcontrol_new snd_vt1724_spdif_maskp = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1789,7 +1789,7 @@ static int snd_vt1724_spdif_sw_put(struct snd_kcontrol *kcontrol, return old != val; } -static struct snd_kcontrol_new snd_vt1724_spdif_switch = +static const struct snd_kcontrol_new snd_vt1724_spdif_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, /* FIXME: the following conflict with IEC958 Playback Route */ @@ -1964,7 +1964,7 @@ static int snd_vt1724_pro_internal_clock_put(struct snd_kcontrol *kcontrol, return old_rate != new_rate; } -static struct snd_kcontrol_new snd_vt1724_pro_internal_clock = { +static const struct snd_kcontrol_new snd_vt1724_pro_internal_clock = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Internal Clock", .info = snd_vt1724_pro_internal_clock_info, @@ -1995,7 +1995,7 @@ static int snd_vt1724_pro_rate_locking_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_vt1724_pro_rate_locking = { +static const struct snd_kcontrol_new snd_vt1724_pro_rate_locking = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Rate Locking", .info = snd_vt1724_pro_rate_locking_info, @@ -2026,7 +2026,7 @@ static int snd_vt1724_pro_rate_reset_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_vt1724_pro_rate_reset = { +static const struct snd_kcontrol_new snd_vt1724_pro_rate_reset = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Rate Reset", .info = snd_vt1724_pro_rate_reset_info, @@ -2151,7 +2151,7 @@ static struct snd_kcontrol_new snd_vt1724_mixer_pro_analog_route = .put = snd_vt1724_pro_route_analog_put, }; -static struct snd_kcontrol_new snd_vt1724_mixer_pro_spdif_route = { +static const struct snd_kcontrol_new snd_vt1724_mixer_pro_spdif_route = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, NONE) "Route", .info = snd_vt1724_pro_route_info, @@ -2187,7 +2187,7 @@ static int snd_vt1724_pro_peak_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_vt1724_mixer_pro_peak = { +static const struct snd_kcontrol_new snd_vt1724_mixer_pro_peak = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "Multi Track Peak", .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, diff --git a/sound/pci/lola/lola_mixer.c b/sound/pci/lola/lola_mixer.c index e7fe15dd5a90..cb25acf7bc49 100644 --- a/sound/pci/lola/lola_mixer.c +++ b/sound/pci/lola/lola_mixer.c @@ -645,7 +645,7 @@ static int lola_input_src_put(struct snd_kcontrol *kcontrol, return lola_set_src_config(chip, mask, true); } -static struct snd_kcontrol_new lola_input_src_mixer = { +static const struct snd_kcontrol_new lola_input_src_mixer = { .name = "Digital SRC Capture Switch", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .info = lola_input_src_info, diff --git a/sound/pci/lx6464es/lx6464es.c b/sound/pci/lx6464es/lx6464es.c index c0f0c349c3ec..f9c3e86d55d5 100644 --- a/sound/pci/lx6464es/lx6464es.c +++ b/sound/pci/lx6464es/lx6464es.c @@ -899,7 +899,7 @@ static int lx_control_playback_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new lx_control_playback_switch = { +static const struct snd_kcontrol_new lx_control_playback_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Playback Switch", .index = 0, diff --git a/sound/pci/mixart/mixart_mixer.c b/sound/pci/mixart/mixart_mixer.c index 51e53497f0ad..4a4616aac787 100644 --- a/sound/pci/mixart/mixart_mixer.c +++ b/sound/pci/mixart/mixart_mixer.c @@ -448,7 +448,7 @@ static int mixart_audio_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_ele return changed; } -static struct snd_kcontrol_new mixart_control_output_switch = { +static const struct snd_kcontrol_new mixart_control_output_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Master Playback Switch", .info = mixart_sw_info, /* shared */ @@ -1024,7 +1024,7 @@ static int mixart_monitor_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_ return changed; } -static struct snd_kcontrol_new mixart_control_monitor_vol = { +static const struct snd_kcontrol_new mixart_control_monitor_vol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), @@ -1091,7 +1091,7 @@ static int mixart_monitor_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_e return (changed != 0); } -static struct snd_kcontrol_new mixart_control_monitor_sw = { +static const struct snd_kcontrol_new mixart_control_monitor_sw = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Monitoring Switch", .info = mixart_sw_info, /* shared */ diff --git a/sound/pci/pcxhr/pcxhr_mix22.c b/sound/pci/pcxhr/pcxhr_mix22.c index 6a56e5306a65..8b4d0282efb8 100644 --- a/sound/pci/pcxhr/pcxhr_mix22.c +++ b/sound/pci/pcxhr/pcxhr_mix22.c @@ -744,7 +744,7 @@ static int hr222_mic_vol_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new hr222_control_mic_level = { +static const struct snd_kcontrol_new hr222_control_mic_level = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), @@ -794,7 +794,7 @@ static int hr222_mic_boost_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new hr222_control_mic_boost = { +static const struct snd_kcontrol_new hr222_control_mic_boost = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), @@ -836,7 +836,7 @@ static int hr222_phantom_power_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new hr222_phantom_power_switch = { +static const struct snd_kcontrol_new hr222_phantom_power_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Phantom Power Switch", .info = hr222_phantom_power_info, diff --git a/sound/pci/pcxhr/pcxhr_mixer.c b/sound/pci/pcxhr/pcxhr_mixer.c index 63136c4f3f3d..36875df30dbf 100644 --- a/sound/pci/pcxhr/pcxhr_mixer.c +++ b/sound/pci/pcxhr/pcxhr_mixer.c @@ -235,7 +235,7 @@ static int pcxhr_audio_sw_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new pcxhr_control_output_switch = { +static const struct snd_kcontrol_new pcxhr_control_output_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Master Playback Switch", .info = pcxhr_sw_info, /* shared */ @@ -460,7 +460,7 @@ static int pcxhr_pcm_sw_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new pcxhr_control_pcm_switch = { +static const struct snd_kcontrol_new pcxhr_control_pcm_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Playback Switch", .count = PCXHR_PLAYBACK_STREAMS, @@ -509,7 +509,7 @@ static int pcxhr_monitor_vol_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new pcxhr_control_monitor_vol = { +static const struct snd_kcontrol_new pcxhr_control_monitor_vol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), @@ -562,7 +562,7 @@ static int pcxhr_monitor_sw_put(struct snd_kcontrol *kcontrol, return (changed != 0); } -static struct snd_kcontrol_new pcxhr_control_monitor_sw = { +static const struct snd_kcontrol_new pcxhr_control_monitor_sw = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Monitoring Playback Switch", .info = pcxhr_sw_info, /* shared */ @@ -697,7 +697,7 @@ static int pcxhr_audio_src_put(struct snd_kcontrol *kcontrol, return ret; } -static struct snd_kcontrol_new pcxhr_control_audio_src = { +static const struct snd_kcontrol_new pcxhr_control_audio_src = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture Source", .info = pcxhr_audio_src_info, @@ -798,7 +798,7 @@ static int pcxhr_clock_type_put(struct snd_kcontrol *kcontrol, return ret; } -static struct snd_kcontrol_new pcxhr_control_clock_type = { +static const struct snd_kcontrol_new pcxhr_control_clock_type = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Clock Mode", .info = pcxhr_clock_type_info, @@ -842,7 +842,7 @@ static int pcxhr_clock_rate_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new pcxhr_control_clock_rate = { +static const struct snd_kcontrol_new pcxhr_control_clock_rate = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = "Clock Rates", @@ -1017,14 +1017,14 @@ static int pcxhr_iec958_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new pcxhr_control_playback_iec958_mask = { +static const struct snd_kcontrol_new pcxhr_control_playback_iec958_mask = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK), .info = pcxhr_iec958_info, .get = pcxhr_iec958_mask_get }; -static struct snd_kcontrol_new pcxhr_control_playback_iec958 = { +static const struct snd_kcontrol_new pcxhr_control_playback_iec958 = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), .info = pcxhr_iec958_info, @@ -1033,14 +1033,14 @@ static struct snd_kcontrol_new pcxhr_control_playback_iec958 = { .private_value = 0 /* playback */ }; -static struct snd_kcontrol_new pcxhr_control_capture_iec958_mask = { +static const struct snd_kcontrol_new pcxhr_control_capture_iec958_mask = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",CAPTURE,MASK), .info = pcxhr_iec958_info, .get = pcxhr_iec958_mask_get }; -static struct snd_kcontrol_new pcxhr_control_capture_iec958 = { +static const struct snd_kcontrol_new pcxhr_control_capture_iec958 = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",CAPTURE,DEFAULT), diff --git a/sound/pci/trident/trident_main.c b/sound/pci/trident/trident_main.c index 92ad2d7a6bf8..64d3b8eba4bb 100644 --- a/sound/pci/trident/trident_main.c +++ b/sound/pci/trident/trident_main.c @@ -2356,7 +2356,7 @@ static int snd_trident_spdif_control_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_spdif_control = +static const struct snd_kcontrol_new snd_trident_spdif_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH), @@ -2419,7 +2419,7 @@ static int snd_trident_spdif_default_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_spdif_default = +static const struct snd_kcontrol_new snd_trident_spdif_default = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), @@ -2452,7 +2452,7 @@ static int snd_trident_spdif_mask_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_trident_spdif_mask = +static const struct snd_kcontrol_new snd_trident_spdif_mask = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -2514,7 +2514,7 @@ static int snd_trident_spdif_stream_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_spdif_stream = +static const struct snd_kcontrol_new snd_trident_spdif_stream = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -2564,7 +2564,7 @@ static int snd_trident_ac97_control_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_ac97_rear_control = +static const struct snd_kcontrol_new snd_trident_ac97_rear_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Rear Path", @@ -2622,7 +2622,7 @@ static int snd_trident_vol_control_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_vol_music_control = +static const struct snd_kcontrol_new snd_trident_vol_music_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Music Playback Volume", @@ -2633,7 +2633,7 @@ static struct snd_kcontrol_new snd_trident_vol_music_control = .tlv = { .p = db_scale_gvol }, }; -static struct snd_kcontrol_new snd_trident_vol_wave_control = +static const struct snd_kcontrol_new snd_trident_vol_wave_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Wave Playback Volume", @@ -2700,7 +2700,7 @@ static int snd_trident_pcm_vol_control_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_pcm_vol_control = +static const struct snd_kcontrol_new snd_trident_pcm_vol_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Front Playback Volume", @@ -2764,7 +2764,7 @@ static int snd_trident_pcm_pan_control_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_pcm_pan_control = +static const struct snd_kcontrol_new snd_trident_pcm_pan_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Pan Playback Control", @@ -2821,7 +2821,7 @@ static int snd_trident_pcm_rvol_control_put(struct snd_kcontrol *kcontrol, static const DECLARE_TLV_DB_SCALE(db_scale_crvol, -3175, 25, 1); -static struct snd_kcontrol_new snd_trident_pcm_rvol_control = +static const struct snd_kcontrol_new snd_trident_pcm_rvol_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Reverb Playback Volume", @@ -2877,7 +2877,7 @@ static int snd_trident_pcm_cvol_control_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_pcm_cvol_control = +static const struct snd_kcontrol_new snd_trident_pcm_cvol_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Chorus Playback Volume", diff --git a/sound/pci/via82xx.c b/sound/pci/via82xx.c index 2d8c14e3f8d2..d078e86414c2 100644 --- a/sound/pci/via82xx.c +++ b/sound/pci/via82xx.c @@ -1683,7 +1683,7 @@ static int snd_via8233_dxs3_spdif_put(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_via8233_dxs3_spdif_control = { +static const struct snd_kcontrol_new snd_via8233_dxs3_spdif_control = { .name = SNDRV_CTL_NAME_IEC958("Output ",NONE,SWITCH), .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .info = snd_via8233_dxs3_spdif_info, @@ -1772,7 +1772,7 @@ static int snd_via8233_pcmdxs_volume_put(struct snd_kcontrol *kcontrol, static const DECLARE_TLV_DB_SCALE(db_scale_dxs, -4650, 150, 1); -static struct snd_kcontrol_new snd_via8233_pcmdxs_volume_control = { +static const struct snd_kcontrol_new snd_via8233_pcmdxs_volume_control = { .name = "PCM Playback Volume", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | @@ -1783,7 +1783,7 @@ static struct snd_kcontrol_new snd_via8233_pcmdxs_volume_control = { .tlv = { .p = db_scale_dxs } }; -static struct snd_kcontrol_new snd_via8233_dxs_volume_control = { +static const struct snd_kcontrol_new snd_via8233_dxs_volume_control = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .device = 0, /* .subdevice set later */ diff --git a/sound/pci/vx222/vx222_ops.c b/sound/pci/vx222/vx222_ops.c index 8e457ea27f89..7df1663ea510 100644 --- a/sound/pci/vx222/vx222_ops.c +++ b/sound/pci/vx222/vx222_ops.c @@ -945,7 +945,7 @@ static int vx_mic_level_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v return 0; } -static struct snd_kcontrol_new vx_control_input_level = { +static const struct snd_kcontrol_new vx_control_input_level = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), @@ -956,7 +956,7 @@ static struct snd_kcontrol_new vx_control_input_level = { .tlv = { .p = db_scale_mic }, }; -static struct snd_kcontrol_new vx_control_mic_level = { +static const struct snd_kcontrol_new vx_control_mic_level = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), diff --git a/sound/pci/ymfpci/ymfpci_main.c b/sound/pci/ymfpci/ymfpci_main.c index ffee284898b3..fe4ba463b57c 100644 --- a/sound/pci/ymfpci/ymfpci_main.c +++ b/sound/pci/ymfpci/ymfpci_main.c @@ -1316,7 +1316,7 @@ static int snd_ymfpci_spdif_default_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ymfpci_spdif_default = +static const struct snd_kcontrol_new snd_ymfpci_spdif_default = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), @@ -1344,7 +1344,7 @@ static int snd_ymfpci_spdif_mask_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ymfpci_spdif_mask = +static const struct snd_kcontrol_new snd_ymfpci_spdif_mask = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1391,7 +1391,7 @@ static int snd_ymfpci_spdif_stream_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ymfpci_spdif_stream = +static const struct snd_kcontrol_new snd_ymfpci_spdif_stream = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1439,7 +1439,7 @@ static int snd_ymfpci_drec_source_put(struct snd_kcontrol *kcontrol, struct snd_ return reg != old_reg; } -static struct snd_kcontrol_new snd_ymfpci_drec_source = { +static const struct snd_kcontrol_new snd_ymfpci_drec_source = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Direct Recording Source", @@ -1609,7 +1609,7 @@ static int snd_ymfpci_put_dup4ch(struct snd_kcontrol *kcontrol, struct snd_ctl_e return change; } -static struct snd_kcontrol_new snd_ymfpci_dup4ch = { +static const struct snd_kcontrol_new snd_ymfpci_dup4ch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "4ch Duplication", .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, @@ -1712,7 +1712,7 @@ static int snd_ymfpci_gpio_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_ return 0; } -static struct snd_kcontrol_new snd_ymfpci_rear_shared = { +static const struct snd_kcontrol_new snd_ymfpci_rear_shared = { .name = "Shared Rear/Line-In Switch", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .info = snd_ymfpci_gpio_sw_info, @@ -1776,7 +1776,7 @@ static int snd_ymfpci_pcm_vol_put(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ymfpci_pcm_volume = { +static const struct snd_kcontrol_new snd_ymfpci_pcm_volume = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "PCM Playback Volume", .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | -- cgit v1.2.3-59-g8ed1b From 04bab35044e7aa54031e55e3f7039141c54e70d7 Mon Sep 17 00:00:00 2001 From: Bhumika Goyal Date: Wed, 22 Feb 2017 01:33:27 +0530 Subject: ALSA: usb-audio: constify snd_kcontrol_new structures Declare snd_kcontrol_new structures as const as they are only passed as an argument to the function add_new_ctl. This agrument is of type const, so snd_kcontrol_new structures having this property can be made const too. Signed-off-by: Bhumika Goyal Signed-off-by: Takashi Iwai --- sound/usb/mixer_scarlett.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c index 7438e7c4a842..c33e2378089d 100644 --- a/sound/usb/mixer_scarlett.c +++ b/sound/usb/mixer_scarlett.c @@ -477,7 +477,7 @@ static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl, return 0; } -static struct snd_kcontrol_new usb_scarlett_ctl_switch = { +static const struct snd_kcontrol_new usb_scarlett_ctl_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", .info = scarlett_ctl_switch_info, @@ -487,7 +487,7 @@ static struct snd_kcontrol_new usb_scarlett_ctl_switch = { static const DECLARE_TLV_DB_SCALE(db_scale_scarlett_gain, -12800, 100, 0); -static struct snd_kcontrol_new usb_scarlett_ctl = { +static const struct snd_kcontrol_new usb_scarlett_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, @@ -499,7 +499,7 @@ static struct snd_kcontrol_new usb_scarlett_ctl = { .tlv = { .p = db_scale_scarlett_gain } }; -static struct snd_kcontrol_new usb_scarlett_ctl_master = { +static const struct snd_kcontrol_new usb_scarlett_ctl_master = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, @@ -511,7 +511,7 @@ static struct snd_kcontrol_new usb_scarlett_ctl_master = { .tlv = { .p = db_scale_scarlett_gain } }; -static struct snd_kcontrol_new usb_scarlett_ctl_enum = { +static const struct snd_kcontrol_new usb_scarlett_ctl_enum = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", .info = scarlett_ctl_enum_info, @@ -519,7 +519,7 @@ static struct snd_kcontrol_new usb_scarlett_ctl_enum = { .put = scarlett_ctl_enum_put, }; -static struct snd_kcontrol_new usb_scarlett_ctl_dynamic_enum = { +static const struct snd_kcontrol_new usb_scarlett_ctl_dynamic_enum = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", .info = scarlett_ctl_enum_dynamic_info, @@ -527,7 +527,7 @@ static struct snd_kcontrol_new usb_scarlett_ctl_dynamic_enum = { .put = scarlett_ctl_enum_put, }; -static struct snd_kcontrol_new usb_scarlett_ctl_sync = { +static const struct snd_kcontrol_new usb_scarlett_ctl_sync = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, .name = "", -- cgit v1.2.3-59-g8ed1b From a65895e0ee3a9e4f7f38e0d0fa4054b30b811035 Mon Sep 17 00:00:00 2001 From: Mihai Burduselu Date: Sun, 26 Feb 2017 01:30:38 +0200 Subject: ALSA: vx: remove 'out of memory' message Reported by checkpatch.pl Signed-off-by: Mihai Burduselu Signed-off-by: Takashi Iwai --- sound/drivers/vx/vx_core.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/drivers/vx/vx_core.c b/sound/drivers/vx/vx_core.c index 289f041706cd..f684fffd1397 100644 --- a/sound/drivers/vx/vx_core.c +++ b/sound/drivers/vx/vx_core.c @@ -795,10 +795,8 @@ struct vx_core *snd_vx_create(struct snd_card *card, struct snd_vx_hardware *hw, return NULL; chip = kzalloc(sizeof(*chip) + extra_size, GFP_KERNEL); - if (! chip) { - snd_printk(KERN_ERR "vx_core: no memory\n"); + if (! chip) return NULL; - } mutex_init(&chip->lock); chip->irq = -1; chip->hw = hw; -- cgit v1.2.3-59-g8ed1b From 5824ce8de7b1c0a75e5942e4df4652d04f3e263d Mon Sep 17 00:00:00 2001 From: Chris Chiu Date: Tue, 28 Feb 2017 14:17:11 -0600 Subject: ALSA: hda/realtek - Add support for Acer Aspire E5-475 headset mic The Acer laptop Aspire E5-475 with ALC255 can't detect the headset microphone until we modify a pin definition. Signed-off-by: Chris Chiu Signed-off-by: Daniel Drake Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 73a00460b5c1..a3fa4a8574df 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -4833,6 +4833,7 @@ enum { ALC290_FIXUP_SUBWOOFER_HSJACK, ALC269_FIXUP_THINKPAD_ACPI, ALC269_FIXUP_DMIC_THINKPAD_ACPI, + ALC255_FIXUP_ACER_MIC_NO_PRESENCE, ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, ALC255_FIXUP_DELL2_MIC_NO_PRESENCE, ALC255_FIXUP_HEADSET_MODE, @@ -5287,6 +5288,15 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_THINKPAD_ACPI, }, + [ALC255_FIXUP_ACER_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC255_FIXUP_HEADSET_MODE + }, [ALC255_FIXUP_DELL1_MIC_NO_PRESENCE] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { @@ -5851,6 +5861,10 @@ static const struct hda_model_fixup alc269_fixup_models[] = { {0x21, 0x03211020} static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = { + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1025, "Acer", ALC255_FIXUP_ACER_MIC_NO_PRESENCE, + {0x12, 0x90a601c0}, + {0x14, 0x90171120}, + {0x21, 0x02211030}), SND_HDA_PIN_QUIRK(0x10ec0225, 0x1028, "Dell", ALC225_FIXUP_DELL1_MIC_NO_PRESENCE, ALC225_STANDARD_PINS, {0x12, 0xb7a60130}, -- cgit v1.2.3-59-g8ed1b From 615966adc4b616dbc2f67ec2c44c25034d458cd9 Mon Sep 17 00:00:00 2001 From: Chris Chiu Date: Tue, 28 Feb 2017 14:17:12 -0600 Subject: ALSA: hda/realtek - Fix headset mic on several Asus laptops with ALC255 Add pin quirks to enable use of the headset mic on Asus Z550MA, X540LA, X540LJ, X556UR, Z450LA, and X441NC. Signed-off-by: Chris Chiu Signed-off-by: Daniel Drake Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index a3fa4a8574df..990994077e97 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -4834,6 +4834,7 @@ enum { ALC269_FIXUP_THINKPAD_ACPI, ALC269_FIXUP_DMIC_THINKPAD_ACPI, ALC255_FIXUP_ACER_MIC_NO_PRESENCE, + ALC255_FIXUP_ASUS_MIC_NO_PRESENCE, ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, ALC255_FIXUP_DELL2_MIC_NO_PRESENCE, ALC255_FIXUP_HEADSET_MODE, @@ -5297,6 +5298,15 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC255_FIXUP_HEADSET_MODE }, + [ALC255_FIXUP_ASUS_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC255_FIXUP_HEADSET_MODE + }, [ALC255_FIXUP_DELL1_MIC_NO_PRESENCE] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { @@ -5689,6 +5699,9 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x1a13, "Asus G73Jw", ALC269_FIXUP_ASUS_G73JW), SND_PCI_QUIRK(0x1043, 0x1b13, "Asus U41SV", ALC269_FIXUP_INV_DMIC), SND_PCI_QUIRK(0x1043, 0x1c23, "Asus X55U", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x1bbd, "ASUS Z550MA", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x10d0, "ASUS X540LA/X540LJ", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x11c0, "ASUS X556UR", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x831a, "ASUS P901", ALC269_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x1043, 0x834a, "ASUS S101", ALC269_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x1043, 0x8398, "ASUS P1005", ALC269_FIXUP_STEREO_DMIC), @@ -5865,6 +5878,14 @@ static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = { {0x12, 0x90a601c0}, {0x14, 0x90171120}, {0x21, 0x02211030}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1043, "ASUS", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE, + {0x14, 0x90170110}, + {0x1b, 0x90a70130}, + {0x21, 0x03211020}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1043, "ASUS", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE, + {0x1a, 0x90a70130}, + {0x1b, 0x90170110}, + {0x21, 0x03211020}), SND_HDA_PIN_QUIRK(0x10ec0225, 0x1028, "Dell", ALC225_FIXUP_DELL1_MIC_NO_PRESENCE, ALC225_STANDARD_PINS, {0x12, 0xb7a60130}, -- cgit v1.2.3-59-g8ed1b From c1732ede5e8008324f908861077b50f4ca55701d Mon Sep 17 00:00:00 2001 From: Chris Chiu Date: Tue, 28 Feb 2017 14:17:13 -0600 Subject: ALSA: hda/realtek - Fix headset and mic on several Asus laptops with ALC256 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable jack sensing and fix internal mic and headset mic on Asus X555UB and X540SA. Fix internal mic and headset mic on Asus E402NA and E403NA. Fix headset mic on Asus X541UV, X541SA and Z550SA. Unfortunately jack sensing for the headset mic is still not working. We believe this is a codec limitation. Some of these quirks were authored by João Paulo Rechi Vita. Signed-off-by: Chris Chiu Signed-off-by: Daniel Drake Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 990994077e97..a9f6b1d750d2 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -4872,6 +4872,8 @@ enum { ALC256_FIXUP_DELL_INSPIRON_7559_SUBWOOFER, ALC269_FIXUP_ATIV_BOOK_8, ALC221_FIXUP_HP_MIC_NO_PRESENCE, + ALC256_FIXUP_ASUS_HEADSET_MODE, + ALC256_FIXUP_ASUS_MIC, }; static const struct hda_fixup alc269_fixups[] = { @@ -5578,6 +5580,20 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_HEADSET_MODE }, + [ALC256_FIXUP_ASUS_HEADSET_MODE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode, + }, + [ALC256_FIXUP_ASUS_MIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x13, 0x90a60160 }, /* use as internal mic */ + { 0x19, 0x04a11120 }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC256_FIXUP_ASUS_HEADSET_MODE + }, }; static const struct snd_pci_quirk alc269_fixup_tbl[] = { @@ -5690,9 +5706,14 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8256, "HP", ALC221_FIXUP_HP_FRONT_MIC), SND_PCI_QUIRK(0x103c, 0x82bf, "HP", ALC221_FIXUP_HP_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x82c0, "HP", ALC221_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x103e, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), SND_PCI_QUIRK(0x1043, 0x103f, "ASUS TX300", ALC282_FIXUP_ASUS_TX300), SND_PCI_QUIRK(0x1043, 0x106d, "Asus K53BE", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x10c0, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), SND_PCI_QUIRK(0x1043, 0x115d, "Asus 1015E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x12f0, "ASUS X541UV", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x12e0, "ASUS X541SA", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x13b0, "ASUS Z550SA", ALC256_FIXUP_ASUS_MIC), SND_PCI_QUIRK(0x1043, 0x1427, "Asus Zenbook UX31E", ALC269VB_FIXUP_ASUS_ZENBOOK), SND_PCI_QUIRK(0x1043, 0x1517, "Asus Zenbook UX31A", ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A), SND_PCI_QUIRK(0x1043, 0x16e3, "ASUS UX50", ALC269_FIXUP_STEREO_DMIC), @@ -5702,6 +5723,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x1bbd, "ASUS Z550MA", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x10d0, "ASUS X540LA/X540LJ", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x11c0, "ASUS X556UR", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1ccd, "ASUS X555UB", ALC256_FIXUP_ASUS_MIC), SND_PCI_QUIRK(0x1043, 0x831a, "ASUS P901", ALC269_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x1043, 0x834a, "ASUS S101", ALC269_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x1043, 0x8398, "ASUS P1005", ALC269_FIXUP_STEREO_DMIC), @@ -6006,6 +6028,14 @@ static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = { {0x21, 0x02211020}), SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, ALC256_STANDARD_PINS), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC, + {0x14, 0x90170110}, + {0x1b, 0x90a70130}, + {0x21, 0x04211020}), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC, + {0x14, 0x90170110}, + {0x1b, 0x90a70130}, + {0x21, 0x03211020}), SND_HDA_PIN_QUIRK(0x10ec0280, 0x103c, "HP", ALC280_FIXUP_HP_GPIO4, {0x12, 0x90a60130}, {0x14, 0x90170110}, -- cgit v1.2.3-59-g8ed1b From 216d7aebbfbe1d3361e21c3a97d1607e1c1c48cd Mon Sep 17 00:00:00 2001 From: Chris Chiu Date: Tue, 28 Feb 2017 14:17:14 -0600 Subject: ALSA: hda/realtek - Fix headset mic and speaker on Asus X441SA/X441UV ASUS X441SA and X441UV laptops with ALC3236 (ALC223) codec require the known fixup (ALC269_FIXUP_HEADSET_MIC) and a different pin value on pin 0x19 to make the headset mic work. To make the speaker work, it requires an EAPD verb fixup. Signed-off-by: Chris Chiu Signed-off-by: Daniel Drake Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index a9f6b1d750d2..662ecf0abfa0 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -4874,6 +4874,8 @@ enum { ALC221_FIXUP_HP_MIC_NO_PRESENCE, ALC256_FIXUP_ASUS_HEADSET_MODE, ALC256_FIXUP_ASUS_MIC, + ALC233_FIXUP_ASUS_MIC_NO_PRESENCE, + ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE, }; static const struct hda_fixup alc269_fixups[] = { @@ -5594,6 +5596,26 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC256_FIXUP_ASUS_HEADSET_MODE }, + [ALC233_FIXUP_ASUS_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MIC + }, + [ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Enables internal speaker */ + {0x20, AC_VERB_SET_COEF_INDEX, 0x40}, + {0x20, AC_VERB_SET_PROC_COEF, 0x8800}, + {} + }, + .chained = true, + .chain_id = ALC233_FIXUP_ASUS_MIC_NO_PRESENCE + }, }; static const struct snd_pci_quirk alc269_fixup_tbl[] = { @@ -5723,6 +5745,8 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x1bbd, "ASUS Z550MA", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x10d0, "ASUS X540LA/X540LJ", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x11c0, "ASUS X556UR", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1290, "ASUS X441SA", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x12a0, "ASUS X441UV", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x1ccd, "ASUS X555UB", ALC256_FIXUP_ASUS_MIC), SND_PCI_QUIRK(0x1043, 0x831a, "ASUS P901", ALC269_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x1043, 0x834a, "ASUS S101", ALC269_FIXUP_STEREO_DMIC), -- cgit v1.2.3-59-g8ed1b From eeed4cd15ae0a8d3f2751adae8b56a3571e8aeca Mon Sep 17 00:00:00 2001 From: Chris Chiu Date: Tue, 28 Feb 2017 14:17:15 -0600 Subject: ALSA: hda/realtek - Fix speaker support for Asus AiO ZN270IE Asus AiO ZN270IE with ALC256 has no audio ouput for internal speaker and headphone. It requires GPIO 2 as an amp. This commit enables the GPIO and pulls it high. Signed-off-by: Chris Chiu Signed-off-by: Daniel Drake Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 662ecf0abfa0..f3919bb45af6 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -4874,6 +4874,7 @@ enum { ALC221_FIXUP_HP_MIC_NO_PRESENCE, ALC256_FIXUP_ASUS_HEADSET_MODE, ALC256_FIXUP_ASUS_MIC, + ALC256_FIXUP_ASUS_AIO_GPIO2, ALC233_FIXUP_ASUS_MIC_NO_PRESENCE, ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE, }; @@ -5596,6 +5597,16 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC256_FIXUP_ASUS_HEADSET_MODE }, + [ALC256_FIXUP_ASUS_AIO_GPIO2] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Set up GPIO2 for the speaker amp */ + { 0x01, AC_VERB_SET_GPIO_MASK, 0x04 }, + { 0x01, AC_VERB_SET_GPIO_DIRECTION, 0x04 }, + { 0x01, AC_VERB_SET_GPIO_DATA, 0x04 }, + {} + }, + }, [ALC233_FIXUP_ASUS_MIC_NO_PRESENCE] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { @@ -5748,6 +5759,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x1290, "ASUS X441SA", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x12a0, "ASUS X441UV", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x1ccd, "ASUS X555UB", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x3030, "ASUS ZN270IE", ALC256_FIXUP_ASUS_AIO_GPIO2), SND_PCI_QUIRK(0x1043, 0x831a, "ASUS P901", ALC269_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x1043, 0x834a, "ASUS S101", ALC269_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x1043, 0x8398, "ASUS P1005", ALC269_FIXUP_STEREO_DMIC), -- cgit v1.2.3-59-g8ed1b From cd7d1eab8545c6b7bbcd03dd723247ce1bb9954b Mon Sep 17 00:00:00 2001 From: Nicolas Iooss Date: Mon, 13 Mar 2017 20:22:50 +0100 Subject: ALSA: es1688: Use strcpy() instead of sprintf() There is no point in using sprintf() without a format string when strcpy() can perform the same operation. Signed-off-by: Nicolas Iooss Signed-off-by: Takashi Iwai --- sound/isa/es1688/es1688_lib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/isa/es1688/es1688_lib.c b/sound/isa/es1688/es1688_lib.c index e2cf508841b1..81cf26fa28d6 100644 --- a/sound/isa/es1688/es1688_lib.c +++ b/sound/isa/es1688/es1688_lib.c @@ -742,7 +742,7 @@ int snd_es1688_pcm(struct snd_card *card, struct snd_es1688 *chip, int device) pcm->private_data = chip; pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX; - sprintf(pcm->name, snd_es1688_chip_id(chip)); + strcpy(pcm->name, snd_es1688_chip_id(chip)); chip->pcm = pcm; snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, -- cgit v1.2.3-59-g8ed1b From a16fbb85c78a3ce56dc4515ffb8632b82cc969c7 Mon Sep 17 00:00:00 2001 From: Matthias Kaehlcke Date: Wed, 15 Mar 2017 15:41:23 -0700 Subject: ALSA: hda/ca0132: Remove double parentheses The extra pairs of parantheses are not needed and causes clang to generate warnings like this: sound/pci/hda/patch_ca0132.c:1171:14: error: equality comparison with extraneous parentheses [-Werror,-Wparentheses-equality] if ((buffer == NULL)) ~~~~~~~^~~~~~~ sound/pci/hda/patch_ca0132.c:1171:14: note: remove extraneous parentheses around the comparison to silence this warning if ((buffer == NULL)) ~ ^ ~ sound/pci/hda/patch_ca0132.c:1171:14: note: use '=' to turn this equality comparison into an assignment if ((buffer == NULL)) Signed-off-by: Matthias Kaehlcke Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 07a9deb17477..fb2e242c2522 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -1168,7 +1168,7 @@ static int dspio_write_multiple(struct hda_codec *codec, int status = 0; unsigned int count; - if ((buffer == NULL)) + if (buffer == NULL) return -EINVAL; count = 0; @@ -1210,7 +1210,7 @@ static int dspio_read_multiple(struct hda_codec *codec, unsigned int *buffer, unsigned int skip_count; unsigned int dummy; - if ((buffer == NULL)) + if (buffer == NULL) return -1; count = 0; -- cgit v1.2.3-59-g8ed1b From 13f99ebdd602ebdafb909e15ec6ffb1e34690167 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 23 Mar 2017 16:15:55 +0100 Subject: ALSA: au88x0: avoid theoretical uninitialized access The latest gcc-7.0.1 snapshot points out that we if nr_ch is zero, we never initialize some variables: sound/pci/au88x0/au88x0_core.c: In function 'vortex_adb_allocroute': sound/pci/au88x0/au88x0_core.c:2304:68: error: 'mix[0]' may be used uninitialized in this function [-Werror=maybe-uninitialized] sound/pci/au88x0/au88x0_core.c:2305:58: error: 'src[0]' may be used uninitialized in this function [-Werror=maybe-uninitialized] I assume this can never happen in practice, but adding a check here doesn't hurt either and avoids the warning. The code has been unchanged since the start of git history. Signed-off-by: Arnd Bergmann Signed-off-by: Takashi Iwai --- sound/pci/au88x0/au88x0_core.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'sound') diff --git a/sound/pci/au88x0/au88x0_core.c b/sound/pci/au88x0/au88x0_core.c index e1af24f87566..c308a4f70550 100644 --- a/sound/pci/au88x0/au88x0_core.c +++ b/sound/pci/au88x0/au88x0_core.c @@ -2279,6 +2279,9 @@ vortex_adb_allocroute(vortex_t *vortex, int dma, int nr_ch, int dir, } else { int src[2], mix[2]; + if (nr_ch < 1) + return -EINVAL; + /* Get SRC and MIXER hardware resources. */ for (i = 0; i < nr_ch; i++) { if ((mix[i] = -- cgit v1.2.3-59-g8ed1b From 6c3cef4890d072afa2d77371f358abaea54ec134 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 22 Mar 2017 21:30:11 +0900 Subject: ALSA: firewire-motu: add skeleton for Mark of the unicorn (MOTU) FireWire series This commit adds an new driver for MOTU FireWire series. In this commit, this driver just creates/removes card instance according to bus event. More functionalities will be added in following commits. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/Kconfig | 10 ++++ sound/firewire/Makefile | 1 + sound/firewire/motu/Makefile | 2 + sound/firewire/motu/motu.c | 134 +++++++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.h | 29 ++++++++++ 5 files changed, 176 insertions(+) create mode 100644 sound/firewire/motu/Makefile create mode 100644 sound/firewire/motu/motu.c create mode 100644 sound/firewire/motu/motu.h (limited to 'sound') diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 9f00696c4e4a..11a3285a20b3 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -140,4 +140,14 @@ config SND_FIREWIRE_TASCAM To compile this driver as a module, choose M here: the module will be called snd-firewire-tascam. +config SND_FIREWIRE_MOTU + tristate "Mark of the unicorn FireWire series support" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to enable support for FireWire devices which MOTU produced: + + To compile this driver as a module, choose M here: the module + will be called snd-firewire-motu. + endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index 0ee1fb115d88..9388ded69468 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_SND_FIREWORKS) += fireworks/ obj-$(CONFIG_SND_BEBOB) += bebob/ obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += digi00x/ obj-$(CONFIG_SND_FIREWIRE_TASCAM) += tascam/ +obj-$(CONFIG_SND_FIREWIRE_MOTU) += motu/ diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile new file mode 100644 index 000000000000..d7819d57eadf --- /dev/null +++ b/sound/firewire/motu/Makefile @@ -0,0 +1,2 @@ +snd-firewire-motu-objs := motu.o +obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c new file mode 100644 index 000000000000..2684e7447432 --- /dev/null +++ b/sound/firewire/motu/motu.c @@ -0,0 +1,134 @@ +/* + * motu.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "motu.h" + +#define OUI_MOTU 0x0001f2 + +MODULE_DESCRIPTION("MOTU FireWire driver"); +MODULE_AUTHOR("Takashi Sakamoto "); +MODULE_LICENSE("GPL v2"); + +static void name_card(struct snd_motu *motu) +{ + struct fw_device *fw_dev = fw_parent_device(motu->unit); + struct fw_csr_iterator it; + int key, val; + u32 version = 0; + + fw_csr_iterator_init(&it, motu->unit->directory); + while (fw_csr_iterator_next(&it, &key, &val)) { + switch (key) { + case CSR_VERSION: + version = val; + break; + } + } + + strcpy(motu->card->driver, "FW-MOTU"); + snprintf(motu->card->longname, sizeof(motu->card->longname), + "MOTU (version:%d), GUID %08x%08x at %s, S%d", + version, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&motu->unit->device), 100 << fw_dev->max_speed); +} + +static void motu_card_free(struct snd_card *card) +{ + struct snd_motu *motu = card->private_data; + + fw_unit_put(motu->unit); + + mutex_destroy(&motu->mutex); +} + +static int motu_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_card *card; + struct snd_motu *motu; + int err; + + err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, + sizeof(*motu), &card); + if (err < 0) + return err; + + motu = card->private_data; + motu->card = card; + motu->unit = fw_unit_get(unit); + card->private_free = motu_card_free; + + mutex_init(&motu->mutex); + + name_card(motu); + + err = snd_card_register(card); + if (err < 0) + goto error; + + dev_set_drvdata(&unit->device, motu); + + return 0; +error: + snd_card_free(card); + return err; +} + +static void motu_remove(struct fw_unit *unit) +{ + struct snd_motu *motu = dev_get_drvdata(&unit->device); + + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(motu->card); +} + +static void motu_bus_update(struct fw_unit *unit) +{ + return; +} + +#define SND_MOTU_DEV_ENTRY(model) \ +{ \ + .match_flags = IEEE1394_MATCH_VENDOR_ID | \ + IEEE1394_MATCH_MODEL_ID | \ + IEEE1394_MATCH_SPECIFIER_ID, \ + .vendor_id = OUI_MOTU, \ + .model_id = model, \ + .specifier_id = OUI_MOTU, \ +} + +static const struct ieee1394_device_id motu_id_table[] = { + { } +}; +MODULE_DEVICE_TABLE(ieee1394, motu_id_table); + +static struct fw_driver motu_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = motu_probe, + .update = motu_bus_update, + .remove = motu_remove, + .id_table = motu_id_table, +}; + +static int __init alsa_motu_init(void) +{ + return driver_register(&motu_driver.driver); +} + +static void __exit alsa_motu_exit(void) +{ + driver_unregister(&motu_driver.driver); +} + +module_init(alsa_motu_init); +module_exit(alsa_motu_exit); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h new file mode 100644 index 000000000000..f3d0b2834942 --- /dev/null +++ b/sound/firewire/motu/motu.h @@ -0,0 +1,29 @@ +/* + * motu.h - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef SOUND_FIREWIRE_MOTU_H_INCLUDED +#define SOUND_FIREWIRE_MOTU_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct snd_motu { + struct snd_card *card; + struct fw_unit *unit; + struct mutex mutex; +}; + +#endif -- cgit v1.2.3-59-g8ed1b From 8865a31e0fd8beb157b99e78cdf1f0241a67bd54 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 22 Mar 2017 21:30:12 +0900 Subject: ALSA: firewire-motu: postpone sound card registration Just after appearing on IEEE 1394 bus, this unit generates several bus resets. This is due to loading firmware from on-board flash memory and initialize hardware. It's better to postpone sound card registration. This commit applies this idea. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/motu/motu.c | 97 ++++++++++++++++++++++++++++++++++------------ sound/firewire/motu/motu.h | 5 +++ 2 files changed, 78 insertions(+), 24 deletions(-) (limited to 'sound') diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index 2684e7447432..bdd82ddeb6ec 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -38,59 +38,108 @@ static void name_card(struct snd_motu *motu) dev_name(&motu->unit->device), 100 << fw_dev->max_speed); } -static void motu_card_free(struct snd_card *card) +static void motu_free(struct snd_motu *motu) { - struct snd_motu *motu = card->private_data; - fw_unit_put(motu->unit); mutex_destroy(&motu->mutex); + kfree(motu); } -static int motu_probe(struct fw_unit *unit, - const struct ieee1394_device_id *entry) +/* + * This module releases the FireWire unit data after all ALSA character devices + * are released by applications. This is for releasing stream data or finishing + * transactions safely. Thus at returning from .remove(), this module still keep + * references for the unit. + */ +static void motu_card_free(struct snd_card *card) { - struct snd_card *card; - struct snd_motu *motu; - int err; + motu_free(card->private_data); +} - err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, - sizeof(*motu), &card); - if (err < 0) - return err; +static void do_registration(struct work_struct *work) +{ + struct snd_motu *motu = container_of(work, struct snd_motu, dwork.work); + int err; - motu = card->private_data; - motu->card = card; - motu->unit = fw_unit_get(unit); - card->private_free = motu_card_free; + if (motu->registered) + return; - mutex_init(&motu->mutex); + err = snd_card_new(&motu->unit->device, -1, NULL, THIS_MODULE, 0, + &motu->card); + if (err < 0) + return; name_card(motu); - err = snd_card_register(card); + err = snd_card_register(motu->card); if (err < 0) goto error; + /* + * After registered, motu instance can be released corresponding to + * releasing the sound card instance. + */ + motu->card->private_free = motu_card_free; + motu->card->private_data = motu; + motu->registered = true; + + return; +error: + snd_card_free(motu->card); + dev_info(&motu->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int motu_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_motu *motu; + + /* Allocate this independently of sound card instance. */ + motu = kzalloc(sizeof(struct snd_motu), GFP_KERNEL); + if (motu == NULL) + return -ENOMEM; + + motu->unit = fw_unit_get(unit); dev_set_drvdata(&unit->device, motu); + mutex_init(&motu->mutex); + + /* Allocate and register this sound card later. */ + INIT_DEFERRABLE_WORK(&motu->dwork, do_registration); + snd_fw_schedule_registration(unit, &motu->dwork); + return 0; -error: - snd_card_free(card); - return err; } static void motu_remove(struct fw_unit *unit) { struct snd_motu *motu = dev_get_drvdata(&unit->device); - /* No need to wait for releasing card object in this context. */ - snd_card_free_when_closed(motu->card); + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_delayed_work_sync(&motu->dwork); + + if (motu->registered) { + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(motu->card); + } else { + /* Don't forget this case. */ + motu_free(motu); + } } static void motu_bus_update(struct fw_unit *unit) { - return; + struct snd_motu *motu = dev_get_drvdata(&unit->device); + + /* Postpone a workqueue for deferred registration. */ + if (!motu->registered) + snd_fw_schedule_registration(unit, &motu->dwork); } #define SND_MOTU_DEV_ENTRY(model) \ diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index f3d0b2834942..eb0ffd56c835 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -20,10 +20,15 @@ #include #include +#include "../lib.h" + struct snd_motu { struct snd_card *card; struct fw_unit *unit; struct mutex mutex; + + bool registered; + struct delayed_work dwork; }; #endif -- cgit v1.2.3-59-g8ed1b From 5e03c33e3d8973e2c10abbf13f8f24779babafeb Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 22 Mar 2017 21:30:13 +0900 Subject: ALSA: firewire-motu: add a structure for model-dependent parameters. MOTU FireWire series doesn't tell drivers their capabilities, thus the drivers should have and apply model-dependent parameters to detected models. This commit adds a structure to represent such parameters. Capabilities are represented by enumeration except for the number of analog line in/out. Identification name also be in the structure because the units has no registers for this purpose. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/motu/motu.c | 10 +++++++--- sound/firewire/motu/motu.h | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index bdd82ddeb6ec..e69aa7b5dcde 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -31,9 +31,11 @@ static void name_card(struct snd_motu *motu) } strcpy(motu->card->driver, "FW-MOTU"); + strcpy(motu->card->shortname, motu->spec->name); + strcpy(motu->card->mixername, motu->spec->name); snprintf(motu->card->longname, sizeof(motu->card->longname), - "MOTU (version:%d), GUID %08x%08x at %s, S%d", - version, + "MOTU %s (version:%d), GUID %08x%08x at %s, S%d", + motu->spec->name, version, fw_dev->config_rom[3], fw_dev->config_rom[4], dev_name(&motu->unit->device), 100 << fw_dev->max_speed); } @@ -101,6 +103,7 @@ static int motu_probe(struct fw_unit *unit, if (motu == NULL) return -ENOMEM; + motu->spec = (const struct snd_motu_spec *)entry->driver_data; motu->unit = fw_unit_get(unit); dev_set_drvdata(&unit->device, motu); @@ -142,7 +145,7 @@ static void motu_bus_update(struct fw_unit *unit) snd_fw_schedule_registration(unit, &motu->dwork); } -#define SND_MOTU_DEV_ENTRY(model) \ +#define SND_MOTU_DEV_ENTRY(model, data) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ IEEE1394_MATCH_MODEL_ID | \ @@ -150,6 +153,7 @@ static void motu_bus_update(struct fw_unit *unit) .vendor_id = OUI_MOTU, \ .model_id = model, \ .specifier_id = OUI_MOTU, \ + .driver_data = (kernel_ulong_t)data, \ } static const struct ieee1394_device_id motu_id_table[] = { diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index eb0ffd56c835..cb7324d0d558 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -29,6 +29,29 @@ struct snd_motu { bool registered; struct delayed_work dwork; + + /* Model dependent information. */ + const struct snd_motu_spec *spec; +}; + +enum snd_motu_spec_flags { + SND_MOTU_SPEC_SUPPORT_CLOCK_X2 = 0x0001, + SND_MOTU_SPEC_SUPPORT_CLOCK_X4 = 0x0002, + SND_MOTU_SPEC_TX_MICINST_CHUNK = 0x0004, + SND_MOTU_SPEC_TX_RETURN_CHUNK = 0x0008, + SND_MOTU_SPEC_TX_REVERB_CHUNK = 0x0010, + SND_MOTU_SPEC_TX_AESEBU_CHUNK = 0x0020, + SND_MOTU_SPEC_HAS_OPT_IFACE_A = 0x0040, + SND_MOTU_SPEC_HAS_OPT_IFACE_B = 0x0080, + SND_MOTU_SPEC_HAS_MIDI = 0x0100, +}; + +struct snd_motu_spec { + const char *const name; + enum snd_motu_spec_flags flags; + + unsigned char analog_in_ports; + unsigned char analog_out_ports; }; #endif -- cgit v1.2.3-59-g8ed1b From 59f6482c2183fc3b3e42e0e2ae83a347ee6346c5 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 22 Mar 2017 21:30:14 +0900 Subject: ALSA: firewire-motu: add an abstraction layer for three types of protocols In an aspect of used protocols to communicate, models of MOTU FireWire units are categorized to three generations. This commit adds an abstraction layer of the protocols for features related to packet streaming functionality. This layer includes 5 operations. When configuring packet streaming functionality with sampling rate and sampling transmission frequency, .get_clock_rate and .set_clock_rate are called with proper arguments. MOTU FireWire series supports up to 192.0kHz. When checking current source of sampling clock (not clock for packetization layer), .get_clock_source is used. Enumeration is added to represent the sources supported by this series. This operation can be used to expose available sampling rate to user space applications when the unit is configured to use any input signal as source of clock instead of crystal clock. In the protocols, the path between packet processing layer and digital signal processing layer can be controlled. This looks a functionality to 'mute' the unit. For this feature, .switch_fetching_mode is added. This can be used to suppress noises every time packet streaming starts/stops. In a point of the size of data blocks at a certain sampling transmission frequency, the most units accept several modes. This is due to usage of optical interfaces. The size differs depending on which modes are configured to the interfaces; None, S/PDIF and ADAT. Additionally, format of packet is different depending on protocols. To cache current size of data blocks and its format, .cache_packet_formats is added. This is used by PCM functionality, packet streaming functionality and data block processing layer. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/motu/motu.c | 12 ++++++++++++ sound/firewire/motu/motu.h | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) (limited to 'sound') diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index e69aa7b5dcde..1e6fc74a6458 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -14,6 +14,18 @@ MODULE_DESCRIPTION("MOTU FireWire driver"); MODULE_AUTHOR("Takashi Sakamoto "); MODULE_LICENSE("GPL v2"); +const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT] = { + /* mode 0 */ + [0] = 44100, + [1] = 48000, + /* mode 1 */ + [2] = 88200, + [3] = 96000, + /* mode 2 */ + [4] = 176400, + [5] = 192000, +}; + static void name_card(struct snd_motu *motu) { struct fw_device *fw_dev = fw_parent_device(motu->unit); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index cb7324d0d558..cb6b57353cc1 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -22,6 +22,16 @@ #include "../lib.h" +struct snd_motu_packet_format { + unsigned char midi_flag_offset; + unsigned char midi_byte_offset; + unsigned char pcm_byte_offset; + + unsigned char msg_chunks; + unsigned char fixed_part_pcm_chunks[3]; + unsigned char differed_part_pcm_chunks[3]; +}; + struct snd_motu { struct snd_card *card; struct fw_unit *unit; @@ -32,6 +42,10 @@ struct snd_motu { /* Model dependent information. */ const struct snd_motu_spec *spec; + + /* For packet streaming */ + struct snd_motu_packet_format tx_packet_formats; + struct snd_motu_packet_format rx_packet_formats; }; enum snd_motu_spec_flags { @@ -46,12 +60,41 @@ enum snd_motu_spec_flags { SND_MOTU_SPEC_HAS_MIDI = 0x0100, }; +#define SND_MOTU_CLOCK_RATE_COUNT 6 +extern const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT]; + +enum snd_motu_clock_source { + SND_MOTU_CLOCK_SOURCE_INTERNAL, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX, + SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR, + SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC, + SND_MOTU_CLOCK_SOURCE_UNKNOWN, +}; + +struct snd_motu_protocol { + int (*get_clock_rate)(struct snd_motu *motu, unsigned int *rate); + int (*set_clock_rate)(struct snd_motu *motu, unsigned int rate); + int (*get_clock_source)(struct snd_motu *motu, + enum snd_motu_clock_source *source); + int (*switch_fetching_mode)(struct snd_motu *motu, bool enable); + int (*cache_packet_formats)(struct snd_motu *motu); +}; + struct snd_motu_spec { const char *const name; enum snd_motu_spec_flags flags; unsigned char analog_in_ports; unsigned char analog_out_ports; + + const struct snd_motu_protocol *const protocol; }; #endif -- cgit v1.2.3-59-g8ed1b From a04513f8b1980e7ddd9082461aceaf8b3e8f4981 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 22 Mar 2017 21:30:15 +0900 Subject: ALSA: firewire-lib: record cycle count for the first packet Currently, packet streaming layer passes generated SYT value to data block processing layer. However, this is not enough in a case that the data block processing layer generates time stamps by its own ways. For out-packet stream, the packet streaming layer guarantees 8,000 times calls of data block processing layers per sec. Therefore, when cycle count of the first packet is recorded, data block processing layers can calculate own time stamps with the recorded value. For the reason, this commit allows packet streaming layer to record the first cycle count. Each data block processing layer can read the count by accessing a member of structure for packet streaming layer. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/amdtp-stream.c | 15 +++++++++++++-- sound/firewire/amdtp-stream.h | 1 + 2 files changed, 14 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c index 00060c4a9deb..371cf978fbed 100644 --- a/sound/firewire/amdtp-stream.c +++ b/sound/firewire/amdtp-stream.c @@ -671,6 +671,8 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context, void *header, void *private_data) { struct amdtp_stream *s = private_data; + u32 cycle; + unsigned int packets; /* * For in-stream, first packet has come. @@ -679,10 +681,19 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context, s->callbacked = true; wake_up(&s->callback_wait); - if (s->direction == AMDTP_IN_STREAM) + cycle = compute_cycle_count(tstamp); + + if (s->direction == AMDTP_IN_STREAM) { + packets = header_length / IN_PACKET_HEADER_SIZE; + cycle = decrement_cycle_count(cycle, packets); context->callback.sc = in_stream_callback; - else + } else { + packets = header_length / 4; + cycle = increment_cycle_count(cycle, QUEUE_LENGTH - packets); context->callback.sc = out_stream_callback; + } + + s->start_cycle = cycle; context->callback.sc(context, tstamp, header_length, header, s); } diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h index c1bc7fad056e..edf5646b3708 100644 --- a/sound/firewire/amdtp-stream.h +++ b/sound/firewire/amdtp-stream.h @@ -130,6 +130,7 @@ struct amdtp_stream { /* To wait for first packet. */ bool callbacked; wait_queue_head_t callback_wait; + u32 start_cycle; /* For backends to process data blocks. */ void *protocol; -- cgit v1.2.3-59-g8ed1b From 9863874f02e1cca65bdb112336250890b2ded64a Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 22 Mar 2017 21:30:16 +0900 Subject: ALSA: firewire-lib: add support for source packet header field in CIP header In IEC 61883-1, CIP headers can have a SPH field. When a packet has 1 in SPH field of its CIP header, the packet has a source packet headers. A source packet header consists of 32 bit field (= 1 quadlet) and it transfers time stamp, which is the same value as the lower 25 bits of the IEEE 1394 CYCLE_TIMER register and the rest is zero. This commit just supports source packet header field because IEC 61883-1 includes ambiguity the position of this header and its count. Each protocol layer is allowed to have actual implementation according its requirements. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/amdtp-stream.c | 8 ++++++-- sound/firewire/amdtp-stream.h | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c index 371cf978fbed..65c5ed7488a3 100644 --- a/sound/firewire/amdtp-stream.c +++ b/sound/firewire/amdtp-stream.c @@ -37,6 +37,8 @@ #define CIP_SID_MASK 0x3f000000 #define CIP_DBS_MASK 0x00ff0000 #define CIP_DBS_SHIFT 16 +#define CIP_SPH_MASK 0x00000400 +#define CIP_SPH_SHIFT 10 #define CIP_DBC_MASK 0x000000ff #define CIP_FMT_SHIFT 24 #define CIP_FMT_MASK 0x3f000000 @@ -426,6 +428,7 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle, buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) | (s->data_block_quadlets << CIP_DBS_SHIFT) | + ((s->sph << CIP_SPH_SHIFT) & CIP_SPH_MASK) | s->data_block_counter); buffer[1] = cpu_to_be32(CIP_EOH | ((s->fmt << CIP_FMT_SHIFT) & CIP_FMT_MASK) | @@ -454,7 +457,7 @@ static int handle_in_packet(struct amdtp_stream *s, { __be32 *buffer; u32 cip_header[2]; - unsigned int fmt, fdf, syt; + unsigned int sph, fmt, fdf, syt; unsigned int data_block_quadlets, data_block_counter, dbc_interval; unsigned int data_blocks; struct snd_pcm_substream *pcm; @@ -482,8 +485,9 @@ static int handle_in_packet(struct amdtp_stream *s, } /* Check valid protocol or not. */ + sph = (cip_header[0] & CIP_SPH_MASK) >> CIP_SPH_SHIFT; fmt = (cip_header[1] & CIP_FMT_MASK) >> CIP_FMT_SHIFT; - if (fmt != s->fmt) { + if (sph != s->sph || fmt != s->fmt) { dev_info_ratelimited(&s->unit->device, "Detect unexpected protocol: %08x %08x\n", cip_header[0], cip_header[1]); diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h index edf5646b3708..679053d9c28f 100644 --- a/sound/firewire/amdtp-stream.h +++ b/sound/firewire/amdtp-stream.h @@ -106,6 +106,7 @@ struct amdtp_stream { unsigned int source_node_id_field; unsigned int data_block_quadlets; unsigned int data_block_counter; + unsigned int sph; unsigned int fmt; unsigned int fdf; /* quirk: fixed interval of dbc between previos/current packets. */ -- cgit v1.2.3-59-g8ed1b From 9dae017bf69b1c5aacba7be18cb734b66df30a37 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 22 Mar 2017 21:30:17 +0900 Subject: ALSA: firewire-lib: enable CIP_DBC_IS_END_EVENT for both directions of stream Commit c8bdf49b9935("ALSA: fireworks/firewire-lib: Add a quirk for the meaning of dbc") adds CIP_DBC_IS_END_EVENT flag just for tx packets. However, MOTU FireWire series has this quirk for rx packets. This commit allows both directions with the flag. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/amdtp-stream.c | 8 +++++++- sound/firewire/amdtp-stream.h | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c index 65c5ed7488a3..f9d12f454483 100644 --- a/sound/firewire/amdtp-stream.c +++ b/sound/firewire/amdtp-stream.c @@ -426,6 +426,10 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle, data_blocks = calculate_data_blocks(s, syt); pcm_frames = s->process_data_blocks(s, buffer + 2, data_blocks, &syt); + if (s->flags & CIP_DBC_IS_END_EVENT) + s->data_block_counter = + (s->data_block_counter + data_blocks) & 0xff; + buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) | (s->data_block_quadlets << CIP_DBS_SHIFT) | ((s->sph << CIP_SPH_SHIFT) & CIP_SPH_MASK) | @@ -435,7 +439,9 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle, ((s->fdf << CIP_FDF_SHIFT) & CIP_FDF_MASK) | (syt & CIP_SYT_MASK)); - s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; + if (!(s->flags & CIP_DBC_IS_END_EVENT)) + s->data_block_counter = + (s->data_block_counter + data_blocks) & 0xff; payload_length = 8 + data_blocks * 4 * s->data_block_quadlets; trace_out_packet(s, cycle, buffer, payload_length, index); diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h index 679053d9c28f..d2a316309704 100644 --- a/sound/firewire/amdtp-stream.h +++ b/sound/firewire/amdtp-stream.h @@ -18,8 +18,8 @@ * SYT_INTERVAL samples, with these two types alternating so that * the overall sample rate comes out right. * @CIP_EMPTY_WITH_TAG0: Only for in-stream. Empty in-packets have TAG0. - * @CIP_DBC_IS_END_EVENT: Only for in-stream. The value of dbc in an in-packet - * corresponds to the end of event in the packet. Out of IEC 61883. + * @CIP_DBC_IS_END_EVENT: The value of dbc in an packet corresponds to the end + * of event in the packet. Out of IEC 61883. * @CIP_WRONG_DBS: Only for in-stream. The value of dbs is wrong in in-packets. * The value of data_block_quadlets is used instead of reported value. * @CIP_SKIP_DBC_ZERO_CHECK: Only for in-stream. Packets with zero in dbc is -- cgit v1.2.3-59-g8ed1b From 4641c939401076c0ab7faba024827069723f719c Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 22 Mar 2017 21:30:18 +0900 Subject: ALSA: firewire-motu: add MOTU specific protocol layer MOTU FireWire series uses blocking transmission for AMDTP packet streaming. They transmit/receive 8,000 packets per second, to handle the same number of data blocks as current sampling transmission frequency. Thus, IEC 61883-1/6 packet streaming engine of ALSA firewire stack is available for them. However, the sequence of packet and data blocks includes some quirks. Below sample is a sequence of CIP headers of packets received by 828mk2, at 44.1kHz of sampling transmission frequency. quads CIP1 CIP2 488 0x020F04E8 0x8222FFFF 8 0x020F04F8 0x8222FFFF 488 0x020F0400 0x8222FFFF 488 0x020F0408 0x8222FFFF 8 0x020F04E8 0x8222FFFF 488 0x020F04F0 0x8222FFFF 488 0x020F04F8 0x8222FFFF The SID (source node ID), DBS (data block size), SPH (source packet header), FMT (format ID), FDF (format dependent field) and SYT (time stamp) fields are in IEC 61883-1. Especially, FMT is 0x02, FDF is 0x22 and SYT is 0xffff to define MOTU specific protocol. In an aspect of dbc field, the value represents accumulated number of data blocks included the packet. This is against IEC 61883-1, because according to the specification this value should be the number of data blocks already transferred. In ALSA IEC 61883-1/6 engine, this quirk is already supported by CIP_DBC_IS_END_EVENT flag, because Echo Audio Fireworks has. Each data block includes SPH as its first quadlet field, to represent its presentation time stamp. Actual value of SPH is compliant to IEC 61883-1; lower 25 bits of 32 bits width consists of 13 bits cycle count and 12 bits cycle offset. The rest of each data block consists of 24 bit chunks. All of PCM samples, MIDI messages, status and control messages are transferred by the chunks. This is similar to '24-bit * 4 Audio Pack' in IEC 61883-6. The position of each kind of data depends on generations of each model. The number of whole chunks in a data block is a multiple of 4, to consists of quadlet-aligned packets. This commit adds data block processing layer specific for the MOTU protocol. The remarkable point is the way to generate SPH header. Time stamps for each data blocks are generated by below calculation: * Using pre-computed table for the number of ticks per event * 44,1kHz: (557 + 123/441) * 48.0kHz: (512 + 0/441) * 88.2kHz: (278 + 282/441) * 96.0kHz: (256 + 0/441) * 176.4kHz: (139 + 141/441) * 192.0kHz: (128 + 0/441) * Accumulate the ticks and set the value to SPH for every events. * This way makes sense only for blocking transmission because this mode transfers fixed number or none of events. This calculation assumes that each data block has a PCM frame which is sampled according to event timing clock. Current packet streaming layer has the same assumption. Although this sequence works fine for MOTU FireWire series at sampling transmission frequency based on 48.0kHz, it is not enough at the frequency based on 44.1kHz. The units generate choppy noise every few seconds. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/motu/Makefile | 2 +- sound/firewire/motu/amdtp-motu.c | 292 +++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.h | 13 +- 3 files changed, 304 insertions(+), 3 deletions(-) create mode 100644 sound/firewire/motu/amdtp-motu.c (limited to 'sound') diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index d7819d57eadf..37391f5c623d 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,2 +1,2 @@ -snd-firewire-motu-objs := motu.o +snd-firewire-motu-objs := motu.o amdtp-motu.o obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/amdtp-motu.c b/sound/firewire/motu/amdtp-motu.c new file mode 100644 index 000000000000..11e44123ad65 --- /dev/null +++ b/sound/firewire/motu/amdtp-motu.c @@ -0,0 +1,292 @@ +/* + * amdtp-motu.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include +#include "motu.h" + +#define CIP_FMT_MOTU 0x02 +#define MOTU_FDF_AM824 0x22 + +struct amdtp_motu { + /* For timestamp processing. */ + unsigned int quotient_ticks_per_event; + unsigned int remainder_ticks_per_event; + unsigned int next_ticks; + unsigned int next_accumulated; + unsigned int next_cycles; + unsigned int next_seconds; + + unsigned int pcm_chunks; + unsigned int pcm_byte_offset; +}; + +int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, + struct snd_motu_packet_format *formats) +{ + static const struct { + unsigned int quotient_ticks_per_event; + unsigned int remainder_ticks_per_event; + } params[] = { + [CIP_SFC_44100] = { 557, 123 }, + [CIP_SFC_48000] = { 512, 0 }, + [CIP_SFC_88200] = { 278, 282 }, + [CIP_SFC_96000] = { 256, 0 }, + [CIP_SFC_176400] = { 139, 141 }, + [CIP_SFC_192000] = { 128, 0 }, + }; + struct amdtp_motu *p = s->protocol; + unsigned int pcm_chunks, data_chunks, data_block_quadlets; + unsigned int delay; + unsigned int mode; + int i, err; + + if (amdtp_stream_running(s)) + return -EBUSY; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) { + mode = i >> 1; + break; + } + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + pcm_chunks = formats->fixed_part_pcm_chunks[mode] + + formats->differed_part_pcm_chunks[mode]; + data_chunks = formats->msg_chunks + pcm_chunks; + + /* + * Each data block includes SPH in its head. Data chunks follow with + * 3 byte alignment. Padding follows with zero to conform to quadlet + * alignment. + */ + data_block_quadlets = 1 + DIV_ROUND_UP(data_chunks * 3, 4); + + err = amdtp_stream_set_parameters(s, rate, data_block_quadlets); + if (err < 0) + return err; + + p->pcm_chunks = pcm_chunks; + p->pcm_byte_offset = formats->pcm_byte_offset; + + /* IEEE 1394 bus requires. */ + delay = 0x2e00; + + /* For no-data or empty packets to adjust PCM sampling frequency. */ + delay += 8000 * 3072 * s->syt_interval / rate; + + p->next_seconds = 0; + p->next_cycles = delay / 3072; + p->quotient_ticks_per_event = params[s->sfc].quotient_ticks_per_event; + p->remainder_ticks_per_event = params[s->sfc].remainder_ticks_per_event; + p->next_ticks = delay % 3072; + p->next_accumulated = 0; + + return 0; +} + +static void read_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + u8 *byte; + u32 *dst; + + channels = p->pcm_chunks; + dst = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + *dst = (byte[0] << 24) | (byte[1] << 16) | byte[2]; + byte += 3; + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + u8 *byte; + const u32 *src; + + channels = p->pcm_chunks; + src = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + byte[0] = (*src >> 24) & 0xff; + byte[1] = (*src >> 16) & 0xff; + byte[2] = (*src >> 8) & 0xff; + byte += 3; + src++; + } + + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, i, c; + u8 *byte; + + channels = p->pcm_chunks; + + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + byte[0] = 0; + byte[1] = 0; + byte[2] = 0; + byte += 3; + } + + buffer += s->data_block_quadlets; + } +} + +int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + /* TODO: how to set an constraint for exactly 24bit PCM sample? */ + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + + return amdtp_stream_add_pcm_hw_constraints(s, runtime); +} + +static unsigned int process_tx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, unsigned int data_blocks, + unsigned int *syt) +{ + struct snd_pcm_substream *pcm; + + pcm = ACCESS_ONCE(s->pcm); + if (data_blocks > 0 && pcm) + read_pcm_s32(s, pcm->runtime, buffer, data_blocks); + + return data_blocks; +} + +static inline void compute_next_elapse_from_start(struct amdtp_motu *p) +{ + p->next_accumulated += p->remainder_ticks_per_event; + if (p->next_accumulated >= 441) { + p->next_accumulated -= 441; + p->next_ticks++; + } + + p->next_ticks += p->quotient_ticks_per_event; + if (p->next_ticks >= 3072) { + p->next_ticks -= 3072; + p->next_cycles++; + } + + if (p->next_cycles >= 8000) { + p->next_cycles -= 8000; + p->next_seconds++; + } + + if (p->next_seconds >= 128) + p->next_seconds -= 128; +} + +static void write_sph(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int next_cycles; + unsigned int i; + u32 sph; + + for (i = 0; i < data_blocks; i++) { + next_cycles = (s->start_cycle + p->next_cycles) % 8000; + sph = ((next_cycles << 12) | p->next_ticks) & 0x01ffffff; + *buffer = cpu_to_be32(sph); + + compute_next_elapse_from_start(p); + + buffer += s->data_block_quadlets; + } +} + +static unsigned int process_rx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, unsigned int data_blocks, + unsigned int *syt) +{ + struct snd_pcm_substream *pcm; + + /* Not used. */ + *syt = 0xffff; + + /* TODO: how to interact control messages between userspace? */ + + pcm = ACCESS_ONCE(s->pcm); + if (pcm) + write_pcm_s32(s, pcm->runtime, buffer, data_blocks); + else + write_pcm_silence(s, buffer, data_blocks); + + write_sph(s, buffer, data_blocks); + + return data_blocks; +} + +int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, + const struct snd_motu_protocol *const protocol) +{ + amdtp_stream_process_data_blocks_t process_data_blocks; + int fmt = CIP_FMT_MOTU; + int flags = CIP_BLOCKING; + int err; + + if (dir == AMDTP_IN_STREAM) { + process_data_blocks = process_tx_data_blocks; + } else { + process_data_blocks = process_rx_data_blocks; + flags |= CIP_DBC_IS_END_EVENT; + } + + err = amdtp_stream_init(s, unit, dir, flags, fmt, process_data_blocks, + sizeof(struct amdtp_motu)); + if (err < 0) + return err; + + s->sph = 1; + s->fdf = MOTU_FDF_AM824; + + return 0; +} diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index cb6b57353cc1..cd1b3dd3e371 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -19,12 +19,12 @@ #include #include +#include #include "../lib.h" +#include "../amdtp-stream.h" struct snd_motu_packet_format { - unsigned char midi_flag_offset; - unsigned char midi_byte_offset; unsigned char pcm_byte_offset; unsigned char msg_chunks; @@ -46,6 +46,8 @@ struct snd_motu { /* For packet streaming */ struct snd_motu_packet_format tx_packet_formats; struct snd_motu_packet_format rx_packet_formats; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; }; enum snd_motu_spec_flags { @@ -97,4 +99,11 @@ struct snd_motu_spec { const struct snd_motu_protocol *const protocol; }; +int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, + const struct snd_motu_protocol *const protocol); +int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, + struct snd_motu_packet_format *formats); +int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); #endif -- cgit v1.2.3-59-g8ed1b From 2e76701bbb1fbe55f7d8538ae7f6869070eb3446 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 22 Mar 2017 21:30:19 +0900 Subject: ALSA: firewire-motu: handle transactions specific for MOTU FireWire models All models of MOTU FireWire series can be controlled by write transaction to addresses in a range from 0x'ffff'f0000'0b00 to 0x'ffff'f000'0cff. The models support asynchronous notification. This notification has 32 bit field data, and is transferred when status of clock changes. Meaning of the value is not enough clear yet. Drivers can register its address to receive the notification. Write transaction to 0x'ffff'f000'0b04 registers higher 16 bits of the address. Write transaction to 0x'ffff'f0000'0b08 registers the rest of bits. The address includes node ID, thus it should be registered every time of bus reset. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/motu/Makefile | 2 +- sound/firewire/motu/motu-transaction.c | 117 +++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.c | 10 +++ sound/firewire/motu/motu.h | 12 ++++ 4 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/motu/motu-transaction.c (limited to 'sound') diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index 37391f5c623d..03b07694df66 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,2 +1,2 @@ -snd-firewire-motu-objs := motu.o amdtp-motu.o +snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu-transaction.c b/sound/firewire/motu/motu-transaction.c new file mode 100644 index 000000000000..416dd9833896 --- /dev/null +++ b/sound/firewire/motu/motu-transaction.c @@ -0,0 +1,117 @@ +/* + * motu-transaction.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + + +#include "motu.h" + +#define SND_MOTU_ADDR_BASE 0xfffff0000000ULL +#define ASYNC_ADDR_HI 0x0b04 +#define ASYNC_ADDR_LO 0x0b08 + +int snd_motu_transaction_read(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size) +{ + int tcode; + + if (size % sizeof(__be32) > 0 || size <= 0) + return -EINVAL; + if (size == sizeof(__be32)) + tcode = TCODE_READ_QUADLET_REQUEST; + else + tcode = TCODE_READ_BLOCK_REQUEST; + + return snd_fw_transaction(motu->unit, tcode, + SND_MOTU_ADDR_BASE + offset, reg, size, 0); +} + +int snd_motu_transaction_write(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size) +{ + int tcode; + + if (size % sizeof(__be32) > 0 || size <= 0) + return -EINVAL; + if (size == sizeof(__be32)) + tcode = TCODE_WRITE_QUADLET_REQUEST; + else + tcode = TCODE_WRITE_BLOCK_REQUEST; + + return snd_fw_transaction(motu->unit, tcode, + SND_MOTU_ADDR_BASE + offset, reg, size, 0); +} + +static void handle_message(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + fw_send_response(card, request, RCODE_COMPLETE); +} + +int snd_motu_transaction_reregister(struct snd_motu *motu) +{ + struct fw_device *device = fw_parent_device(motu->unit); + __be32 data; + int err; + + if (motu->async_handler.callback_data == NULL) + return -EINVAL; + + /* Register messaging address. Block transaction is not allowed. */ + data = cpu_to_be32((device->card->node_id << 16) | + (motu->async_handler.offset >> 32)); + err = snd_motu_transaction_write(motu, ASYNC_ADDR_HI, &data, + sizeof(data)); + if (err < 0) + return err; + + data = cpu_to_be32(motu->async_handler.offset); + return snd_motu_transaction_write(motu, ASYNC_ADDR_LO, &data, + sizeof(data)); +} + +int snd_motu_transaction_register(struct snd_motu *motu) +{ + static const struct fw_address_region resp_register_region = { + .start = 0xffffe0000000ull, + .end = 0xffffe000ffffull, + }; + int err; + + /* Perhaps, 4 byte messages are transferred. */ + motu->async_handler.length = 4; + motu->async_handler.address_callback = handle_message; + motu->async_handler.callback_data = motu; + + err = fw_core_add_address_handler(&motu->async_handler, + &resp_register_region); + if (err < 0) + return err; + + err = snd_motu_transaction_reregister(motu); + if (err < 0) { + fw_core_remove_address_handler(&motu->async_handler); + motu->async_handler.address_callback = NULL; + } + + return err; +} + +void snd_motu_transaction_unregister(struct snd_motu *motu) +{ + __be32 data; + + if (motu->async_handler.address_callback != NULL) + fw_core_remove_address_handler(&motu->async_handler); + motu->async_handler.address_callback = NULL; + + /* Unregister the address. */ + data = cpu_to_be32(0x00000000); + snd_motu_transaction_write(motu, ASYNC_ADDR_HI, &data, sizeof(data)); + snd_motu_transaction_write(motu, ASYNC_ADDR_LO, &data, sizeof(data)); +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index 1e6fc74a6458..db6014c2f16d 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -54,6 +54,8 @@ static void name_card(struct snd_motu *motu) static void motu_free(struct snd_motu *motu) { + snd_motu_transaction_unregister(motu); + fw_unit_put(motu->unit); mutex_destroy(&motu->mutex); @@ -86,6 +88,10 @@ static void do_registration(struct work_struct *work) name_card(motu); + err = snd_motu_transaction_register(motu); + if (err < 0) + goto error; + err = snd_card_register(motu->card); if (err < 0) goto error; @@ -100,6 +106,7 @@ static void do_registration(struct work_struct *work) return; error: + snd_motu_transaction_unregister(motu); snd_card_free(motu->card); dev_info(&motu->unit->device, "Sound card registration failed: %d\n", err); @@ -155,6 +162,9 @@ static void motu_bus_update(struct fw_unit *unit) /* Postpone a workqueue for deferred registration. */ if (!motu->registered) snd_fw_schedule_registration(unit, &motu->dwork); + + /* The handler address register becomes initialized. */ + snd_motu_transaction_reregister(motu); } #define SND_MOTU_DEV_ENTRY(model, data) \ diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index cd1b3dd3e371..ed1d779c0dcc 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -48,6 +48,10 @@ struct snd_motu { struct snd_motu_packet_format rx_packet_formats; struct amdtp_stream tx_stream; struct amdtp_stream rx_stream; + + /* For notification. */ + struct fw_address_handler async_handler; + u32 msg; }; enum snd_motu_spec_flags { @@ -106,4 +110,12 @@ int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, struct snd_motu_packet_format *formats); int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s, struct snd_pcm_runtime *runtime); + +int snd_motu_transaction_read(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size); +int snd_motu_transaction_write(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size); +int snd_motu_transaction_register(struct snd_motu *motu); +int snd_motu_transaction_reregister(struct snd_motu *motu); +void snd_motu_transaction_unregister(struct snd_motu *motu); #endif -- cgit v1.2.3-59-g8ed1b From 9b2bb4f2f4a213a768a84fa25c14be54844f5bb6 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 22 Mar 2017 21:30:20 +0900 Subject: ALSA: firewire-motu: add stream management functionality This commit adds a functionality to manage packet streaming for MOTU FireWire series. The streaming is not controlled by CMP, thus against IEC 61883-1. Write transaction to certain addresses start/stop packet streaming. Transactions to 0x'ffff'f000'0b00 results in isochronous channel number for both directions and starting/stopping transmission of packets. The isochronous channel number is represented in 6 bit field, thus units can identify the channels up to 64, as IEEE 1394 bus specification described. Transactions to 0x'ffff'f000'0b10 results in packet format for both directions and transmission speed. When each of data block includes fixed part of data chunks only, corresponding flags stand. When bus reset occurs, the units continue to transmit packets with non-contiguous data block counter. This causes discontinuity detection in packet streaming engine and ALSA PCM applications receives EPIPE from any I/O operation. In this case, typical applications manage to recover corresponding PCM substream. This behaviour is kicked much earlier than callback of bus reset handler by Linux FireWire subsystem, therefore status of packet streaming is not changed in the handler. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/motu/Makefile | 2 +- sound/firewire/motu/motu-stream.c | 340 ++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.c | 5 + sound/firewire/motu/motu.h | 10 ++ 4 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/motu/motu-stream.c (limited to 'sound') diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index 03b07694df66..504a4f9dea6d 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,2 +1,2 @@ -snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o +snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu-stream.c b/sound/firewire/motu/motu-stream.c new file mode 100644 index 000000000000..9aa698fc8da2 --- /dev/null +++ b/sound/firewire/motu/motu-stream.c @@ -0,0 +1,340 @@ +/* + * motu-stream.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "motu.h" + +#define CALLBACK_TIMEOUT 200 + +#define ISOC_COMM_CONTROL_OFFSET 0x0b00 +#define ISOC_COMM_CONTROL_MASK 0xffff0000 +#define CHANGE_RX_ISOC_COMM_STATE 0x80000000 +#define RX_ISOC_COMM_IS_ACTIVATED 0x40000000 +#define RX_ISOC_COMM_CHANNEL_MASK 0x3f000000 +#define RX_ISOC_COMM_CHANNEL_SHIFT 24 +#define CHANGE_TX_ISOC_COMM_STATE 0x00800000 +#define TX_ISOC_COMM_IS_ACTIVATED 0x00400000 +#define TX_ISOC_COMM_CHANNEL_MASK 0x003f0000 +#define TX_ISOC_COMM_CHANNEL_SHIFT 16 + +#define PACKET_FORMAT_OFFSET 0x0b10 +#define TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS 0x00000080 +#define RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS 0x00000040 +#define TX_PACKET_TRANSMISSION_SPEED_MASK 0x0000000f + +static int start_both_streams(struct snd_motu *motu, unsigned int rate) +{ + __be32 reg; + u32 data; + int err; + + /* Set packet formation to our packet streaming engine. */ + err = amdtp_motu_set_parameters(&motu->rx_stream, rate, + &motu->rx_packet_formats); + if (err < 0) + return err; + + err = amdtp_motu_set_parameters(&motu->tx_stream, rate, + &motu->tx_packet_formats); + if (err < 0) + return err; + + + /* Get isochronous resources on the bus. */ + err = fw_iso_resources_allocate(&motu->rx_resources, + amdtp_stream_get_max_payload(&motu->rx_stream), + fw_parent_device(motu->unit)->max_speed); + if (err < 0) + return err; + + err = fw_iso_resources_allocate(&motu->tx_resources, + amdtp_stream_get_max_payload(&motu->tx_stream), + fw_parent_device(motu->unit)->max_speed); + if (err < 0) + return err; + + /* Configure the unit to start isochronous communication. */ + err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg) & ~ISOC_COMM_CONTROL_MASK; + + data |= CHANGE_RX_ISOC_COMM_STATE | RX_ISOC_COMM_IS_ACTIVATED | + (motu->rx_resources.channel << RX_ISOC_COMM_CHANNEL_SHIFT) | + CHANGE_TX_ISOC_COMM_STATE | TX_ISOC_COMM_IS_ACTIVATED | + (motu->tx_resources.channel << TX_ISOC_COMM_CHANNEL_SHIFT); + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); +} + +static void stop_both_streams(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = motu->spec->protocol->switch_fetching_mode(motu, false); + if (err < 0) + return; + + err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return; + data = be32_to_cpu(reg); + + data &= ~(RX_ISOC_COMM_IS_ACTIVATED | TX_ISOC_COMM_IS_ACTIVATED); + data |= CHANGE_RX_ISOC_COMM_STATE | CHANGE_TX_ISOC_COMM_STATE; + + reg = cpu_to_be32(data); + snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); + + fw_iso_resources_free(&motu->tx_resources); + fw_iso_resources_free(&motu->rx_resources); +} + +static int start_isoc_ctx(struct snd_motu *motu, struct amdtp_stream *stream) +{ + struct fw_iso_resources *resources; + int err; + + if (stream == &motu->rx_stream) + resources = &motu->rx_resources; + else + resources = &motu->tx_resources; + + err = amdtp_stream_start(stream, resources->channel, + fw_parent_device(motu->unit)->max_speed); + if (err < 0) + return err; + + if (!amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT)) { + amdtp_stream_stop(stream); + fw_iso_resources_free(resources); + return -ETIMEDOUT; + } + + return 0; +} + +static void stop_isoc_ctx(struct snd_motu *motu, struct amdtp_stream *stream) +{ + struct fw_iso_resources *resources; + + if (stream == &motu->rx_stream) + resources = &motu->rx_resources; + else + resources = &motu->tx_resources; + + amdtp_stream_stop(stream); + fw_iso_resources_free(resources); +} + +static int ensure_packet_formats(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, PACKET_FORMAT_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~(TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS | + RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS| + TX_PACKET_TRANSMISSION_SPEED_MASK); + if (motu->tx_packet_formats.differed_part_pcm_chunks[0] == 0) + data |= TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS; + if (motu->rx_packet_formats.differed_part_pcm_chunks[0] == 0) + data |= RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS; + data |= fw_parent_device(motu->unit)->max_speed; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, PACKET_FORMAT_OFFSET, ®, + sizeof(reg)); +} + +int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate) +{ + const struct snd_motu_protocol *protocol = motu->spec->protocol; + unsigned int curr_rate; + int err = 0; + + if (motu->capture_substreams == 0 && motu->playback_substreams == 0) + return 0; + + /* Some packet queueing errors. */ + if (amdtp_streaming_error(&motu->rx_stream) || + amdtp_streaming_error(&motu->tx_stream)) { + amdtp_stream_stop(&motu->rx_stream); + amdtp_stream_stop(&motu->tx_stream); + stop_both_streams(motu); + } + + err = protocol->cache_packet_formats(motu); + if (err < 0) + return err; + + /* Stop stream if rate is different. */ + err = protocol->get_clock_rate(motu, &curr_rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to get sampling rate: %d\n", err); + return err; + } + if (rate == 0) + rate = curr_rate; + if (rate != curr_rate) { + amdtp_stream_stop(&motu->rx_stream); + amdtp_stream_stop(&motu->tx_stream); + stop_both_streams(motu); + } + + if (!amdtp_stream_running(&motu->rx_stream)) { + err = protocol->set_clock_rate(motu, rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to set sampling rate: %d\n", err); + return err; + } + + err = ensure_packet_formats(motu); + if (err < 0) + return err; + + err = start_both_streams(motu, rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to start isochronous comm: %d\n", err); + stop_both_streams(motu); + return err; + } + + err = start_isoc_ctx(motu, &motu->rx_stream); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to start IT context: %d\n", err); + stop_both_streams(motu); + return err; + } + + err = protocol->switch_fetching_mode(motu, true); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to enable frame fetching: %d\n", err); + stop_both_streams(motu); + return err; + } + } + + if (!amdtp_stream_running(&motu->tx_stream) && + motu->capture_substreams > 0) { + err = start_isoc_ctx(motu, &motu->tx_stream); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to start IR context: %d", err); + amdtp_stream_stop(&motu->rx_stream); + stop_both_streams(motu); + return err; + } + } + + return 0; +} + +void snd_motu_stream_stop_duplex(struct snd_motu *motu) +{ + if (motu->capture_substreams == 0) { + if (amdtp_stream_running(&motu->tx_stream)) + stop_isoc_ctx(motu, &motu->tx_stream); + + if (motu->playback_substreams == 0) { + if (amdtp_stream_running(&motu->rx_stream)) + stop_isoc_ctx(motu, &motu->rx_stream); + stop_both_streams(motu); + } + } +} + +static int init_stream(struct snd_motu *motu, enum amdtp_stream_direction dir) +{ + int err; + struct amdtp_stream *stream; + struct fw_iso_resources *resources; + + if (dir == AMDTP_IN_STREAM) { + stream = &motu->tx_stream; + resources = &motu->tx_resources; + } else { + stream = &motu->rx_stream; + resources = &motu->rx_resources; + } + + err = fw_iso_resources_init(resources, motu->unit); + if (err < 0) + return err; + + err = amdtp_motu_init(stream, motu->unit, dir, motu->spec->protocol); + if (err < 0) { + amdtp_stream_destroy(stream); + fw_iso_resources_destroy(resources); + } + + return err; +} + +static void destroy_stream(struct snd_motu *motu, + enum amdtp_stream_direction dir) +{ + struct amdtp_stream *stream; + struct fw_iso_resources *resources; + + if (dir == AMDTP_IN_STREAM) { + stream = &motu->tx_stream; + resources = &motu->tx_resources; + } else { + stream = &motu->rx_stream; + resources = &motu->rx_resources; + } + + amdtp_stream_destroy(stream); + fw_iso_resources_free(resources); +} + +int snd_motu_stream_init_duplex(struct snd_motu *motu) +{ + int err; + + err = init_stream(motu, AMDTP_IN_STREAM); + if (err < 0) + return err; + + err = init_stream(motu, AMDTP_OUT_STREAM); + if (err < 0) + destroy_stream(motu, AMDTP_IN_STREAM); + + return err; +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +void snd_motu_stream_destroy_duplex(struct snd_motu *motu) +{ + destroy_stream(motu, AMDTP_IN_STREAM); + destroy_stream(motu, AMDTP_OUT_STREAM); + + motu->playback_substreams = 0; + motu->capture_substreams = 0; +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index db6014c2f16d..9d52238d898e 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -56,6 +56,7 @@ static void motu_free(struct snd_motu *motu) { snd_motu_transaction_unregister(motu); + snd_motu_stream_destroy_duplex(motu); fw_unit_put(motu->unit); mutex_destroy(&motu->mutex); @@ -92,6 +93,10 @@ static void do_registration(struct work_struct *work) if (err < 0) goto error; + err = snd_motu_stream_init_duplex(motu); + if (err < 0) + goto error; + err = snd_card_register(motu->card); if (err < 0) goto error; diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index ed1d779c0dcc..90d274167a4a 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -23,6 +23,7 @@ #include "../lib.h" #include "../amdtp-stream.h" +#include "../iso-resources.h" struct snd_motu_packet_format { unsigned char pcm_byte_offset; @@ -48,6 +49,10 @@ struct snd_motu { struct snd_motu_packet_format rx_packet_formats; struct amdtp_stream tx_stream; struct amdtp_stream rx_stream; + struct fw_iso_resources tx_resources; + struct fw_iso_resources rx_resources; + unsigned int capture_substreams; + unsigned int playback_substreams; /* For notification. */ struct fw_address_handler async_handler; @@ -118,4 +123,9 @@ int snd_motu_transaction_write(struct snd_motu *motu, u32 offset, __be32 *reg, int snd_motu_transaction_register(struct snd_motu *motu); int snd_motu_transaction_reregister(struct snd_motu *motu); void snd_motu_transaction_unregister(struct snd_motu *motu); + +int snd_motu_stream_init_duplex(struct snd_motu *motu); +void snd_motu_stream_destroy_duplex(struct snd_motu *motu); +int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate); +void snd_motu_stream_stop_duplex(struct snd_motu *motu); #endif -- cgit v1.2.3-59-g8ed1b From 4638ec6ede0847c75bd943d54237efb118f4abae Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 22 Mar 2017 21:30:21 +0900 Subject: ALSA: firewire-motu: add proc node to show current statuc of clock and packet formats This commit adds a proc node for debugging purpose. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/motu/Makefile | 3 +- sound/firewire/motu/motu-proc.c | 118 ++++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.c | 2 + sound/firewire/motu/motu.h | 3 + 4 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/motu/motu-proc.c (limited to 'sound') diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index 504a4f9dea6d..0eccbe215f5e 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,2 +1,3 @@ -snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o +snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \ + motu-proc.o obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu-proc.c b/sound/firewire/motu/motu-proc.c new file mode 100644 index 000000000000..4edc064999ed --- /dev/null +++ b/sound/firewire/motu/motu-proc.c @@ -0,0 +1,118 @@ +/* + * motu-proc.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./motu.h" + +static const char *const clock_names[] = { + [SND_MOTU_CLOCK_SOURCE_INTERNAL] = "Internal", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB] = "ADAT on Dsub-9pin interface", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT] = "ADAT on optical interface", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A] = "ADAT on optical interface A", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B] = "ADAT on optical interface B", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT] = "S/PDIF on optical interface", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A] = "S/PDIF on optical interface A", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B] = "S/PDIF on optical interface B", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX] = "S/PCIF on coaxial interface", + [SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR] = "AESEBU on XLR interface", + [SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC] = "Word clock on BNC interface", +}; + +static void proc_read_clock(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + + struct snd_motu *motu = entry->private_data; + const struct snd_motu_protocol *const protocol = motu->spec->protocol; + unsigned int rate; + enum snd_motu_clock_source source; + + if (protocol->get_clock_rate(motu, &rate) < 0) + return; + if (protocol->get_clock_source(motu, &source) < 0) + return; + + snd_iprintf(buffer, "Rate:\t%d\n", rate); + snd_iprintf(buffer, "Source:\t%s\n", clock_names[source]); +} + +static void proc_read_format(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_motu *motu = entry->private_data; + const struct snd_motu_protocol *const protocol = motu->spec->protocol; + unsigned int mode; + struct snd_motu_packet_format *formats; + int i; + + if (protocol->cache_packet_formats(motu) < 0) + return; + + snd_iprintf(buffer, "tx:\tmsg\tfixed\tdiffered\n"); + for (i = 0; i < SND_MOTU_CLOCK_RATE_COUNT; ++i) { + mode = i >> 1; + + formats = &motu->tx_packet_formats; + snd_iprintf(buffer, + "%u:\t%u\t%u\t%u\n", + snd_motu_clock_rates[i], + formats->msg_chunks, + formats->fixed_part_pcm_chunks[mode], + formats->differed_part_pcm_chunks[mode]); + } + + snd_iprintf(buffer, "rx:\tmsg\tfixed\tdiffered\n"); + for (i = 0; i < SND_MOTU_CLOCK_RATE_COUNT; ++i) { + mode = i >> 1; + + formats = &motu->rx_packet_formats; + snd_iprintf(buffer, + "%u:\t%u\t%u\t%u\n", + snd_motu_clock_rates[i], + formats->msg_chunks, + formats->fixed_part_pcm_chunks[mode], + formats->differed_part_pcm_chunks[mode]); + } +} + +static void add_node(struct snd_motu *motu, struct snd_info_entry *root, + const char *name, + void (*op)(struct snd_info_entry *e, + struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(motu->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, motu, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_motu_proc_init(struct snd_motu *motu) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(motu->card, "firewire", + motu->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(motu, root, "clock", proc_read_clock); + add_node(motu, root, "format", proc_read_format); +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index 9d52238d898e..cbf4ed0f5234 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -97,6 +97,8 @@ static void do_registration(struct work_struct *work) if (err < 0) goto error; + snd_motu_proc_init(motu); + err = snd_card_register(motu->card); if (err < 0) goto error; diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index 90d274167a4a..4d079d66cf77 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -20,6 +20,7 @@ #include #include #include +#include #include "../lib.h" #include "../amdtp-stream.h" @@ -128,4 +129,6 @@ int snd_motu_stream_init_duplex(struct snd_motu *motu); void snd_motu_stream_destroy_duplex(struct snd_motu *motu); int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate); void snd_motu_stream_stop_duplex(struct snd_motu *motu); + +void snd_motu_proc_init(struct snd_motu *motu); #endif -- cgit v1.2.3-59-g8ed1b From dd49b2d1f04af9b1f44e9fe82c85f374f662c61b Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 22 Mar 2017 21:30:22 +0900 Subject: ALSA: firewire-motu: add PCM functionality This commit adds PCM functionality to transmit/receive PCM samples. When one of PCM substreams are running or external clock source is selected, current sampling rate is used. Else, the sampling rate is changed according to requests from a userspace application. Available number of samples in a frame of PCM substream is determined at open(2) to corresponding PCM character device. Later, packet streaming starts by ioctl(2) with SNDRV_PCM_IOCTL_PREPARE. In theory, between them, applications can change state of the unit by any write transaction to change the number. In this case, this driver may fail packet streaming due to wrong data format. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/motu/Makefile | 2 +- sound/firewire/motu/motu-pcm.c | 386 +++++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.c | 4 + sound/firewire/motu/motu.h | 2 + 4 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/motu/motu-pcm.c (limited to 'sound') diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index 0eccbe215f5e..508b6894826a 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,3 +1,3 @@ snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \ - motu-proc.o + motu-proc.o motu-pcm.o obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu-pcm.c b/sound/firewire/motu/motu-pcm.c new file mode 100644 index 000000000000..a50bcd6f4a63 --- /dev/null +++ b/sound/firewire/motu/motu-pcm.c @@ -0,0 +1,386 @@ +/* + * motu-pcm.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include "motu.h" + +static int motu_rate_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_motu_packet_format *formats = rule->private; + + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval rates = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, pcm_channels, rate, mode; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + pcm_channels = formats->fixed_part_pcm_chunks[mode] + + formats->differed_part_pcm_chunks[mode]; + if (!snd_interval_test(c, pcm_channels)) + continue; + + rates.min = min(rates.min, rate); + rates.max = max(rates.max, rate); + } + + return snd_interval_refine(r, &rates); +} + +static int motu_channels_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_motu_packet_format *formats = rule->private; + + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval channels = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, pcm_channels, rate, mode; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + if (!snd_interval_test(r, rate)) + continue; + + pcm_channels = formats->fixed_part_pcm_chunks[mode] + + formats->differed_part_pcm_chunks[mode]; + channels.min = min(channels.min, pcm_channels); + channels.max = max(channels.max, pcm_channels); + } + + return snd_interval_refine(c, &channels); +} + +static void limit_channels_and_rates(struct snd_motu *motu, + struct snd_pcm_runtime *runtime, + struct snd_motu_packet_format *formats) +{ + struct snd_pcm_hardware *hw = &runtime->hw; + unsigned int i, pcm_channels, rate, mode; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + pcm_channels = formats->fixed_part_pcm_chunks[mode] + + formats->differed_part_pcm_chunks[mode]; + if (pcm_channels == 0) + continue; + + hw->rates |= snd_pcm_rate_to_rate_bit(rate); + hw->channels_min = min(hw->channels_min, pcm_channels); + hw->channels_max = max(hw->channels_max, pcm_channels); + } + + snd_pcm_limit_hw_rates(runtime); +} + +static void limit_period_and_buffer(struct snd_pcm_hardware *hw) +{ + hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */ + hw->periods_max = UINT_MAX; + + hw->period_bytes_min = 4 * hw->channels_max; /* byte for a frame */ + + /* Just to prevent from allocating much pages. */ + hw->period_bytes_max = hw->period_bytes_min * 2048; + hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min; +} + +static int init_hw_info(struct snd_motu *motu, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; + struct amdtp_stream *stream; + struct snd_motu_packet_format *formats; + int err; + + hw->info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | + SNDRV_PCM_INFO_BLOCK_TRANSFER; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + hw->formats = SNDRV_PCM_FMTBIT_S32; + stream = &motu->tx_stream; + formats = &motu->tx_packet_formats; + } else { + hw->formats = SNDRV_PCM_FMTBIT_S32; + stream = &motu->rx_stream; + formats = &motu->rx_packet_formats; + } + + limit_channels_and_rates(motu, runtime, formats); + limit_period_and_buffer(hw); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + motu_rate_constraint, formats, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + motu_channels_constraint, formats, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + return amdtp_motu_add_pcm_hw_constraints(stream, runtime); +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + const struct snd_motu_protocol *const protocol = motu->spec->protocol; + enum snd_motu_clock_source src; + unsigned int rate; + int err; + + mutex_lock(&motu->mutex); + + err = protocol->cache_packet_formats(motu); + if (err < 0) + return err; + + err = init_hw_info(motu, substream); + if (err < 0) + return err; + + /* + * When source of clock is not internal or any PCM streams are running, + * available sampling rate is limited at current sampling rate. + */ + err = protocol->get_clock_source(motu, &src); + if (err < 0) + return err; + if (src != SND_MOTU_CLOCK_SOURCE_INTERNAL || + amdtp_stream_pcm_running(&motu->tx_stream) || + amdtp_stream_pcm_running(&motu->rx_stream)) { + err = protocol->get_clock_rate(motu, &rate); + if (err < 0) + return err; + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } + + snd_pcm_set_sync(substream); + + mutex_unlock(&motu->mutex); + + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_motu *motu = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&motu->mutex); + motu->capture_substreams++; + mutex_unlock(&motu->mutex); + } + + return 0; +} +static int playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_motu *motu = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&motu->mutex); + motu->playback_substreams++; + mutex_unlock(&motu->mutex); + } + + return 0; +} + +static int capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + mutex_lock(&motu->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + motu->capture_substreams--; + + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + mutex_lock(&motu->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + motu->playback_substreams--; + + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + int err; + + mutex_lock(&motu->mutex); + err = snd_motu_stream_start_duplex(motu, substream->runtime->rate); + mutex_unlock(&motu->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(&motu->tx_stream); + + return 0; +} +static int playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + int err; + + mutex_lock(&motu->mutex); + err = snd_motu_stream_start_duplex(motu, substream->runtime->rate); + mutex_unlock(&motu->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(&motu->rx_stream); + + return err; +} + +static int capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_motu *motu = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&motu->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&motu->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_motu *motu = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&motu->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&motu->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_stream_pcm_pointer(&motu->tx_stream); +} +static snd_pcm_uframes_t playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_stream_pcm_pointer(&motu->rx_stream); +} + +int snd_motu_create_pcm_devices(struct snd_motu *motu) +{ + static struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = capture_hw_params, + .hw_free = capture_hw_free, + .prepare = capture_prepare, + .trigger = capture_trigger, + .pointer = capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, + }; + static struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = playback_hw_params, + .hw_free = playback_hw_free, + .prepare = playback_prepare, + .trigger = playback_trigger, + .pointer = playback_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(motu->card, motu->card->driver, 0, 1, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = motu; + strcpy(pcm->name, motu->card->shortname); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + + return 0; +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index cbf4ed0f5234..801d6a73b0f3 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -99,6 +99,10 @@ static void do_registration(struct work_struct *work) snd_motu_proc_init(motu); + err = snd_motu_create_pcm_devices(motu); + if (err < 0) + goto error; + err = snd_card_register(motu->card); if (err < 0) goto error; diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index 4d079d66cf77..afc6de654daa 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -131,4 +131,6 @@ int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate); void snd_motu_stream_stop_duplex(struct snd_motu *motu); void snd_motu_proc_init(struct snd_motu *motu); + +int snd_motu_create_pcm_devices(struct snd_motu *motu); #endif -- cgit v1.2.3-59-g8ed1b From 9e796e7d59e71f8a556cfbdc2ffa3aff0555dd0e Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 22 Mar 2017 21:30:23 +0900 Subject: ALSA: firewire-motu: add MIDI functionality In MOTU FireWire series, MIDI messages are multiplexed to isochronous packets as well as PCM frames, while the way is different from the one in IEC 61883-6. MIDI messages are put into a certain position in message chunks. One data block can includes one byte of the MIDI messages. When data block includes a MIDI byte, the block has a flag in a certain position of the message chunk. These positions are unique depending on protocols. Once a data block includes a MIDI byte, some following data blocks includes no MIDI bytes. Next MIDI byte appears on a data block corresponding to next cycle of physical MIDI bus. This seems to avoid buffer overflow caused by bandwidth differences between IEEE 1394 bus and physical MIDI bus. This commit adds MIDI functionality to transfer/receive MIDI messages. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/motu/Makefile | 2 +- sound/firewire/motu/amdtp-motu.c | 84 +++++++++++++++++++++ sound/firewire/motu/motu-midi.c | 153 ++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu-stream.c | 9 ++- sound/firewire/motu/motu.c | 7 ++ sound/firewire/motu/motu.h | 9 +++ 6 files changed, 260 insertions(+), 4 deletions(-) create mode 100644 sound/firewire/motu/motu-midi.c (limited to 'sound') diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index 508b6894826a..a512c1e0f49c 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,3 +1,3 @@ snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \ - motu-proc.o motu-pcm.o + motu-proc.o motu-pcm.o motu-midi.o obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/amdtp-motu.c b/sound/firewire/motu/amdtp-motu.c index 11e44123ad65..0930cd8ca2cb 100644 --- a/sound/firewire/motu/amdtp-motu.c +++ b/sound/firewire/motu/amdtp-motu.c @@ -13,6 +13,12 @@ #define CIP_FMT_MOTU 0x02 #define MOTU_FDF_AM824 0x22 +/* + * Nominally 3125 bytes/second, but the MIDI port's clock might be + * 1% too slow, and the bus clock 100 ppm too fast. + */ +#define MIDI_BYTES_PER_SECOND 3093 + struct amdtp_motu { /* For timestamp processing. */ unsigned int quotient_ticks_per_event; @@ -24,9 +30,18 @@ struct amdtp_motu { unsigned int pcm_chunks; unsigned int pcm_byte_offset; + + struct snd_rawmidi_substream *midi; + unsigned int midi_ports; + unsigned int midi_flag_offset; + unsigned int midi_byte_offset; + + int midi_db_count; + unsigned int midi_db_interval; }; int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int midi_ports, struct snd_motu_packet_format *formats) { static const struct { @@ -76,6 +91,13 @@ int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, p->pcm_chunks = pcm_chunks; p->pcm_byte_offset = formats->pcm_byte_offset; + p->midi_ports = midi_ports; + p->midi_flag_offset = formats->midi_flag_offset; + p->midi_byte_offset = formats->midi_byte_offset; + + p->midi_db_count = 0; + p->midi_db_interval = rate / MIDI_BYTES_PER_SECOND; + /* IEEE 1394 bus requires. */ delay = 0x2e00; @@ -187,12 +209,70 @@ int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s, return amdtp_stream_add_pcm_hw_constraints(s, runtime); } +void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi) +{ + struct amdtp_motu *p = s->protocol; + + if (port < p->midi_ports) + WRITE_ONCE(p->midi, midi); +} + +static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + struct snd_rawmidi_substream *midi = READ_ONCE(p->midi); + u8 *b; + int i; + + for (i = 0; i < data_blocks; i++) { + b = (u8 *)buffer; + + if (midi && p->midi_db_count == 0 && + snd_rawmidi_transmit(midi, b + p->midi_byte_offset, 1) == 1) { + b[p->midi_flag_offset] = 0x01; + } else { + b[p->midi_byte_offset] = 0x00; + b[p->midi_flag_offset] = 0x00; + } + + buffer += s->data_block_quadlets; + + if (--p->midi_db_count < 0) + p->midi_db_count = p->midi_db_interval; + } +} + +static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + struct snd_rawmidi_substream *midi; + u8 *b; + int i; + + for (i = 0; i < data_blocks; i++) { + b = (u8 *)buffer; + midi = READ_ONCE(p->midi); + + if (midi && (b[p->midi_flag_offset] & 0x01)) + snd_rawmidi_receive(midi, b + p->midi_byte_offset, 1); + + buffer += s->data_block_quadlets; + } +} + static unsigned int process_tx_data_blocks(struct amdtp_stream *s, __be32 *buffer, unsigned int data_blocks, unsigned int *syt) { + struct amdtp_motu *p = s->protocol; struct snd_pcm_substream *pcm; + if (p->midi_ports) + read_midi_messages(s, buffer, data_blocks); + pcm = ACCESS_ONCE(s->pcm); if (data_blocks > 0 && pcm) read_pcm_s32(s, pcm->runtime, buffer, data_blocks); @@ -246,6 +326,7 @@ static unsigned int process_rx_data_blocks(struct amdtp_stream *s, __be32 *buffer, unsigned int data_blocks, unsigned int *syt) { + struct amdtp_motu *p = (struct amdtp_motu *)s->protocol; struct snd_pcm_substream *pcm; /* Not used. */ @@ -253,6 +334,9 @@ static unsigned int process_rx_data_blocks(struct amdtp_stream *s, /* TODO: how to interact control messages between userspace? */ + if (p->midi_ports) + write_midi_messages(s, buffer, data_blocks); + pcm = ACCESS_ONCE(s->pcm); if (pcm) write_pcm_s32(s, pcm->runtime, buffer, data_blocks); diff --git a/sound/firewire/motu/motu-midi.c b/sound/firewire/motu/motu-midi.c new file mode 100644 index 000000000000..f232f29589d0 --- /dev/null +++ b/sound/firewire/motu/motu-midi.c @@ -0,0 +1,153 @@ +/* + * motu-midi.h - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ +#include "motu.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + int err; + + mutex_lock(&motu->mutex); + + motu->capture_substreams++; + err = snd_motu_stream_start_duplex(motu, 0); + + mutex_unlock(&motu->mutex); + + return err; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + int err; + + mutex_lock(&motu->mutex); + + motu->playback_substreams++; + err = snd_motu_stream_start_duplex(motu, 0); + + mutex_unlock(&motu->mutex); + + return err; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + + mutex_lock(&motu->mutex); + + motu->capture_substreams--; + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + + mutex_lock(&motu->mutex); + + motu->playback_substreams--; + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_motu *motu = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&motu->lock, flags); + + if (up) + amdtp_motu_midi_trigger(&motu->tx_stream, substrm->number, + substrm); + else + amdtp_motu_midi_trigger(&motu->tx_stream, substrm->number, + NULL); + + spin_unlock_irqrestore(&motu->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_motu *motu = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&motu->lock, flags); + + if (up) + amdtp_motu_midi_trigger(&motu->rx_stream, substrm->number, + substrm); + else + amdtp_motu_midi_trigger(&motu->rx_stream, substrm->number, + NULL); + + spin_unlock_irqrestore(&motu->lock, flags); +} + +static void set_midi_substream_names(struct snd_motu *motu, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", motu->card->shortname, subs->number + 1); + } +} + +int snd_motu_create_midi_devices(struct snd_motu *motu) +{ + static struct snd_rawmidi_ops capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, + }; + static struct snd_rawmidi_ops playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, + }; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + /* create midi ports */ + err = snd_rawmidi_new(motu->card, motu->card->driver, 0, 1, 1, &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", motu->card->shortname); + rmidi->private_data = motu; + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &capture_ops); + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + set_midi_substream_names(motu, str); + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &playback_ops); + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + set_midi_substream_names(motu, str); + + return 0; +} diff --git a/sound/firewire/motu/motu-stream.c b/sound/firewire/motu/motu-stream.c index 9aa698fc8da2..911d3487f775 100644 --- a/sound/firewire/motu/motu-stream.c +++ b/sound/firewire/motu/motu-stream.c @@ -28,22 +28,25 @@ static int start_both_streams(struct snd_motu *motu, unsigned int rate) { + unsigned int midi_ports = 0; __be32 reg; u32 data; int err; + if (motu->spec->flags & SND_MOTU_SPEC_HAS_MIDI) + midi_ports = 1; + /* Set packet formation to our packet streaming engine. */ - err = amdtp_motu_set_parameters(&motu->rx_stream, rate, + err = amdtp_motu_set_parameters(&motu->rx_stream, rate, midi_ports, &motu->rx_packet_formats); if (err < 0) return err; - err = amdtp_motu_set_parameters(&motu->tx_stream, rate, + err = amdtp_motu_set_parameters(&motu->tx_stream, rate, midi_ports, &motu->tx_packet_formats); if (err < 0) return err; - /* Get isochronous resources on the bus. */ err = fw_iso_resources_allocate(&motu->rx_resources, amdtp_stream_get_max_payload(&motu->rx_stream), diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index 801d6a73b0f3..d4da1377fa50 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -103,6 +103,12 @@ static void do_registration(struct work_struct *work) if (err < 0) goto error; + if (motu->spec->flags & SND_MOTU_SPEC_HAS_MIDI) { + err = snd_motu_create_midi_devices(motu); + if (err < 0) + goto error; + } + err = snd_card_register(motu->card); if (err < 0) goto error; @@ -138,6 +144,7 @@ static int motu_probe(struct fw_unit *unit, dev_set_drvdata(&unit->device, motu); mutex_init(&motu->mutex); + spin_lock_init(&motu->lock); /* Allocate and register this sound card later. */ INIT_DEFERRABLE_WORK(&motu->dwork, do_registration); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index afc6de654daa..338b35193001 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -21,12 +21,15 @@ #include #include #include +#include #include "../lib.h" #include "../amdtp-stream.h" #include "../iso-resources.h" struct snd_motu_packet_format { + unsigned char midi_flag_offset; + unsigned char midi_byte_offset; unsigned char pcm_byte_offset; unsigned char msg_chunks; @@ -38,6 +41,7 @@ struct snd_motu { struct snd_card *card; struct fw_unit *unit; struct mutex mutex; + spinlock_t lock; bool registered; struct delayed_work dwork; @@ -113,9 +117,12 @@ int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, enum amdtp_stream_direction dir, const struct snd_motu_protocol *const protocol); int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int midi_ports, struct snd_motu_packet_format *formats); int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s, struct snd_pcm_runtime *runtime); +void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi); int snd_motu_transaction_read(struct snd_motu *motu, u32 offset, __be32 *reg, size_t size); @@ -133,4 +140,6 @@ void snd_motu_stream_stop_duplex(struct snd_motu *motu); void snd_motu_proc_init(struct snd_motu *motu); int snd_motu_create_pcm_devices(struct snd_motu *motu); + +int snd_motu_create_midi_devices(struct snd_motu *motu); #endif -- cgit v1.2.3-59-g8ed1b From 71c3797779d3cd8378767f5b2d8cfd3b2f88c5c1 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 22 Mar 2017 21:30:24 +0900 Subject: ALSA: firewire-motu: add hwdep interface This commit adds hwdep interface so as the other sound drivers for units on IEEE 1394 bus have. This interface is designed for mixer/control applications. By using this interface, an application can get information about firewire node, can lock/unlock kernel streaming and can get notification at starting/stopping kernel streaming. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- include/uapi/sound/asound.h | 3 +- include/uapi/sound/firewire.h | 3 +- sound/firewire/motu/Makefile | 2 +- sound/firewire/motu/motu-hwdep.c | 192 ++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu-midi.c | 16 ++++ sound/firewire/motu/motu-pcm.c | 20 +++- sound/firewire/motu/motu-stream.c | 38 ++++++++ sound/firewire/motu/motu.c | 5 + sound/firewire/motu/motu.h | 13 +++ 9 files changed, 285 insertions(+), 7 deletions(-) create mode 100644 sound/firewire/motu/motu-hwdep.c (limited to 'sound') diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index be353a78c303..fd7b561af768 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -107,9 +107,10 @@ enum { SNDRV_HWDEP_IFACE_FW_DIGI00X, /* Digidesign Digi 002/003 family */ SNDRV_HWDEP_IFACE_FW_TASCAM, /* TASCAM FireWire series */ SNDRV_HWDEP_IFACE_LINE6, /* Line6 USB processors */ + SNDRV_HWDEP_IFACE_FW_MOTU, /* MOTU FireWire series */ /* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_LINE6 + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_MOTU }; struct snd_hwdep_info { diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index db79a12fcc78..59c6d81f5364 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -65,7 +65,8 @@ union snd_firewire_event { #define SNDRV_FIREWIRE_TYPE_OXFW 4 #define SNDRV_FIREWIRE_TYPE_DIGI00X 5 #define SNDRV_FIREWIRE_TYPE_TASCAM 6 -/* RME, MOTU, ... */ +#define SNDRV_FIREWIRE_TYPE_MOTU 7 +/* RME... */ struct snd_firewire_get_info { unsigned int type; /* SNDRV_FIREWIRE_TYPE_xxx */ diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index a512c1e0f49c..cc195d5a5a6e 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,3 +1,3 @@ snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \ - motu-proc.o motu-pcm.o motu-midi.o + motu-proc.o motu-pcm.o motu-midi.o motu-hwdep.o obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu-hwdep.c b/sound/firewire/motu/motu-hwdep.c new file mode 100644 index 000000000000..e795a5219a21 --- /dev/null +++ b/sound/firewire/motu/motu-hwdep.c @@ -0,0 +1,192 @@ +/* + * motu-hwdep.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes have five functionalities. + * + * 1.get information about firewire node + * 2.get notification about starting/stopping stream + * 3.lock/unlock streaming + * + */ + +#include "motu.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_motu *motu = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&motu->lock); + + while (!motu->dev_lock_changed) { + prepare_to_wait(&motu->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&motu->lock); + schedule(); + finish_wait(&motu->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&motu->lock); + } + + memset(&event, 0, sizeof(event)); + if (motu->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (motu->dev_lock_count > 0); + motu->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } + + spin_unlock_irq(&motu->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_motu *motu = hwdep->private_data; + unsigned int events; + + poll_wait(file, &motu->hwdep_wait, wait); + + spin_lock_irq(&motu->lock); + if (motu->dev_lock_changed) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&motu->lock); + + return events | POLLOUT; +} + +static int hwdep_get_info(struct snd_motu *motu, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(motu->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_MOTU; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int hwdep_lock(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count == 0) { + motu->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&motu->lock); + + return err; +} + +static int hwdep_unlock(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count == -1) { + motu->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&motu->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_motu *motu = hwdep->private_data; + + spin_lock_irq(&motu->lock); + if (motu->dev_lock_count == -1) + motu->dev_lock_count = 0; + spin_unlock_irq(&motu->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_motu *motu = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(motu, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(motu); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(motu); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +int snd_motu_create_hwdep_device(struct snd_motu *motu) +{ + static const struct snd_hwdep_ops ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(motu->card, motu->card->driver, 0, &hwdep); + if (err < 0) + return err; + + strcpy(hwdep->name, "MOTU"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_MOTU; + hwdep->ops = ops; + hwdep->private_data = motu; + hwdep->exclusive = true; + + return 0; +} diff --git a/sound/firewire/motu/motu-midi.c b/sound/firewire/motu/motu-midi.c index f232f29589d0..e3acfcc53f4e 100644 --- a/sound/firewire/motu/motu-midi.c +++ b/sound/firewire/motu/motu-midi.c @@ -12,6 +12,10 @@ static int midi_capture_open(struct snd_rawmidi_substream *substream) struct snd_motu *motu = substream->rmidi->private_data; int err; + err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + mutex_lock(&motu->mutex); motu->capture_substreams++; @@ -19,6 +23,9 @@ static int midi_capture_open(struct snd_rawmidi_substream *substream) mutex_unlock(&motu->mutex); + if (err < 0) + snd_motu_stream_lock_release(motu); + return err; } @@ -27,6 +34,10 @@ static int midi_playback_open(struct snd_rawmidi_substream *substream) struct snd_motu *motu = substream->rmidi->private_data; int err; + err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + mutex_lock(&motu->mutex); motu->playback_substreams++; @@ -34,6 +45,9 @@ static int midi_playback_open(struct snd_rawmidi_substream *substream) mutex_unlock(&motu->mutex); + if (err < 0) + snd_motu_stream_lock_release(motu); + return err; } @@ -48,6 +62,7 @@ static int midi_capture_close(struct snd_rawmidi_substream *substream) mutex_unlock(&motu->mutex); + snd_motu_stream_lock_release(motu); return 0; } @@ -62,6 +77,7 @@ static int midi_playback_close(struct snd_rawmidi_substream *substream) mutex_unlock(&motu->mutex); + snd_motu_stream_lock_release(motu); return 0; } diff --git a/sound/firewire/motu/motu-pcm.c b/sound/firewire/motu/motu-pcm.c index a50bcd6f4a63..94558f3d218b 100644 --- a/sound/firewire/motu/motu-pcm.c +++ b/sound/firewire/motu/motu-pcm.c @@ -159,15 +159,19 @@ static int pcm_open(struct snd_pcm_substream *substream) unsigned int rate; int err; + err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + mutex_lock(&motu->mutex); err = protocol->cache_packet_formats(motu); if (err < 0) - return err; + goto err_locked; err = init_hw_info(motu, substream); if (err < 0) - return err; + goto err_locked; /* * When source of clock is not internal or any PCM streams are running, @@ -175,13 +179,13 @@ static int pcm_open(struct snd_pcm_substream *substream) */ err = protocol->get_clock_source(motu, &src); if (err < 0) - return err; + goto err_locked; if (src != SND_MOTU_CLOCK_SOURCE_INTERNAL || amdtp_stream_pcm_running(&motu->tx_stream) || amdtp_stream_pcm_running(&motu->rx_stream)) { err = protocol->get_clock_rate(motu, &rate); if (err < 0) - return err; + goto err_locked; substream->runtime->hw.rate_min = rate; substream->runtime->hw.rate_max = rate; } @@ -190,11 +194,19 @@ static int pcm_open(struct snd_pcm_substream *substream) mutex_unlock(&motu->mutex); + return err; +err_locked: + mutex_unlock(&motu->mutex); + snd_motu_stream_lock_release(motu); return err; } static int pcm_close(struct snd_pcm_substream *substream) { + struct snd_motu *motu = substream->private_data; + + snd_motu_stream_lock_release(motu); + return 0; } diff --git a/sound/firewire/motu/motu-stream.c b/sound/firewire/motu/motu-stream.c index 911d3487f775..bd458029099e 100644 --- a/sound/firewire/motu/motu-stream.c +++ b/sound/firewire/motu/motu-stream.c @@ -341,3 +341,41 @@ void snd_motu_stream_destroy_duplex(struct snd_motu *motu) motu->playback_substreams = 0; motu->capture_substreams = 0; } + +static void motu_lock_changed(struct snd_motu *motu) +{ + motu->dev_lock_changed = true; + wake_up(&motu->hwdep_wait); +} + +int snd_motu_stream_lock_try(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count < 0) { + err = -EBUSY; + goto out; + } + + if (motu->dev_lock_count++ == 0) + motu_lock_changed(motu); + err = 0; +out: + spin_unlock_irq(&motu->lock); + return err; +} + +void snd_motu_stream_lock_release(struct snd_motu *motu) +{ + spin_lock_irq(&motu->lock); + + if (WARN_ON(motu->dev_lock_count <= 0)) + goto out; + + if (--motu->dev_lock_count == 0) + motu_lock_changed(motu); +out: + spin_unlock_irq(&motu->lock); +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index d4da1377fa50..619554b9dbef 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -109,6 +109,10 @@ static void do_registration(struct work_struct *work) goto error; } + err = snd_motu_create_hwdep_device(motu); + if (err < 0) + goto error; + err = snd_card_register(motu->card); if (err < 0) goto error; @@ -145,6 +149,7 @@ static int motu_probe(struct fw_unit *unit, mutex_init(&motu->mutex); spin_lock_init(&motu->lock); + init_waitqueue_head(&motu->hwdep_wait); /* Allocate and register this sound card later. */ INIT_DEFERRABLE_WORK(&motu->dwork, do_registration); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index 338b35193001..7b1d85f29b49 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -16,12 +16,16 @@ #include #include #include +#include +#include #include #include #include #include #include +#include +#include #include "../lib.h" #include "../amdtp-stream.h" @@ -62,6 +66,11 @@ struct snd_motu { /* For notification. */ struct fw_address_handler async_handler; u32 msg; + + /* For uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; }; enum snd_motu_spec_flags { @@ -136,10 +145,14 @@ int snd_motu_stream_init_duplex(struct snd_motu *motu); void snd_motu_stream_destroy_duplex(struct snd_motu *motu); int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate); void snd_motu_stream_stop_duplex(struct snd_motu *motu); +int snd_motu_stream_lock_try(struct snd_motu *motu); +void snd_motu_stream_lock_release(struct snd_motu *motu); void snd_motu_proc_init(struct snd_motu *motu); int snd_motu_create_pcm_devices(struct snd_motu *motu); int snd_motu_create_midi_devices(struct snd_motu *motu); + +int snd_motu_create_hwdep_device(struct snd_motu *motu); #endif -- cgit v1.2.3-59-g8ed1b From 5aaab1bf37ede45df4f5d13d735faf824edf3ec8 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 22 Mar 2017 21:30:25 +0900 Subject: ALSA: firewire-motu: enable to read transaction cache via hwdep interface MOTU FireWire series can transfer messages to registered address. These messages are transferred for the status of internal clock synchronization just after starting streams. When the synchronization is stable, it's 0x01ffffff. Else, it's 0x05ffffff. This commit adds a functionality for user space applications to receive content of the message. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- include/uapi/sound/firewire.h | 7 +++++++ sound/firewire/motu/motu-hwdep.c | 10 ++++++++-- sound/firewire/motu/motu-transaction.c | 20 ++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index 59c6d81f5364..29afc5eab42d 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -10,6 +10,7 @@ #define SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION 0xd1ce004e #define SNDRV_FIREWIRE_EVENT_EFW_RESPONSE 0x4e617475 #define SNDRV_FIREWIRE_EVENT_DIGI00X_MESSAGE 0x746e736c +#define SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION 0x64776479 struct snd_firewire_event_common { unsigned int type; /* SNDRV_FIREWIRE_EVENT_xxx */ @@ -46,12 +47,18 @@ struct snd_firewire_event_digi00x_message { __u32 message; /* Digi00x-specific message */ }; +struct snd_firewire_event_motu_notification { + unsigned int type; + __u32 message; /* MOTU-specific bits. */ +}; + union snd_firewire_event { struct snd_firewire_event_common common; struct snd_firewire_event_lock_status lock_status; struct snd_firewire_event_dice_notification dice_notification; struct snd_firewire_event_efw_response efw_response; struct snd_firewire_event_digi00x_message digi00x_message; + struct snd_firewire_event_motu_notification motu_notification; }; diff --git a/sound/firewire/motu/motu-hwdep.c b/sound/firewire/motu/motu-hwdep.c index e795a5219a21..b87ccb69d597 100644 --- a/sound/firewire/motu/motu-hwdep.c +++ b/sound/firewire/motu/motu-hwdep.c @@ -26,7 +26,7 @@ static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, spin_lock_irq(&motu->lock); - while (!motu->dev_lock_changed) { + while (!motu->dev_lock_changed && motu->msg == 0) { prepare_to_wait(&motu->hwdep_wait, &wait, TASK_INTERRUPTIBLE); spin_unlock_irq(&motu->lock); schedule(); @@ -43,6 +43,12 @@ static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, motu->dev_lock_changed = false; count = min_t(long, count, sizeof(event.lock_status)); + } else { + event.motu_notification.type = SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION; + event.motu_notification.message = motu->msg; + motu->msg = 0; + + count = min_t(long, count, sizeof(event.motu_notification)); } spin_unlock_irq(&motu->lock); @@ -62,7 +68,7 @@ static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_wait(file, &motu->hwdep_wait, wait); spin_lock_irq(&motu->lock); - if (motu->dev_lock_changed) + if (motu->dev_lock_changed || motu->msg) events = POLLIN | POLLRDNORM; else events = 0; diff --git a/sound/firewire/motu/motu-transaction.c b/sound/firewire/motu/motu-transaction.c index 416dd9833896..7fc30091e0de 100644 --- a/sound/firewire/motu/motu-transaction.c +++ b/sound/firewire/motu/motu-transaction.c @@ -50,7 +50,27 @@ static void handle_message(struct fw_card *card, struct fw_request *request, int generation, unsigned long long offset, void *data, size_t length, void *callback_data) { + struct snd_motu *motu = callback_data; + __be32 *buf = (__be32 *)data; + unsigned long flags; + + if (tcode != TCODE_WRITE_QUADLET_REQUEST) { + fw_send_response(card, request, RCODE_COMPLETE); + return; + } + + if (offset != motu->async_handler.offset || length != 4) { + fw_send_response(card, request, RCODE_ADDRESS_ERROR); + return; + } + + spin_lock_irqsave(&motu->lock, flags); + motu->msg = be32_to_cpu(*buf); + spin_unlock_irqrestore(&motu->lock, flags); + fw_send_response(card, request, RCODE_COMPLETE); + + wake_up(&motu->hwdep_wait); } int snd_motu_transaction_reregister(struct snd_motu *motu) -- cgit v1.2.3-59-g8ed1b From 949613e366ed436a7639722b0ab6ed66a0199ae9 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 22 Mar 2017 21:30:26 +0900 Subject: ALSA: firewire-motu: add support for MOTU 828mk2 as a model with protocol version 2 MOTU 828mk2 is one of second generation in MOTU FireWire series, produced in 2003. This model consists of four chips: * TI TSB41AB2 (Physical layer for IEEE 1394 bus) * PDI 1394L40BE (Link layer for IEEE 1394 bus and packet processing layer) * ALTERA ACEX 1K EP1K30 Series FPGA (Data block processing layer) * TI TMS320VC5402 (Digital signal processing) This commit adds a support for this model, with its unique protocol as version 2. The features of this protocol are: * Support data chunks for status and control messages for both directions. * Support a pair of MIDI input/output. * Support a data chunk for mic/instrument independent of analog line in. * Support a data chunk for playback return. * Support independent data chunks for S/PDIF of both optical/coaxial interfaces. * Support independent data chunks for each of main out and phone out. Status of clock is configured by write transactions to 0x'ffff'f000'0b14. Modes of optical interfaces are configured by write transactions to 0x'ffff'f000'0c04. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/Kconfig | 1 + sound/firewire/motu/Makefile | 3 +- sound/firewire/motu/motu-protocol-v2.c | 237 +++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.c | 14 ++ sound/firewire/motu/motu.h | 2 + 5 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/motu/motu-protocol-v2.c (limited to 'sound') diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 11a3285a20b3..951d51000c3f 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -146,6 +146,7 @@ config SND_FIREWIRE_MOTU select SND_HWDEP help Say Y here to enable support for FireWire devices which MOTU produced: + * 828mk2 To compile this driver as a module, choose M here: the module will be called snd-firewire-motu. diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index cc195d5a5a6e..21968bcf68dc 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,3 +1,4 @@ snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \ - motu-proc.o motu-pcm.o motu-midi.o motu-hwdep.o + motu-proc.o motu-pcm.o motu-midi.o motu-hwdep.o \ + motu-protocol-v2.o obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu-protocol-v2.c b/sound/firewire/motu/motu-protocol-v2.c new file mode 100644 index 000000000000..05b5d287c2f3 --- /dev/null +++ b/sound/firewire/motu/motu-protocol-v2.c @@ -0,0 +1,237 @@ +/* + * motu-protocol-v2.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "motu.h" + +#define V2_CLOCK_STATUS_OFFSET 0x0b14 +#define V2_CLOCK_RATE_MASK 0x00000038 +#define V2_CLOCK_RATE_SHIFT 3 +#define V2_CLOCK_SRC_MASK 0x00000007 +#define V2_CLOCK_SRC_SHIFT 0 + +#define V2_IN_OUT_CONF_OFFSET 0x0c04 +#define V2_OPT_OUT_IFACE_MASK 0x00000c00 +#define V2_OPT_OUT_IFACE_SHIFT 10 +#define V2_OPT_IN_IFACE_MASK 0x00000300 +#define V2_OPT_IN_IFACE_SHIFT 8 +#define V2_OPT_IFACE_MODE_NONE 0 +#define V2_OPT_IFACE_MODE_ADAT 1 +#define V2_OPT_IFACE_MODE_SPDIF 2 + +static int v2_get_clock_rate(struct snd_motu *motu, unsigned int *rate) +{ + __be32 reg; + unsigned int index; + int err; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + index = (be32_to_cpu(reg) & V2_CLOCK_RATE_MASK) >> V2_CLOCK_RATE_SHIFT; + if (index >= ARRAY_SIZE(snd_motu_clock_rates)) + return -EIO; + + *rate = snd_motu_clock_rates[index]; + + return 0; +} + +static int v2_set_clock_rate(struct snd_motu *motu, unsigned int rate) +{ + __be32 reg; + u32 data; + int i; + int err; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) + break; + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~V2_CLOCK_RATE_MASK; + data |= i << V2_CLOCK_RATE_SHIFT; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); +} + +static int v2_get_clock_source(struct snd_motu *motu, + enum snd_motu_clock_source *src) +{ + __be32 reg; + unsigned int index; + int err; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + index = be32_to_cpu(reg) & V2_CLOCK_SRC_MASK; + if (index > 5) + return -EIO; + + /* To check the configuration of optical interface. */ + err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + switch (index) { + case 0: + *src = SND_MOTU_CLOCK_SOURCE_INTERNAL; + break; + case 1: + if (be32_to_cpu(reg) & 0x00000200) + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT; + else + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT; + break; + case 2: + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX; + break; + case 4: + *src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC; + break; + case 5: + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB; + break; + default: + return -EIO; + } + + return 0; +} + +static int v2_switch_fetching_mode(struct snd_motu *motu, bool enable) +{ + /* V2 protocol doesn't have this feature. */ + return 0; +} + +static void calculate_fixed_part(struct snd_motu_packet_format *formats, + enum amdtp_stream_direction dir, + enum snd_motu_spec_flags flags, + unsigned char analog_ports) +{ + unsigned char pcm_chunks[3] = {0, 0, 0}; + + formats->msg_chunks = 2; + + pcm_chunks[0] = analog_ports; + pcm_chunks[1] = analog_ports; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + pcm_chunks[2] = analog_ports; + + if (dir == AMDTP_IN_STREAM) { + if (flags & SND_MOTU_SPEC_TX_MICINST_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + if (flags & SND_MOTU_SPEC_TX_RETURN_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + } else { + /* + * Packets to v2 units transfer main-out-1/2 and phone-out-1/2. + */ + pcm_chunks[0] += 4; + pcm_chunks[1] += 4; + } + + /* + * All of v2 models have a pair of coaxial interfaces for digital in/out + * port. At 44.1/48.0/88.2/96.0 kHz, packets includes PCM from these + * ports. + */ + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + + /* This part should be multiples of 4. */ + formats->fixed_part_pcm_chunks[0] = round_up(2 + pcm_chunks[0], 4) - 2; + formats->fixed_part_pcm_chunks[1] = round_up(2 + pcm_chunks[1], 4) - 2; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + formats->fixed_part_pcm_chunks[2] = + round_up(2 + pcm_chunks[2], 4) - 2; +} + +static void calculate_differed_part(struct snd_motu_packet_format *formats, + enum snd_motu_spec_flags flags, + u32 data, u32 mask, u32 shift) +{ + unsigned char pcm_chunks[3] = {0, 0}; + + /* + * When optical interfaces are configured for S/PDIF (TOSLINK), + * the above PCM frames come from them, instead of coaxial + * interfaces. + */ + data = (data & mask) >> shift; + if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_A) && + data == V2_OPT_IFACE_MODE_ADAT) { + pcm_chunks[0] += 8; + pcm_chunks[1] += 4; + } + + /* At mode x4, no data chunks are supported in this part. */ + formats->differed_part_pcm_chunks[0] = pcm_chunks[0]; + formats->differed_part_pcm_chunks[1] = pcm_chunks[1]; +} + +static int v2_cache_packet_formats(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + calculate_fixed_part(&motu->tx_packet_formats, AMDTP_IN_STREAM, + motu->spec->flags, motu->spec->analog_in_ports); + calculate_differed_part(&motu->tx_packet_formats, motu->spec->flags, + data, V2_OPT_IN_IFACE_MASK, V2_OPT_IN_IFACE_SHIFT); + + calculate_fixed_part(&motu->rx_packet_formats, AMDTP_OUT_STREAM, + motu->spec->flags, motu->spec->analog_out_ports); + calculate_differed_part(&motu->rx_packet_formats, motu->spec->flags, + data, V2_OPT_OUT_IFACE_MASK, V2_OPT_OUT_IFACE_SHIFT); + + motu->tx_packet_formats.midi_flag_offset = 4; + motu->tx_packet_formats.midi_byte_offset = 6; + motu->tx_packet_formats.pcm_byte_offset = 10; + + motu->rx_packet_formats.midi_flag_offset = 4; + motu->rx_packet_formats.midi_byte_offset = 6; + motu->rx_packet_formats.pcm_byte_offset = 10; + + return 0; +} + +const struct snd_motu_protocol snd_motu_protocol_v2 = { + .get_clock_rate = v2_get_clock_rate, + .set_clock_rate = v2_set_clock_rate, + .get_clock_source = v2_get_clock_source, + .switch_fetching_mode = v2_switch_fetching_mode, + .cache_packet_formats = v2_cache_packet_formats, +}; diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index 619554b9dbef..0acd134125df 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -190,6 +190,19 @@ static void motu_bus_update(struct fw_unit *unit) snd_motu_transaction_reregister(motu); } +static struct snd_motu_spec motu_828mk2 = { + .name = "828mk2", + .protocol = &snd_motu_protocol_v2, + .flags = SND_MOTU_SPEC_SUPPORT_CLOCK_X2 | + SND_MOTU_SPEC_TX_MICINST_CHUNK | + SND_MOTU_SPEC_TX_RETURN_CHUNK | + SND_MOTU_SPEC_HAS_OPT_IFACE_A | + SND_MOTU_SPEC_HAS_MIDI, + + .analog_in_ports = 8, + .analog_out_ports = 8, +}; + #define SND_MOTU_DEV_ENTRY(model, data) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ @@ -202,6 +215,7 @@ static void motu_bus_update(struct fw_unit *unit) } static const struct ieee1394_device_id motu_id_table[] = { + SND_MOTU_DEV_ENTRY(0x101800, &motu_828mk2), { } }; MODULE_DEVICE_TABLE(ieee1394, motu_id_table); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index 7b1d85f29b49..29f20a5eff51 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -122,6 +122,8 @@ struct snd_motu_spec { const struct snd_motu_protocol *const protocol; }; +extern const struct snd_motu_protocol snd_motu_protocol_v2; + int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, enum amdtp_stream_direction dir, const struct snd_motu_protocol *const protocol); -- cgit v1.2.3-59-g8ed1b From 2128f78f75a36a34dfef0e127273c2f820c5c904 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 22 Mar 2017 21:30:27 +0900 Subject: ALSA: firewire-lib: add a quirk of packet without valid EOH in CIP format In IEC 61883-1, when two quadlets CIP header is used, the most significant bit in second CIP header stands. However, packets from units with MOTU protocol version 3 have a quirk without this flag. Current packet streaming layer handles this as protocol error. This commit adds a new enumeration constant for this quirk, to handle MOTU protocol version 3. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/amdtp-stream.c | 5 +++-- sound/firewire/amdtp-stream.h | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c index f9d12f454483..112ad039ed25 100644 --- a/sound/firewire/amdtp-stream.c +++ b/sound/firewire/amdtp-stream.c @@ -480,8 +480,9 @@ static int handle_in_packet(struct amdtp_stream *s, * This module supports 'Two-quadlet CIP header with SYT field'. * For convenience, also check FMT field is AM824 or not. */ - if (((cip_header[0] & CIP_EOH_MASK) == CIP_EOH) || - ((cip_header[1] & CIP_EOH_MASK) != CIP_EOH)) { + if ((((cip_header[0] & CIP_EOH_MASK) == CIP_EOH) || + ((cip_header[1] & CIP_EOH_MASK) != CIP_EOH)) && + (!(s->flags & CIP_HEADER_WITHOUT_EOH))) { dev_info_ratelimited(&s->unit->device, "Invalid CIP header for AMDTP: %08X:%08X\n", cip_header[0], cip_header[1]); diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h index d2a316309704..a31dfd849821 100644 --- a/sound/firewire/amdtp-stream.h +++ b/sound/firewire/amdtp-stream.h @@ -29,6 +29,8 @@ * @CIP_JUMBO_PAYLOAD: Only for in-stream. The number of data blocks in an * packet is larger than IEC 61883-6 defines. Current implementation * allows 5 times as large as IEC 61883-6 defines. + * @CIP_HEADER_WITHOUT_EOH: Only for in-stream. CIP Header doesn't include + * valid EOH. */ enum cip_flags { CIP_NONBLOCKING = 0x00, @@ -39,6 +41,7 @@ enum cip_flags { CIP_SKIP_DBC_ZERO_CHECK = 0x10, CIP_EMPTY_HAS_WRONG_DBC = 0x20, CIP_JUMBO_PAYLOAD = 0x40, + CIP_HEADER_WITHOUT_EOH = 0x80, }; /** -- cgit v1.2.3-59-g8ed1b From 5992e30034c43917968f2327d2948d22be6d4603 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 22 Mar 2017 21:30:28 +0900 Subject: ALSA: firewire-motu: add support for MOTU 828mk3 (FireWire/Hybrid) as a model with protocol version 3 MOTU 828mk3 (FireWire/Hybrid) is one of third generation in MOTU FireWire series, produced in 2008/2014. This model consists of three chips for functionality on IEEE 1394 bus: * TI TSB41AB2 (Physical layer for IEEE 1394 bus) * Xilinx Spartan-3E FPGA Family (Link layer for IEEE 1394 bus, packet processing and data block processing layer) * TI TMS320C6722 (Digital signal processing) This commit adds a support for this model, with its unique protocol as version 3. This protocol has some additional features to protocol version 2. * Support several optical interfaces. * Support a data chunk for return of reverb effect. * Have a quirk of tx packets. * Support heartbeat asynchronous transaction. In this protocol, series of transferred packets has some quirks. Below fields in CIP headers of the packets are out of IEC 61883-1: - SID (source node id): always 0x0d - DBS (data block size): always 0x04 - DBC (data block counter): always 0x00 - EOH (End of header): always 0x00 Below is an actual sample of transferred packets. quads CIP1 CIP2 520 0x0D040400 0x22FFFFFF 8 0x0D040400 0x22FFFFFF 520 0x0D040400 0x22FFFFFF 520 0x0D040400 0x22FFFFFF 8 0x0D040400 0x22FFFFFF Status of clock is configured by write transactions to 0x'ffff'f000'0b14, as well as version 2, while meanings of fields are different from the former protocols. Modes of optical interfaces are configured by write transactions to 0x'ffff'f000'0c94. Drivers can register its address to receive heatbeat transactions from the unit. 0x'ffff'f000'0b0c is for the higher part and 0x'ffff'f000'0b10 is for the lower part. Nevertheless, this feature is not useless for this driver and this commit omits it. Each data block consists of two parts in a point of the number of included data chunks. In both of 'fixed' and 'differed' parts, the number of included data blocks are a multiple of 4, thus depending on models there's some empty data chunks. For example, 828mk3 includes one pair of empty data chunks in its fixed part. When optical interface is configured to S/PDIF, 828mk3 includes one pair of empty data chunks in its differed part. To reduce consumption of CPU cycles with additional conditions/loops, this commit just exposes these empty chunks to user space as PCM channels. Additionally, 828mk3 has a non-negligible overhead to change its sampling transfer frequency. When softwares send asynchronous transaction to perform it, LED on the unit starts to blink. In a worst case, it continues blink during several seconds; e.g. 10 seconds. When stopping blinking, the unit seems to be prepared for the requested sampling transfer frequency. To wait for the preparation, this commit forces the driver to call task scheduler and applications sleeps for 4 seconds. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/Kconfig | 1 + sound/firewire/motu/Makefile | 2 +- sound/firewire/motu/amdtp-motu.c | 12 ++ sound/firewire/motu/motu-protocol-v3.c | 312 +++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.c | 18 ++ sound/firewire/motu/motu.h | 1 + 6 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/motu/motu-protocol-v3.c (limited to 'sound') diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 951d51000c3f..6acfacf75daf 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -147,6 +147,7 @@ config SND_FIREWIRE_MOTU help Say Y here to enable support for FireWire devices which MOTU produced: * 828mk2 + * 828mk3 To compile this driver as a module, choose M here: the module will be called snd-firewire-motu. diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index 21968bcf68dc..ae84ae61d274 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,4 +1,4 @@ snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \ motu-proc.o motu-pcm.o motu-midi.o motu-hwdep.o \ - motu-protocol-v2.o + motu-protocol-v2.o motu-protocol-v3.o obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/amdtp-motu.c b/sound/firewire/motu/amdtp-motu.c index 0930cd8ca2cb..08bd1760b1b4 100644 --- a/sound/firewire/motu/amdtp-motu.c +++ b/sound/firewire/motu/amdtp-motu.c @@ -11,6 +11,7 @@ #include "motu.h" #define CIP_FMT_MOTU 0x02 +#define CIP_FMT_MOTU_TX_V3 0x22 #define MOTU_FDF_AM824 0x22 /* @@ -359,6 +360,17 @@ int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, if (dir == AMDTP_IN_STREAM) { process_data_blocks = process_tx_data_blocks; + + /* + * Units of version 3 transmits packets with invalid CIP header + * against IEC 61883-1. + */ + if (protocol == &snd_motu_protocol_v3) { + flags |= CIP_WRONG_DBS | + CIP_SKIP_DBC_ZERO_CHECK | + CIP_HEADER_WITHOUT_EOH; + fmt = CIP_FMT_MOTU_TX_V3; + } } else { process_data_blocks = process_rx_data_blocks; flags |= CIP_DBC_IS_END_EVENT; diff --git a/sound/firewire/motu/motu-protocol-v3.c b/sound/firewire/motu/motu-protocol-v3.c new file mode 100644 index 000000000000..b463da99feb1 --- /dev/null +++ b/sound/firewire/motu/motu-protocol-v3.c @@ -0,0 +1,312 @@ +/* + * motu-protocol-v3.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include "motu.h" + +#define V3_CLOCK_STATUS_OFFSET 0x0b14 +#define V3_FETCH_PCM_FRAMES 0x02000000 +#define V3_CLOCK_RATE_MASK 0x0000ff00 +#define V3_CLOCK_RATE_SHIFT 8 +#define V3_CLOCK_SOURCE_MASK 0x000000ff +#define V3_CLOCK_SOURCE_SHIFT 8 + +#define V3_OPT_IFACE_MODE_OFFSET 0x0c94 +#define V3_ENABLE_OPT_IN_IFACE_A 0x00000001 +#define V3_ENABLE_OPT_IN_IFACE_B 0x00000002 +#define V3_ENABLE_OPT_OUT_IFACE_A 0x00000100 +#define V3_ENABLE_OPT_OUT_IFACE_B 0x00000200 +#define V3_NO_ADAT_OPT_IN_IFACE_A 0x00010000 +#define V3_NO_ADAT_OPT_IN_IFACE_B 0x00100000 +#define V3_NO_ADAT_OPT_OUT_IFACE_A 0x00040000 +#define V3_NO_ADAT_OPT_OUT_IFACE_B 0x00400000 + +static int v3_get_clock_rate(struct snd_motu *motu, unsigned int *rate) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data = (data & V3_CLOCK_RATE_MASK) >> V3_CLOCK_RATE_SHIFT; + if (data >= ARRAY_SIZE(snd_motu_clock_rates)) + return -EIO; + + *rate = snd_motu_clock_rates[data]; + + return 0; +} + +static int v3_set_clock_rate(struct snd_motu *motu, unsigned int rate) +{ + __be32 reg; + u32 data; + bool need_to_wait; + int i, err; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) + break; + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~(V3_CLOCK_RATE_MASK | V3_FETCH_PCM_FRAMES); + data |= i << V3_CLOCK_RATE_SHIFT; + + need_to_wait = data != be32_to_cpu(reg); + + reg = cpu_to_be32(data); + err = snd_motu_transaction_write(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + if (need_to_wait) { + /* Cost expensive. */ + if (msleep_interruptible(4000) > 0) + return -EINTR; + } + + return 0; +} + +static int v3_get_clock_source(struct snd_motu *motu, + enum snd_motu_clock_source *src) +{ + __be32 reg; + u32 data; + unsigned int val; + int err; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + val = (data & V3_CLOCK_SOURCE_MASK) >> V3_CLOCK_SOURCE_SHIFT; + if (val == 0x00) { + *src = SND_MOTU_CLOCK_SOURCE_INTERNAL; + } else if (val == 0x01) { + *src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC; + } else if (val == 0x10) { + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX; + } else if (val == 0x18 || val == 0x19) { + err = snd_motu_transaction_read(motu, V3_OPT_IFACE_MODE_OFFSET, + ®, sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + if (val == 0x18) { + if (data & V3_NO_ADAT_OPT_IN_IFACE_A) + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A; + else + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A; + } else { + if (data & V3_NO_ADAT_OPT_IN_IFACE_B) + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B; + else + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B; + } + } else { + *src = SND_MOTU_CLOCK_SOURCE_UNKNOWN; + } + + return 0; +} + +static int v3_switch_fetching_mode(struct snd_motu *motu, bool enable) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return 0; + data = be32_to_cpu(reg); + + if (enable) + data |= V3_FETCH_PCM_FRAMES; + else + data &= ~V3_FETCH_PCM_FRAMES; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); +} + +static void calculate_fixed_part(struct snd_motu_packet_format *formats, + enum amdtp_stream_direction dir, + enum snd_motu_spec_flags flags, + unsigned char analog_ports) +{ + unsigned char pcm_chunks[3] = {0, 0, 0}; + + formats->msg_chunks = 2; + + pcm_chunks[0] = analog_ports; + pcm_chunks[1] = analog_ports; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + pcm_chunks[2] = analog_ports; + + if (dir == AMDTP_IN_STREAM) { + if (flags & SND_MOTU_SPEC_TX_MICINST_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + pcm_chunks[2] += 2; + } + + if (flags & SND_MOTU_SPEC_TX_RETURN_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + pcm_chunks[2] += 2; + } + + if (flags & SND_MOTU_SPEC_TX_REVERB_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + } else { + /* + * Packets to v2 units transfer main-out-1/2 and phone-out-1/2. + */ + pcm_chunks[0] += 4; + pcm_chunks[1] += 4; + } + + /* + * At least, packets have two data chunks for S/PDIF on coaxial + * interface. + */ + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + + /* + * Fixed part consists of PCM chunks multiple of 4, with msg chunks. As + * a result, this part can includes empty data chunks. + */ + formats->fixed_part_pcm_chunks[0] = round_up(2 + pcm_chunks[0], 4) - 2; + formats->fixed_part_pcm_chunks[1] = round_up(2 + pcm_chunks[1], 4) - 2; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + formats->fixed_part_pcm_chunks[2] = + round_up(2 + pcm_chunks[2], 4) - 2; +} + +static void calculate_differed_part(struct snd_motu_packet_format *formats, + enum snd_motu_spec_flags flags, u32 data, + u32 a_enable_mask, u32 a_no_adat_mask, + u32 b_enable_mask, u32 b_no_adat_mask) +{ + unsigned char pcm_chunks[3] = {0, 0, 0}; + int i; + + if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_A) && (data & a_enable_mask)) { + if (data & a_no_adat_mask) { + /* + * Additional two data chunks for S/PDIF on optical + * interface A. This includes empty data chunks. + */ + pcm_chunks[0] += 4; + pcm_chunks[1] += 4; + } else { + /* + * Additional data chunks for ADAT on optical interface + * A. + */ + pcm_chunks[0] += 8; + pcm_chunks[1] += 4; + } + } + + if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_B) && (data & b_enable_mask)) { + if (data & b_no_adat_mask) { + /* + * Additional two data chunks for S/PDIF on optical + * interface B. This includes empty data chunks. + */ + pcm_chunks[0] += 4; + pcm_chunks[1] += 4; + } else { + /* + * Additional data chunks for ADAT on optical interface + * B. + */ + pcm_chunks[0] += 8; + pcm_chunks[1] += 4; + } + } + + for (i = 0; i < 3; ++i) { + if (pcm_chunks[i] > 0) + pcm_chunks[i] = round_up(pcm_chunks[i], 4); + + formats->differed_part_pcm_chunks[i] = pcm_chunks[i]; + } +} + +static int v3_cache_packet_formats(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V3_OPT_IFACE_MODE_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + calculate_fixed_part(&motu->tx_packet_formats, AMDTP_IN_STREAM, + motu->spec->flags, motu->spec->analog_in_ports); + calculate_differed_part(&motu->tx_packet_formats, + motu->spec->flags, data, + V3_ENABLE_OPT_IN_IFACE_A, V3_NO_ADAT_OPT_IN_IFACE_A, + V3_ENABLE_OPT_IN_IFACE_B, V3_NO_ADAT_OPT_IN_IFACE_B); + + calculate_fixed_part(&motu->rx_packet_formats, AMDTP_OUT_STREAM, + motu->spec->flags, motu->spec->analog_out_ports); + calculate_differed_part(&motu->rx_packet_formats, + motu->spec->flags, data, + V3_ENABLE_OPT_OUT_IFACE_A, V3_NO_ADAT_OPT_OUT_IFACE_A, + V3_ENABLE_OPT_OUT_IFACE_B, V3_NO_ADAT_OPT_OUT_IFACE_B); + + motu->tx_packet_formats.midi_flag_offset = 8; + motu->tx_packet_formats.midi_byte_offset = 7; + motu->tx_packet_formats.pcm_byte_offset = 10; + + motu->rx_packet_formats.midi_flag_offset = 8; + motu->rx_packet_formats.midi_byte_offset = 7; + motu->rx_packet_formats.pcm_byte_offset = 10; + + return 0; +} + +const struct snd_motu_protocol snd_motu_protocol_v3 = { + .get_clock_rate = v3_get_clock_rate, + .set_clock_rate = v3_set_clock_rate, + .get_clock_source = v3_get_clock_source, + .switch_fetching_mode = v3_switch_fetching_mode, + .cache_packet_formats = v3_cache_packet_formats, +}; diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index 0acd134125df..bf779cfeef0d 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -203,6 +203,22 @@ static struct snd_motu_spec motu_828mk2 = { .analog_out_ports = 8, }; +static struct snd_motu_spec motu_828mk3 = { + .name = "828mk3", + .protocol = &snd_motu_protocol_v3, + .flags = SND_MOTU_SPEC_SUPPORT_CLOCK_X2 | + SND_MOTU_SPEC_SUPPORT_CLOCK_X4 | + SND_MOTU_SPEC_TX_MICINST_CHUNK | + SND_MOTU_SPEC_TX_RETURN_CHUNK | + SND_MOTU_SPEC_TX_REVERB_CHUNK | + SND_MOTU_SPEC_HAS_OPT_IFACE_A | + SND_MOTU_SPEC_HAS_OPT_IFACE_B | + SND_MOTU_SPEC_HAS_MIDI, + + .analog_in_ports = 8, + .analog_out_ports = 8, +}; + #define SND_MOTU_DEV_ENTRY(model, data) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ @@ -216,6 +232,8 @@ static struct snd_motu_spec motu_828mk2 = { static const struct ieee1394_device_id motu_id_table[] = { SND_MOTU_DEV_ENTRY(0x101800, &motu_828mk2), + SND_MOTU_DEV_ENTRY(0x106800, &motu_828mk3), /* FireWire only. */ + SND_MOTU_DEV_ENTRY(0x100800, &motu_828mk3), /* Hybrid. */ { } }; MODULE_DEVICE_TABLE(ieee1394, motu_id_table); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index 29f20a5eff51..8d6a4a3af9cc 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -123,6 +123,7 @@ struct snd_motu_spec { }; extern const struct snd_motu_protocol snd_motu_protocol_v2; +extern const struct snd_motu_protocol snd_motu_protocol_v3; int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, enum amdtp_stream_direction dir, -- cgit v1.2.3-59-g8ed1b From ac310dc9fafcffaddbde7ca2137edb59cd4f414f Mon Sep 17 00:00:00 2001 From: Clemens Ladisch Date: Wed, 29 Mar 2017 20:55:50 +0200 Subject: ALSA: oxygen: simply setting of the shortname for Xonar DG cards We don't need to manually set the card name; with an entry in the names[] array, this happens automatically. Signed-off-by: Clemens Ladisch Signed-off-by: Takashi Iwai --- sound/pci/oxygen/oxygen.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/pci/oxygen/oxygen.c b/sound/pci/oxygen/oxygen.c index 74afb6b75976..e36ed8af55ad 100644 --- a/sound/pci/oxygen/oxygen.c +++ b/sound/pci/oxygen/oxygen.c @@ -767,6 +767,8 @@ static int get_oxygen_model(struct oxygen *chip, [MODEL_FANTASIA] = "TempoTec HiFier Fantasia", [MODEL_SERENADE] = "TempoTec HiFier Serenade", [MODEL_HG2PCI] = "CMI8787-HG2PCI", + [MODEL_XONAR_DG] = "Xonar DG", + [MODEL_XONAR_DGX] = "Xonar DGX", }; chip->model = model_generic; @@ -829,12 +831,8 @@ static int get_oxygen_model(struct oxygen *chip, chip->model.dac_channels_mixer = 2; break; case MODEL_XONAR_DG: - chip->model = model_xonar_dg; - chip->model.shortname = "Xonar DG"; - break; case MODEL_XONAR_DGX: chip->model = model_xonar_dg; - chip->model.shortname = "Xonar DGX"; break; } if (id->driver_data == MODEL_MERIDIAN || -- cgit v1.2.3-59-g8ed1b From 03a1f48e5371a252b3b8c16a7cbea3c0bd8689bb Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 31 Mar 2017 11:19:19 +0200 Subject: ALSA: usb-audio: Fake also USB device id when alias is given Recently snd-usb-audio driver received a new option, quirk_alias, to allow user to apply the existing quirk for a different device. This works for many quirks as is, but some still need more tune-ups: namely, some quirks check the USB vendor/device IDs in various places, thus it doesn't work as long as the ID is different from the expected one. With this patch, the driver stores the aliased USB ID, so that these rest quirks per device ID are applied. The transition to use the cached USB ID was already done in the past, so what we needed now is only to overwrite chip->usb_id. Signed-off-by: Takashi Iwai --- sound/usb/card.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/usb/card.c b/sound/usb/card.c index f36cb068dad3..6640277a725b 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -332,6 +332,7 @@ static int snd_usb_audio_dev_free(struct snd_device *device) static int snd_usb_audio_create(struct usb_interface *intf, struct usb_device *dev, int idx, const struct snd_usb_audio_quirk *quirk, + unsigned int usb_id, struct snd_usb_audio **rchip) { struct snd_card *card; @@ -381,8 +382,7 @@ static int snd_usb_audio_create(struct usb_interface *intf, atomic_set(&chip->usage_count, 0); atomic_set(&chip->shutdown, 0); - chip->usb_id = USB_ID(le16_to_cpu(dev->descriptor.idVendor), - le16_to_cpu(dev->descriptor.idProduct)); + chip->usb_id = usb_id; INIT_LIST_HEAD(&chip->pcm_list); INIT_LIST_HEAD(&chip->ep_list); INIT_LIST_HEAD(&chip->midi_list); @@ -569,7 +569,7 @@ static int usb_audio_probe(struct usb_interface *intf, (vid[i] == -1 || vid[i] == USB_ID_VENDOR(id)) && (pid[i] == -1 || pid[i] == USB_ID_PRODUCT(id))) { err = snd_usb_audio_create(intf, dev, i, quirk, - &chip); + id, &chip); if (err < 0) goto __error; chip->pm_intf = intf; -- cgit v1.2.3-59-g8ed1b From 5885615e44faebaf379fa0cdd2b9c084960dae38 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 31 Mar 2017 16:53:40 +0300 Subject: ALSA: emux: stop if copy_from_user() fails If we can't fill the "patch" struct because "count" is too small (it can be as low as 4 bytes) or because copy_from_user() failed, then just return instead of using unintialized data. Signed-off-by: Dan Carpenter Signed-off-by: Takashi Iwai --- sound/synth/emux/emux_oss.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/synth/emux/emux_oss.c b/sound/synth/emux/emux_oss.c index ac75816ada7c..850fab4a8f3b 100644 --- a/sound/synth/emux/emux_oss.c +++ b/sound/synth/emux/emux_oss.c @@ -225,9 +225,9 @@ snd_emux_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format, else if (format == SNDRV_OSS_SOUNDFONT_PATCH) { struct soundfont_patch_info patch; if (count < (int)sizeof(patch)) - rc = -EINVAL; + return -EINVAL; if (copy_from_user(&patch, buf, sizeof(patch))) - rc = -EFAULT; + return -EFAULT; if (patch.type >= SNDRV_SFNT_LOAD_INFO && patch.type <= SNDRV_SFNT_PROBE_DATA) rc = snd_soundfont_load(emu->sflist, buf, count, SF_CLIENT_NO(p->chset.port)); -- cgit v1.2.3-59-g8ed1b From e8ed68205f39a7a934901eab80a5cbf3a062b7b4 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 31 Mar 2017 18:21:41 +0300 Subject: ALSA: timer: remove some dead code We just checked "id.card < 0" on the lines before so we know it's not true here. We can delete that check. Also checkpatch.pl complains about some extra curly braces so we may as well fix that while we're at it. Signed-off-by: Dan Carpenter Signed-off-by: Takashi Iwai --- sound/core/timer.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'sound') diff --git a/sound/core/timer.c b/sound/core/timer.c index 6d4fbc439246..8b9e7943a83b 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -1430,18 +1430,13 @@ static int snd_timer_user_next_device(struct snd_timer_id __user *_tid) if (id.card < 0) { id.card = 0; } else { - if (id.card < 0) { - id.card = 0; + if (id.device < 0) { + id.device = 0; } else { - if (id.device < 0) { - id.device = 0; - } else { - if (id.subdevice < 0) { - id.subdevice = 0; - } else { - id.subdevice++; - } - } + if (id.subdevice < 0) + id.subdevice = 0; + else + id.subdevice++; } } list_for_each(p, &snd_timer_list) { -- cgit v1.2.3-59-g8ed1b From a8c006aafead3c45ae5d5601e3717055bccf41bc Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 31 Mar 2017 18:22:23 +0300 Subject: ALSA: timer: Info leak in snd_timer_user_tinterrupt() The "r1" struct has memory holes. We clear it with memset on one path where it is used but not the other. Let's just memset it at the start of the function so it's always safe. Signed-off-by: Dan Carpenter Signed-off-by: Takashi Iwai --- sound/core/timer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/core/timer.c b/sound/core/timer.c index 8b9e7943a83b..2f836ca09860 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -1277,6 +1277,7 @@ static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri, struct timespec tstamp; int prev, append = 0; + memset(&r1, 0, sizeof(r1)); memset(&tstamp, 0, sizeof(tstamp)); spin_lock(&tu->qlock); if ((tu->filter & ((1 << SNDRV_TIMER_EVENT_RESOLUTION) | @@ -1292,7 +1293,6 @@ static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri, } if ((tu->filter & (1 << SNDRV_TIMER_EVENT_RESOLUTION)) && tu->last_resolution != resolution) { - memset(&r1, 0, sizeof(r1)); r1.event = SNDRV_TIMER_EVENT_RESOLUTION; r1.tstamp = tstamp; r1.val = resolution; -- cgit v1.2.3-59-g8ed1b From d1600401faad4bc186bfdb291d8af644465e20bd Mon Sep 17 00:00:00 2001 From: Matthias Kaehlcke Date: Fri, 31 Mar 2017 18:00:04 -0700 Subject: ALSA: hda/ca0132: Limit values for chip addresses to 32-bit With the previous unsigned long value clang generates warnings like this: sound/pci/hda/patch_ca0132.c:860:37: error: implicit conversion from 'unsigned long' to 'u32' (aka 'unsigned int') changes value from 18446744073709551615 to 4294967295 [-Werror,-Wconstant-conversion] spec->curr_chip_addx = (res < 0) ? ~0UL : chip_addx; ~ ^~~~ Signed-off-by: Matthias Kaehlcke Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_ca0132.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index fb2e242c2522..a148176c16a9 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -857,7 +857,7 @@ static int chipio_write_address(struct hda_codec *codec, chip_addx >> 16); } - spec->curr_chip_addx = (res < 0) ? ~0UL : chip_addx; + spec->curr_chip_addx = (res < 0) ? ~0U : chip_addx; return res; } @@ -882,7 +882,7 @@ static int chipio_write_data(struct hda_codec *codec, unsigned int data) /*If no error encountered, automatically increment the address as per chip behaviour*/ spec->curr_chip_addx = (res != -EIO) ? - (spec->curr_chip_addx + 4) : ~0UL; + (spec->curr_chip_addx + 4) : ~0U; return res; } @@ -933,7 +933,7 @@ static int chipio_read_data(struct hda_codec *codec, unsigned int *data) /*If no error encountered, automatically increment the address as per chip behaviour*/ spec->curr_chip_addx = (res != -EIO) ? - (spec->curr_chip_addx + 4) : ~0UL; + (spec->curr_chip_addx + 4) : ~0U; return res; } -- cgit v1.2.3-59-g8ed1b From 2c1f81381eadc6dd3c288ec4477b2fe572cf86dc Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 29 Mar 2017 08:27:15 +0200 Subject: ALSA: hda - Avoid tricky macros The macros _snd_hdac_chip_read() and *_write() expand to different types (b,w,l) per their argument. They were thought to be used only internally for other snd_hdac_chip_*() macros, but in some situations we need to call these directly, and they are way too ugly. Instead of saving a few lines, we just write these macros explicitly with the types, so that they can be used in a saner way. Acked-by: Vinod Koul Signed-off-by: Takashi Iwai --- include/sound/hdaudio.h | 28 ++++++++++++++++++---------- sound/hda/hdac_controller.c | 2 +- sound/hda/hdac_stream.c | 4 ++-- 3 files changed, 21 insertions(+), 13 deletions(-) (limited to 'sound') diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 56004ec8d441..96546b30e900 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -368,24 +368,32 @@ void snd_hdac_bus_free_stream_pages(struct hdac_bus *bus); /* * macros for easy use */ -#define _snd_hdac_chip_write(type, chip, reg, value) \ - ((chip)->io_ops->reg_write ## type(value, (chip)->remap_addr + (reg))) -#define _snd_hdac_chip_read(type, chip, reg) \ - ((chip)->io_ops->reg_read ## type((chip)->remap_addr + (reg))) +#define _snd_hdac_chip_writeb(chip, reg, value) \ + ((chip)->io_ops->reg_writeb(value, (chip)->remap_addr + (reg))) +#define _snd_hdac_chip_readb(chip, reg) \ + ((chip)->io_ops->reg_readb((chip)->remap_addr + (reg))) +#define _snd_hdac_chip_writew(chip, reg, value) \ + ((chip)->io_ops->reg_writew(value, (chip)->remap_addr + (reg))) +#define _snd_hdac_chip_readw(chip, reg) \ + ((chip)->io_ops->reg_readw((chip)->remap_addr + (reg))) +#define _snd_hdac_chip_writel(chip, reg, value) \ + ((chip)->io_ops->reg_writel(value, (chip)->remap_addr + (reg))) +#define _snd_hdac_chip_readl(chip, reg) \ + ((chip)->io_ops->reg_readl((chip)->remap_addr + (reg))) /* read/write a register, pass without AZX_REG_ prefix */ #define snd_hdac_chip_writel(chip, reg, value) \ - _snd_hdac_chip_write(l, chip, AZX_REG_ ## reg, value) + _snd_hdac_chip_writel(chip, AZX_REG_ ## reg, value) #define snd_hdac_chip_writew(chip, reg, value) \ - _snd_hdac_chip_write(w, chip, AZX_REG_ ## reg, value) + _snd_hdac_chip_writew(chip, AZX_REG_ ## reg, value) #define snd_hdac_chip_writeb(chip, reg, value) \ - _snd_hdac_chip_write(b, chip, AZX_REG_ ## reg, value) + _snd_hdac_chip_writeb(chip, AZX_REG_ ## reg, value) #define snd_hdac_chip_readl(chip, reg) \ - _snd_hdac_chip_read(l, chip, AZX_REG_ ## reg) + _snd_hdac_chip_readl(chip, AZX_REG_ ## reg) #define snd_hdac_chip_readw(chip, reg) \ - _snd_hdac_chip_read(w, chip, AZX_REG_ ## reg) + _snd_hdac_chip_readw(chip, AZX_REG_ ## reg) #define snd_hdac_chip_readb(chip, reg) \ - _snd_hdac_chip_read(b, chip, AZX_REG_ ## reg) + _snd_hdac_chip_readb(chip, AZX_REG_ ## reg) /* update a register, pass without AZX_REG_ prefix */ #define snd_hdac_chip_updatel(chip, reg, mask, val) \ diff --git a/sound/hda/hdac_controller.c b/sound/hda/hdac_controller.c index 043065867656..d15b653de0bf 100644 --- a/sound/hda/hdac_controller.c +++ b/sound/hda/hdac_controller.c @@ -272,7 +272,7 @@ int snd_hdac_bus_parse_capabilities(struct hdac_bus *bus) /* Lets walk the linked capabilities list */ do { - cur_cap = _snd_hdac_chip_read(l, bus, offset); + cur_cap = _snd_hdac_chip_readl(bus, offset); dev_dbg(bus->dev, "Capability version: 0x%x\n", (cur_cap & AZX_CAP_HDR_VER_MASK) >> AZX_CAP_HDR_VER_OFF); diff --git a/sound/hda/hdac_stream.c b/sound/hda/hdac_stream.c index c6994ebb4567..e1472c7ab6c1 100644 --- a/sound/hda/hdac_stream.c +++ b/sound/hda/hdac_stream.c @@ -555,12 +555,12 @@ void snd_hdac_stream_sync_trigger(struct hdac_stream *azx_dev, bool set, if (!reg) reg = AZX_REG_SSYNC; - val = _snd_hdac_chip_read(l, bus, reg); + val = _snd_hdac_chip_readl(bus, reg); if (set) val |= streams; else val &= ~streams; - _snd_hdac_chip_write(l, bus, reg, val); + _snd_hdac_chip_writel(bus, reg, val); } EXPORT_SYMBOL_GPL(snd_hdac_stream_sync_trigger); -- cgit v1.2.3-59-g8ed1b From 70eafad849f8af3a83e139eda36712d3c1da5b6a Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 29 Mar 2017 08:39:19 +0200 Subject: ALSA: hda - Move SKL+ vendor specific register definitions to hda_register.h They may be used by both legacy and ASoC drivers. Acked-by: Vinod Koul Signed-off-by: Takashi Iwai --- include/sound/hda_register.h | 22 ++++++++++++++++++++-- sound/pci/hda/hda_intel.c | 4 ++-- sound/soc/intel/skylake/skl.h | 21 --------------------- 3 files changed, 22 insertions(+), 25 deletions(-) (limited to 'sound') diff --git a/include/sound/hda_register.h b/include/sound/hda_register.h index 0013063db7f2..1251ff41c9d3 100644 --- a/include/sound/hda_register.h +++ b/include/sound/hda_register.h @@ -106,8 +106,26 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 }; #define AZX_REG_HSW_EM4 0x100c #define AZX_REG_HSW_EM5 0x1010 -/* Skylake/Broxton display HD-A controller Extended Mode registers */ -#define AZX_REG_SKL_EM4L 0x1040 +/* Skylake/Broxton vendor-specific registers */ +#define AZX_REG_VS_EM1 0x1000 +#define AZX_REG_VS_INRC 0x1004 +#define AZX_REG_VS_OUTRC 0x1008 +#define AZX_REG_VS_FIFOTRK 0x100C +#define AZX_REG_VS_FIFOTRK2 0x1010 +#define AZX_REG_VS_EM2 0x1030 +#define AZX_REG_VS_EM3L 0x1038 +#define AZX_REG_VS_EM3U 0x103C +#define AZX_REG_VS_EM4L 0x1040 +#define AZX_REG_VS_EM4U 0x1044 +#define AZX_REG_VS_LTRC 0x1048 +#define AZX_REG_VS_D0I3C 0x104A +#define AZX_REG_VS_PCE 0x104B +#define AZX_REG_VS_L2MAGC 0x1050 +#define AZX_REG_VS_L2LAHPT 0x1054 +#define AZX_REG_VS_SDXDPIB_XBASE 0x1084 +#define AZX_REG_VS_SDXDPIB_XINTERVAL 0x20 +#define AZX_REG_VS_SDXEFIFOS_XBASE 0x1094 +#define AZX_REG_VS_SDXEFIFOS_XINTERVAL 0x20 /* PCI space */ #define AZX_PCIREG_TCSEL 0x44 diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index c8256a89375a..a48330f4a1a9 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -534,9 +534,9 @@ static void bxt_reduce_dma_latency(struct azx *chip) { u32 val; - val = azx_readl(chip, SKL_EM4L); + val = azx_readl(chip, VS_EM4L); val &= (0x3 << 20); - azx_writel(chip, SKL_EM4L, val); + azx_writel(chip, VS_EM4L, val); } static void hda_intel_init_chip(struct azx *chip, bool full_reset) diff --git a/sound/soc/intel/skylake/skl.h b/sound/soc/intel/skylake/skl.h index bbef77d2b917..8e2878012d53 100644 --- a/sound/soc/intel/skylake/skl.h +++ b/sound/soc/intel/skylake/skl.h @@ -27,27 +27,6 @@ #define SKL_SUSPEND_DELAY 2000 -/* Vendor Specific Registers */ -#define AZX_REG_VS_EM1 0x1000 -#define AZX_REG_VS_INRC 0x1004 -#define AZX_REG_VS_OUTRC 0x1008 -#define AZX_REG_VS_FIFOTRK 0x100C -#define AZX_REG_VS_FIFOTRK2 0x1010 -#define AZX_REG_VS_EM2 0x1030 -#define AZX_REG_VS_EM3L 0x1038 -#define AZX_REG_VS_EM3U 0x103C -#define AZX_REG_VS_EM4L 0x1040 -#define AZX_REG_VS_EM4U 0x1044 -#define AZX_REG_VS_LTRC 0x1048 -#define AZX_REG_VS_D0I3C 0x104A -#define AZX_REG_VS_PCE 0x104B -#define AZX_REG_VS_L2MAGC 0x1050 -#define AZX_REG_VS_L2LAHPT 0x1054 -#define AZX_REG_VS_SDXDPIB_XBASE 0x1084 -#define AZX_REG_VS_SDXDPIB_XINTERVAL 0x20 -#define AZX_REG_VS_SDXEFIFOS_XBASE 0x1094 -#define AZX_REG_VS_SDXEFIFOS_XINTERVAL 0x20 - #define AZX_PCIREG_PGCTL 0x44 #define AZX_PGCTL_LSRMD_MASK (1 << 4) #define AZX_PCIREG_CGCTL 0x48 -- cgit v1.2.3-59-g8ed1b From f87e7f25893d7db4da465c6b50882197e518d4af Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 29 Mar 2017 08:46:00 +0200 Subject: ALSA: hda - Improved position reporting on SKL+ Apply the same methods to obtain the current stream position as ASoC Intel SKL driver uses. It reads the position from DPIB for a playback stream while it still reads from the position buffer for a capture stream. For a capture stream, some ugly workaround is needed to settle down the inconsistent position. Acked-by: Vinod Koul Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_intel.c | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index a48330f4a1a9..64db6698214c 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -77,6 +77,7 @@ enum { POS_FIX_POSBUF, POS_FIX_VIACOMBO, POS_FIX_COMBO, + POS_FIX_SKL, }; /* Defines for ATI HD Audio support in SB450 south bridge */ @@ -148,7 +149,7 @@ module_param_array(model, charp, NULL, 0444); MODULE_PARM_DESC(model, "Use the given board model."); module_param_array(position_fix, int, NULL, 0444); MODULE_PARM_DESC(position_fix, "DMA pointer read method." - "(-1 = system default, 0 = auto, 1 = LPIB, 2 = POSBUF, 3 = VIACOMBO, 4 = COMBO)."); + "(-1 = system default, 0 = auto, 1 = LPIB, 2 = POSBUF, 3 = VIACOMBO, 4 = COMBO, 5 = SKL+)."); module_param_array(bdl_pos_adj, int, NULL, 0644); MODULE_PARM_DESC(bdl_pos_adj, "BDL position adjustment offset."); module_param_array(probe_mask, int, NULL, 0444); @@ -815,6 +816,31 @@ static unsigned int azx_via_get_position(struct azx *chip, return bound_pos + mod_dma_pos; } +static unsigned int azx_skl_get_dpib_pos(struct azx *chip, + struct azx_dev *azx_dev) +{ + return _snd_hdac_chip_readl(azx_bus(chip), + AZX_REG_VS_SDXDPIB_XBASE + + (AZX_REG_VS_SDXDPIB_XINTERVAL * + azx_dev->core.index)); +} + +/* get the current DMA position with correction on SKL+ chips */ +static unsigned int azx_get_pos_skl(struct azx *chip, struct azx_dev *azx_dev) +{ + /* DPIB register gives a more accurate position for playback */ + if (azx_dev->core.substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return azx_skl_get_dpib_pos(chip, azx_dev); + + /* For capture, we need to read posbuf, but it requires a delay + * for the possible boundary overlap; the read of DPIB fetches the + * actual posbuf + */ + udelay(20); + azx_skl_get_dpib_pos(chip, azx_dev); + return azx_get_pos_posbuf(chip, azx_dev); +} + #ifdef CONFIG_PM static DEFINE_MUTEX(card_list_lock); static LIST_HEAD(card_list); @@ -1351,6 +1377,7 @@ static int check_position_fix(struct azx *chip, int fix) case POS_FIX_POSBUF: case POS_FIX_VIACOMBO: case POS_FIX_COMBO: + case POS_FIX_SKL: return fix; } @@ -1371,6 +1398,10 @@ static int check_position_fix(struct azx *chip, int fix) dev_dbg(chip->card->dev, "Using LPIB position fix\n"); return POS_FIX_LPIB; } + if (IS_SKL_PLUS(chip->pci)) { + dev_dbg(chip->card->dev, "Using SKL position fix\n"); + return POS_FIX_SKL; + } return POS_FIX_AUTO; } @@ -1382,6 +1413,7 @@ static void assign_position_fix(struct azx *chip, int fix) [POS_FIX_POSBUF] = azx_get_pos_posbuf, [POS_FIX_VIACOMBO] = azx_via_get_position, [POS_FIX_COMBO] = azx_get_pos_lpib, + [POS_FIX_SKL] = azx_get_pos_skl, }; chip->get_position[0] = chip->get_position[1] = callbacks[fix]; @@ -1390,7 +1422,7 @@ static void assign_position_fix(struct azx *chip, int fix) if (fix == POS_FIX_COMBO) chip->get_position[1] = NULL; - if (fix == POS_FIX_POSBUF && + if ((fix == POS_FIX_POSBUF || fix == POS_FIX_SKL) && (chip->driver_caps & AZX_DCAPS_COUNT_LPIB_DELAY)) { chip->get_delay[0] = chip->get_delay[1] = azx_get_delay_from_lpib; -- cgit v1.2.3-59-g8ed1b From 17c4e5eadc4ab7db4c0655c124174a6d8e5f4dc5 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 31 Mar 2017 22:05:59 +0900 Subject: ALSA: fireface: add skeleton for RME Fireface series This commit adds a new driver for RME Fireface series. This commit just creates/removes card instance according to IEEE 1394 bus event. More functions will be added in following commits. Three types of firmware have released by RME GmbH; for Fireface 400, for Fireface 800 and for UCX/802/UFX. It's reasonable that these models use different protocol for communication. Currently, I've investigated Fireface 400 and nothing others. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/Kconfig | 6 +++ sound/firewire/Makefile | 1 + sound/firewire/fireface/Makefile | 2 + sound/firewire/fireface/ff.c | 113 +++++++++++++++++++++++++++++++++++++++ sound/firewire/fireface/ff.h | 28 ++++++++++ 5 files changed, 150 insertions(+) create mode 100644 sound/firewire/fireface/Makefile create mode 100644 sound/firewire/fireface/ff.c create mode 100644 sound/firewire/fireface/ff.h (limited to 'sound') diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 6acfacf75daf..b75a82288f74 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -152,4 +152,10 @@ config SND_FIREWIRE_MOTU To compile this driver as a module, choose M here: the module will be called snd-firewire-motu. +config SND_FIREFACE + tristate "RME Fireface series support" + select SND_FIREWIRE_LIB + help + Say Y here to include support for RME fireface series. + endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index 9388ded69468..1b98fa3fa3d4 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -14,3 +14,4 @@ obj-$(CONFIG_SND_BEBOB) += bebob/ obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += digi00x/ obj-$(CONFIG_SND_FIREWIRE_TASCAM) += tascam/ obj-$(CONFIG_SND_FIREWIRE_MOTU) += motu/ +obj-$(CONFIG_SND_FIREFACE) += fireface/ diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile new file mode 100644 index 000000000000..2c64ef6efacb --- /dev/null +++ b/sound/firewire/fireface/Makefile @@ -0,0 +1,2 @@ +snd-fireface-objs := ff.o +obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c new file mode 100644 index 000000000000..358bba23deeb --- /dev/null +++ b/sound/firewire/fireface/ff.c @@ -0,0 +1,113 @@ +/* + * ff.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +#define OUI_RME 0x000a35 + +MODULE_DESCRIPTION("RME Fireface series Driver"); +MODULE_AUTHOR("Takashi Sakamoto "); +MODULE_LICENSE("GPL v2"); + +static void name_card(struct snd_ff *ff) +{ + struct fw_device *fw_dev = fw_parent_device(ff->unit); + const char *const model = "Fireface Skeleton"; + + strcpy(ff->card->driver, "Fireface"); + strcpy(ff->card->shortname, model); + strcpy(ff->card->mixername, model); + snprintf(ff->card->longname, sizeof(ff->card->longname), + "RME %s, GUID %08x%08x at %s, S%d", model, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&ff->unit->device), 100 << fw_dev->max_speed); +} + +static void ff_card_free(struct snd_card *card) +{ + struct snd_ff *ff = card->private_data; + + fw_unit_put(ff->unit); + + mutex_destroy(&ff->mutex); +} + +static int snd_ff_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_card *card; + struct snd_ff *ff; + int err; + + err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, + sizeof(struct snd_ff), &card); + if (err < 0) + return err; + card->private_free = ff_card_free; + + /* initialize myself */ + ff = card->private_data; + ff->card = card; + ff->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, ff); + + mutex_init(&ff->mutex); + + name_card(ff); + + err = snd_card_register(card); + if (err < 0) { + snd_card_free(card); + return err; + } + + return 0; +} + +static void snd_ff_update(struct fw_unit *unit) +{ + return; +} + +static void snd_ff_remove(struct fw_unit *unit) +{ + struct snd_ff *ff = dev_get_drvdata(&unit->device); + + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(ff->card); +} + +static const struct ieee1394_device_id snd_ff_id_table[] = { + {} +}; +MODULE_DEVICE_TABLE(ieee1394, snd_ff_id_table); + +static struct fw_driver ff_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "snd-fireface", + .bus = &fw_bus_type, + }, + .probe = snd_ff_probe, + .update = snd_ff_update, + .remove = snd_ff_remove, + .id_table = snd_ff_id_table, +}; + +static int __init snd_ff_init(void) +{ + return driver_register(&ff_driver.driver); +} + +static void __exit snd_ff_exit(void) +{ + driver_unregister(&ff_driver.driver); +} + +module_init(snd_ff_init); +module_exit(snd_ff_exit); diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h new file mode 100644 index 000000000000..64d488ec8264 --- /dev/null +++ b/sound/firewire/fireface/ff.h @@ -0,0 +1,28 @@ +/* + * ff.h - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef SOUND_FIREFACE_H_INCLUDED +#define SOUND_FIREFACE_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct snd_ff { + struct snd_card *card; + struct fw_unit *unit; + struct mutex mutex; +}; +#endif -- cgit v1.2.3-59-g8ed1b From 324540c4e05c09c007f9e358cacb30b38f296bcc Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 31 Mar 2017 22:06:00 +0900 Subject: ALSA: fireface: postpone sound card registration Just after appearing on IEEE 1394 bus, this unit generates several bus resets. This is due to loading firmware from on-board flash memory and initialize hardware. It's better to postpone sound card registration. This commit schedules workqueue to process actual probe processing 2 seconds after the last bus-reset. The card instance is kept at unit probe callback and released at card free callback. Therefore, when the actual probe processing fails, the memory block is wasted. This is due to simplify driver implementation. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/fireface/ff.c | 84 ++++++++++++++++++++++++++++++++------------ sound/firewire/fireface/ff.h | 5 +++ 2 files changed, 67 insertions(+), 22 deletions(-) (limited to 'sound') diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c index 358bba23deeb..7c026396b8b5 100644 --- a/sound/firewire/fireface/ff.c +++ b/sound/firewire/fireface/ff.c @@ -28,58 +28,98 @@ static void name_card(struct snd_ff *ff) dev_name(&ff->unit->device), 100 << fw_dev->max_speed); } -static void ff_card_free(struct snd_card *card) +static void ff_free(struct snd_ff *ff) { - struct snd_ff *ff = card->private_data; - fw_unit_put(ff->unit); mutex_destroy(&ff->mutex); + kfree(ff); +} + +static void ff_card_free(struct snd_card *card) +{ + ff_free(card->private_data); +} + +static void do_registration(struct work_struct *work) +{ + struct snd_ff *ff = container_of(work, struct snd_ff, dwork.work); + int err; + + if (ff->registered) + return; + + err = snd_card_new(&ff->unit->device, -1, NULL, THIS_MODULE, 0, + &ff->card); + if (err < 0) + return; + + name_card(ff); + + err = snd_card_register(ff->card); + if (err < 0) + goto error; + + ff->card->private_free = ff_card_free; + ff->card->private_data = ff; + ff->registered = true; + + return; +error: + snd_card_free(ff->card); + dev_info(&ff->unit->device, + "Sound card registration failed: %d\n", err); } static int snd_ff_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry) { - struct snd_card *card; struct snd_ff *ff; - int err; - err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, - sizeof(struct snd_ff), &card); - if (err < 0) - return err; - card->private_free = ff_card_free; + ff = kzalloc(sizeof(struct snd_ff), GFP_KERNEL); + if (ff == NULL) + return -ENOMEM; /* initialize myself */ - ff = card->private_data; - ff->card = card; ff->unit = fw_unit_get(unit); dev_set_drvdata(&unit->device, ff); mutex_init(&ff->mutex); - name_card(ff); - - err = snd_card_register(card); - if (err < 0) { - snd_card_free(card); - return err; - } + /* Register this sound card later. */ + INIT_DEFERRABLE_WORK(&ff->dwork, do_registration); + snd_fw_schedule_registration(unit, &ff->dwork); return 0; } static void snd_ff_update(struct fw_unit *unit) { - return; + struct snd_ff *ff = dev_get_drvdata(&unit->device); + + /* Postpone a workqueue for deferred registration. */ + if (!ff->registered) + snd_fw_schedule_registration(unit, &ff->dwork); } static void snd_ff_remove(struct fw_unit *unit) { struct snd_ff *ff = dev_get_drvdata(&unit->device); - /* No need to wait for releasing card object in this context. */ - snd_card_free_when_closed(ff->card); + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_work_sync(&ff->dwork.work); + + if (ff->registered) { + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(ff->card); + } else { + /* Don't forget this case. */ + ff_free(ff); + } } static const struct ieee1394_device_id snd_ff_id_table[] = { diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index 64d488ec8264..a0faae18018a 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -20,9 +20,14 @@ #include +#include "../lib.h" + struct snd_ff { struct snd_card *card; struct fw_unit *unit; struct mutex mutex; + + bool registered; + struct delayed_work dwork; }; #endif -- cgit v1.2.3-59-g8ed1b From ed90f91a17112d474909bd820f1bb65a5480959d Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 31 Mar 2017 22:06:01 +0900 Subject: ALSA: fireface: add model specific structure RME Fireface series has several models and their specifications are different. Currently, we find no way to retrieve the specifications from actual devices and need to implement them in this driver. This commit adds a structure to describe model specific data. This structure has an identical name for each unit, and maximum number of data channels in each mode. I'll describe about the mode in following commits. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/fireface/ff.c | 9 +++++---- sound/firewire/fireface/ff.h | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c index 7c026396b8b5..5c2bd9222c97 100644 --- a/sound/firewire/fireface/ff.c +++ b/sound/firewire/fireface/ff.c @@ -17,13 +17,12 @@ MODULE_LICENSE("GPL v2"); static void name_card(struct snd_ff *ff) { struct fw_device *fw_dev = fw_parent_device(ff->unit); - const char *const model = "Fireface Skeleton"; strcpy(ff->card->driver, "Fireface"); - strcpy(ff->card->shortname, model); - strcpy(ff->card->mixername, model); + strcpy(ff->card->shortname, ff->spec->name); + strcpy(ff->card->mixername, ff->spec->name); snprintf(ff->card->longname, sizeof(ff->card->longname), - "RME %s, GUID %08x%08x at %s, S%d", model, + "RME %s, GUID %08x%08x at %s, S%d", ff->spec->name, fw_dev->config_rom[3], fw_dev->config_rom[4], dev_name(&ff->unit->device), 100 << fw_dev->max_speed); } @@ -86,6 +85,8 @@ static int snd_ff_probe(struct fw_unit *unit, mutex_init(&ff->mutex); + ff->spec = (const struct snd_ff_spec *)entry->driver_data; + /* Register this sound card later. */ INIT_DEFERRABLE_WORK(&ff->dwork, do_registration); snd_fw_schedule_registration(unit, &ff->dwork); diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index a0faae18018a..269fa259d5a7 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -22,6 +22,18 @@ #include "../lib.h" +#define SND_FF_STREAM_MODES 3 + +struct snd_ff_spec { + const char *const name; + + const unsigned int pcm_capture_channels[SND_FF_STREAM_MODES]; + const unsigned int pcm_playback_channels[SND_FF_STREAM_MODES]; + + unsigned int midi_in_ports; + unsigned int midi_out_ports; +}; + struct snd_ff { struct snd_card *card; struct fw_unit *unit; @@ -29,5 +41,7 @@ struct snd_ff { bool registered; struct delayed_work dwork; + + const struct snd_ff_spec *spec; }; #endif -- cgit v1.2.3-59-g8ed1b From 53eb086750f3535eeb70eb177b0fa89d458f1479 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 31 Mar 2017 22:06:02 +0900 Subject: ALSA: fireface: add an abstraction layer for model-specific protocols As of 2016, RME discontinued its Fireface series, thus it's OK for us to focus on released firmwares to drive known units. As long as investigating Fireface 400 with Windows driver and comparing the result to FFADO implementation, I can see these firmwares have different register assignments. On the other hand, according to manuals of each models, features relevant to packet streaming seem to be common, because GUIs for these models have the same options. It's reasonable to assume an abstraction layer of protocols to communicate to each models. This commit adds the abstraction layer for the protocols. This layer includes some functions to operate common features of models in this series. In IEC 61883-1/6, the sequence of packet can transfer timing information to synchronize receivers to transmitters. Units of each node on IEEE 1394 bus can generate transmitter's timing clock by handling value of SYT field in CIP header with high-precision clock. For audio and music units on IEEE 1394 bus, this recovered clock is designed to used for sampling clock to capture/generate PCM frames on DSP/ADC/DAC. (Actually, in this world, there's no units to implement this specification as is, as long as I know). Fireface series doesn't use this mechanism. Besides, It doesn't use isochronous packet with CIP header. It uses internal crystal unit as its initial sampling clock. When detecting input signals which can be available for sampling clock (e.g. ADAT input), drivers can configure units to use the signals as source of sampling clock. When something goes wrong, e.g. frequency mismatching between the signal and configured value, units fallback to the other detected signals alternatively. When detecting no alternatives, internal crystal unit is used as source of sampling clock. On manual of Fireface 400, this mechanism is described as 'Autosync'. On the units, packet streaming is controlled by write transactions to certain registers. Format of the packet, e.g. the number of data channels in a data block, is also configured by the same manner. For this purpose, .begin_session and .finish_session is added. The remarkable point of this protocol is to allow drivers to configure arbitrary sampling transmission frequency; e.g. 12.345 Hz. As long as I know, there's no actual DAC/ADC chips which support this kind of capability. I think a pair of packet streaming layer and data block processing layer is isolated from sampling data processing layer in a point of governed clock. In short, between these parts, resampling layer exists. Actually, for Fireface 400, write transactions to 0x'0000'8010'051c has an effect to change sampling clock frequency with base frequencies (32.0/44.1/48.0 kHz) and its multipliers (x2/x4), regardless of sampling transmission frequency. For this reason, the abstraction layer doesn't handle parameters for sampling clock. Instead, each implementation of .begin_session is expected to configure sampling transmission frequency. For packet streaming layer, it's enough to get current selection of source signals for the sampling clock and its frequency. In the abstraction layer, when internal crystal is selected, drivers can sets arbitrary sampling frequency, else they should follow configured frequency. For this purpose, .get_clock is added. Drivers are allows to bank up data fetching from a pair of packet streaming/data block processing layer and sampling data processing layer. This feature seems to suppress noises at starting/stopping packet streaming. For this purpose, .switch_fetching_mode is added. As I described in the above, units have remarkable mechanism to manage sampling clock and process sampling data. For debugging purpose, .dump_sync_status and .dump_clock_config are added. I don't have a need to common interface to represent the status and configuration, developers can add actual implementation of the abstraction layer as they like. Unlike PCM frames, MIDI messages are transferred by asynchronous communication over IEEE 1394 bus, thus target addresses are important for this feature. The .midi_high_addr_reg, .midi_rx_port_0_reg and .midi_rx_port_1_reg are for this purpose. I'll describe them in following commit. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/fireface/ff.h | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'sound') diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index 269fa259d5a7..7be0ea4aaa4b 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -19,11 +19,13 @@ #include #include +#include #include "../lib.h" #define SND_FF_STREAM_MODES 3 +struct snd_ff_protocol; struct snd_ff_spec { const char *const name; @@ -32,6 +34,8 @@ struct snd_ff_spec { unsigned int midi_in_ports; unsigned int midi_out_ports; + + struct snd_ff_protocol *protocol; }; struct snd_ff { @@ -44,4 +48,31 @@ struct snd_ff { const struct snd_ff_spec *spec; }; + +enum snd_ff_clock_src { + SND_FF_CLOCK_SRC_INTERNAL, + SND_FF_CLOCK_SRC_SPDIF, + SND_FF_CLOCK_SRC_ADAT, + SND_FF_CLOCK_SRC_WORD, + SND_FF_CLOCK_SRC_LTC, + /* TODO: perhaps ADAT2 and TCO exists. */ +}; + +struct snd_ff_protocol { + int (*get_clock)(struct snd_ff *ff, unsigned int *rate, + enum snd_ff_clock_src *src); + int (*begin_session)(struct snd_ff *ff, unsigned int rate); + void (*finish_session)(struct snd_ff *ff); + int (*switch_fetching_mode)(struct snd_ff *ff, bool enable); + + void (*dump_sync_status)(struct snd_ff *ff, + struct snd_info_buffer *buffer); + void (*dump_clock_config)(struct snd_ff *ff, + struct snd_info_buffer *buffer); + + u64 midi_high_addr_reg; + u64 midi_rx_port_0_reg; + u64 midi_rx_port_1_reg; +}; + #endif -- cgit v1.2.3-59-g8ed1b From 19174295788de77dd58dc6060b0d1bcfda21625e Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 31 Mar 2017 22:06:03 +0900 Subject: ALSA: fireface: add transaction support As long as investigating Fireface 400, MIDI messages are transferred by asynchronous communication over IEEE 1394 bus. Fireface 400 receives MIDI messages by write transactions to two addresses; 0x'0000'0801'8000 and 0x'0000'0801'9000. Each of two seems to correspond to MIDI port 1 and 2. Fireface 400 transfers MIDI messages by write transactions to certain addresses which configured by drivers. The drivers can decide upper 4 byte of the addresses by write transactions to 0x'0000'0801'03f4. For the rest part of the address, drivers can select from below options: * 0x'0000'0000 * 0x'0000'0080 * 0x'0000'0100 * 0x'0000'0180 Selected options are represented in register 0x'0000'0801'051c as bit flags. Due to this mechanism, drivers are restricted to use addresses on 'Memory space' of IEEE 1222, even if transactions to the address have some side effects. This commit adds transaction support for MIDI messaging, based on my assumption that the similar mechanism is used on the other protocols. To receive asynchronous transactions, the driver allocates a range of address in 'Memory space'. I apply a strategy to use 0x'0000'0000 as lower 4 byte of the address. When getting failure from Linux FireWire subsystem, this driver retries to allocate addresses. Unfortunately, read transaction to address 0x'0000'0801'051c returns zero always, however write transactions have effects to the other features such as status of sampling clock. For this reason, this commit delegates a task to configure this register to user space applications. The applications should set 3rd bit in LSB in little endian order. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/fireface/Makefile | 2 +- sound/firewire/fireface/ff-transaction.c | 295 +++++++++++++++++++++++++++++++ sound/firewire/fireface/ff.c | 9 + sound/firewire/fireface/ff.h | 23 +++ 4 files changed, 328 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/fireface/ff-transaction.c (limited to 'sound') diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile index 2c64ef6efacb..864aacc80256 100644 --- a/sound/firewire/fireface/Makefile +++ b/sound/firewire/fireface/Makefile @@ -1,2 +1,2 @@ -snd-fireface-objs := ff.o +snd-fireface-objs := ff.o ff-transaction.o obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/ff-transaction.c b/sound/firewire/fireface/ff-transaction.c new file mode 100644 index 000000000000..d1b098f8ae42 --- /dev/null +++ b/sound/firewire/fireface/ff-transaction.c @@ -0,0 +1,295 @@ +/* + * ff-transaction.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +static void finish_transmit_midi_msg(struct snd_ff *ff, unsigned int port, + int rcode) +{ + struct snd_rawmidi_substream *substream = + ACCESS_ONCE(ff->rx_midi_substreams[port]); + + if (rcode_is_permanent_error(rcode)) { + ff->rx_midi_error[port] = true; + return; + } + + if (rcode != RCODE_COMPLETE) { + /* Transfer the message again, immediately. */ + ff->next_ktime[port] = ktime_set(0, 0); + schedule_work(&ff->rx_midi_work[port]); + return; + } + + snd_rawmidi_transmit_ack(substream, ff->rx_bytes[port]); + ff->rx_bytes[port] = 0; + + if (!snd_rawmidi_transmit_empty(substream)) + schedule_work(&ff->rx_midi_work[port]); +} + +static void finish_transmit_midi0_msg(struct fw_card *card, int rcode, + void *data, size_t length, + void *callback_data) +{ + struct snd_ff *ff = + container_of(callback_data, struct snd_ff, transactions[0]); + finish_transmit_midi_msg(ff, 0, rcode); +} + +static void finish_transmit_midi1_msg(struct fw_card *card, int rcode, + void *data, size_t length, + void *callback_data) +{ + struct snd_ff *ff = + container_of(callback_data, struct snd_ff, transactions[1]); + finish_transmit_midi_msg(ff, 1, rcode); +} + +static inline void fill_midi_buf(struct snd_ff *ff, unsigned int port, + unsigned int index, u8 byte) +{ + ff->msg_buf[port][index] = cpu_to_le32(byte); +} + +static void transmit_midi_msg(struct snd_ff *ff, unsigned int port) +{ + struct snd_rawmidi_substream *substream = + ACCESS_ONCE(ff->rx_midi_substreams[port]); + u8 *buf = (u8 *)ff->msg_buf[port]; + int i, len; + + struct fw_device *fw_dev = fw_parent_device(ff->unit); + unsigned long long addr; + int generation; + fw_transaction_callback_t callback; + + if (substream == NULL || snd_rawmidi_transmit_empty(substream)) + return; + + if (ff->rx_bytes[port] > 0 || ff->rx_midi_error[port]) + return; + + /* Do it in next chance. */ + if (ktime_after(ff->next_ktime[port], ktime_get())) { + schedule_work(&ff->rx_midi_work[port]); + return; + } + + len = snd_rawmidi_transmit_peek(substream, buf, + SND_FF_MAXIMIM_MIDI_QUADS); + if (len <= 0) + return; + + for (i = len - 1; i >= 0; i--) + fill_midi_buf(ff, port, i, buf[i]); + + if (port == 0) { + addr = ff->spec->protocol->midi_rx_port_0_reg; + callback = finish_transmit_midi0_msg; + } else { + addr = ff->spec->protocol->midi_rx_port_1_reg; + callback = finish_transmit_midi1_msg; + } + + /* Set interval to next transaction. */ + ff->next_ktime[port] = ktime_add_ns(ktime_get(), + len * 8 * NSEC_PER_SEC / 31250); + ff->rx_bytes[port] = len; + + /* + * In Linux FireWire core, when generation is updated with memory + * barrier, node id has already been updated. In this module, After + * this smp_rmb(), load/store instructions to memory are completed. + * Thus, both of generation and node id are available with recent + * values. This is a light-serialization solution to handle bus reset + * events on IEEE 1394 bus. + */ + generation = fw_dev->generation; + smp_rmb(); + fw_send_request(fw_dev->card, &ff->transactions[port], + TCODE_WRITE_BLOCK_REQUEST, + fw_dev->node_id, generation, fw_dev->max_speed, + addr, &ff->msg_buf[port], len * 4, + callback, &ff->transactions[port]); +} + +static void transmit_midi0_msg(struct work_struct *work) +{ + struct snd_ff *ff = container_of(work, struct snd_ff, rx_midi_work[0]); + + transmit_midi_msg(ff, 0); +} + +static void transmit_midi1_msg(struct work_struct *work) +{ + struct snd_ff *ff = container_of(work, struct snd_ff, rx_midi_work[1]); + + transmit_midi_msg(ff, 1); +} + +static void handle_midi_msg(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + struct snd_ff *ff = callback_data; + __le32 *buf = data; + u32 quad; + u8 byte; + unsigned int index; + struct snd_rawmidi_substream *substream; + int i; + + fw_send_response(card, request, RCODE_COMPLETE); + + for (i = 0; i < length / 4; i++) { + quad = le32_to_cpu(buf[i]); + + /* Message in first port. */ + /* + * This value may represent the index of this unit when the same + * units are on the same IEEE 1394 bus. This driver doesn't use + * it. + */ + index = (quad >> 8) & 0xff; + if (index > 0) { + substream = ACCESS_ONCE(ff->tx_midi_substreams[0]); + if (substream != NULL) { + byte = quad & 0xff; + snd_rawmidi_receive(substream, &byte, 1); + } + } + + /* Message in second port. */ + index = (quad >> 24) & 0xff; + if (index > 0) { + substream = ACCESS_ONCE(ff->tx_midi_substreams[1]); + if (substream != NULL) { + byte = (quad >> 16) & 0xff; + snd_rawmidi_receive(substream, &byte, 1); + } + } + } +} + +static int allocate_own_address(struct snd_ff *ff, int i) +{ + struct fw_address_region midi_msg_region; + int err; + + ff->async_handler.length = SND_FF_MAXIMIM_MIDI_QUADS * 4; + ff->async_handler.address_callback = handle_midi_msg; + ff->async_handler.callback_data = ff; + + midi_msg_region.start = 0x000100000000ull * i; + midi_msg_region.end = midi_msg_region.start + ff->async_handler.length; + + err = fw_core_add_address_handler(&ff->async_handler, &midi_msg_region); + if (err >= 0) { + /* Controllers are allowed to register this region. */ + if (ff->async_handler.offset & 0x0000ffffffff) { + fw_core_remove_address_handler(&ff->async_handler); + err = -EAGAIN; + } + } + + return err; +} + +/* + * The configuration to start asynchronous transactions for MIDI messages is in + * 0x'0000'8010'051c. This register includes the other options, thus this driver + * doesn't touch it and leaves the decision to userspace. The userspace MUST add + * 0x04000000 to write transactions to the register to receive any MIDI + * messages. + * + * Here, I just describe MIDI-related offsets of the register, in little-endian + * order. + * + * Controllers are allowed to register higher 4 bytes of address to receive + * the transactions. The register is 0x'0000'8010'03f4. On the other hand, the + * controllers are not allowed to register lower 4 bytes of the address. They + * are forced to select from 4 options by writing corresponding bits to + * 0x'0000'8010'051c. + * + * The 3rd-6th bits in MSB of this register are used to indicate lower 4 bytes + * of address to which the device transferrs the transactions. + * - 6th: 0x'....'....'0000'0180 + * - 5th: 0x'....'....'0000'0100 + * - 4th: 0x'....'....'0000'0080 + * - 3rd: 0x'....'....'0000'0000 + * + * This driver configure 0x'....'....'0000'0000 for units to receive MIDI + * messages. 3rd bit of the register should be configured, however this driver + * deligates this task to user space applications due to a restriction that + * this register is write-only and the other bits have own effects. + * + * The 1st and 2nd bits in LSB of this register are used to cancel transferring + * asynchronous transactions. These two bits have the same effect. + * - 1st/2nd: cancel transferring + */ +int snd_ff_transaction_reregister(struct snd_ff *ff) +{ + struct fw_card *fw_card = fw_parent_device(ff->unit)->card; + u32 addr; + __le32 reg; + + /* + * Controllers are allowed to register its node ID and upper 2 byte of + * local address to listen asynchronous transactions. + */ + addr = (fw_card->node_id << 16) | (ff->async_handler.offset >> 32); + reg = cpu_to_le32(addr); + return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + ff->spec->protocol->midi_high_addr_reg, + ®, sizeof(reg), 0); +} + +int snd_ff_transaction_register(struct snd_ff *ff) +{ + int i, err; + + /* + * Allocate in Memory Space of IEC 13213, but lower 4 byte in LSB should + * be zero due to device specification. + */ + for (i = 0; i < 0xffff; i++) { + err = allocate_own_address(ff, i); + if (err != -EBUSY && err != -EAGAIN) + break; + } + if (err < 0) + return err; + + err = snd_ff_transaction_reregister(ff); + if (err < 0) + return err; + + INIT_WORK(&ff->rx_midi_work[0], transmit_midi0_msg); + INIT_WORK(&ff->rx_midi_work[1], transmit_midi1_msg); + + return 0; +} + +void snd_ff_transaction_unregister(struct snd_ff *ff) +{ + __le32 reg; + + if (ff->async_handler.callback_data == NULL) + return; + ff->async_handler.callback_data = NULL; + + /* Release higher 4 bytes of address. */ + reg = cpu_to_le32(0x00000000); + snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + ff->spec->protocol->midi_high_addr_reg, + ®, sizeof(reg), 0); + + fw_core_remove_address_handler(&ff->async_handler); +} diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c index 5c2bd9222c97..4db630fe241c 100644 --- a/sound/firewire/fireface/ff.c +++ b/sound/firewire/fireface/ff.c @@ -29,6 +29,8 @@ static void name_card(struct snd_ff *ff) static void ff_free(struct snd_ff *ff) { + snd_ff_transaction_unregister(ff); + fw_unit_put(ff->unit); mutex_destroy(&ff->mutex); @@ -53,6 +55,10 @@ static void do_registration(struct work_struct *work) if (err < 0) return; + err = snd_ff_transaction_register(ff); + if (err < 0) + goto error; + name_card(ff); err = snd_card_register(ff->card); @@ -65,6 +71,7 @@ static void do_registration(struct work_struct *work) return; error: + snd_ff_transaction_unregister(ff); snd_card_free(ff->card); dev_info(&ff->unit->device, "Sound card registration failed: %d\n", err); @@ -101,6 +108,8 @@ static void snd_ff_update(struct fw_unit *unit) /* Postpone a workqueue for deferred registration. */ if (!ff->registered) snd_fw_schedule_registration(unit, &ff->dwork); + + snd_ff_transaction_reregister(ff); } static void snd_ff_remove(struct fw_unit *unit) diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index 7be0ea4aaa4b..bac2e58b2e35 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -20,11 +20,16 @@ #include #include +#include #include "../lib.h" #define SND_FF_STREAM_MODES 3 +#define SND_FF_MAXIMIM_MIDI_QUADS 9 +#define SND_FF_IN_MIDI_PORTS 2 +#define SND_FF_OUT_MIDI_PORTS 2 + struct snd_ff_protocol; struct snd_ff_spec { const char *const name; @@ -47,6 +52,20 @@ struct snd_ff { struct delayed_work dwork; const struct snd_ff_spec *spec; + + /* To handle MIDI tx. */ + struct snd_rawmidi_substream *tx_midi_substreams[SND_FF_IN_MIDI_PORTS]; + struct fw_address_handler async_handler; + + /* TO handle MIDI rx. */ + struct snd_rawmidi_substream *rx_midi_substreams[SND_FF_OUT_MIDI_PORTS]; + u8 running_status[SND_FF_OUT_MIDI_PORTS]; + __le32 msg_buf[SND_FF_OUT_MIDI_PORTS][SND_FF_MAXIMIM_MIDI_QUADS]; + struct work_struct rx_midi_work[SND_FF_OUT_MIDI_PORTS]; + struct fw_transaction transactions[SND_FF_OUT_MIDI_PORTS]; + ktime_t next_ktime[SND_FF_OUT_MIDI_PORTS]; + bool rx_midi_error[SND_FF_OUT_MIDI_PORTS]; + unsigned int rx_bytes[SND_FF_OUT_MIDI_PORTS]; }; enum snd_ff_clock_src { @@ -75,4 +94,8 @@ struct snd_ff_protocol { u64 midi_rx_port_1_reg; }; +int snd_ff_transaction_register(struct snd_ff *ff); +int snd_ff_transaction_reregister(struct snd_ff *ff); +void snd_ff_transaction_unregister(struct snd_ff *ff); + #endif -- cgit v1.2.3-59-g8ed1b From ff2c293efaf43feb120f6b166891e5eca0cf3ccc Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 31 Mar 2017 22:06:04 +0900 Subject: ALSA: fireface: add support for MIDI functionality In previous commit, fireface driver supports unique transaction mechanism for MIDI feature. This commit adds MIDI functionality for userspace applications. As I wrote in a followed commit, user space applications get some requirement from this driver. It should not touch a register to which units transmit MIDI messages. It should configure a register in which MIDI transmission is controlled. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/fireface/Makefile | 2 +- sound/firewire/fireface/ff-midi.c | 131 ++++++++++++++++++++++++++++++++++++++ sound/firewire/fireface/ff.c | 5 ++ sound/firewire/fireface/ff.h | 3 + 4 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/fireface/ff-midi.c (limited to 'sound') diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile index 864aacc80256..8e465e4bd539 100644 --- a/sound/firewire/fireface/Makefile +++ b/sound/firewire/fireface/Makefile @@ -1,2 +1,2 @@ -snd-fireface-objs := ff.o ff-transaction.o +snd-fireface-objs := ff.o ff-transaction.o ff-midi.o obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/ff-midi.c b/sound/firewire/fireface/ff-midi.c new file mode 100644 index 000000000000..29ee0a7365c3 --- /dev/null +++ b/sound/firewire/fireface/ff-midi.c @@ -0,0 +1,131 @@ +/* + * ff-midi.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + /* Do nothing. */ + return 0; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_ff *ff = substream->rmidi->private_data; + + /* Initialize internal status. */ + ff->running_status[substream->number] = 0; + ff->rx_midi_error[substream->number] = false; + + ACCESS_ONCE(ff->rx_midi_substreams[substream->number]) = substream; + + return 0; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + /* Do nothing. */ + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_ff *ff = substream->rmidi->private_data; + + cancel_work_sync(&ff->rx_midi_work[substream->number]); + ACCESS_ONCE(ff->rx_midi_substreams[substream->number]) = NULL; + + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_ff *ff = substream->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&ff->lock, flags); + + if (up) + ACCESS_ONCE(ff->tx_midi_substreams[substream->number]) = + substream; + else + ACCESS_ONCE(ff->tx_midi_substreams[substream->number]) = NULL; + + spin_unlock_irqrestore(&ff->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_ff *ff = substream->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&ff->lock, flags); + + if (up || !ff->rx_midi_error[substream->number]) + schedule_work(&ff->rx_midi_work[substream->number]); + + spin_unlock_irqrestore(&ff->lock, flags); +} + +static struct snd_rawmidi_ops midi_capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, +}; + +static struct snd_rawmidi_ops midi_playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, +}; + +static void set_midi_substream_names(struct snd_rawmidi_str *stream, + const char *const name) +{ + struct snd_rawmidi_substream *substream; + + list_for_each_entry(substream, &stream->substreams, list) { + snprintf(substream->name, sizeof(substream->name), + "%s MIDI %d", name, substream->number + 1); + } +} + +int snd_ff_create_midi_devices(struct snd_ff *ff) +{ + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *stream; + int err; + + err = snd_rawmidi_new(ff->card, ff->card->driver, 0, + ff->spec->midi_out_ports, ff->spec->midi_in_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", ff->card->shortname); + rmidi->private_data = ff; + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &midi_capture_ops); + stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + set_midi_substream_names(stream, ff->card->shortname); + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &midi_playback_ops); + stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + set_midi_substream_names(stream, ff->card->shortname); + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c index 4db630fe241c..11d76b372cd9 100644 --- a/sound/firewire/fireface/ff.c +++ b/sound/firewire/fireface/ff.c @@ -61,6 +61,10 @@ static void do_registration(struct work_struct *work) name_card(ff); + err = snd_ff_create_midi_devices(ff); + if (err < 0) + goto error; + err = snd_card_register(ff->card); if (err < 0) goto error; @@ -91,6 +95,7 @@ static int snd_ff_probe(struct fw_unit *unit, dev_set_drvdata(&unit->device, ff); mutex_init(&ff->mutex); + spin_lock_init(&ff->lock); ff->spec = (const struct snd_ff_spec *)entry->driver_data; diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index bac2e58b2e35..2944bde250bf 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -47,6 +47,7 @@ struct snd_ff { struct snd_card *card; struct fw_unit *unit; struct mutex mutex; + spinlock_t lock; bool registered; struct delayed_work dwork; @@ -98,4 +99,6 @@ int snd_ff_transaction_register(struct snd_ff *ff); int snd_ff_transaction_reregister(struct snd_ff *ff); void snd_ff_transaction_unregister(struct snd_ff *ff); +int snd_ff_create_midi_devices(struct snd_ff *ff); + #endif -- cgit v1.2.3-59-g8ed1b From d3fc7aac11dc54f97f4f28c60a489a555529fa1c Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 31 Mar 2017 22:06:05 +0900 Subject: ALSA: fireface: add proc node to help debugging Drivers can retrieve the state and configuration of clock by read transactions. This commit allows protocol abstraction layer to to dump the information for debugging, via proc interface. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/fireface/Makefile | 2 +- sound/firewire/fireface/ff-proc.c | 63 +++++++++++++++++++++++++++++++++++++++ sound/firewire/fireface/ff.c | 2 ++ sound/firewire/fireface/ff.h | 2 ++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/fireface/ff-proc.c (limited to 'sound') diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile index 8e465e4bd539..e88ff9e2cf47 100644 --- a/sound/firewire/fireface/Makefile +++ b/sound/firewire/fireface/Makefile @@ -1,2 +1,2 @@ -snd-fireface-objs := ff.o ff-transaction.o ff-midi.o +snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/ff-proc.c b/sound/firewire/fireface/ff-proc.c new file mode 100644 index 000000000000..69441d121f71 --- /dev/null +++ b/sound/firewire/fireface/ff-proc.c @@ -0,0 +1,63 @@ +/* + * ff-proc.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./ff.h" + +static void proc_dump_clock_config(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ff *ff = entry->private_data; + + ff->spec->protocol->dump_clock_config(ff, buffer); +} + +static void proc_dump_sync_status(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ff *ff = entry->private_data; + + ff->spec->protocol->dump_sync_status(ff, buffer); +} + +static void add_node(struct snd_ff *ff, struct snd_info_entry *root, + const char *name, + void (*op)(struct snd_info_entry *e, + struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(ff->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, ff, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_ff_proc_init(struct snd_ff *ff) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(ff->card, "firewire", + ff->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(ff, root, "clock-config", proc_dump_clock_config); + add_node(ff, root, "sync-status", proc_dump_sync_status); +} diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c index 11d76b372cd9..22e7bcb4bd51 100644 --- a/sound/firewire/fireface/ff.c +++ b/sound/firewire/fireface/ff.c @@ -61,6 +61,8 @@ static void do_registration(struct work_struct *work) name_card(ff); + snd_ff_proc_init(ff); + err = snd_ff_create_midi_devices(ff); if (err < 0) goto error; diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index 2944bde250bf..2d1fab2c3467 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -99,6 +99,8 @@ int snd_ff_transaction_register(struct snd_ff *ff); int snd_ff_transaction_reregister(struct snd_ff *ff); void snd_ff_transaction_unregister(struct snd_ff *ff); +void snd_ff_proc_init(struct snd_ff *ff); + int snd_ff_create_midi_devices(struct snd_ff *ff); #endif -- cgit v1.2.3-59-g8ed1b From ff0fb5aaa8799701aa01bd1f2cdaf93ce98bbe60 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 31 Mar 2017 22:06:06 +0900 Subject: ALSA: firewire-lib: use the same prototype for functions to handle packet Audio and music units of RME Fireface series use its own protocol for isochronous packets to transfer data. This protocol requires ALSA IEC 61883-1/6 engine to have alternative functions. This commit is a preparation for the protocol. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/amdtp-stream-trace.h | 6 +++--- sound/firewire/amdtp-stream.c | 31 ++++++++++++++++--------------- 2 files changed, 19 insertions(+), 18 deletions(-) (limited to 'sound') diff --git a/sound/firewire/amdtp-stream-trace.h b/sound/firewire/amdtp-stream-trace.h index 9c04faf206b2..850b36e27d0d 100644 --- a/sound/firewire/amdtp-stream-trace.h +++ b/sound/firewire/amdtp-stream-trace.h @@ -14,8 +14,8 @@ #include TRACE_EVENT(in_packet, - TP_PROTO(const struct amdtp_stream *s, u32 cycles, u32 cip_header[2], unsigned int payload_quadlets, unsigned int index), - TP_ARGS(s, cycles, cip_header, payload_quadlets, index), + TP_PROTO(const struct amdtp_stream *s, u32 cycles, u32 cip_header[2], unsigned int payload_length, unsigned int index), + TP_ARGS(s, cycles, cip_header, payload_length, index), TP_STRUCT__entry( __field(unsigned int, second) __field(unsigned int, cycle) @@ -37,7 +37,7 @@ TRACE_EVENT(in_packet, __entry->dest = fw_parent_device(s->unit)->card->node_id; __entry->cip_header0 = cip_header[0]; __entry->cip_header1 = cip_header[1]; - __entry->payload_quadlets = payload_quadlets; + __entry->payload_quadlets = payload_length / 4; __entry->packet_index = s->packet_index; __entry->irq = !!in_interrupt(); __entry->index = index; diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c index 112ad039ed25..646e8e390773 100644 --- a/sound/firewire/amdtp-stream.c +++ b/sound/firewire/amdtp-stream.c @@ -411,13 +411,13 @@ static inline int queue_in_packet(struct amdtp_stream *s) amdtp_stream_get_max_payload(s)); } -static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle, +static int handle_out_packet(struct amdtp_stream *s, + unsigned int payload_length, unsigned int cycle, unsigned int index) { __be32 *buffer; unsigned int syt; unsigned int data_blocks; - unsigned int payload_length; unsigned int pcm_frames; struct snd_pcm_substream *pcm; @@ -458,7 +458,7 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle, } static int handle_in_packet(struct amdtp_stream *s, - unsigned int payload_quadlets, unsigned int cycle, + unsigned int payload_length, unsigned int cycle, unsigned int index) { __be32 *buffer; @@ -474,7 +474,7 @@ static int handle_in_packet(struct amdtp_stream *s, cip_header[0] = be32_to_cpu(buffer[0]); cip_header[1] = be32_to_cpu(buffer[1]); - trace_in_packet(s, cycle, cip_header, payload_quadlets, index); + trace_in_packet(s, cycle, cip_header, payload_length, index); /* * This module supports 'Two-quadlet CIP header with SYT field'. @@ -505,7 +505,7 @@ static int handle_in_packet(struct amdtp_stream *s, /* Calculate data blocks */ fdf = (cip_header[1] & CIP_FDF_MASK) >> CIP_FDF_SHIFT; - if (payload_quadlets < 3 || + if (payload_length < 12 || (fmt == CIP_FMT_AM && fdf == AMDTP_FDF_NO_DATA)) { data_blocks = 0; } else { @@ -521,7 +521,8 @@ static int handle_in_packet(struct amdtp_stream *s, if (s->flags & CIP_WRONG_DBS) data_block_quadlets = s->data_block_quadlets; - data_blocks = (payload_quadlets - 2) / data_block_quadlets; + data_blocks = (payload_length / 4 - 2) / + data_block_quadlets; } /* Check data block counter continuity */ @@ -615,7 +616,7 @@ static void out_stream_callback(struct fw_iso_context *context, u32 tstamp, for (i = 0; i < packets; ++i) { cycle = increment_cycle_count(cycle, 1); - if (handle_out_packet(s, cycle, i) < 0) { + if (handle_out_packet(s, 0, cycle, i) < 0) { s->packet_index = -1; amdtp_stream_pcm_abort(s); return; @@ -631,7 +632,7 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp, { struct amdtp_stream *s = private_data; unsigned int i, packets; - unsigned int payload_quadlets, max_payload_quadlets; + unsigned int payload_length, max_payload_length; __be32 *headers = header; u32 cycle; @@ -647,22 +648,22 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp, cycle = decrement_cycle_count(cycle, packets); /* For buffer-over-run prevention. */ - max_payload_quadlets = amdtp_stream_get_max_payload(s) / 4; + max_payload_length = amdtp_stream_get_max_payload(s); for (i = 0; i < packets; i++) { cycle = increment_cycle_count(cycle, 1); /* The number of quadlets in this packet */ - payload_quadlets = - (be32_to_cpu(headers[i]) >> ISO_DATA_LENGTH_SHIFT) / 4; - if (payload_quadlets > max_payload_quadlets) { + payload_length = + (be32_to_cpu(headers[i]) >> ISO_DATA_LENGTH_SHIFT); + if (payload_length > max_payload_length) { dev_err(&s->unit->device, - "Detect jumbo payload: %02x %02x\n", - payload_quadlets, max_payload_quadlets); + "Detect jumbo payload: %04x %04x\n", + payload_length, max_payload_length); break; } - if (handle_in_packet(s, payload_quadlets, cycle, i) < 0) + if (handle_in_packet(s, payload_length, cycle, i) < 0) break; } -- cgit v1.2.3-59-g8ed1b From 3b196c394dd9f8f34064f5814bb287757c80ee35 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 31 Mar 2017 22:06:07 +0900 Subject: ALSA: firewire-lib: add no-header packet processing As long as investigating Fireface 400, IEC 61883-1/6 is not applied to its packet streaming protocol. Remarks of the specific protocol are: * Each packet doesn't include CIP headers. * 64,0 and 128,0 kHz are supported. * The device doesn't necessarily transmit 8,000 packets per second. * 0, 1, 2, 3 are used as tag for rx isochronous packets, however 0 is used for tx isochronous packets. On the other hand, there's a common feature. The number of data blocks transferred in a second is the same as sampling transmission frequency. Current ALSA IEC 61883-1/6 engine already has a method to calculate it and this driver can utilize it for rx packets, as well as tx packets. This commit adds support for the transferring protocol. CIP_NO_HEADERS flag is newly added. When this flag is set: * Both of 0 (without CIP header) and 1 (with CIP header) are used as tag to handle incoming isochronous packet. * 0 (without CIP header) is used as tag to transfer outgoing isochronous packet. * Skip CIP header evaluation. * Use unique way to calculate the quadlets of isochronous packet payload. In ALSA PCM interface, 128.0 kHz is not supported, and the ALSA IEC 61883-1/6 engine doesn't support 64.0 kHz. These modes are dropped. The sequence of rx packet has a remarkable quirk about tag. This will be described in later commits. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/amdtp-stream.c | 80 ++++++++++++++++++++++++++++++++++++++++--- sound/firewire/amdtp-stream.h | 6 ++++ 2 files changed, 81 insertions(+), 5 deletions(-) (limited to 'sound') diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c index 646e8e390773..a03b37bdc274 100644 --- a/sound/firewire/amdtp-stream.c +++ b/sound/firewire/amdtp-stream.c @@ -27,6 +27,7 @@ /* isochronous header parameters */ #define ISO_DATA_LENGTH_SHIFT 16 +#define TAG_NO_CIP_HEADER 0 #define TAG_CIP 1 /* common isochronous packet header parameters */ @@ -234,11 +235,15 @@ EXPORT_SYMBOL(amdtp_stream_set_parameters); unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s) { unsigned int multiplier = 1; + unsigned int header_size = 0; if (s->flags & CIP_JUMBO_PAYLOAD) multiplier = 5; + if (!(s->flags & CIP_NO_HEADER)) + header_size = 8; - return 8 + s->syt_interval * s->data_block_quadlets * 4 * multiplier; + return header_size + + s->syt_interval * s->data_block_quadlets * 4 * multiplier; } EXPORT_SYMBOL(amdtp_stream_get_max_payload); @@ -380,7 +385,7 @@ static int queue_packet(struct amdtp_stream *s, unsigned int header_length, goto end; p.interrupt = IS_ALIGNED(s->packet_index + 1, INTERRUPT_INTERVAL); - p.tag = TAG_CIP; + p.tag = s->tag; p.header_length = header_length; if (payload_length > 0) p.payload_length = payload_length; @@ -457,6 +462,34 @@ static int handle_out_packet(struct amdtp_stream *s, return 0; } +static int handle_out_packet_without_header(struct amdtp_stream *s, + unsigned int payload_length, unsigned int cycle, + unsigned int index) +{ + __be32 *buffer; + unsigned int syt; + unsigned int data_blocks; + unsigned int pcm_frames; + struct snd_pcm_substream *pcm; + + buffer = s->buffer.packets[s->packet_index].buffer; + syt = calculate_syt(s, cycle); + data_blocks = calculate_data_blocks(s, syt); + pcm_frames = s->process_data_blocks(s, buffer, data_blocks, &syt); + s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; + + payload_length = data_blocks * 4 * s->data_block_quadlets; + if (queue_out_packet(s, payload_length) < 0) + return -EIO; + + pcm = ACCESS_ONCE(s->pcm); + if (pcm && pcm_frames > 0) + update_pcm_pointers(s, pcm, pcm_frames); + + /* No need to return the number of handled data blocks. */ + return 0; +} + static int handle_in_packet(struct amdtp_stream *s, unsigned int payload_length, unsigned int cycle, unsigned int index) @@ -573,6 +606,30 @@ end: return 0; } +static int handle_in_packet_without_header(struct amdtp_stream *s, + unsigned int payload_quadlets, unsigned int cycle, + unsigned int index) +{ + __be32 *buffer; + unsigned int data_blocks; + struct snd_pcm_substream *pcm; + unsigned int pcm_frames; + + buffer = s->buffer.packets[s->packet_index].buffer; + data_blocks = payload_quadlets / s->data_block_quadlets; + pcm_frames = s->process_data_blocks(s, buffer, data_blocks, NULL); + s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; + + if (queue_in_packet(s) < 0) + return -EIO; + + pcm = ACCESS_ONCE(s->pcm); + if (pcm && pcm_frames > 0) + update_pcm_pointers(s, pcm, pcm_frames); + + return 0; +} + /* * In CYCLE_TIMER register of IEEE 1394, 7 bits are used to represent second. On * the other hand, in DMA descriptors of 1394 OHCI, 3 bits are used to represent @@ -616,7 +673,7 @@ static void out_stream_callback(struct fw_iso_context *context, u32 tstamp, for (i = 0; i < packets; ++i) { cycle = increment_cycle_count(cycle, 1); - if (handle_out_packet(s, 0, cycle, i) < 0) { + if (s->handle_packet(s, 0, cycle, i) < 0) { s->packet_index = -1; amdtp_stream_pcm_abort(s); return; @@ -663,7 +720,7 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp, break; } - if (handle_in_packet(s, payload_length, cycle, i) < 0) + if (s->handle_packet(s, payload_length, cycle, i) < 0) break; } @@ -699,10 +756,18 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context, packets = header_length / IN_PACKET_HEADER_SIZE; cycle = decrement_cycle_count(cycle, packets); context->callback.sc = in_stream_callback; + if (s->flags & CIP_NO_HEADER) + s->handle_packet = handle_in_packet_without_header; + else + s->handle_packet = handle_in_packet; } else { packets = header_length / 4; cycle = increment_cycle_count(cycle, QUEUE_LENGTH - packets); context->callback.sc = out_stream_callback; + if (s->flags & CIP_NO_HEADER) + s->handle_packet = handle_out_packet_without_header; + else + s->handle_packet = handle_out_packet; } s->start_cycle = cycle; @@ -782,6 +847,11 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) amdtp_stream_update(s); + if (s->flags & CIP_NO_HEADER) + s->tag = TAG_NO_CIP_HEADER; + else + s->tag = TAG_CIP; + s->packet_index = 0; do { if (s->direction == AMDTP_IN_STREAM) @@ -794,7 +864,7 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) /* NOTE: TAG1 matches CIP. This just affects in stream. */ tag = FW_ISO_CONTEXT_MATCH_TAG1; - if (s->flags & CIP_EMPTY_WITH_TAG0) + if ((s->flags & CIP_EMPTY_WITH_TAG0) || (s->flags & CIP_NO_HEADER)) tag |= FW_ISO_CONTEXT_MATCH_TAG0; s->callbacked = false; diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h index a31dfd849821..2bd4de4c7bb7 100644 --- a/sound/firewire/amdtp-stream.h +++ b/sound/firewire/amdtp-stream.h @@ -31,6 +31,7 @@ * allows 5 times as large as IEC 61883-6 defines. * @CIP_HEADER_WITHOUT_EOH: Only for in-stream. CIP Header doesn't include * valid EOH. + * @CIP_NO_HEADERS: a lack of headers in packets */ enum cip_flags { CIP_NONBLOCKING = 0x00, @@ -42,6 +43,7 @@ enum cip_flags { CIP_EMPTY_HAS_WRONG_DBC = 0x20, CIP_JUMBO_PAYLOAD = 0x40, CIP_HEADER_WITHOUT_EOH = 0x80, + CIP_NO_HEADER = 0x100, }; /** @@ -104,6 +106,10 @@ struct amdtp_stream { struct fw_iso_context *context; struct iso_packets_buffer buffer; int packet_index; + int tag; + int (*handle_packet)(struct amdtp_stream *s, + unsigned int payload_quadlets, unsigned int cycle, + unsigned int index); /* For CIP headers. */ unsigned int source_node_id_field; -- cgit v1.2.3-59-g8ed1b From 6fb7db902bbe6358b39f359b917f10e3c923058c Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 31 Mar 2017 22:06:08 +0900 Subject: ALSA: fireface: add unique data processing layer As long as investigating Fireface 400, format of payload of each isochronous packet is not IEC 61883-1/6, thus its format of data block is not AM824. The remarkable points of the format are: * The payload just consists of some data channels of quadlet size without CIP header. * Each data channels includes data aligned to little endian order. * One data channel consists of two parts; 8 bit ancillary field and 24 bit PCM frame. Due to lack of CIP headers, rx/tx packets include no CIP headers and different way to check packet discontinuity. For tx packet, the ancillary field is used for counter. However, the way of counting is different depending on positions of data channels. At 44.1 kHz, ancillary field in: * 1st/6th/9th/10th/14th/17th data channels: not used for this purpose. * 2nd/18th data channels: incremented every data block (0x00-0xff). * 3rd/4th/5th/11th/12th/13th data channels: incremented every 256 data blocks (0x00-0x07). * 7th/8th/15th/16th data channels: incremented per the number of data blocks in a packet. The increment can occur per packet (0x00-0xff). For tx packet, tag of each isochronous packet is used for this purpose. The value of tag cyclically changes between 0, 1, 2 and 3 in this order. The interval is different depending on sampling transmission frequency. At 44.1/48.0 kHz, it's 256 data blocks. At 88.2 kHz, it's 96 data blocks. The number of data blocks in tx packet is exactly the same as SYT_INTERVAL. There's no empty packet or no-data packet, thus the throughput is not 8,000 packets per sec. On the other hand, the one in rx packet is 8,000 packets per sec, thus the number of data blocks is different between each packet, depending on sampling transmission frequency: * 44.1 kHz: 5 or 6 * 48.0 kHz: 5 or 6 or 7 * 88.2 kHz: 10 or 11 or 12 This commit adds data processing layer to satisfy the above specification in a policy of 'best effort'. Although PCM frames are handled for intermediate buffer to user space, the ancillary data is not handled at all to reduce CPU usage, thus counter is not checked. 0 is always used for tag of isochronous packet. Furthermore, the packet streaming layer is responsible for calculation of the number of data blocks for each packet, thus it's not exactly the same sequence from the above observation. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/fireface/Makefile | 2 +- sound/firewire/fireface/amdtp-ff.c | 155 +++++++++++++++++++++++++++++++++++++ sound/firewire/fireface/ff.h | 8 ++ 3 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/fireface/amdtp-ff.c (limited to 'sound') diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile index e88ff9e2cf47..e06e9da36581 100644 --- a/sound/firewire/fireface/Makefile +++ b/sound/firewire/fireface/Makefile @@ -1,2 +1,2 @@ -snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o +snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/amdtp-ff.c b/sound/firewire/fireface/amdtp-ff.c new file mode 100644 index 000000000000..780da9deb2f0 --- /dev/null +++ b/sound/firewire/fireface/amdtp-ff.c @@ -0,0 +1,155 @@ +/* + * amdtp-ff.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include "ff.h" + +struct amdtp_ff { + unsigned int pcm_channels; +}; + +int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels) +{ + struct amdtp_ff *p = s->protocol; + unsigned int data_channels; + + if (amdtp_stream_running(s)) + return -EBUSY; + + p->pcm_channels = pcm_channels; + data_channels = pcm_channels; + + return amdtp_stream_set_parameters(s, rate, data_channels); +} + +static void write_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __le32 *buffer, unsigned int frames) +{ + struct amdtp_ff *p = s->protocol; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, i, c; + const u32 *src; + + channels = p->pcm_channels; + src = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + buffer[c] = cpu_to_le32(*src); + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void read_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __le32 *buffer, unsigned int frames) +{ + struct amdtp_ff *p = s->protocol; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, i, c; + u32 *dst; + + channels = p->pcm_channels; + dst = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + *dst = le32_to_cpu(buffer[c]) & 0xffffff00; + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, + __le32 *buffer, unsigned int frames) +{ + struct amdtp_ff *p = s->protocol; + unsigned int i, c, channels = p->pcm_channels; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) + buffer[c] = cpu_to_le32(0x00000000); + buffer += s->data_block_quadlets; + } +} + +int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + + return amdtp_stream_add_pcm_hw_constraints(s, runtime); +} + +static unsigned int process_rx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm); + unsigned int pcm_frames; + + if (pcm) { + write_pcm_s32(s, pcm, (__le32 *)buffer, data_blocks); + pcm_frames = data_blocks; + } else { + write_pcm_silence(s, (__le32 *)buffer, data_blocks); + pcm_frames = 0; + } + + return pcm_frames; +} + +static unsigned int process_tx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm); + unsigned int pcm_frames; + + if (pcm) { + read_pcm_s32(s, pcm, (__le32 *)buffer, data_blocks); + pcm_frames = data_blocks; + } else { + pcm_frames = 0; + } + + return pcm_frames; +} + +int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir) +{ + amdtp_stream_process_data_blocks_t process_data_blocks; + + if (dir == AMDTP_IN_STREAM) + process_data_blocks = process_tx_data_blocks; + else + process_data_blocks = process_rx_data_blocks; + + return amdtp_stream_init(s, unit, dir, CIP_NO_HEADER, 0, + process_data_blocks, sizeof(struct amdtp_ff)); +} diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index 2d1fab2c3467..fa7242fd9b8c 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -23,6 +23,7 @@ #include #include "../lib.h" +#include "../amdtp-stream.h" #define SND_FF_STREAM_MODES 3 @@ -99,6 +100,13 @@ int snd_ff_transaction_register(struct snd_ff *ff); int snd_ff_transaction_reregister(struct snd_ff *ff); void snd_ff_transaction_unregister(struct snd_ff *ff); +int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels); +int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); +int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir); + void snd_ff_proc_init(struct snd_ff *ff); int snd_ff_create_midi_devices(struct snd_ff *ff); -- cgit v1.2.3-59-g8ed1b From 75d6d898977830d6d789083bf0a63ea6826124c8 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 31 Mar 2017 22:06:09 +0900 Subject: ALSA: fireface: add stream management functionality This commit adds management functionality for packet streaming. As long as investigating Fireface 400, there're three modes depending on sampling transmission frequency. The number of data channels in each data block is different depending on the mode. The set of available data channels for each mode might be different for each protocol and model. The length of registers for the number of isochronous channel is just three bits, therefore 0-7ch are available. When bus reset occurs on IEEE 1394 bus, the device discontinues to transmit packets. This commit aborts PCM substreams at bus reset handler. As I described in followed commits, The device manages its sampling clock independently of sampling transmission frequency against IEC 61883-6. Thus, it's a lower cost to change the sampling transmission frequency, while data fetch between streaming layer and DSP require larger buffer for resampling. As a result, device latency might tend to be larger than ASICs for IEC 61883-1/6 such as DM1000/DM1100/DM1500 (BeBoB), DiceII/TCD2210/TCD2220/TCD3070 and OXFW970/971. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/fireface/Makefile | 3 +- sound/firewire/fireface/ff-stream.c | 243 ++++++++++++++++++++++++++++++++++++ sound/firewire/fireface/ff.c | 9 ++ sound/firewire/fireface/ff.h | 13 ++ 4 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/fireface/ff-stream.c (limited to 'sound') diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile index e06e9da36581..b772fdc20101 100644 --- a/sound/firewire/fireface/Makefile +++ b/sound/firewire/fireface/Makefile @@ -1,2 +1,3 @@ -snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o +snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \ + ff-stream.o obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/ff-stream.c b/sound/firewire/fireface/ff-stream.c new file mode 100644 index 000000000000..0ef6177aff20 --- /dev/null +++ b/sound/firewire/fireface/ff-stream.c @@ -0,0 +1,243 @@ +/* + * ff-stream.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +#define CALLBACK_TIMEOUT_MS 200 + +static int get_rate_mode(unsigned int rate, unsigned int *mode) +{ + int i; + + for (i = 0; i < CIP_SFC_COUNT; i++) { + if (amdtp_rate_table[i] == rate) + break; + } + + if (i == CIP_SFC_COUNT) + return -EINVAL; + + *mode = ((int)i - 1) / 2; + + return 0; +} + +/* + * Fireface 400 manages isochronous channel number in 3 bit field. Therefore, + * we can allocate between 0 and 7 channel. + */ +static int keep_resources(struct snd_ff *ff, unsigned int rate) +{ + int mode; + int err; + + err = get_rate_mode(rate, &mode); + if (err < 0) + return err; + + /* Keep resources for in-stream. */ + err = amdtp_ff_set_parameters(&ff->tx_stream, rate, + ff->spec->pcm_capture_channels[mode]); + if (err < 0) + return err; + ff->tx_resources.channels_mask = 0x00000000000000ffuLL; + err = fw_iso_resources_allocate(&ff->tx_resources, + amdtp_stream_get_max_payload(&ff->tx_stream), + fw_parent_device(ff->unit)->max_speed); + if (err < 0) + return err; + + /* Keep resources for out-stream. */ + err = amdtp_ff_set_parameters(&ff->rx_stream, rate, + ff->spec->pcm_playback_channels[mode]); + if (err < 0) + return err; + ff->rx_resources.channels_mask = 0x00000000000000ffuLL; + err = fw_iso_resources_allocate(&ff->rx_resources, + amdtp_stream_get_max_payload(&ff->rx_stream), + fw_parent_device(ff->unit)->max_speed); + if (err < 0) + fw_iso_resources_free(&ff->tx_resources); + + return err; +} + +static void release_resources(struct snd_ff *ff) +{ + fw_iso_resources_free(&ff->tx_resources); + fw_iso_resources_free(&ff->rx_resources); +} + +static inline void finish_session(struct snd_ff *ff) +{ + ff->spec->protocol->finish_session(ff); + ff->spec->protocol->switch_fetching_mode(ff, false); +} + +static int init_stream(struct snd_ff *ff, enum amdtp_stream_direction dir) +{ + int err; + struct fw_iso_resources *resources; + struct amdtp_stream *stream; + + if (dir == AMDTP_IN_STREAM) { + resources = &ff->tx_resources; + stream = &ff->tx_stream; + } else { + resources = &ff->rx_resources; + stream = &ff->rx_stream; + } + + err = fw_iso_resources_init(resources, ff->unit); + if (err < 0) + return err; + + err = amdtp_ff_init(stream, ff->unit, dir); + if (err < 0) + fw_iso_resources_destroy(resources); + + return err; +} + +static void destroy_stream(struct snd_ff *ff, enum amdtp_stream_direction dir) +{ + if (dir == AMDTP_IN_STREAM) { + amdtp_stream_destroy(&ff->tx_stream); + fw_iso_resources_destroy(&ff->tx_resources); + } else { + amdtp_stream_destroy(&ff->rx_stream); + fw_iso_resources_destroy(&ff->rx_resources); + } +} + +int snd_ff_stream_init_duplex(struct snd_ff *ff) +{ + int err; + + err = init_stream(ff, AMDTP_OUT_STREAM); + if (err < 0) + goto end; + + err = init_stream(ff, AMDTP_IN_STREAM); + if (err < 0) + destroy_stream(ff, AMDTP_OUT_STREAM); +end: + return err; +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +void snd_ff_stream_destroy_duplex(struct snd_ff *ff) +{ + destroy_stream(ff, AMDTP_IN_STREAM); + destroy_stream(ff, AMDTP_OUT_STREAM); +} + +int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate) +{ + unsigned int curr_rate; + enum snd_ff_clock_src src; + int err; + + if (ff->substreams_counter == 0) + return 0; + + err = ff->spec->protocol->get_clock(ff, &curr_rate, &src); + if (err < 0) + return err; + if (curr_rate != rate || + amdtp_streaming_error(&ff->tx_stream) || + amdtp_streaming_error(&ff->rx_stream)) { + finish_session(ff); + + amdtp_stream_stop(&ff->tx_stream); + amdtp_stream_stop(&ff->rx_stream); + + release_resources(ff); + } + + /* + * Regardless of current source of clock signal, drivers transfer some + * packets. Then, the device transfers packets. + */ + if (!amdtp_stream_running(&ff->rx_stream)) { + err = keep_resources(ff, rate); + if (err < 0) + goto error; + + err = ff->spec->protocol->begin_session(ff, rate); + if (err < 0) + goto error; + + err = amdtp_stream_start(&ff->rx_stream, + ff->rx_resources.channel, + fw_parent_device(ff->unit)->max_speed); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&ff->rx_stream, + CALLBACK_TIMEOUT_MS)) { + err = -ETIMEDOUT; + goto error; + } + + err = ff->spec->protocol->switch_fetching_mode(ff, true); + if (err < 0) + goto error; + } + + if (!amdtp_stream_running(&ff->tx_stream)) { + err = amdtp_stream_start(&ff->tx_stream, + ff->tx_resources.channel, + fw_parent_device(ff->unit)->max_speed); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&ff->tx_stream, + CALLBACK_TIMEOUT_MS)) { + err = -ETIMEDOUT; + goto error; + } + } + + return 0; +error: + amdtp_stream_stop(&ff->tx_stream); + amdtp_stream_stop(&ff->rx_stream); + + finish_session(ff); + release_resources(ff); + + return err; +} + +void snd_ff_stream_stop_duplex(struct snd_ff *ff) +{ + if (ff->substreams_counter > 0) + return; + + amdtp_stream_stop(&ff->tx_stream); + amdtp_stream_stop(&ff->rx_stream); + finish_session(ff); + release_resources(ff); +} + +void snd_ff_stream_update_duplex(struct snd_ff *ff) +{ + /* The device discontinue to transfer packets. */ + amdtp_stream_pcm_abort(&ff->tx_stream); + amdtp_stream_stop(&ff->tx_stream); + + amdtp_stream_pcm_abort(&ff->rx_stream); + amdtp_stream_stop(&ff->rx_stream); + + fw_iso_resources_update(&ff->tx_resources); + fw_iso_resources_update(&ff->rx_resources); +} diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c index 22e7bcb4bd51..6bdbebd9f61b 100644 --- a/sound/firewire/fireface/ff.c +++ b/sound/firewire/fireface/ff.c @@ -29,6 +29,7 @@ static void name_card(struct snd_ff *ff) static void ff_free(struct snd_ff *ff) { + snd_ff_stream_destroy_duplex(ff); snd_ff_transaction_unregister(ff); fw_unit_put(ff->unit); @@ -61,6 +62,10 @@ static void do_registration(struct work_struct *work) name_card(ff); + err = snd_ff_stream_init_duplex(ff); + if (err < 0) + goto error; + snd_ff_proc_init(ff); err = snd_ff_create_midi_devices(ff); @@ -78,6 +83,7 @@ static void do_registration(struct work_struct *work) return; error: snd_ff_transaction_unregister(ff); + snd_ff_stream_destroy_duplex(ff); snd_card_free(ff->card); dev_info(&ff->unit->device, "Sound card registration failed: %d\n", err); @@ -117,6 +123,9 @@ static void snd_ff_update(struct fw_unit *unit) snd_fw_schedule_registration(unit, &ff->dwork); snd_ff_transaction_reregister(ff); + + if (ff->registered) + snd_ff_stream_update_duplex(ff); } static void snd_ff_remove(struct fw_unit *unit) diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index fa7242fd9b8c..6599c11744ae 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -24,6 +24,7 @@ #include "../lib.h" #include "../amdtp-stream.h" +#include "../iso-resources.h" #define SND_FF_STREAM_MODES 3 @@ -68,6 +69,12 @@ struct snd_ff { ktime_t next_ktime[SND_FF_OUT_MIDI_PORTS]; bool rx_midi_error[SND_FF_OUT_MIDI_PORTS]; unsigned int rx_bytes[SND_FF_OUT_MIDI_PORTS]; + + unsigned int substreams_counter; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + struct fw_iso_resources tx_resources; + struct fw_iso_resources rx_resources; }; enum snd_ff_clock_src { @@ -107,6 +114,12 @@ int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s, int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit, enum amdtp_stream_direction dir); +int snd_ff_stream_init_duplex(struct snd_ff *ff); +void snd_ff_stream_destroy_duplex(struct snd_ff *ff); +int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate); +void snd_ff_stream_stop_duplex(struct snd_ff *ff); +void snd_ff_stream_update_duplex(struct snd_ff *ff); + void snd_ff_proc_init(struct snd_ff *ff); int snd_ff_create_midi_devices(struct snd_ff *ff); -- cgit v1.2.3-59-g8ed1b From 4b316436ab2e0b74e4986fc66b7cd7902cfd7054 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 31 Mar 2017 22:06:10 +0900 Subject: ALSA: fireface: add support for PCM functionality This commit adds PCM functionality to transmit/receive PCM frames on isochronous packet streaming. This commit enables userspace applications to start/stop packet streaming via ALSA PCM interface. Sampling rate requested by applications is used as sampling transmission frequency of IEC 61883-1/6packet streaming. As I described in followed commits, units in this series manages sampling clock frequency independently of sampling transmission frequency, and they supports resampling between their packet streaming/data block processing layer and sampling data processing layer. This commit take this driver to utilize these features for usability. When internal clock is selected as source signal of sampling clock, this driver allows user space applications to start PCM substreams at any rate which packet streaming engine supports as sampling transmission frequency. In this case, this driver expects units to perform resampling PCM frames for rx/tx packets when sampling clock frequency and sampling transmission frequency are mismatched. This is for daily use cases. When any external clock is selected as the source signal, this driver gets configured sampling rate from units, then restricts available sampling rate to the rate for PCM applications. This is for studio use cases. Models in this series supports 64.0/128.0 kHz of sampling rate, however these frequencies are not supported by IEC 61883-6 as sampling transmission frequency. Therefore, packet streaming engine of ALSA firewire stack can't handle them. When units are configured to use any external clock as source signal of sampling clock and one of these unsupported rate is configured as rate of the sampling clock, this driver returns EIO to user space applications. Anyway, this driver doesn't voluntarily configure parameters of sampling clock. It's better for users to work with appropriate user space implementations to configure the parameters in advance of usage. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/fireface/Makefile | 2 +- sound/firewire/fireface/ff-pcm.c | 395 +++++++++++++++++++++++++++++++++++++++ sound/firewire/fireface/ff.c | 4 + sound/firewire/fireface/ff.h | 4 + 4 files changed, 404 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/fireface/ff-pcm.c (limited to 'sound') diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile index b772fdc20101..e62693811519 100644 --- a/sound/firewire/fireface/Makefile +++ b/sound/firewire/fireface/Makefile @@ -1,3 +1,3 @@ snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \ - ff-stream.o + ff-stream.o ff-pcm.o obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/ff-pcm.c b/sound/firewire/fireface/ff-pcm.c new file mode 100644 index 000000000000..d282467d39a6 --- /dev/null +++ b/sound/firewire/fireface/ff-pcm.c @@ -0,0 +1,395 @@ +/* + * ff-pcm.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +static inline unsigned int get_multiplier_mode_with_index(unsigned int index) +{ + return ((int)index - 1) / 2; +} + +static int hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + const unsigned int *pcm_channels = rule->private; + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, mode; + + for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (!snd_interval_test(c, pcm_channels[mode])) + continue; + + t.min = min(t.min, amdtp_rate_table[i]); + t.max = max(t.max, amdtp_rate_table[i]); + } + + return snd_interval_refine(r, &t); +} + +static int hw_rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + const unsigned int *pcm_channels = rule->private; + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, mode; + + for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (!snd_interval_test(r, amdtp_rate_table[i])) + continue; + + t.min = min(t.min, pcm_channels[mode]); + t.max = max(t.max, pcm_channels[mode]); + } + + return snd_interval_refine(c, &t); +} + +static void limit_channels_and_rates(struct snd_pcm_hardware *hw, + const unsigned int *pcm_channels) +{ + unsigned int mode; + unsigned int rate, channels; + int i; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + hw->rate_min = UINT_MAX; + hw->rate_max = 0; + + for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) { + mode = get_multiplier_mode_with_index(i); + + channels = pcm_channels[mode]; + if (pcm_channels[mode] == 0) + continue; + hw->channels_min = min(hw->channels_min, channels); + hw->channels_max = max(hw->channels_max, channels); + + rate = amdtp_rate_table[i]; + hw->rates |= snd_pcm_rate_to_rate_bit(rate); + hw->rate_min = min(hw->rate_min, rate); + hw->rate_max = max(hw->rate_max, rate); + } +} + +static void limit_period_and_buffer(struct snd_pcm_hardware *hw) +{ + hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */ + hw->periods_max = UINT_MAX; + + hw->period_bytes_min = 4 * hw->channels_max; /* bytes for a frame */ + + /* Just to prevent from allocating much pages. */ + hw->period_bytes_max = hw->period_bytes_min * 2048; + hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min; +} + +static int pcm_init_hw_params(struct snd_ff *ff, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct amdtp_stream *s; + const unsigned int *pcm_channels; + int err; + + runtime->hw.info = SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + s = &ff->tx_stream; + pcm_channels = ff->spec->pcm_capture_channels; + } else { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + s = &ff->rx_stream; + pcm_channels = ff->spec->pcm_playback_channels; + } + + /* limit rates */ + limit_channels_and_rates(&runtime->hw, pcm_channels); + limit_period_and_buffer(&runtime->hw); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, (void *)pcm_channels, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, (void *)pcm_channels, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + + return amdtp_ff_add_pcm_hw_constraints(s, runtime); +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + unsigned int rate; + enum snd_ff_clock_src src; + int i, err; + + err = pcm_init_hw_params(ff, substream); + if (err < 0) + return err; + + err = ff->spec->protocol->get_clock(ff, &rate, &src); + if (err < 0) + return err; + + if (src != SND_FF_CLOCK_SRC_INTERNAL) { + for (i = 0; i < CIP_SFC_COUNT; ++i) { + if (amdtp_rate_table[i] == rate) + break; + } + /* + * The unit is configured at sampling frequency which packet + * streaming engine can't support. + */ + if (i >= CIP_SFC_COUNT) + return -EIO; + + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } else { + if (amdtp_stream_pcm_running(&ff->rx_stream) || + amdtp_stream_pcm_running(&ff->tx_stream)) { + rate = amdtp_rate_table[ff->rx_stream.sfc]; + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } + } + + snd_pcm_set_sync(substream); + + return 0; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_ff *ff = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&ff->mutex); + ff->substreams_counter++; + mutex_unlock(&ff->mutex); + } + + return 0; +} + +static int pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_ff *ff = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&ff->mutex); + ff->substreams_counter++; + mutex_unlock(&ff->mutex); + } + + return 0; +} + +static int pcm_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + + mutex_lock(&ff->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + ff->substreams_counter--; + + snd_ff_stream_stop_duplex(ff); + + mutex_unlock(&ff->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + + mutex_lock(&ff->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + ff->substreams_counter--; + + snd_ff_stream_stop_duplex(ff); + + mutex_unlock(&ff->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&ff->mutex); + + err = snd_ff_stream_start_duplex(ff, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&ff->tx_stream); + + mutex_unlock(&ff->mutex); + + return err; +} + +static int pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&ff->mutex); + + err = snd_ff_stream_start_duplex(ff, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&ff->rx_stream); + + mutex_unlock(&ff->mutex); + + return err; +} + +static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_ff *ff = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&ff->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&ff->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_ff *ff = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&ff->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&ff->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_ff *ff = sbstrm->private_data; + + return amdtp_stream_pcm_pointer(&ff->tx_stream); +} + +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_ff *ff = sbstrm->private_data; + + return amdtp_stream_pcm_pointer(&ff->rx_stream); +} + +static struct snd_pcm_ops pcm_capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_capture_hw_params, + .hw_free = pcm_capture_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static struct snd_pcm_ops pcm_playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_playback_hw_params, + .hw_free = pcm_playback_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, +}; + +int snd_ff_create_pcm_devices(struct snd_ff *ff) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(ff->card, ff->card->driver, 0, 1, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = ff; + snprintf(pcm->name, sizeof(pcm->name), + "%s PCM", ff->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops); + + return 0; +} diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c index 6bdbebd9f61b..ff62d16fec0f 100644 --- a/sound/firewire/fireface/ff.c +++ b/sound/firewire/fireface/ff.c @@ -72,6 +72,10 @@ static void do_registration(struct work_struct *work) if (err < 0) goto error; + err = snd_ff_create_pcm_devices(ff); + if (err < 0) + goto error; + err = snd_card_register(ff->card); if (err < 0) goto error; diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index 6599c11744ae..0d5228c905ea 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include "../lib.h" #include "../amdtp-stream.h" @@ -124,4 +126,6 @@ void snd_ff_proc_init(struct snd_ff *ff); int snd_ff_create_midi_devices(struct snd_ff *ff); +int snd_ff_create_pcm_devices(struct snd_ff *ff); + #endif -- cgit v1.2.3-59-g8ed1b From f656edd5fb33d889561978b81ec2897087c2f4ca Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 31 Mar 2017 22:06:11 +0900 Subject: ALSA: fireface: add hwdep interface This commit adds hwdep interface so as the other drivers for audio and music units on IEEE 1394 have. This interface is designed for mixer/control applications. By using this interface, an application can get information about firewire node, can lock/unlock kernel streaming and can get notification at starting/stopping kernel streaming. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- include/uapi/sound/asound.h | 3 +- include/uapi/sound/firewire.h | 2 +- sound/firewire/Kconfig | 1 + sound/firewire/fireface/Makefile | 2 +- sound/firewire/fireface/ff-hwdep.c | 191 ++++++++++++++++++++++++++++++++++++ sound/firewire/fireface/ff-pcm.c | 20 +++- sound/firewire/fireface/ff-stream.c | 39 ++++++++ sound/firewire/fireface/ff.c | 5 + sound/firewire/fireface/ff.h | 13 +++ 9 files changed, 270 insertions(+), 6 deletions(-) create mode 100644 sound/firewire/fireface/ff-hwdep.c (limited to 'sound') diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index fd7b561af768..fd41697cb4d3 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -108,9 +108,10 @@ enum { SNDRV_HWDEP_IFACE_FW_TASCAM, /* TASCAM FireWire series */ SNDRV_HWDEP_IFACE_LINE6, /* Line6 USB processors */ SNDRV_HWDEP_IFACE_FW_MOTU, /* MOTU FireWire series */ + SNDRV_HWDEP_IFACE_FW_FIREFACE, /* RME Fireface series */ /* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_MOTU + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_FIREFACE }; struct snd_hwdep_info { diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index 29afc5eab42d..622900488bdc 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -73,7 +73,7 @@ union snd_firewire_event { #define SNDRV_FIREWIRE_TYPE_DIGI00X 5 #define SNDRV_FIREWIRE_TYPE_TASCAM 6 #define SNDRV_FIREWIRE_TYPE_MOTU 7 -/* RME... */ +#define SNDRV_FIREWIRE_TYPE_FIREFACE 8 struct snd_firewire_get_info { unsigned int type; /* SNDRV_FIREWIRE_TYPE_xxx */ diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index b75a82288f74..70f02eea4a3e 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -155,6 +155,7 @@ config SND_FIREWIRE_MOTU config SND_FIREFACE tristate "RME Fireface series support" select SND_FIREWIRE_LIB + select SND_HWDEP help Say Y here to include support for RME fireface series. diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile index e62693811519..8d6c612a15a0 100644 --- a/sound/firewire/fireface/Makefile +++ b/sound/firewire/fireface/Makefile @@ -1,3 +1,3 @@ snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \ - ff-stream.o ff-pcm.o + ff-stream.o ff-pcm.o ff-hwdep.o obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/ff-hwdep.c b/sound/firewire/fireface/ff-hwdep.c new file mode 100644 index 000000000000..3ee04b054585 --- /dev/null +++ b/sound/firewire/fireface/ff-hwdep.c @@ -0,0 +1,191 @@ +/* + * ff-hwdep.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node information + * 2.get notification about starting/stopping stream + * 3.lock/unlock stream + */ + +#include "ff.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_ff *ff = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&ff->lock); + + while (!ff->dev_lock_changed) { + prepare_to_wait(&ff->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&ff->lock); + schedule(); + finish_wait(&ff->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&ff->lock); + } + + memset(&event, 0, sizeof(event)); + if (ff->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (ff->dev_lock_count > 0); + ff->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } + + spin_unlock_irq(&ff->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_ff *ff = hwdep->private_data; + unsigned int events; + + poll_wait(file, &ff->hwdep_wait, wait); + + spin_lock_irq(&ff->lock); + if (ff->dev_lock_changed) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&ff->lock); + + return events; +} + +static int hwdep_get_info(struct snd_ff *ff, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(ff->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_FIREFACE; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int hwdep_lock(struct snd_ff *ff) +{ + int err; + + spin_lock_irq(&ff->lock); + + if (ff->dev_lock_count == 0) { + ff->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&ff->lock); + + return err; +} + +static int hwdep_unlock(struct snd_ff *ff) +{ + int err; + + spin_lock_irq(&ff->lock); + + if (ff->dev_lock_count == -1) { + ff->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&ff->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_ff *ff = hwdep->private_data; + + spin_lock_irq(&ff->lock); + if (ff->dev_lock_count == -1) + ff->dev_lock_count = 0; + spin_unlock_irq(&ff->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_ff *ff = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(ff, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(ff); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(ff); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +int snd_ff_create_hwdep_devices(struct snd_ff *ff) +{ + static const struct snd_hwdep_ops hwdep_ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(ff->card, ff->card->driver, 0, &hwdep); + if (err < 0) + return err; + + strcpy(hwdep->name, ff->card->driver); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREFACE; + hwdep->ops = hwdep_ops; + hwdep->private_data = ff; + hwdep->exclusive = true; + + return 0; +} diff --git a/sound/firewire/fireface/ff-pcm.c b/sound/firewire/fireface/ff-pcm.c index d282467d39a6..93cee1978e8e 100644 --- a/sound/firewire/fireface/ff-pcm.c +++ b/sound/firewire/fireface/ff-pcm.c @@ -154,13 +154,21 @@ static int pcm_open(struct snd_pcm_substream *substream) enum snd_ff_clock_src src; int i, err; - err = pcm_init_hw_params(ff, substream); + err = snd_ff_stream_lock_try(ff); if (err < 0) return err; + err = pcm_init_hw_params(ff, substream); + if (err < 0) { + snd_ff_stream_lock_release(ff); + return err; + } + err = ff->spec->protocol->get_clock(ff, &rate, &src); - if (err < 0) + if (err < 0) { + snd_ff_stream_lock_release(ff); return err; + } if (src != SND_FF_CLOCK_SRC_INTERNAL) { for (i = 0; i < CIP_SFC_COUNT; ++i) { @@ -171,8 +179,10 @@ static int pcm_open(struct snd_pcm_substream *substream) * The unit is configured at sampling frequency which packet * streaming engine can't support. */ - if (i >= CIP_SFC_COUNT) + if (i >= CIP_SFC_COUNT) { + snd_ff_stream_lock_release(ff); return -EIO; + } substream->runtime->hw.rate_min = rate; substream->runtime->hw.rate_max = rate; @@ -192,6 +202,10 @@ static int pcm_open(struct snd_pcm_substream *substream) static int pcm_close(struct snd_pcm_substream *substream) { + struct snd_ff *ff = substream->private_data; + + snd_ff_stream_lock_release(ff); + return 0; } diff --git a/sound/firewire/fireface/ff-stream.c b/sound/firewire/fireface/ff-stream.c index 0ef6177aff20..78880922120e 100644 --- a/sound/firewire/fireface/ff-stream.c +++ b/sound/firewire/fireface/ff-stream.c @@ -241,3 +241,42 @@ void snd_ff_stream_update_duplex(struct snd_ff *ff) fw_iso_resources_update(&ff->tx_resources); fw_iso_resources_update(&ff->rx_resources); } + +void snd_ff_stream_lock_changed(struct snd_ff *ff) +{ + ff->dev_lock_changed = true; + wake_up(&ff->hwdep_wait); +} + +int snd_ff_stream_lock_try(struct snd_ff *ff) +{ + int err; + + spin_lock_irq(&ff->lock); + + /* user land lock this */ + if (ff->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (ff->dev_lock_count++ == 0) + snd_ff_stream_lock_changed(ff); + err = 0; +end: + spin_unlock_irq(&ff->lock); + return err; +} + +void snd_ff_stream_lock_release(struct snd_ff *ff) +{ + spin_lock_irq(&ff->lock); + + if (WARN_ON(ff->dev_lock_count <= 0)) + goto end; + if (--ff->dev_lock_count == 0) + snd_ff_stream_lock_changed(ff); +end: + spin_unlock_irq(&ff->lock); +} diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c index ff62d16fec0f..f57b434144dc 100644 --- a/sound/firewire/fireface/ff.c +++ b/sound/firewire/fireface/ff.c @@ -76,6 +76,10 @@ static void do_registration(struct work_struct *work) if (err < 0) goto error; + err = snd_ff_create_hwdep_devices(ff); + if (err < 0) + goto error; + err = snd_card_register(ff->card); if (err < 0) goto error; @@ -108,6 +112,7 @@ static int snd_ff_probe(struct fw_unit *unit, mutex_init(&ff->mutex); spin_lock_init(&ff->lock); + init_waitqueue_head(&ff->hwdep_wait); ff->spec = (const struct snd_ff_spec *)entry->driver_data; diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index 0d5228c905ea..a143b5ab8b71 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -17,12 +17,15 @@ #include #include #include +#include #include #include #include #include #include +#include +#include #include "../lib.h" #include "../amdtp-stream.h" @@ -77,6 +80,10 @@ struct snd_ff { struct amdtp_stream rx_stream; struct fw_iso_resources tx_resources; struct fw_iso_resources rx_resources; + + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; }; enum snd_ff_clock_src { @@ -122,10 +129,16 @@ int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate); void snd_ff_stream_stop_duplex(struct snd_ff *ff); void snd_ff_stream_update_duplex(struct snd_ff *ff); +void snd_ff_stream_lock_changed(struct snd_ff *ff); +int snd_ff_stream_lock_try(struct snd_ff *ff); +void snd_ff_stream_lock_release(struct snd_ff *ff); + void snd_ff_proc_init(struct snd_ff *ff); int snd_ff_create_midi_devices(struct snd_ff *ff); int snd_ff_create_pcm_devices(struct snd_ff *ff); +int snd_ff_create_hwdep_devices(struct snd_ff *ff); + #endif -- cgit v1.2.3-59-g8ed1b From 76fdb3a9e13a781df8bf8652312f6a7cbf5e2f43 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 31 Mar 2017 22:06:12 +0900 Subject: ALSA: fireface: add support for Fireface 400 Fireface 400 is a second model of RME Fireface series, released in 2006. This commit adds support for this model. This model supports 8 analog channels, 2 S/PDIF channels and 8 ADAT channels in both of tx/rx packet. The number of ADAT channels differs depending on each mode of sampling transmission frequency. $ python2 linux-firewire-utils/src/crpp < /sys/bus/firewire/devices/fw1/config_rom ROM header and bus information block ----------------------------------------------------------------- 400 04107768 bus_info_length 4, crc_length 16, crc 30568 (should be 61311) 404 31333934 bus_name "1394" 408 20009002 irmc 0, cmc 0, isc 1, bmc 0, cyc_clk_acc 0, max_rec 9 (1024) 40c 000a3501 company_id 000a35 | 410 1bd0862a device_id 011bd0862a | EUI-64 000a35011bd0862a root directory ----------------------------------------------------------------- 414 000485ec directory_length 4, crc 34284 418 03000a35 vendor 41c 0c0083c0 node capabilities per IEEE 1394 420 8d000006 --> eui-64 leaf at 438 424 d1000001 --> unit directory at 428 unit directory at 428 ----------------------------------------------------------------- 428 000314c4 directory_length 3, crc 5316 42c 12000a35 specifier id 430 13000002 version 434 17101800 model eui-64 leaf at 438 ----------------------------------------------------------------- 438 000261a8 leaf_length 2, crc 25000 43c 000a3501 company_id 000a35 | 440 1bd0862a device_id 011bd0862a | EUI-64 000a35011bd0862a Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/Kconfig | 1 + sound/firewire/fireface/Makefile | 2 +- sound/firewire/fireface/ff-protocol-ff400.c | 371 ++++++++++++++++++++++++++++ sound/firewire/fireface/ff.c | 21 ++ sound/firewire/fireface/ff.h | 2 + 5 files changed, 396 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/fireface/ff-protocol-ff400.c (limited to 'sound') diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 70f02eea4a3e..529d9f405fa9 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -158,5 +158,6 @@ config SND_FIREFACE select SND_HWDEP help Say Y here to include support for RME fireface series. + * Fireface 400 endif # SND_FIREWIRE diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile index 8d6c612a15a0..8f807284ba54 100644 --- a/sound/firewire/fireface/Makefile +++ b/sound/firewire/fireface/Makefile @@ -1,3 +1,3 @@ snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \ - ff-stream.o ff-pcm.o ff-hwdep.o + ff-stream.o ff-pcm.o ff-hwdep.o ff-protocol-ff400.o obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/ff-protocol-ff400.c b/sound/firewire/fireface/ff-protocol-ff400.c new file mode 100644 index 000000000000..fcec6de80eeb --- /dev/null +++ b/sound/firewire/fireface/ff-protocol-ff400.c @@ -0,0 +1,371 @@ +/* + * ff-protocol-ff400.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include "ff.h" + +#define FF400_STF 0x000080100500ull +#define FF400_RX_PACKET_FORMAT 0x000080100504ull +#define FF400_ISOC_COMM_START 0x000080100508ull +#define FF400_TX_PACKET_FORMAT 0x00008010050cull +#define FF400_ISOC_COMM_STOP 0x000080100510ull +#define FF400_SYNC_STATUS 0x0000801c0000ull +#define FF400_FETCH_PCM_FRAMES 0x0000801c0000ull /* For block request. */ +#define FF400_CLOCK_CONFIG 0x0000801c0004ull + +#define FF400_MIDI_HIGH_ADDR 0x0000801003f4ull +#define FF400_MIDI_RX_PORT_0 0x000080180000ull +#define FF400_MIDI_RX_PORT_1 0x000080190000ull + +static int ff400_get_clock(struct snd_ff *ff, unsigned int *rate, + enum snd_ff_clock_src *src) +{ + __le32 reg; + u32 data; + int err; + + err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST, + FF400_SYNC_STATUS, ®, sizeof(reg), 0); + if (err < 0) + return err; + data = le32_to_cpu(reg); + + /* Calculate sampling rate. */ + switch ((data >> 1) & 0x03) { + case 0x01: + *rate = 32000; + break; + case 0x00: + *rate = 44100; + break; + case 0x03: + *rate = 48000; + break; + case 0x02: + default: + return -EIO; + } + + if (data & 0x08) + *rate *= 2; + else if (data & 0x10) + *rate *= 4; + + /* Calculate source of clock. */ + if (data & 0x01) { + *src = SND_FF_CLOCK_SRC_INTERNAL; + } else { + /* TODO: 0x00, 0x01, 0x02, 0x06, 0x07? */ + switch ((data >> 10) & 0x07) { + case 0x03: + *src = SND_FF_CLOCK_SRC_SPDIF; + break; + case 0x04: + *src = SND_FF_CLOCK_SRC_WORD; + break; + case 0x05: + *src = SND_FF_CLOCK_SRC_LTC; + break; + case 0x00: + default: + *src = SND_FF_CLOCK_SRC_ADAT; + break; + } + } + + return 0; +} + +static int ff400_begin_session(struct snd_ff *ff, unsigned int rate) +{ + __le32 reg; + int i, err; + + /* Check whether the given value is supported or not. */ + for (i = 0; i < CIP_SFC_COUNT; i++) { + if (amdtp_rate_table[i] == rate) + break; + } + if (i == CIP_SFC_COUNT) + return -EINVAL; + + /* Set the number of data blocks transferred in a second. */ + reg = cpu_to_le32(rate); + err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_STF, ®, sizeof(reg), 0); + if (err < 0) + return err; + + msleep(100); + + /* + * Set isochronous channel and the number of quadlets of received + * packets. + */ + reg = cpu_to_le32(((ff->rx_stream.data_block_quadlets << 3) << 8) | + ff->rx_resources.channel); + err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_RX_PACKET_FORMAT, ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* + * Set isochronous channel and the number of quadlets of transmitted + * packet. + */ + /* TODO: investigate the purpose of this 0x80. */ + reg = cpu_to_le32((0x80 << 24) | + (ff->tx_resources.channel << 5) | + (ff->tx_stream.data_block_quadlets)); + err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_TX_PACKET_FORMAT, ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* Allow to transmit packets. */ + reg = cpu_to_le32(0x00000001); + return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_ISOC_COMM_START, ®, sizeof(reg), 0); +} + +static void ff400_finish_session(struct snd_ff *ff) +{ + __le32 reg; + + reg = cpu_to_le32(0x80000000); + snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_ISOC_COMM_STOP, ®, sizeof(reg), 0); +} + +static int ff400_switch_fetching_mode(struct snd_ff *ff, bool enable) +{ + __le32 *reg; + int i; + + reg = kzalloc(sizeof(__le32) * 18, GFP_KERNEL); + if (reg == NULL) + return -ENOMEM; + + if (enable) { + /* + * Each quadlet is corresponding to data channels in a data + * blocks in reverse order. Precisely, quadlets for available + * data channels should be enabled. Here, I take second best + * to fetch PCM frames from all of data channels regardless of + * stf. + */ + for (i = 0; i < 18; ++i) + reg[i] = cpu_to_le32(0x00000001); + } + + return snd_fw_transaction(ff->unit, TCODE_WRITE_BLOCK_REQUEST, + FF400_FETCH_PCM_FRAMES, reg, + sizeof(__le32) * 18, 0); +} + +static void ff400_dump_sync_status(struct snd_ff *ff, + struct snd_info_buffer *buffer) +{ + __le32 reg; + u32 data; + int err; + + err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST, + FF400_SYNC_STATUS, ®, sizeof(reg), 0); + if (err < 0) + return; + + data = le32_to_cpu(reg); + + snd_iprintf(buffer, "External source detection:\n"); + + snd_iprintf(buffer, "Word Clock:"); + if ((data >> 24) & 0x20) { + if ((data >> 24) & 0x40) + snd_iprintf(buffer, "sync\n"); + else + snd_iprintf(buffer, "lock\n"); + } else { + snd_iprintf(buffer, "none\n"); + } + + snd_iprintf(buffer, "S/PDIF:"); + if ((data >> 16) & 0x10) { + if ((data >> 16) & 0x04) + snd_iprintf(buffer, "sync\n"); + else + snd_iprintf(buffer, "lock\n"); + } else { + snd_iprintf(buffer, "none\n"); + } + + snd_iprintf(buffer, "ADAT:"); + if ((data >> 8) & 0x04) { + if ((data >> 8) & 0x10) + snd_iprintf(buffer, "sync\n"); + else + snd_iprintf(buffer, "lock\n"); + } else { + snd_iprintf(buffer, "none\n"); + } + + snd_iprintf(buffer, "\nUsed external source:\n"); + + if (((data >> 22) & 0x07) == 0x07) { + snd_iprintf(buffer, "None\n"); + } else { + switch ((data >> 22) & 0x07) { + case 0x00: + snd_iprintf(buffer, "ADAT:"); + break; + case 0x03: + snd_iprintf(buffer, "S/PDIF:"); + break; + case 0x04: + snd_iprintf(buffer, "Word:"); + break; + case 0x07: + snd_iprintf(buffer, "Nothing:"); + break; + case 0x01: + case 0x02: + case 0x05: + case 0x06: + default: + snd_iprintf(buffer, "unknown:"); + break; + } + + if ((data >> 25) & 0x07) { + switch ((data >> 25) & 0x07) { + case 0x01: + snd_iprintf(buffer, "32000\n"); + break; + case 0x02: + snd_iprintf(buffer, "44100\n"); + break; + case 0x03: + snd_iprintf(buffer, "48000\n"); + break; + case 0x04: + snd_iprintf(buffer, "64000\n"); + break; + case 0x05: + snd_iprintf(buffer, "88200\n"); + break; + case 0x06: + snd_iprintf(buffer, "96000\n"); + break; + case 0x07: + snd_iprintf(buffer, "128000\n"); + break; + case 0x08: + snd_iprintf(buffer, "176400\n"); + break; + case 0x09: + snd_iprintf(buffer, "192000\n"); + break; + case 0x00: + snd_iprintf(buffer, "unknown\n"); + break; + } + } + } + + snd_iprintf(buffer, "Multiplied:"); + snd_iprintf(buffer, "%d\n", (data & 0x3ff) * 250); +} + +static void ff400_dump_clock_config(struct snd_ff *ff, + struct snd_info_buffer *buffer) +{ + __le32 reg; + u32 data; + unsigned int rate; + const char *src; + int err; + + err = snd_fw_transaction(ff->unit, TCODE_READ_BLOCK_REQUEST, + FF400_CLOCK_CONFIG, ®, sizeof(reg), 0); + if (err < 0) + return; + + data = le32_to_cpu(reg); + + snd_iprintf(buffer, "Output S/PDIF format: %s (Emphasis: %s)\n", + (data & 0x20) ? "Professional" : "Consumer", + (data & 0x40) ? "on" : "off"); + + snd_iprintf(buffer, "Optical output interface format: %s\n", + ((data >> 8) & 0x01) ? "S/PDIF" : "ADAT"); + + snd_iprintf(buffer, "Word output single speed: %s\n", + ((data >> 8) & 0x20) ? "on" : "off"); + + snd_iprintf(buffer, "S/PDIF input interface: %s\n", + ((data >> 8) & 0x02) ? "Optical" : "Coaxial"); + + switch ((data >> 1) & 0x03) { + case 0x01: + rate = 32000; + break; + case 0x00: + rate = 44100; + break; + case 0x03: + rate = 48000; + break; + case 0x02: + default: + return; + } + + if (data & 0x08) + rate *= 2; + else if (data & 0x10) + rate *= 4; + + snd_iprintf(buffer, "Sampling rate: %d\n", rate); + + if (data & 0x01) { + src = "Internal"; + } else { + switch ((data >> 10) & 0x07) { + case 0x00: + src = "ADAT"; + break; + case 0x03: + src = "S/PDIF"; + break; + case 0x04: + src = "Word"; + break; + case 0x05: + src = "LTC"; + break; + default: + return; + } + } + + snd_iprintf(buffer, "Sync to clock source: %s\n", src); +} + +struct snd_ff_protocol snd_ff_protocol_ff400 = { + .get_clock = ff400_get_clock, + .begin_session = ff400_begin_session, + .finish_session = ff400_finish_session, + .switch_fetching_mode = ff400_switch_fetching_mode, + + .dump_sync_status = ff400_dump_sync_status, + .dump_clock_config = ff400_dump_clock_config, + + .midi_high_addr_reg = FF400_MIDI_HIGH_ADDR, + .midi_rx_port_0_reg = FF400_MIDI_RX_PORT_0, + .midi_rx_port_1_reg = FF400_MIDI_RX_PORT_1, +}; diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c index f57b434144dc..eee7c8eac7a6 100644 --- a/sound/firewire/fireface/ff.c +++ b/sound/firewire/fireface/ff.c @@ -157,7 +157,28 @@ static void snd_ff_remove(struct fw_unit *unit) } } +static struct snd_ff_spec spec_ff400 = { + .name = "Fireface400", + .pcm_capture_channels = {18, 14, 10}, + .pcm_playback_channels = {18, 14, 10}, + .midi_in_ports = 2, + .midi_out_ports = 2, + .protocol = &snd_ff_protocol_ff400, +}; + static const struct ieee1394_device_id snd_ff_id_table[] = { + /* Fireface 400 */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_RME, + .specifier_id = 0x000a35, + .version = 0x000002, + .model_id = 0x101800, + .driver_data = (kernel_ulong_t)&spec_ff400, + }, {} }; MODULE_DEVICE_TABLE(ieee1394, snd_ff_id_table); diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index a143b5ab8b71..3cb812a50030 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -112,6 +112,8 @@ struct snd_ff_protocol { u64 midi_rx_port_1_reg; }; +extern struct snd_ff_protocol snd_ff_protocol_ff400; + int snd_ff_transaction_register(struct snd_ff *ff); int snd_ff_transaction_reregister(struct snd_ff *ff); void snd_ff_transaction_unregister(struct snd_ff *ff); -- cgit v1.2.3-59-g8ed1b From 13e005f9f933a35b5e55c9d36f151efe2a8383ec Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sun, 2 Apr 2017 23:48:24 +0900 Subject: ALSA: firewire-digi00x: add support for console models of Digi00x series Digi00x series includes two types of unit; rack and console. As long as reading information on config rom of Digi 002 console, 'MODEL_ID' field has a different value from the one on Digi 002 rack. We've already got a test report from users with Digi 003 rack. We can assume that console type and rack type has different value in the field. This commit adds a device entry for console type. For following commits, this commit also adds a member to 'struct snd_digi00x' to identify console type. $ cd linux-firewire-utils/src $ python2 ./crpp < /sys/bus/firewire/devices/fw1/config_rom ROM header and bus information block ----------------------------------------------------------------- 400 0404f9d0 bus_info_length 4, crc_length 4, crc 63952 404 31333934 bus_name "1394" 408 60647002 irmc 0, cmc 1, isc 1, bmc 0, cyc_clk_acc 100, max_rec 7 (256) 40c 00a07e00 company_id 00a07e | 410 00a30000 device_id 0000a30000 | EUI-64 00a07e0000a30000 root directory ----------------------------------------------------------------- 414 00058a39 directory_length 5, crc 35385 418 0c0043a0 node capabilities 41c 04000001 hardware version 420 0300a07e vendor 424 81000007 --> descriptor leaf at 440 428 d1000001 --> unit directory at 42c unit directory at 42c ----------------------------------------------------------------- 42c 00046674 directory_length 4, crc 26228 430 120000a3 specifier id 434 13000001 version 438 17000001 model 43c 81000007 --> descriptor leaf at 458 descriptor leaf at 440 ----------------------------------------------------------------- 440 00055913 leaf_length 5, crc 22803 444 000050f2 descriptor_type 00, specifier_ID 50f2 448 80000000 44c 44696769 450 64657369 454 676e0000 descriptor leaf at 458 ----------------------------------------------------------------- 458 0004a6fd leaf_length 4, crc 42749 45c 00000000 textual descriptor 460 00000000 minimal ASCII 464 44696769 "Digi" 468 20303032 " 002" Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/digi00x/digi00x.c | 13 +++++++++++-- sound/firewire/digi00x/digi00x.h | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c index cc4776c6ded3..1f5e1d23f31a 100644 --- a/sound/firewire/digi00x/digi00x.c +++ b/sound/firewire/digi00x/digi00x.c @@ -13,7 +13,8 @@ MODULE_AUTHOR("Takashi Sakamoto "); MODULE_LICENSE("GPL v2"); #define VENDOR_DIGIDESIGN 0x00a07e -#define MODEL_DIGI00X 0x000002 +#define MODEL_CONSOLE 0x000001 +#define MODEL_RACK 0x000002 static int name_card(struct snd_dg00x *dg00x) { @@ -129,6 +130,8 @@ static int snd_dg00x_probe(struct fw_unit *unit, spin_lock_init(&dg00x->lock); init_waitqueue_head(&dg00x->hwdep_wait); + dg00x->is_console = entry->model_id == MODEL_CONSOLE; + /* Allocate and register this sound card later. */ INIT_DEFERRABLE_WORK(&dg00x->dwork, do_registration); snd_fw_schedule_registration(unit, &dg00x->dwork); @@ -183,7 +186,13 @@ static const struct ieee1394_device_id snd_dg00x_id_table[] = { .match_flags = IEEE1394_MATCH_VENDOR_ID | IEEE1394_MATCH_MODEL_ID, .vendor_id = VENDOR_DIGIDESIGN, - .model_id = MODEL_DIGI00X, + .model_id = MODEL_CONSOLE, + }, + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = VENDOR_DIGIDESIGN, + .model_id = MODEL_RACK, }, {} }; diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h index 9dc761bdacca..4aefe45d8906 100644 --- a/sound/firewire/digi00x/digi00x.h +++ b/sound/firewire/digi00x/digi00x.h @@ -61,6 +61,7 @@ struct snd_dg00x { /* For asynchronous MIDI controls. */ struct snd_rawmidi_substream *in_control; struct snd_fw_async_midi_port out_control; + bool is_console; }; #define DG00X_ADDR_BASE 0xffffe0000000ull -- cgit v1.2.3-59-g8ed1b From 8820a4cf0cb4cd5c6540a9a18b2cedbdfd5a6891 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sun, 2 Apr 2017 23:48:25 +0900 Subject: ALSA: firewire-digi00x: handle all MIDI messages on streaming packets At a commit 9dc5d31cdceb ("ALSA: firewire-digi00x: handle MIDI messages in isochronous packets"), a functionality to handle MIDI messages on isochronous packet was supported. But this includes some of my misunderstanding. This commit is to fix them. For digi00x series, first data channel of data blocks in rx/tx packet includes MIDI messages. The data channel has 0x80 in 8 bit of its MSB, however it's against IEC 61883-6. Unique data format is applied: - Upper 4 bits of LSB represent port number. - 0x0: port 1. - 0x2: port 2. - 0xe: console port. - Lower 4 bits of LSB represent the number of included MIDI message bytes; 0x0/0x1/0x2. - Two bytes of middle of this data channel have MIDI bytes. Especially, MIDI messages from/to console surface are also transferred by isochronous packets, as well as physical MIDI ports. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/digi00x/amdtp-dot.c | 55 +++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 18 deletions(-) (limited to 'sound') diff --git a/sound/firewire/digi00x/amdtp-dot.c b/sound/firewire/digi00x/amdtp-dot.c index b3cffd01a19f..a4688545339c 100644 --- a/sound/firewire/digi00x/amdtp-dot.c +++ b/sound/firewire/digi00x/amdtp-dot.c @@ -28,6 +28,9 @@ */ #define MAX_MIDI_RX_BLOCKS 8 +/* 3 = MAX(DOT_MIDI_IN_PORTS, DOT_MIDI_OUT_PORTS) + 1. */ +#define MAX_MIDI_PORTS 3 + /* * The double-oh-three algorithm was discovered by Robin Gareus and Damien * Zammit in 2012, with reverse-engineering for Digi 003 Rack. @@ -42,10 +45,8 @@ struct amdtp_dot { unsigned int pcm_channels; struct dot_state state; - unsigned int midi_ports; - /* 2 = MAX(DOT_MIDI_IN_PORTS, DOT_MIDI_OUT_PORTS) */ - struct snd_rawmidi_substream *midi[2]; - int midi_fifo_used[2]; + struct snd_rawmidi_substream *midi[MAX_MIDI_PORTS]; + int midi_fifo_used[MAX_MIDI_PORTS]; int midi_fifo_limit; void (*transfer_samples)(struct amdtp_stream *s, @@ -124,8 +125,8 @@ int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate, return -EBUSY; /* - * A first data channel is for MIDI conformant data channel, the rest is - * Multi Bit Linear Audio data channel. + * A first data channel is for MIDI messages, the rest is Multi Bit + * Linear Audio data channel. */ err = amdtp_stream_set_parameters(s, rate, pcm_channels + 1); if (err < 0) @@ -135,11 +136,6 @@ int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate, p->pcm_channels = pcm_channels; - if (s->direction == AMDTP_IN_STREAM) - p->midi_ports = DOT_MIDI_IN_PORTS; - else - p->midi_ports = DOT_MIDI_OUT_PORTS; - /* * We do not know the actual MIDI FIFO size of most devices. Just * assume two bytes, i.e., one byte can be received over the bus while @@ -281,13 +277,25 @@ static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, b = (u8 *)&buffer[0]; len = 0; - if (port < p->midi_ports && + if (port < MAX_MIDI_PORTS && midi_ratelimit_per_packet(s, port) && p->midi[port] != NULL) len = snd_rawmidi_transmit(p->midi[port], b + 1, 2); if (len > 0) { - b[3] = (0x10 << port) | len; + /* + * Upper 4 bits of LSB represent port number. + * - 0000b: physical MIDI port 1. + * - 0010b: physical MIDI port 2. + * - 1110b: console MIDI port. + */ + if (port == 2) + b[3] = 0xe0; + else if (port == 1) + b[3] = 0x20; + else + b[3] = 0x00; + b[3] |= len; midi_use_bytes(s, port, len); } else { b[1] = 0; @@ -309,11 +317,22 @@ static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer, for (f = 0; f < data_blocks; f++) { b = (u8 *)&buffer[0]; - port = b[3] >> 4; - len = b[3] & 0x0f; - if (port < p->midi_ports && p->midi[port] && len > 0) - snd_rawmidi_receive(p->midi[port], b + 1, len); + len = b[3] & 0x0f; + if (len > 0) { + /* + * Upper 4 bits of LSB represent port number. + * - 0000b: physical MIDI port 1. Use port 0. + * - 1110b: console MIDI port. Use port 2. + */ + if (b[3] >> 4 > 0) + port = 2; + else + port = 0; + + if (port < MAX_MIDI_PORTS && p->midi[port]) + snd_rawmidi_receive(p->midi[port], b + 1, len); + } buffer += s->data_block_quadlets; } @@ -364,7 +383,7 @@ void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port, { struct amdtp_dot *p = s->protocol; - if (port < p->midi_ports) + if (port < MAX_MIDI_PORTS) ACCESS_ONCE(p->midi[port]) = midi; } -- cgit v1.2.3-59-g8ed1b From 0c3f15f39cfd7697e0c4979a85fef1a3c3d17248 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sun, 2 Apr 2017 23:48:26 +0900 Subject: ALSA: firewire-digi00x: allow user space applications to read/write MIDI messages for all ports At a commit c5fcee0373b3 ("ALSA: firewire-digi00x: add MIDI operations for MIDI control port"), I described that MIDI messages for control surface is transferred by a different way from the messages for physical ports. However, this is wrong. MIDI messages to/from all of MIDI ports are transferred by isochronous packets. This commit removes codes to transfer MIDI messages via asynchronous transaction, from MIDI handling layer. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/digi00x/digi00x-midi.c | 208 +++++++++++++--------------------- 1 file changed, 79 insertions(+), 129 deletions(-) (limited to 'sound') diff --git a/sound/firewire/digi00x/digi00x-midi.c b/sound/firewire/digi00x/digi00x-midi.c index 915d2a21223e..7ab3d0810f6b 100644 --- a/sound/firewire/digi00x/digi00x-midi.c +++ b/sound/firewire/digi00x/digi00x-midi.c @@ -8,7 +8,7 @@ #include "digi00x.h" -static int midi_phys_open(struct snd_rawmidi_substream *substream) +static int midi_open(struct snd_rawmidi_substream *substream) { struct snd_dg00x *dg00x = substream->rmidi->private_data; int err; @@ -27,7 +27,7 @@ static int midi_phys_open(struct snd_rawmidi_substream *substream) return err; } -static int midi_phys_close(struct snd_rawmidi_substream *substream) +static int midi_close(struct snd_rawmidi_substream *substream) { struct snd_dg00x *dg00x = substream->rmidi->private_data; @@ -40,180 +40,130 @@ static int midi_phys_close(struct snd_rawmidi_substream *substream) return 0; } -static void midi_phys_capture_trigger(struct snd_rawmidi_substream *substream, - int up) +static void midi_capture_trigger(struct snd_rawmidi_substream *substream, + int up) { struct snd_dg00x *dg00x = substream->rmidi->private_data; + unsigned int port; unsigned long flags; - spin_lock_irqsave(&dg00x->lock, flags); - - if (up) - amdtp_dot_midi_trigger(&dg00x->tx_stream, substream->number, - substream); + if (substream->rmidi->device == 0) + port = substream->number; else - amdtp_dot_midi_trigger(&dg00x->tx_stream, substream->number, - NULL); - - spin_unlock_irqrestore(&dg00x->lock, flags); -} - -static void midi_phys_playback_trigger(struct snd_rawmidi_substream *substream, - int up) -{ - struct snd_dg00x *dg00x = substream->rmidi->private_data; - unsigned long flags; + port = 2; spin_lock_irqsave(&dg00x->lock, flags); if (up) - amdtp_dot_midi_trigger(&dg00x->rx_stream, substream->number, - substream); + amdtp_dot_midi_trigger(&dg00x->tx_stream, port, substream); else - amdtp_dot_midi_trigger(&dg00x->rx_stream, substream->number, - NULL); + amdtp_dot_midi_trigger(&dg00x->tx_stream, port, NULL); spin_unlock_irqrestore(&dg00x->lock, flags); } -static int midi_ctl_open(struct snd_rawmidi_substream *substream) -{ - /* Do nothing. */ - return 0; -} - -static int midi_ctl_capture_close(struct snd_rawmidi_substream *substream) -{ - /* Do nothing. */ - return 0; -} - -static int midi_ctl_playback_close(struct snd_rawmidi_substream *substream) -{ - struct snd_dg00x *dg00x = substream->rmidi->private_data; - - snd_fw_async_midi_port_finish(&dg00x->out_control); - - return 0; -} - -static void midi_ctl_capture_trigger(struct snd_rawmidi_substream *substream, - int up) +static void midi_playback_trigger(struct snd_rawmidi_substream *substream, + int up) { struct snd_dg00x *dg00x = substream->rmidi->private_data; + unsigned int port; unsigned long flags; - spin_lock_irqsave(&dg00x->lock, flags); - - if (up) - dg00x->in_control = substream; + if (substream->rmidi->device == 0) + port = substream->number; else - dg00x->in_control = NULL; - - spin_unlock_irqrestore(&dg00x->lock, flags); -} - -static void midi_ctl_playback_trigger(struct snd_rawmidi_substream *substream, - int up) -{ - struct snd_dg00x *dg00x = substream->rmidi->private_data; - unsigned long flags; + port = 2; spin_lock_irqsave(&dg00x->lock, flags); if (up) - snd_fw_async_midi_port_run(&dg00x->out_control, substream); + amdtp_dot_midi_trigger(&dg00x->rx_stream, port, substream); + else + amdtp_dot_midi_trigger(&dg00x->rx_stream, port, NULL); spin_unlock_irqrestore(&dg00x->lock, flags); } -static void set_midi_substream_names(struct snd_dg00x *dg00x, - struct snd_rawmidi_str *str, - bool is_ctl) +static void set_substream_names(struct snd_dg00x *dg00x, + struct snd_rawmidi *rmidi, bool is_console) { struct snd_rawmidi_substream *subs; - - list_for_each_entry(subs, &str->substreams, list) { - if (!is_ctl) - snprintf(subs->name, sizeof(subs->name), - "%s MIDI %d", - dg00x->card->shortname, subs->number + 1); - else - /* This port is for asynchronous transaction. */ - snprintf(subs->name, sizeof(subs->name), - "%s control", - dg00x->card->shortname); + struct snd_rawmidi_str *str; + int i; + + for (i = 0; i < 2; ++i) { + str = &rmidi->streams[i]; + + list_for_each_entry(subs, &str->substreams, list) { + if (!is_console) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", + dg00x->card->shortname, + subs->number + 1); + } else { + snprintf(subs->name, sizeof(subs->name), + "%s control", + dg00x->card->shortname); + } + } } } -int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x) +static int add_substream_pair(struct snd_dg00x *dg00x, unsigned int out_ports, + unsigned int in_ports, bool is_console) { - static const struct snd_rawmidi_ops phys_capture_ops = { - .open = midi_phys_open, - .close = midi_phys_close, - .trigger = midi_phys_capture_trigger, - }; - static const struct snd_rawmidi_ops phys_playback_ops = { - .open = midi_phys_open, - .close = midi_phys_close, - .trigger = midi_phys_playback_trigger, + static const struct snd_rawmidi_ops capture_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_capture_trigger, }; - static const struct snd_rawmidi_ops ctl_capture_ops = { - .open = midi_ctl_open, - .close = midi_ctl_capture_close, - .trigger = midi_ctl_capture_trigger, + static const struct snd_rawmidi_ops playback_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_playback_trigger, }; - static const struct snd_rawmidi_ops ctl_playback_ops = { - .open = midi_ctl_open, - .close = midi_ctl_playback_close, - .trigger = midi_ctl_playback_trigger, - }; - struct snd_rawmidi *rmidi[2]; - struct snd_rawmidi_str *str; - unsigned int i; + const char *label; + struct snd_rawmidi *rmidi; int err; /* Add physical midi ports. */ - err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 0, - DOT_MIDI_OUT_PORTS, DOT_MIDI_IN_PORTS, &rmidi[0]); + err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, is_console, + out_ports, in_ports, &rmidi); if (err < 0) return err; + rmidi->private_data = dg00x; - snprintf(rmidi[0]->name, sizeof(rmidi[0]->name), - "%s MIDI", dg00x->card->shortname); - - snd_rawmidi_set_ops(rmidi[0], SNDRV_RAWMIDI_STREAM_INPUT, - &phys_capture_ops); - snd_rawmidi_set_ops(rmidi[0], SNDRV_RAWMIDI_STREAM_OUTPUT, - &phys_playback_ops); + if (!is_console) + label = "%s control"; + else + label = "%s MIDI"; + snprintf(rmidi->name, sizeof(rmidi->name), label, + dg00x->card->shortname); - /* Add a pair of control midi ports. */ - err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 1, - 1, 1, &rmidi[1]); - if (err < 0) - return err; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &playback_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &capture_ops); - snprintf(rmidi[1]->name, sizeof(rmidi[1]->name), - "%s control", dg00x->card->shortname); + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; - snd_rawmidi_set_ops(rmidi[1], SNDRV_RAWMIDI_STREAM_INPUT, - &ctl_capture_ops); - snd_rawmidi_set_ops(rmidi[1], SNDRV_RAWMIDI_STREAM_OUTPUT, - &ctl_playback_ops); + set_substream_names(dg00x, rmidi, is_console); - for (i = 0; i < ARRAY_SIZE(rmidi); i++) { - rmidi[i]->private_data = dg00x; + return 0; +} - rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; - str = &rmidi[i]->streams[SNDRV_RAWMIDI_STREAM_INPUT]; - set_midi_substream_names(dg00x, str, i); +int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x) +{ + int err; - rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; - str = &rmidi[i]->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; - set_midi_substream_names(dg00x, str, i); + /* Add physical midi ports. */ + err = add_substream_pair(dg00x, DOT_MIDI_OUT_PORTS, DOT_MIDI_IN_PORTS, + false); + if (err < 0) + return err; - rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; - } + if (dg00x->is_console) + err = add_substream_pair(dg00x, 1, 1, true); - return 0; + return err; } -- cgit v1.2.3-59-g8ed1b From fdb2b2eee6bc33f4dcb1b05176e3008a4e494612 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sun, 2 Apr 2017 23:48:27 +0900 Subject: ALSA: firewire-digi00x: remove transaction handler for unknown purpose For digi00x series, asynchronous transaction is not used to transfer MIDI messages to/from control surface. One of transction handlers in my previous work loses its practical meaning. This commit removes the handler. I note that unit of console type transfers 0x00001000 to registered address of host space when switching to 'standalone' mode. Then the unit generates bus reset. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/digi00x/digi00x-transaction.c | 88 +++++----------------------- sound/firewire/digi00x/digi00x.h | 6 +- 2 files changed, 17 insertions(+), 77 deletions(-) (limited to 'sound') diff --git a/sound/firewire/digi00x/digi00x-transaction.c b/sound/firewire/digi00x/digi00x-transaction.c index 735d35640807..af9bc8504781 100644 --- a/sound/firewire/digi00x/digi00x-transaction.c +++ b/sound/firewire/digi00x/digi00x-transaction.c @@ -9,40 +9,6 @@ #include #include "digi00x.h" -static int fill_midi_message(struct snd_rawmidi_substream *substream, u8 *buf) -{ - int bytes; - - buf[0] = 0x80; - bytes = snd_rawmidi_transmit_peek(substream, buf + 1, 2); - if (bytes >= 0) - buf[3] = 0xc0 | bytes; - - return bytes; -} - -static void handle_midi_control(struct snd_dg00x *dg00x, __be32 *buf, - unsigned int length) -{ - struct snd_rawmidi_substream *substream; - unsigned int i; - unsigned int len; - u8 *b; - - substream = ACCESS_ONCE(dg00x->in_control); - if (substream == NULL) - return; - - length /= 4; - - for (i = 0; i < length; i++) { - b = (u8 *)&buf[i]; - len = b[3] & 0xf; - if (len > 0) - snd_rawmidi_receive(dg00x->in_control, b + 1, len); - } -} - static void handle_unknown_message(struct snd_dg00x *dg00x, unsigned long long offset, __be32 *buf) { @@ -63,39 +29,36 @@ static void handle_message(struct fw_card *card, struct fw_request *request, struct snd_dg00x *dg00x = callback_data; __be32 *buf = (__be32 *)data; + fw_send_response(card, request, RCODE_COMPLETE); + if (offset == dg00x->async_handler.offset) handle_unknown_message(dg00x, offset, buf); - else if (offset == dg00x->async_handler.offset + 4) - handle_midi_control(dg00x, buf, length); - - fw_send_response(card, request, RCODE_COMPLETE); } int snd_dg00x_transaction_reregister(struct snd_dg00x *dg00x) { struct fw_device *device = fw_parent_device(dg00x->unit); __be32 data[2]; - int err; /* Unknown. 4bytes. */ data[0] = cpu_to_be32((device->card->node_id << 16) | (dg00x->async_handler.offset >> 32)); data[1] = cpu_to_be32(dg00x->async_handler.offset); - err = snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST, - DG00X_ADDR_BASE + DG00X_OFFSET_MESSAGE_ADDR, - &data, sizeof(data), 0); - if (err < 0) - return err; - - /* Asynchronous transactions for MIDI control message. */ - data[0] = cpu_to_be32((device->card->node_id << 16) | - (dg00x->async_handler.offset >> 32)); - data[1] = cpu_to_be32(dg00x->async_handler.offset + 4); return snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST, - DG00X_ADDR_BASE + DG00X_OFFSET_MIDI_CTL_ADDR, + DG00X_ADDR_BASE + DG00X_OFFSET_MESSAGE_ADDR, &data, sizeof(data), 0); } +void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x) +{ + if (dg00x->async_handler.callback_data == NULL) + return; + + fw_core_remove_address_handler(&dg00x->async_handler); + + dg00x->async_handler.callback_data = NULL; +} + int snd_dg00x_transaction_register(struct snd_dg00x *dg00x) { static const struct fw_address_region resp_register_region = { @@ -104,7 +67,7 @@ int snd_dg00x_transaction_register(struct snd_dg00x *dg00x) }; int err; - dg00x->async_handler.length = 12; + dg00x->async_handler.length = 4; dg00x->async_handler.address_callback = handle_message; dg00x->async_handler.callback_data = dg00x; @@ -115,28 +78,7 @@ int snd_dg00x_transaction_register(struct snd_dg00x *dg00x) err = snd_dg00x_transaction_reregister(dg00x); if (err < 0) - goto error; - - err = snd_fw_async_midi_port_init(&dg00x->out_control, dg00x->unit, - DG00X_ADDR_BASE + DG00X_OFFSET_MMC, - 4, fill_midi_message); - if (err < 0) - goto error; + snd_dg00x_transaction_unregister(dg00x); return err; -error: - fw_core_remove_address_handler(&dg00x->async_handler); - dg00x->async_handler.callback_data = NULL; - return err; -} - -void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x) -{ - if (dg00x->async_handler.callback_data == NULL) - return; - - snd_fw_async_midi_port_destroy(&dg00x->out_control); - fw_core_remove_address_handler(&dg00x->async_handler); - - dg00x->async_handler.callback_data = NULL; } diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h index 4aefe45d8906..1275a50956c0 100644 --- a/sound/firewire/digi00x/digi00x.h +++ b/sound/firewire/digi00x/digi00x.h @@ -58,9 +58,7 @@ struct snd_dg00x { struct fw_address_handler async_handler; u32 msg; - /* For asynchronous MIDI controls. */ - struct snd_rawmidi_substream *in_control; - struct snd_fw_async_midi_port out_control; + /* Console models have additional MIDI ports for control surface. */ bool is_console; }; @@ -68,7 +66,7 @@ struct snd_dg00x { #define DG00X_OFFSET_STREAMING_STATE 0x0000 #define DG00X_OFFSET_STREAMING_SET 0x0004 -#define DG00X_OFFSET_MIDI_CTL_ADDR 0x0008 +/* unknown but address in host space 0x0008 */ /* For LSB of the address 0x000c */ /* unknown 0x0010 */ #define DG00X_OFFSET_MESSAGE_ADDR 0x0014 -- cgit v1.2.3-59-g8ed1b From 5b33504bada4d85092b69b3aaa7048c398b21dbb Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Mon, 3 Apr 2017 21:13:50 +0900 Subject: ALSA: firewire-motu: remove invalid bitshift for register value In protocol version 3, drivers can read current sampling clock status from register 0x'ffff'f000'0b14. 8 bits of LSB of this register represents type of signal as source of clock. Current driver code includes invalid bitshift to handle the parameter. This commit fixes the bug. Reported-by: Dan Carpenter Fixes: 5992e30034c4 ("ALSA: firewire-motu: add support for MOTU 828mk3 (FireWire/Hybrid) as a model with protocol version 3") Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/motu/motu-protocol-v3.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/firewire/motu/motu-protocol-v3.c b/sound/firewire/motu/motu-protocol-v3.c index b463da99feb1..ddb647254ed2 100644 --- a/sound/firewire/motu/motu-protocol-v3.c +++ b/sound/firewire/motu/motu-protocol-v3.c @@ -14,7 +14,6 @@ #define V3_CLOCK_RATE_MASK 0x0000ff00 #define V3_CLOCK_RATE_SHIFT 8 #define V3_CLOCK_SOURCE_MASK 0x000000ff -#define V3_CLOCK_SOURCE_SHIFT 8 #define V3_OPT_IFACE_MODE_OFFSET 0x0c94 #define V3_ENABLE_OPT_IN_IFACE_A 0x00000001 @@ -101,7 +100,7 @@ static int v3_get_clock_source(struct snd_motu *motu, return err; data = be32_to_cpu(reg); - val = (data & V3_CLOCK_SOURCE_MASK) >> V3_CLOCK_SOURCE_SHIFT; + val = data & V3_CLOCK_SOURCE_MASK; if (val == 0x00) { *src = SND_MOTU_CLOCK_SOURCE_INTERNAL; } else if (val == 0x01) { -- cgit v1.2.3-59-g8ed1b From 7e1621de146fbed6172252f14a6a41b2c5999a93 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Mon, 3 Apr 2017 21:13:55 +0900 Subject: ALSA: firewire-lib/bebob/oxfw: improve response evaluation for AV/C commands In ALSA firewire stack, some AV/C commands are supported, including vendor's extensions. Drivers includes response parser of each command, according to its requirements, while the parser is written with loose fashion in two points; error check and length check. This doesn't cause any issues such as kernel corruption, but should be improved. This commit modifies evaluations of return value on each parsers. Reported-by: Dan Carpenter Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/bebob/bebob_command.c | 30 ++++++++++++++++++++++-------- sound/firewire/fcp.c | 12 +++++++++--- sound/firewire/oxfw/oxfw-command.c | 12 +++++++++--- 3 files changed, 40 insertions(+), 14 deletions(-) (limited to 'sound') diff --git a/sound/firewire/bebob/bebob_command.c b/sound/firewire/bebob/bebob_command.c index 9402cc15dbc1..f9b4225dd86f 100644 --- a/sound/firewire/bebob/bebob_command.c +++ b/sound/firewire/bebob/bebob_command.c @@ -31,13 +31,15 @@ int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id, err = fcp_avc_transaction(unit, buf, 12, buf, 12, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(8)); - if (err > 0 && err < 9) + if (err < 0) + ; + else if (err < 9) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; else if (buf[0] == 0x0a) /* REJECTED */ err = -EINVAL; - else if (err > 0) + else err = 0; kfree(buf); @@ -67,7 +69,9 @@ int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id, err = fcp_avc_transaction(unit, buf, 12, buf, 12, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(8)); - if (err > 0 && err < 9) + if (err < 0) + ; + else if (err < 9) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -120,7 +124,9 @@ int avc_bridgeco_get_plug_type(struct fw_unit *unit, err = fcp_avc_transaction(unit, buf, 12, buf, 12, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(9)); - if ((err >= 0) && (err < 8)) + if (err < 0) + ; + else if (err < 11) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -150,7 +156,9 @@ int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit, err = fcp_avc_transaction(unit, buf, 12, buf, 256, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(9)); - if ((err >= 0) && (err < 8)) + if (err < 0) + ; + else if (err < 11) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -187,7 +195,9 @@ int avc_bridgeco_get_plug_section_type(struct fw_unit *unit, err = fcp_avc_transaction(unit, buf, 12, buf, 12, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(9) | BIT(10)); - if ((err >= 0) && (err < 8)) + if (err < 0) + ; + else if (err < 12) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -221,7 +231,9 @@ int avc_bridgeco_get_plug_input(struct fw_unit *unit, err = fcp_avc_transaction(unit, buf, 16, buf, 16, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7)); - if ((err >= 0) && (err < 8)) + if (err < 0) + ; + else if (err < 16) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -260,7 +272,9 @@ int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit, err = fcp_avc_transaction(unit, buf, 12, buf, *len, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(10)); - if ((err >= 0) && (err < 12)) + if (err < 0) + ; + else if (err < 12) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; diff --git a/sound/firewire/fcp.c b/sound/firewire/fcp.c index cce19768f43d..61dda828f767 100644 --- a/sound/firewire/fcp.c +++ b/sound/firewire/fcp.c @@ -63,7 +63,9 @@ int avc_general_set_sig_fmt(struct fw_unit *unit, unsigned int rate, /* do transaction and check buf[1-5] are the same against command */ err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); - if (err >= 0 && err < 8) + if (err < 0) + ; + else if (err < 8) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -106,7 +108,9 @@ int avc_general_get_sig_fmt(struct fw_unit *unit, unsigned int *rate, /* do transaction and check buf[1-4] are the same against command */ err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2) | BIT(3) | BIT(4)); - if (err >= 0 && err < 8) + if (err < 0) + ; + else if (err < 8) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -154,7 +158,9 @@ int avc_general_get_plug_info(struct fw_unit *unit, unsigned int subunit_type, buf[3] = 0xff & subfunction; err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2)); - if (err >= 0 && err < 8) + if (err < 0) + ; + else if (err < 8) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; diff --git a/sound/firewire/oxfw/oxfw-command.c b/sound/firewire/oxfw/oxfw-command.c index 12ef3253bc89..ac3e2e301666 100644 --- a/sound/firewire/oxfw/oxfw-command.c +++ b/sound/firewire/oxfw/oxfw-command.c @@ -34,7 +34,9 @@ int avc_stream_set_format(struct fw_unit *unit, enum avc_general_plug_dir dir, err = fcp_avc_transaction(unit, buf, len + 10, buf, len + 10, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(8)); - if ((err > 0) && (err < len + 10)) + if (err < 0) + ; + else if (err < len + 10) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -77,7 +79,9 @@ int avc_stream_get_format(struct fw_unit *unit, err = fcp_avc_transaction(unit, buf, 12, buf, *len, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7)); - if ((err > 0) && (err < 10)) + if (err < 0) + ; + else if (err < 12) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -139,7 +143,9 @@ int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate, /* do transaction and check buf[1-5] are the same against command */ err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); - if ((err > 0) && (err < 8)) + if (err < 0) + ; + else if (err < 8) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; -- cgit v1.2.3-59-g8ed1b From dde5bff5415953f9cc7413f1b1ceebcdfd583c07 Mon Sep 17 00:00:00 2001 From: Libin Yang Date: Thu, 6 Apr 2017 19:18:20 +0800 Subject: ALSA: hda - add more ML register definitions This patch refines the definition of AZX_MLCTL_SPA and AZX_MLCTL_CPA and add more definitions of ML registers Signed-off-by: Libin Yang Signed-off-by: Takashi Iwai --- include/sound/hda_register.h | 8 +++++--- sound/hda/ext/hdac_ext_controller.c | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/include/sound/hda_register.h b/include/sound/hda_register.h index 1251ff41c9d3..15fc6daf9096 100644 --- a/include/sound/hda_register.h +++ b/include/sound/hda_register.h @@ -261,9 +261,11 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 }; #define AZX_REG_ML_LOUTPAY 0x20 #define AZX_REG_ML_LINPAY 0x30 -#define AZX_MLCTL_SPA (1<<16) -#define AZX_MLCTL_CPA 23 - +#define ML_LCTL_SCF_MASK 0xF +#define AZX_MLCTL_SPA (0x1 << 16) +#define AZX_MLCTL_CPA (0x1 << 23) +#define AZX_MLCTL_SPA_SHIFT 16 +#define AZX_MLCTL_CPA_SHIFT 23 /* registers for DMA Resume Capability Structure */ #define AZX_DRSM_CAP_ID 0x5 diff --git a/sound/hda/ext/hdac_ext_controller.c b/sound/hda/ext/hdac_ext_controller.c index 261469188566..84f3b8168716 100644 --- a/sound/hda/ext/hdac_ext_controller.c +++ b/sound/hda/ext/hdac_ext_controller.c @@ -171,7 +171,7 @@ static int check_hdac_link_power_active(struct hdac_ext_link *link, bool enable) { int timeout; u32 val; - int mask = (1 << AZX_MLCTL_CPA); + int mask = (1 << AZX_MLCTL_CPA_SHIFT); udelay(3); timeout = 150; @@ -179,10 +179,10 @@ static int check_hdac_link_power_active(struct hdac_ext_link *link, bool enable) do { val = readl(link->ml_addr + AZX_REG_ML_LCTL); if (enable) { - if (((val & mask) >> AZX_MLCTL_CPA)) + if (((val & mask) >> AZX_MLCTL_CPA_SHIFT)) return 0; } else { - if (!((val & mask) >> AZX_MLCTL_CPA)) + if (!((val & mask) >> AZX_MLCTL_CPA_SHIFT)) return 0; } udelay(3); -- cgit v1.2.3-59-g8ed1b From 1f9d3d98694b1cef93f99a54e6830e9717616ba6 Mon Sep 17 00:00:00 2001 From: Libin Yang Date: Thu, 6 Apr 2017 19:18:21 +0800 Subject: ALSA: hda - set intel audio clock to a proper value On some Intel platforms, the audio clock may not be set correctly with initial setting. This will cause the audio playback/capture rates wrong. This patch checks the audio clock setting and will set it to a proper value if it is set incorrectly. Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=188411 Signed-off-by: Libin Yang Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_intel.c | 95 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 64db6698214c..59ab34fa0bc8 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -540,6 +540,98 @@ static void bxt_reduce_dma_latency(struct azx *chip) azx_writel(chip, VS_EM4L, val); } +/* + * ML_LCAP bits: + * bit 0: 6 MHz Supported + * bit 1: 12 MHz Supported + * bit 2: 24 MHz Supported + * bit 3: 48 MHz Supported + * bit 4: 96 MHz Supported + * bit 5: 192 MHz Supported + */ +static int intel_get_lctl_scf(struct azx *chip) +{ + struct hdac_bus *bus = azx_bus(chip); + static int preferred_bits[] = { 2, 3, 1, 4, 5 }; + u32 val, t; + int i; + + val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCAP); + + for (i = 0; i < ARRAY_SIZE(preferred_bits); i++) { + t = preferred_bits[i]; + if (val & (1 << t)) + return t; + } + + dev_warn(chip->card->dev, "set audio clock frequency to 6MHz"); + return 0; +} + +static int intel_ml_lctl_set_power(struct azx *chip, int state) +{ + struct hdac_bus *bus = azx_bus(chip); + u32 val; + int timeout; + + /* + * the codecs are sharing the first link setting by default + * If other links are enabled for stream, they need similar fix + */ + val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL); + val &= ~AZX_MLCTL_SPA; + val |= state << AZX_MLCTL_SPA_SHIFT; + writel(val, bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL); + /* wait for CPA */ + timeout = 50; + while (timeout) { + if (((readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL)) & + AZX_MLCTL_CPA) == (state << AZX_MLCTL_CPA_SHIFT)) + return 0; + timeout--; + udelay(10); + } + + return -1; +} + +static void intel_init_lctl(struct azx *chip) +{ + struct hdac_bus *bus = azx_bus(chip); + u32 val; + int ret; + + /* 0. check lctl register value is correct or not */ + val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL); + /* if SCF is already set, let's use it */ + if ((val & ML_LCTL_SCF_MASK) != 0) + return; + + /* + * Before operating on SPA, CPA must match SPA. + * Any deviation may result in undefined behavior. + */ + if (((val & AZX_MLCTL_SPA) >> AZX_MLCTL_SPA_SHIFT) != + ((val & AZX_MLCTL_CPA) >> AZX_MLCTL_CPA_SHIFT)) + return; + + /* 1. turn link down: set SPA to 0 and wait CPA to 0 */ + ret = intel_ml_lctl_set_power(chip, 0); + udelay(100); + if (ret) + goto set_spa; + + /* 2. update SCF to select a properly audio clock*/ + val &= ~ML_LCTL_SCF_MASK; + val |= intel_get_lctl_scf(chip); + writel(val, bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL); + +set_spa: + /* 4. turn link up: set SPA to 1 and wait CPA to 1 */ + intel_ml_lctl_set_power(chip, 1); + udelay(100); +} + static void hda_intel_init_chip(struct azx *chip, bool full_reset) { struct hdac_bus *bus = azx_bus(chip); @@ -565,6 +657,9 @@ static void hda_intel_init_chip(struct azx *chip, bool full_reset) /* reduce dma latency to avoid noise */ if (IS_BXT(pci)) bxt_reduce_dma_latency(chip); + + if (bus->mlcap != NULL) + intel_init_lctl(chip); } /* calculate runtime delay from LPIB */ -- cgit v1.2.3-59-g8ed1b From 9f3dadb156692e15933a4a97ef659f65151be4a1 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 10 Apr 2017 17:12:33 +0200 Subject: ALSA: hda - A new flag to enforce prefix to each pin This is a preliminary patch for a smooth multi-codec support, and it introduces a new flag, force_pin_prefix, to struct hda_codec. This flag is used to force to add the pin location prefix to each input pin. For example, when there is only one microphone pin, usually the auto-parser assigns the string "Mic". With this flag on, it'll be like "Front Mic". Also, the creation of "Master" or "PCM" playback volume for a single pin is suppressed, too. Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=195305 Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_auto_parser.c | 1 + sound/pci/hda/hda_codec.h | 1 + sound/pci/hda/hda_generic.c | 2 ++ 3 files changed, 4 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/hda_auto_parser.c b/sound/pci/hda/hda_auto_parser.c index a03cf68d0bcd..d3ea73171a3d 100644 --- a/sound/pci/hda/hda_auto_parser.c +++ b/sound/pci/hda/hda_auto_parser.c @@ -580,6 +580,7 @@ const char *hda_get_autocfg_input_label(struct hda_codec *codec, has_multiple_pins = 1; if (has_multiple_pins && type == AUTO_PIN_MIC) has_multiple_pins &= check_mic_location_need(codec, cfg, input); + has_multiple_pins |= codec->force_pin_prefix; return hda_get_input_pin_label(codec, &cfg->inputs[input], cfg->inputs[input].pin, has_multiple_pins); diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h index f17f25245e52..d6fb2d5d01a7 100644 --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -256,6 +256,7 @@ struct hda_codec { unsigned int dump_coef:1; /* dump processing coefs in codec proc file */ unsigned int power_save_node:1; /* advanced PM for each widget */ unsigned int auto_runtime_pm:1; /* enable automatic codec runtime pm */ + unsigned int force_pin_prefix:1; /* Add location prefix */ #ifdef CONFIG_PM unsigned long power_on_acct; unsigned long power_off_acct; diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c index e7c8f4f076d5..443832870a44 100644 --- a/sound/pci/hda/hda_generic.c +++ b/sound/pci/hda/hda_generic.c @@ -1125,6 +1125,7 @@ static const char *get_line_out_pfx(struct hda_codec *codec, int ch, *index = 0; if (cfg->line_outs == 1 && !spec->multi_ios && + !codec->force_pin_prefix && !cfg->hp_outs && !cfg->speaker_outs) return spec->vmaster_mute.hook ? "PCM" : "Master"; @@ -1132,6 +1133,7 @@ static const char *get_line_out_pfx(struct hda_codec *codec, int ch, * use it master (or "PCM" if a vmaster hook is present) */ if (spec->multiout.num_dacs == 1 && !spec->mixer_nid && + !codec->force_pin_prefix && !spec->multiout.hp_out_nid[0] && !spec->multiout.extra_out_nid[0]) return spec->vmaster_mute.hook ? "PCM" : "Master"; -- cgit v1.2.3-59-g8ed1b From 7480316c265c9fcdbf73b1b8dec061b893b7e987 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 10 Apr 2017 17:37:34 +0200 Subject: ALSA: hda - Allow to enable/disable vmaster build explicitly Another preliminary patch for the dual-codec support: since the support of vmaster over multiple codecs is difficult, simply disable it by a new flag to hda_codec struct. A new user hint is added as well for consistency. Signed-off-by: Takashi Iwai --- Documentation/sound/hd-audio/notes.rst | 2 ++ sound/pci/hda/hda_generic.c | 7 +++++-- sound/pci/hda/hda_generic.h | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/Documentation/sound/hd-audio/notes.rst b/Documentation/sound/hd-audio/notes.rst index 9eeb9b468706..f59c3cdbfaf4 100644 --- a/Documentation/sound/hd-audio/notes.rst +++ b/Documentation/sound/hd-audio/notes.rst @@ -494,6 +494,8 @@ add_hp_mic (bool) hp_mic_detect (bool) enable/disable the hp/mic shared input for a single built-in mic case; default true +vmaster (bool) + enable/disable the virtual Master control; default true mixer_nid (int) specifies the widget NID of the analog-loopback mixer diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c index 443832870a44..2842c82363c0 100644 --- a/sound/pci/hda/hda_generic.c +++ b/sound/pci/hda/hda_generic.c @@ -196,6 +196,9 @@ static void parse_user_hints(struct hda_codec *codec) val = snd_hda_get_bool_hint(codec, "hp_mic_detect"); if (val >= 0) spec->suppress_hp_mic_detect = !val; + val = snd_hda_get_bool_hint(codec, "vmaster"); + if (val >= 0) + spec->suppress_vmaster = !val; if (!snd_hda_get_int_hint(codec, "mixer_nid", &val)) spec->mixer_nid = val; @@ -5033,7 +5036,7 @@ int snd_hda_gen_build_controls(struct hda_codec *codec) } /* if we have no master control, let's create it */ - if (!spec->no_analog && + if (!spec->no_analog && !spec->suppress_vmaster && !snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) { err = snd_hda_add_vmaster(codec, "Master Playback Volume", spec->vmaster_tlv, slave_pfxs, @@ -5041,7 +5044,7 @@ int snd_hda_gen_build_controls(struct hda_codec *codec) if (err < 0) return err; } - if (!spec->no_analog && + if (!spec->no_analog && !spec->suppress_vmaster && !snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) { err = __snd_hda_add_vmaster(codec, "Master Playback Switch", NULL, slave_pfxs, diff --git a/sound/pci/hda/hda_generic.h b/sound/pci/hda/hda_generic.h index f66fc7e25e07..61772317de46 100644 --- a/sound/pci/hda/hda_generic.h +++ b/sound/pci/hda/hda_generic.h @@ -229,6 +229,7 @@ struct hda_gen_spec { unsigned int add_jack_modes:1; /* add i/o jack mode enum ctls */ unsigned int power_down_unused:1; /* power down unused widgets */ unsigned int dac_min_mute:1; /* minimal = mute for DACs */ + unsigned int suppress_vmaster:1; /* don't create vmaster kctls */ /* other internal flags */ unsigned int no_analog:1; /* digital I/O only */ -- cgit v1.2.3-59-g8ed1b From b164d2fd6e4985e9270755477dde063e6e48461e Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sun, 9 Apr 2017 21:33:27 +0900 Subject: ALSA: firewire_lib: add tracepoints for packets without CIP headers Unique protocol is used for RME Fireface series. In this protocol, payload format for isochronous packet is not compliant to CIP in IEC 61883-1/6. The packet includes data blocks just with data channels, without headers and any metadata. In previous commits, ALSA IEC 61883-1/6 engine supports this protocol. However, tracepoints are not supported yet, unlike implementation for IEC 61883-1/6 protocol. This commit adds support of tracepoints for the protocol. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/amdtp-stream-trace.h | 88 +++++++++++++++++++++++++++++++++++++ sound/firewire/amdtp-stream.c | 8 ++++ 2 files changed, 96 insertions(+) (limited to 'sound') diff --git a/sound/firewire/amdtp-stream-trace.h b/sound/firewire/amdtp-stream-trace.h index 850b36e27d0d..ea0d486652c8 100644 --- a/sound/firewire/amdtp-stream-trace.h +++ b/sound/firewire/amdtp-stream-trace.h @@ -101,6 +101,94 @@ TRACE_EVENT(out_packet, __entry->index) ); +TRACE_EVENT(in_packet_without_header, + TP_PROTO(const struct amdtp_stream *s, u32 cycles, unsigned int payload_quadlets, unsigned int data_blocks, unsigned int index), + TP_ARGS(s, cycles, payload_quadlets, data_blocks, index), + TP_STRUCT__entry( + __field(unsigned int, second) + __field(unsigned int, cycle) + __field(int, channel) + __field(int, src) + __field(int, dest) + __field(unsigned int, payload_quadlets) + __field(unsigned int, data_blocks) + __field(unsigned int, data_block_counter) + __field(unsigned int, packet_index) + __field(unsigned int, irq) + __field(unsigned int, index) + ), + TP_fast_assign( + __entry->second = cycles / CYCLES_PER_SECOND; + __entry->cycle = cycles % CYCLES_PER_SECOND; + __entry->channel = s->context->channel; + __entry->src = fw_parent_device(s->unit)->node_id; + __entry->dest = fw_parent_device(s->unit)->card->node_id; + __entry->payload_quadlets = payload_quadlets; + __entry->data_blocks = data_blocks, + __entry->data_block_counter = s->data_block_counter, + __entry->packet_index = s->packet_index; + __entry->irq = !!in_interrupt(); + __entry->index = index; + ), + TP_printk( + "%02u %04u %04x %04x %02d %03u %3u %3u %02u %01u %02u", + __entry->second, + __entry->cycle, + __entry->src, + __entry->dest, + __entry->channel, + __entry->payload_quadlets, + __entry->data_blocks, + __entry->data_block_counter, + __entry->packet_index, + __entry->irq, + __entry->index) +); + +TRACE_EVENT(out_packet_without_header, + TP_PROTO(const struct amdtp_stream *s, u32 cycles, unsigned int payload_length, unsigned int data_blocks, unsigned int index), + TP_ARGS(s, cycles, payload_length, data_blocks, index), + TP_STRUCT__entry( + __field(unsigned int, second) + __field(unsigned int, cycle) + __field(int, channel) + __field(int, src) + __field(int, dest) + __field(unsigned int, payload_quadlets) + __field(unsigned int, data_blocks) + __field(unsigned int, data_block_counter) + __field(unsigned int, packet_index) + __field(unsigned int, irq) + __field(unsigned int, index) + ), + TP_fast_assign( + __entry->second = cycles / CYCLES_PER_SECOND; + __entry->cycle = cycles % CYCLES_PER_SECOND; + __entry->channel = s->context->channel; + __entry->src = fw_parent_device(s->unit)->card->node_id; + __entry->dest = fw_parent_device(s->unit)->node_id; + __entry->payload_quadlets = payload_length / 4; + __entry->data_blocks = data_blocks, + __entry->data_blocks = s->data_block_counter, + __entry->packet_index = s->packet_index; + __entry->irq = !!in_interrupt(); + __entry->index = index; + ), + TP_printk( + "%02u %04u %04x %04x %02d %03u %02u %03u %02u %01u %02u", + __entry->second, + __entry->cycle, + __entry->src, + __entry->dest, + __entry->channel, + __entry->payload_quadlets, + __entry->data_blocks, + __entry->data_block_counter, + __entry->packet_index, + __entry->irq, + __entry->index) +); + #endif #undef TRACE_INCLUDE_PATH diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c index a03b37bdc274..f6af8e64c2cd 100644 --- a/sound/firewire/amdtp-stream.c +++ b/sound/firewire/amdtp-stream.c @@ -479,6 +479,10 @@ static int handle_out_packet_without_header(struct amdtp_stream *s, s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; payload_length = data_blocks * 4 * s->data_block_quadlets; + + trace_out_packet_without_header(s, cycle, payload_length, data_blocks, + index); + if (queue_out_packet(s, payload_length) < 0) return -EIO; @@ -617,6 +621,10 @@ static int handle_in_packet_without_header(struct amdtp_stream *s, buffer = s->buffer.packets[s->packet_index].buffer; data_blocks = payload_quadlets / s->data_block_quadlets; + + trace_in_packet_without_header(s, cycle, payload_quadlets, data_blocks, + index); + pcm_frames = s->process_data_blocks(s, buffer, data_blocks, NULL); s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; -- cgit v1.2.3-59-g8ed1b From 17909c1b3058b315698965aeb33ed6434501567c Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sun, 9 Apr 2017 21:33:28 +0900 Subject: ALSA: firewire-motu: add tracepoints for SPH in IEC 61883-1 fashion Unique protocol is used for MOTU FireWire series. In this protocol, data block format is not compliant to AM824 in IEC 61883-1/6. Each of the data block consists of 24 bit data chunks, except for a first quadlet. The quadlet is used for source packet header (SPH) described in IEC 61883-1. The sequence of SPH seems to represent presentation timestamp corresponding to included data. Developers have experienced that invalid sequence brings disorder of units in the series. Unfortunately, current implementation of ALSA IEC 61883-1/6 engine and firewire-motu driver brings periodical noises to the units at sampling transmission frequency based on 44.1 kHz. The engine generates the SPH with even interval and this mechanism seems not to be suitable to the units. Further work is required for this issue and infrastructure is preferable to assist the work. This commit adds tracepoints for the purpose. In the tracepoints, events are probed to gather the SPHs from each data blocks. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/motu/Makefile | 2 + sound/firewire/motu/amdtp-motu-trace.h | 73 ++++++++++++++++++++++++++++++++++ sound/firewire/motu/amdtp-motu.c | 20 ++++++++++ 3 files changed, 95 insertions(+) create mode 100644 sound/firewire/motu/amdtp-motu-trace.h (limited to 'sound') diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index ae84ae61d274..728f586e754b 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,3 +1,5 @@ +CFLAGS_amdtp-motu.o := -I$(src) + snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \ motu-proc.o motu-pcm.o motu-midi.o motu-hwdep.o \ motu-protocol-v2.o motu-protocol-v3.o diff --git a/sound/firewire/motu/amdtp-motu-trace.h b/sound/firewire/motu/amdtp-motu-trace.h new file mode 100644 index 000000000000..5862bf930c56 --- /dev/null +++ b/sound/firewire/motu/amdtp-motu-trace.h @@ -0,0 +1,73 @@ +/* + * amdtp-motu-trace.h - tracepoint definitions to dump a part of packet data + * + * Copyright (c) 2017 Takashi Sakamoto + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM snd_firewire_motu + +#if !defined(_SND_FIREWIRE_MOTU_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _SND_FIREWIRE_MOTU_TRACE_H + +#include + +static void copy_sph(u32 *frame, __be32 *buffer, unsigned int data_blocks, + unsigned int data_block_quadlets); + +TRACE_EVENT(in_data_block_sph, + TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), + TP_ARGS(s, data_blocks, buffer), + TP_STRUCT__entry( + __field(int, src) + __field(int, dst) + __field(unsigned int, data_blocks) + __dynamic_array(u32, tstamps, data_blocks) + ), + TP_fast_assign( + __entry->src = fw_parent_device(s->unit)->node_id; + __entry->dst = fw_parent_device(s->unit)->card->node_id; + __entry->data_blocks = data_blocks; + copy_sph(__get_dynamic_array(tstamps), buffer, data_blocks, s->data_block_quadlets); + ), + TP_printk( + "%04x %04x %u %s", + __entry->src, + __entry->dst, + __entry->data_blocks, + __print_array(__get_dynamic_array(tstamps), __entry->data_blocks, 4) + ) +); + +TRACE_EVENT(out_data_block_sph, + TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), + TP_ARGS(s, data_blocks, buffer), + TP_STRUCT__entry( + __field(int, src) + __field(int, dst) + __field(unsigned int, data_blocks) + __dynamic_array(u32, tstamps, data_blocks) + ), + TP_fast_assign( + __entry->src = fw_parent_device(s->unit)->card->node_id; + __entry->dst = fw_parent_device(s->unit)->node_id; + __entry->data_blocks = data_blocks; + copy_sph(__get_dynamic_array(tstamps), buffer, data_blocks, s->data_block_quadlets); + ), + TP_printk( + "%04x %04x %u %s", + __entry->src, + __entry->dst, + __entry->data_blocks, + __print_array(__get_dynamic_array(tstamps), __entry->data_blocks, 4) + ) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE amdtp-motu-trace +#include diff --git a/sound/firewire/motu/amdtp-motu.c b/sound/firewire/motu/amdtp-motu.c index 08bd1760b1b4..2c77e8bb5424 100644 --- a/sound/firewire/motu/amdtp-motu.c +++ b/sound/firewire/motu/amdtp-motu.c @@ -10,6 +10,9 @@ #include #include "motu.h" +#define CREATE_TRACE_POINTS +#include "amdtp-motu-trace.h" + #define CIP_FMT_MOTU 0x02 #define CIP_FMT_MOTU_TX_V3 0x22 #define MOTU_FDF_AM824 0x22 @@ -264,6 +267,19 @@ static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer, } } +/* For tracepoints. */ +static void copy_sph(u32 *frames, __be32 *buffer, unsigned int data_blocks, + unsigned int data_block_quadlets) +{ + unsigned int i; + + for (i = 0; i < data_blocks; ++i) { + *frames = be32_to_cpu(*buffer); + buffer += data_block_quadlets; + frames++; + } +} + static unsigned int process_tx_data_blocks(struct amdtp_stream *s, __be32 *buffer, unsigned int data_blocks, unsigned int *syt) @@ -271,6 +287,8 @@ static unsigned int process_tx_data_blocks(struct amdtp_stream *s, struct amdtp_motu *p = s->protocol; struct snd_pcm_substream *pcm; + trace_in_data_block_sph(s, data_blocks, buffer); + if (p->midi_ports) read_midi_messages(s, buffer, data_blocks); @@ -346,6 +364,8 @@ static unsigned int process_rx_data_blocks(struct amdtp_stream *s, write_sph(s, buffer, data_blocks); + trace_out_data_block_sph(s, data_blocks, buffer); + return data_blocks; } -- cgit v1.2.3-59-g8ed1b From c6b0b9e65f094deabc0c1d499b96ed5085b0f7db Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sun, 9 Apr 2017 21:33:29 +0900 Subject: ALSA: firewire-motu: add tracepoints for messages for unique protocol MOTU units transfer/receive messages in each data block of their isochronous packet payload. A part of content in the message is cleard for MIDI message transmission, while the rest is unknown yet. Additional features are required to assist users and developers to reveal the details. This commit adds tracepoints for the purpose. The tracepoints are designed for MOTU's protocol version 2 and 3 (Protocol version 1 is not upstreamed yet). In the tracepoints, events are probed to gather first two 24 bit data chunks of each data block. The chunks are formatted into elements of 64 bit array with padding in MSB. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/motu/amdtp-motu-trace.h | 50 ++++++++++++++++++++++++++++++++++ sound/firewire/motu/amdtp-motu.c | 17 ++++++++++++ 2 files changed, 67 insertions(+) (limited to 'sound') diff --git a/sound/firewire/motu/amdtp-motu-trace.h b/sound/firewire/motu/amdtp-motu-trace.h index 5862bf930c56..cd0cbfa9f96f 100644 --- a/sound/firewire/motu/amdtp-motu-trace.h +++ b/sound/firewire/motu/amdtp-motu-trace.h @@ -15,6 +15,8 @@ static void copy_sph(u32 *frame, __be32 *buffer, unsigned int data_blocks, unsigned int data_block_quadlets); +static void copy_message(u64 *frames, __be32 *buffer, unsigned int data_blocks, + unsigned int data_block_quadlets); TRACE_EVENT(in_data_block_sph, TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), @@ -64,6 +66,54 @@ TRACE_EVENT(out_data_block_sph, ) ); +TRACE_EVENT(in_data_block_message, + TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), + TP_ARGS(s, data_blocks, buffer), + TP_STRUCT__entry( + __field(int, src) + __field(int, dst) + __field(unsigned int, data_blocks) + __dynamic_array(u64, messages, data_blocks) + ), + TP_fast_assign( + __entry->src = fw_parent_device(s->unit)->node_id; + __entry->dst = fw_parent_device(s->unit)->card->node_id; + __entry->data_blocks = data_blocks; + copy_message(__get_dynamic_array(messages), buffer, data_blocks, s->data_block_quadlets); + ), + TP_printk( + "%04x %04x %u %s", + __entry->src, + __entry->dst, + __entry->data_blocks, + __print_array(__get_dynamic_array(messages), __entry->data_blocks, 8) + ) +); + +TRACE_EVENT(out_data_block_message, + TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), + TP_ARGS(s, data_blocks, buffer), + TP_STRUCT__entry( + __field(int, src) + __field(int, dst) + __field(unsigned int, data_blocks) + __dynamic_array(u64, messages, data_blocks) + ), + TP_fast_assign( + __entry->src = fw_parent_device(s->unit)->card->node_id; + __entry->dst = fw_parent_device(s->unit)->node_id; + __entry->data_blocks = data_blocks; + copy_message(__get_dynamic_array(messages), buffer, data_blocks, s->data_block_quadlets); + ), + TP_printk( + "%04x %04x %u %s", + __entry->src, + __entry->dst, + __entry->data_blocks, + __print_array(__get_dynamic_array(messages), __entry->data_blocks, 8) + ) +); + #endif #undef TRACE_INCLUDE_PATH diff --git a/sound/firewire/motu/amdtp-motu.c b/sound/firewire/motu/amdtp-motu.c index 2c77e8bb5424..996b5f818918 100644 --- a/sound/firewire/motu/amdtp-motu.c +++ b/sound/firewire/motu/amdtp-motu.c @@ -280,6 +280,21 @@ static void copy_sph(u32 *frames, __be32 *buffer, unsigned int data_blocks, } } +/* For tracepoints. */ +static void copy_message(u64 *frames, __be32 *buffer, unsigned int data_blocks, + unsigned int data_block_quadlets) +{ + unsigned int i; + + /* This is just for v2/v3 protocol. */ + for (i = 0; i < data_blocks; ++i) { + *frames = (be32_to_cpu(buffer[1]) << 16) | + (be32_to_cpu(buffer[2]) >> 16); + buffer += data_block_quadlets; + frames++; + } +} + static unsigned int process_tx_data_blocks(struct amdtp_stream *s, __be32 *buffer, unsigned int data_blocks, unsigned int *syt) @@ -288,6 +303,7 @@ static unsigned int process_tx_data_blocks(struct amdtp_stream *s, struct snd_pcm_substream *pcm; trace_in_data_block_sph(s, data_blocks, buffer); + trace_in_data_block_message(s, data_blocks, buffer); if (p->midi_ports) read_midi_messages(s, buffer, data_blocks); @@ -365,6 +381,7 @@ static unsigned int process_rx_data_blocks(struct amdtp_stream *s, write_sph(s, buffer, data_blocks); trace_out_data_block_sph(s, data_blocks, buffer); + trace_out_data_block_message(s, data_blocks, buffer); return data_blocks; } -- cgit v1.2.3-59-g8ed1b From 12ee4022f67f8854061b46e5c0a7ad6258ab66c2 Mon Sep 17 00:00:00 2001 From: "Subhransu S. Prusty" Date: Wed, 12 Apr 2017 09:54:00 +0530 Subject: ALSA: hda: Add Geminilake id to SKL_PLUS Geminilake is Skylake family platform. So add it's id to skl_plus check. Fixes: 126cfa2f5e15 ("ALSA: hda: Add Geminilake HDMI codec ID") Signed-off-by: Subhransu S. Prusty Cc: Senthilnathan Veppur Cc: Vinod Koul Cc: Takashi Iwai Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_intel.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 59ab34fa0bc8..b786fbab029f 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -370,8 +370,10 @@ enum { #define IS_KBL_LP(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x9d71) #define IS_KBL_H(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0xa2f0) #define IS_BXT(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x5a98) +#define IS_GLK(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x3198) #define IS_SKL_PLUS(pci) (IS_SKL(pci) || IS_SKL_LP(pci) || IS_BXT(pci)) || \ - IS_KBL(pci) || IS_KBL_LP(pci) || IS_KBL_H(pci) + IS_KBL(pci) || IS_KBL_LP(pci) || IS_KBL_H(pci) || \ + IS_GLK(pci) static char *driver_short_names[] = { [AZX_DRIVER_ICH] = "HDA Intel", -- cgit v1.2.3-59-g8ed1b From f91c9d7610a2fe306273a83e2fd5351bceb85d28 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Tue, 11 Apr 2017 20:33:18 +0900 Subject: ALSA: firewire-lib: cache maximum length of payload to reduce function calls During packet streaming, maximum length of payload for isochronous packet is invariable, therefore no need to recalculate. Current ALSA IEC 61883-1/6 engine calls a function to calculate it 8,000 or more times per second for incoming packet processing. This commit adds a member to have maximum length of payload into 'struct amdtp_stream', to reduces the function calls. At first callback from isochronous context, the length is calculated and stored for later processing. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/amdtp-stream.c | 9 +++++---- sound/firewire/amdtp-stream.h | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c index f6af8e64c2cd..9e6f54f8c45d 100644 --- a/sound/firewire/amdtp-stream.c +++ b/sound/firewire/amdtp-stream.c @@ -412,8 +412,7 @@ static inline int queue_out_packet(struct amdtp_stream *s, static inline int queue_in_packet(struct amdtp_stream *s) { - return queue_packet(s, IN_PACKET_HEADER_SIZE, - amdtp_stream_get_max_payload(s)); + return queue_packet(s, IN_PACKET_HEADER_SIZE, s->max_payload_length); } static int handle_out_packet(struct amdtp_stream *s, @@ -713,12 +712,12 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp, cycle = decrement_cycle_count(cycle, packets); /* For buffer-over-run prevention. */ - max_payload_length = amdtp_stream_get_max_payload(s); + max_payload_length = s->max_payload_length; for (i = 0; i < packets; i++) { cycle = increment_cycle_count(cycle, 1); - /* The number of quadlets in this packet */ + /* The number of bytes in this packet */ payload_length = (be32_to_cpu(headers[i]) >> ISO_DATA_LENGTH_SHIFT); if (payload_length > max_payload_length) { @@ -751,6 +750,8 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context, u32 cycle; unsigned int packets; + s->max_payload_length = amdtp_stream_get_max_payload(s); + /* * For in-stream, first packet has come. * For out-stream, prepared to transmit first packet diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h index 2bd4de4c7bb7..7e8831722821 100644 --- a/sound/firewire/amdtp-stream.h +++ b/sound/firewire/amdtp-stream.h @@ -110,6 +110,7 @@ struct amdtp_stream { int (*handle_packet)(struct amdtp_stream *s, unsigned int payload_quadlets, unsigned int cycle, unsigned int index); + unsigned int max_payload_length; /* For CIP headers. */ unsigned int source_node_id_field; -- cgit v1.2.3-59-g8ed1b From 8fdaebbb83b4e734c7ec1fcab5f1b26ec264965d Mon Sep 17 00:00:00 2001 From: Bhumika Goyal Date: Wed, 12 Apr 2017 18:38:06 +0530 Subject: ALSA: usb-audio: constify snd_kcontrol_new structures Declare snd_kcontrol_new strcutures as const as they are only passed as an argument to the function snd_ctl_new1. This argument is of type const, so snd_kcontrol_new structures having this property can be made const too. Done using Coccinelle: @r disable optional_qualifier@ identifier x; position p; @@ static struct snd_kcontrol_new x@p={...}; @ok@ identifier r.x; position p; @@ snd_ctl_new1(&x@p,...) @bad@ position p != {r.p,ok.p}; identifier r.x; @@ x@p @depends on !bad disable optional_qualifier@ identifier r.x; @@ +const struct snd_kcontrol_new x; Signed-off-by: Bhumika Goyal Signed-off-by: Takashi Iwai --- sound/usb/midi.c | 2 +- sound/usb/mixer.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/usb/midi.c b/sound/usb/midi.c index 6e763bc8d7db..a35f41467237 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -1922,7 +1922,7 @@ static int roland_load_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new roland_load_ctl = { +static const struct snd_kcontrol_new roland_load_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "MIDI Input Mode", .info = roland_load_info, diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 4703caea56b2..082736c539bc 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1172,7 +1172,7 @@ static struct snd_kcontrol_new usb_feature_unit_ctl = { }; /* the read-only variant */ -static struct snd_kcontrol_new usb_feature_unit_ctl_ro = { +static const struct snd_kcontrol_new usb_feature_unit_ctl_ro = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", /* will be filled later manually */ .info = mixer_ctl_feature_info, @@ -1745,7 +1745,7 @@ static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol, } /* alsa control interface for processing/extension unit */ -static struct snd_kcontrol_new mixer_procunit_ctl = { +static const struct snd_kcontrol_new mixer_procunit_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", /* will be filled later */ .info = mixer_ctl_feature_info, @@ -2033,7 +2033,7 @@ static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol, } /* alsa control interface for selector unit */ -static struct snd_kcontrol_new mixer_selectunit_ctl = { +static const struct snd_kcontrol_new mixer_selectunit_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", /* will be filled later */ .info = mixer_ctl_selector_info, -- cgit v1.2.3-59-g8ed1b From 49c41e1f23882f7950cff66992115bba03f1bbcf Mon Sep 17 00:00:00 2001 From: Bhumika Goyal Date: Wed, 12 Apr 2017 18:40:47 +0530 Subject: ALSA: line6: constify snd_kcontrol_new structures Declare snd_kcontrol_new strcutures as const as they are only passed as an argument to the function snd_ctl_new1. This argument is of type const, so snd_kcontrol_new structures having this property can be made const too. Done using Coccinelle: @r disable optional_qualifier@ identifier x; position p; @@ static struct snd_kcontrol_new x@p={...}; @ok@ identifier r.x; position p; @@ snd_ctl_new1(&x@p,...) @bad@ position p != {r.p,ok.p}; identifier r.x; @@ x@p @depends on !bad disable optional_qualifier@ identifier r.x; @@ +const struct snd_kcontrol_new x; Signed-off-by: Bhumika Goyal Signed-off-by: Takashi Iwai --- sound/usb/line6/pod.c | 2 +- sound/usb/line6/toneport.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/usb/line6/pod.c b/sound/usb/line6/pod.c index 17aa616e61f5..358224cc5638 100644 --- a/sound/usb/line6/pod.c +++ b/sound/usb/line6/pod.c @@ -380,7 +380,7 @@ static int snd_pod_control_monitor_put(struct snd_kcontrol *kcontrol, } /* control definition */ -static struct snd_kcontrol_new pod_control_monitor = { +static const struct snd_kcontrol_new pod_control_monitor = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Monitor Playback Volume", .index = 0, diff --git a/sound/usb/line6/toneport.c b/sound/usb/line6/toneport.c index 8e22f430d700..ba7975c0d03d 100644 --- a/sound/usb/line6/toneport.c +++ b/sound/usb/line6/toneport.c @@ -250,7 +250,7 @@ static void toneport_start_pcm(unsigned long arg) } /* control definition */ -static struct snd_kcontrol_new toneport_control_monitor = { +static const struct snd_kcontrol_new toneport_control_monitor = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Monitor Playback Volume", .index = 0, @@ -261,7 +261,7 @@ static struct snd_kcontrol_new toneport_control_monitor = { }; /* source selector definition */ -static struct snd_kcontrol_new toneport_control_source = { +static const struct snd_kcontrol_new toneport_control_source = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Capture Source", .index = 0, -- cgit v1.2.3-59-g8ed1b From a87a4d23e86b7ff9b3f1300774c942de139ba16a Mon Sep 17 00:00:00 2001 From: Ander Conselvan De Oliveira Date: Thu, 13 Apr 2017 13:05:35 +0530 Subject: ALSA: hda: Move common haswell init to a helper Geminilake vendor nid is different from other Skylake variants, but rest of the initialization code is same. So a variable is added in hdmi_spec to store the platform specific vendor nid and move the initialization code to a helper function to be used by both platform specific init. Fixes: 126cfa2f5e15 ("ALSA: hda: Add Geminilake HDMI codec ID") Signed-off-by: Ander Conselvan De Oliveira Signed-off-by: Subhransu S. Prusty Signed-off-by: Jaikrishna Nemallapudi Cc: Senthilnathan Veppur Cc: Vinod Koul Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_hdmi.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index 37f11560186a..90e4ff87445e 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -177,6 +177,7 @@ struct hdmi_spec { bool i915_bound; /* was i915 bound in this driver? */ struct hdac_chmap chmap; + hda_nid_t vendor_nid; }; #ifdef CONFIG_SND_HDA_I915 @@ -2372,6 +2373,7 @@ static void intel_haswell_fixup_connect_list(struct hda_codec *codec, } #define INTEL_VENDOR_NID 0x08 +#define INTEL_GLK_VENDOR_NID 0x0B #define INTEL_GET_VENDOR_VERB 0xf81 #define INTEL_SET_VENDOR_VERB 0x781 #define INTEL_EN_DP12 0x02 /* enable DP 1.2 features */ @@ -2381,14 +2383,15 @@ static void intel_haswell_enable_all_pins(struct hda_codec *codec, bool update_tree) { unsigned int vendor_param; + struct hdmi_spec *spec = codec->spec; - vendor_param = snd_hda_codec_read(codec, INTEL_VENDOR_NID, 0, + vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0, INTEL_GET_VENDOR_VERB, 0); if (vendor_param == -1 || vendor_param & INTEL_EN_ALL_PIN_CVTS) return; vendor_param |= INTEL_EN_ALL_PIN_CVTS; - vendor_param = snd_hda_codec_read(codec, INTEL_VENDOR_NID, 0, + vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0, INTEL_SET_VENDOR_VERB, vendor_param); if (vendor_param == -1) return; @@ -2400,8 +2403,9 @@ static void intel_haswell_enable_all_pins(struct hda_codec *codec, static void intel_haswell_fixup_enable_dp12(struct hda_codec *codec) { unsigned int vendor_param; + struct hdmi_spec *spec = codec->spec; - vendor_param = snd_hda_codec_read(codec, INTEL_VENDOR_NID, 0, + vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0, INTEL_GET_VENDOR_VERB, 0); if (vendor_param == -1 || vendor_param & INTEL_EN_DP12) return; @@ -2409,7 +2413,7 @@ static void intel_haswell_fixup_enable_dp12(struct hda_codec *codec) /* enable DP1.2 mode */ vendor_param |= INTEL_EN_DP12; snd_hdac_regmap_add_vendor_verb(&codec->core, INTEL_SET_VENDOR_VERB); - snd_hda_codec_write_cache(codec, INTEL_VENDOR_NID, 0, + snd_hda_codec_write_cache(codec, spec->vendor_nid, 0, INTEL_SET_VENDOR_VERB, vendor_param); } @@ -2503,7 +2507,7 @@ static void i915_pin_cvt_fixup(struct hda_codec *codec, } /* Intel Haswell and onwards; audio component with eld notifier */ -static int patch_i915_hsw_hdmi(struct hda_codec *codec) +static int intel_hsw_common_init(struct hda_codec *codec, hda_nid_t vendor_nid) { struct hdmi_spec *spec; int err; @@ -2520,6 +2524,7 @@ static int patch_i915_hsw_hdmi(struct hda_codec *codec) spec = codec->spec; codec->dp_mst = true; spec->dyn_pcm_assign = true; + spec->vendor_nid = vendor_nid; intel_haswell_enable_all_pins(codec, true); intel_haswell_fixup_enable_dp12(codec); @@ -2548,6 +2553,16 @@ static int patch_i915_hsw_hdmi(struct hda_codec *codec) return 0; } +static int patch_i915_hsw_hdmi(struct hda_codec *codec) +{ + return intel_hsw_common_init(codec, INTEL_VENDOR_NID); +} + +static int patch_i915_glk_hdmi(struct hda_codec *codec) +{ + return intel_hsw_common_init(codec, INTEL_GLK_VENDOR_NID); +} + /* Intel Baytrail and Braswell; with eld notifier */ static int patch_i915_byt_hdmi(struct hda_codec *codec) { @@ -3800,7 +3815,7 @@ HDA_CODEC_ENTRY(0x80862808, "Broadwell HDMI", patch_i915_hsw_hdmi), HDA_CODEC_ENTRY(0x80862809, "Skylake HDMI", patch_i915_hsw_hdmi), HDA_CODEC_ENTRY(0x8086280a, "Broxton HDMI", patch_i915_hsw_hdmi), HDA_CODEC_ENTRY(0x8086280b, "Kabylake HDMI", patch_i915_hsw_hdmi), -HDA_CODEC_ENTRY(0x8086280d, "Geminilake HDMI", patch_i915_hsw_hdmi), +HDA_CODEC_ENTRY(0x8086280d, "Geminilake HDMI", patch_i915_glk_hdmi), HDA_CODEC_ENTRY(0x80862880, "CedarTrail HDMI", patch_generic_hdmi), HDA_CODEC_ENTRY(0x80862882, "Valleyview2 HDMI", patch_i915_byt_hdmi), HDA_CODEC_ENTRY(0x80862883, "Braswell HDMI", patch_i915_byt_hdmi), -- cgit v1.2.3-59-g8ed1b From 1e0f8f68f764b93e8d2d0e87e23532f2186a23a1 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 14 Apr 2017 13:13:50 +0900 Subject: ALSA: usb-line6: constify snd_kcontrol_new strucutre array In kernel APIs of ALSA control interface, drivers can create a control element set by a call of snd_ctl_new1() with a template. This template is known to have const qualifier in general cases. This commit adds the qualifier to template array, for safer program and runtime. Application of this change moves the symbol from .data section to .rodata section. Cc: Bhumika Goyal Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/usb/line6/pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index fab53f58d447..b3854f8c0c67 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -430,7 +430,7 @@ static int snd_line6_control_playback_put(struct snd_kcontrol *kcontrol, } /* control definition */ -static struct snd_kcontrol_new line6_controls[] = { +static const struct snd_kcontrol_new line6_controls[] = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Playback Volume", -- cgit v1.2.3-59-g8ed1b From 531f471834227d0321110c071ea352bb14aca36d Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 14 Apr 2017 16:46:25 +0900 Subject: ALSA: firewire-lib/firewire-tascam: localize async midi port In Linux kernel 4.4, firewire-lib got a feature called as 'async midi port' for transmission of MIDI message via IEEE 1394 asynchronous communication, however actual consumer of this feature is ALSA driver for TASCAM FireWire series only. When adding this feature, I assumed that ALSA driver for Digi00x might also be a consumer, actually it's not. This commit moves the feature from firewire-lib to firewire-tascam module. Two minor kernel APIs are removed. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/lib.c | 141 ----------------------------- sound/firewire/lib.h | 54 ----------- sound/firewire/tascam/tascam-transaction.c | 125 +++++++++++++++++++++++++ sound/firewire/tascam/tascam.h | 45 +++++++++ 4 files changed, 170 insertions(+), 195 deletions(-) (limited to 'sound') diff --git a/sound/firewire/lib.c b/sound/firewire/lib.c index 7683238283b6..39dfa74906ef 100644 --- a/sound/firewire/lib.c +++ b/sound/firewire/lib.c @@ -99,147 +99,6 @@ void snd_fw_schedule_registration(struct fw_unit *unit, } EXPORT_SYMBOL(snd_fw_schedule_registration); -static void async_midi_port_callback(struct fw_card *card, int rcode, - void *data, size_t length, - void *callback_data) -{ - struct snd_fw_async_midi_port *port = callback_data; - struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream); - - /* This port is closed. */ - if (substream == NULL) - return; - - if (rcode == RCODE_COMPLETE) - snd_rawmidi_transmit_ack(substream, port->consume_bytes); - else if (!rcode_is_permanent_error(rcode)) - /* To start next transaction immediately for recovery. */ - port->next_ktime = 0; - else - /* Don't continue processing. */ - port->error = true; - - port->idling = true; - - if (!snd_rawmidi_transmit_empty(substream)) - schedule_work(&port->work); -} - -static void midi_port_work(struct work_struct *work) -{ - struct snd_fw_async_midi_port *port = - container_of(work, struct snd_fw_async_midi_port, work); - struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream); - int generation; - int type; - - /* Under transacting or error state. */ - if (!port->idling || port->error) - return; - - /* Nothing to do. */ - if (substream == NULL || snd_rawmidi_transmit_empty(substream)) - return; - - /* Do it in next chance. */ - if (ktime_after(port->next_ktime, ktime_get())) { - schedule_work(&port->work); - return; - } - - /* - * Fill the buffer. The callee must use snd_rawmidi_transmit_peek(). - * Later, snd_rawmidi_transmit_ack() is called. - */ - memset(port->buf, 0, port->len); - port->consume_bytes = port->fill(substream, port->buf); - if (port->consume_bytes <= 0) { - /* Do it in next chance, immediately. */ - if (port->consume_bytes == 0) { - port->next_ktime = 0; - schedule_work(&port->work); - } else { - /* Fatal error. */ - port->error = true; - } - return; - } - - /* Calculate type of transaction. */ - if (port->len == 4) - type = TCODE_WRITE_QUADLET_REQUEST; - else - type = TCODE_WRITE_BLOCK_REQUEST; - - /* Set interval to next transaction. */ - port->next_ktime = ktime_add_ns(ktime_get(), - port->consume_bytes * 8 * NSEC_PER_SEC / 31250); - - /* Start this transaction. */ - port->idling = false; - - /* - * In Linux FireWire core, when generation is updated with memory - * barrier, node id has already been updated. In this module, After - * this smp_rmb(), load/store instructions to memory are completed. - * Thus, both of generation and node id are available with recent - * values. This is a light-serialization solution to handle bus reset - * events on IEEE 1394 bus. - */ - generation = port->parent->generation; - smp_rmb(); - - fw_send_request(port->parent->card, &port->transaction, type, - port->parent->node_id, generation, - port->parent->max_speed, port->addr, - port->buf, port->len, async_midi_port_callback, - port); -} - -/** - * snd_fw_async_midi_port_init - initialize asynchronous MIDI port structure - * @port: the asynchronous MIDI port to initialize - * @unit: the target of the asynchronous transaction - * @addr: the address to which transactions are transferred - * @len: the length of transaction - * @fill: the callback function to fill given buffer, and returns the - * number of consumed bytes for MIDI message. - * - */ -int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, - struct fw_unit *unit, u64 addr, unsigned int len, - snd_fw_async_midi_port_fill fill) -{ - port->len = DIV_ROUND_UP(len, 4) * 4; - port->buf = kzalloc(port->len, GFP_KERNEL); - if (port->buf == NULL) - return -ENOMEM; - - port->parent = fw_parent_device(unit); - port->addr = addr; - port->fill = fill; - port->idling = true; - port->next_ktime = 0; - port->error = false; - - INIT_WORK(&port->work, midi_port_work); - - return 0; -} -EXPORT_SYMBOL(snd_fw_async_midi_port_init); - -/** - * snd_fw_async_midi_port_destroy - free asynchronous MIDI port structure - * @port: the asynchronous MIDI port structure - */ -void snd_fw_async_midi_port_destroy(struct snd_fw_async_midi_port *port) -{ - snd_fw_async_midi_port_finish(port); - cancel_work_sync(&port->work); - kfree(port->buf); -} -EXPORT_SYMBOL(snd_fw_async_midi_port_destroy); - MODULE_DESCRIPTION("FireWire audio helper functions"); MODULE_AUTHOR("Clemens Ladisch "); MODULE_LICENSE("GPL v2"); diff --git a/sound/firewire/lib.h b/sound/firewire/lib.h index c3768cd494a5..eef70922ed89 100644 --- a/sound/firewire/lib.h +++ b/sound/firewire/lib.h @@ -25,58 +25,4 @@ static inline bool rcode_is_permanent_error(int rcode) void snd_fw_schedule_registration(struct fw_unit *unit, struct delayed_work *dwork); -struct snd_fw_async_midi_port; -typedef int (*snd_fw_async_midi_port_fill)( - struct snd_rawmidi_substream *substream, - u8 *buf); - -struct snd_fw_async_midi_port { - struct fw_device *parent; - struct work_struct work; - bool idling; - ktime_t next_ktime; - bool error; - - u64 addr; - struct fw_transaction transaction; - - u8 *buf; - unsigned int len; - - struct snd_rawmidi_substream *substream; - snd_fw_async_midi_port_fill fill; - int consume_bytes; -}; - -int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, - struct fw_unit *unit, u64 addr, unsigned int len, - snd_fw_async_midi_port_fill fill); -void snd_fw_async_midi_port_destroy(struct snd_fw_async_midi_port *port); - -/** - * snd_fw_async_midi_port_run - run transactions for the async MIDI port - * @port: the asynchronous MIDI port - * @substream: the MIDI substream - */ -static inline void -snd_fw_async_midi_port_run(struct snd_fw_async_midi_port *port, - struct snd_rawmidi_substream *substream) -{ - if (!port->error) { - port->substream = substream; - schedule_work(&port->work); - } -} - -/** - * snd_fw_async_midi_port_finish - finish the asynchronous MIDI port - * @port: the asynchronous MIDI port - */ -static inline void -snd_fw_async_midi_port_finish(struct snd_fw_async_midi_port *port) -{ - port->substream = NULL; - port->error = false; -} - #endif diff --git a/sound/firewire/tascam/tascam-transaction.c b/sound/firewire/tascam/tascam-transaction.c index 040a96d1ba8e..8ba006e456e8 100644 --- a/sound/firewire/tascam/tascam-transaction.c +++ b/sound/firewire/tascam/tascam-transaction.c @@ -144,6 +144,131 @@ static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf) return consume; } +static void async_midi_port_callback(struct fw_card *card, int rcode, + void *data, size_t length, + void *callback_data) +{ + struct snd_fw_async_midi_port *port = callback_data; + struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream); + + /* This port is closed. */ + if (substream == NULL) + return; + + if (rcode == RCODE_COMPLETE) + snd_rawmidi_transmit_ack(substream, port->consume_bytes); + else if (!rcode_is_permanent_error(rcode)) + /* To start next transaction immediately for recovery. */ + port->next_ktime = 0; + else + /* Don't continue processing. */ + port->error = true; + + port->idling = true; + + if (!snd_rawmidi_transmit_empty(substream)) + schedule_work(&port->work); +} + +static void midi_port_work(struct work_struct *work) +{ + struct snd_fw_async_midi_port *port = + container_of(work, struct snd_fw_async_midi_port, work); + struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream); + int generation; + int type; + + /* Under transacting or error state. */ + if (!port->idling || port->error) + return; + + /* Nothing to do. */ + if (substream == NULL || snd_rawmidi_transmit_empty(substream)) + return; + + /* Do it in next chance. */ + if (ktime_after(port->next_ktime, ktime_get())) { + schedule_work(&port->work); + return; + } + + /* + * Fill the buffer. The callee must use snd_rawmidi_transmit_peek(). + * Later, snd_rawmidi_transmit_ack() is called. + */ + memset(port->buf, 0, port->len); + port->consume_bytes = port->fill(substream, port->buf); + if (port->consume_bytes <= 0) { + /* Do it in next chance, immediately. */ + if (port->consume_bytes == 0) { + port->next_ktime = 0; + schedule_work(&port->work); + } else { + /* Fatal error. */ + port->error = true; + } + return; + } + + /* Calculate type of transaction. */ + if (port->len == 4) + type = TCODE_WRITE_QUADLET_REQUEST; + else + type = TCODE_WRITE_BLOCK_REQUEST; + + /* Set interval to next transaction. */ + port->next_ktime = ktime_add_ns(ktime_get(), + port->consume_bytes * 8 * NSEC_PER_SEC / 31250); + + /* Start this transaction. */ + port->idling = false; + + /* + * In Linux FireWire core, when generation is updated with memory + * barrier, node id has already been updated. In this module, After + * this smp_rmb(), load/store instructions to memory are completed. + * Thus, both of generation and node id are available with recent + * values. This is a light-serialization solution to handle bus reset + * events on IEEE 1394 bus. + */ + generation = port->parent->generation; + smp_rmb(); + + fw_send_request(port->parent->card, &port->transaction, type, + port->parent->node_id, generation, + port->parent->max_speed, port->addr, + port->buf, port->len, async_midi_port_callback, + port); +} + +int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, + struct fw_unit *unit, u64 addr, unsigned int len, + snd_fw_async_midi_port_fill fill) +{ + port->len = DIV_ROUND_UP(len, 4) * 4; + port->buf = kzalloc(port->len, GFP_KERNEL); + if (port->buf == NULL) + return -ENOMEM; + + port->parent = fw_parent_device(unit); + port->addr = addr; + port->fill = fill; + port->idling = true; + port->next_ktime = 0; + port->error = false; + + INIT_WORK(&port->work, midi_port_work); + + return 0; +} + +void snd_fw_async_midi_port_destroy(struct snd_fw_async_midi_port *port) +{ + snd_fw_async_midi_port_finish(port); + cancel_work_sync(&port->work); + kfree(port->buf); +} + static void handle_midi_tx(struct fw_card *card, struct fw_request *request, int tcode, int destination, int source, int generation, unsigned long long offset, diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index d3cd4065722b..de76313e5d50 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -45,6 +45,29 @@ struct snd_tscm_spec { #define TSCM_MIDI_IN_PORT_MAX 4 #define TSCM_MIDI_OUT_PORT_MAX 4 +struct snd_fw_async_midi_port; +typedef int (*snd_fw_async_midi_port_fill)( + struct snd_rawmidi_substream *substream, + u8 *buf); + +struct snd_fw_async_midi_port { + struct fw_device *parent; + struct work_struct work; + bool idling; + ktime_t next_ktime; + bool error; + + u64 addr; + struct fw_transaction transaction; + + u8 *buf; + unsigned int len; + + struct snd_rawmidi_substream *substream; + snd_fw_async_midi_port_fill fill; + int consume_bytes; +}; + struct snd_tscm { struct snd_card *card; struct fw_unit *unit; @@ -131,6 +154,28 @@ void snd_tscm_stream_lock_changed(struct snd_tscm *tscm); int snd_tscm_stream_lock_try(struct snd_tscm *tscm); void snd_tscm_stream_lock_release(struct snd_tscm *tscm); +int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, + struct fw_unit *unit, u64 addr, unsigned int len, + snd_fw_async_midi_port_fill fill); +void snd_fw_async_midi_port_destroy(struct snd_fw_async_midi_port *port); + +static inline void +snd_fw_async_midi_port_run(struct snd_fw_async_midi_port *port, + struct snd_rawmidi_substream *substream) +{ + if (!port->error) { + port->substream = substream; + schedule_work(&port->work); + } +} + +static inline void +snd_fw_async_midi_port_finish(struct snd_fw_async_midi_port *port) +{ + port->substream = NULL; + port->error = false; +} + int snd_tscm_transaction_register(struct snd_tscm *tscm); int snd_tscm_transaction_reregister(struct snd_tscm *tscm); void snd_tscm_transaction_unregister(struct snd_tscm *tscm); -- cgit v1.2.3-59-g8ed1b From 9bae2150d0144e0be423367e5385442141979213 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 14 Apr 2017 16:46:26 +0900 Subject: ALSA: firewire-tascam: remove callback function from async midi port As a result of localization of async midi port, ALSA driver for TASCAM FireWire series can call helper function directly instead of callback registration. This commit removes the redundant design. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/tascam/tascam-transaction.c | 8 +++----- sound/firewire/tascam/tascam.h | 9 +-------- 2 files changed, 4 insertions(+), 13 deletions(-) (limited to 'sound') diff --git a/sound/firewire/tascam/tascam-transaction.c b/sound/firewire/tascam/tascam-transaction.c index 8ba006e456e8..dcbc0033a3ad 100644 --- a/sound/firewire/tascam/tascam-transaction.c +++ b/sound/firewire/tascam/tascam-transaction.c @@ -197,7 +197,7 @@ static void midi_port_work(struct work_struct *work) * Later, snd_rawmidi_transmit_ack() is called. */ memset(port->buf, 0, port->len); - port->consume_bytes = port->fill(substream, port->buf); + port->consume_bytes = fill_message(substream, port->buf); if (port->consume_bytes <= 0) { /* Do it in next chance, immediately. */ if (port->consume_bytes == 0) { @@ -242,8 +242,7 @@ static void midi_port_work(struct work_struct *work) } int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, - struct fw_unit *unit, u64 addr, unsigned int len, - snd_fw_async_midi_port_fill fill) + struct fw_unit *unit, u64 addr, unsigned int len) { port->len = DIV_ROUND_UP(len, 4) * 4; port->buf = kzalloc(port->len, GFP_KERNEL); @@ -252,7 +251,6 @@ int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, port->parent = fw_parent_device(unit); port->addr = addr; - port->fill = fill; port->idling = true; port->next_ktime = 0; port->error = false; @@ -347,7 +345,7 @@ int snd_tscm_transaction_register(struct snd_tscm *tscm) err = snd_fw_async_midi_port_init( &tscm->out_ports[i], tscm->unit, TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_RX_QUAD, - 4, fill_message); + 4); if (err < 0) goto error; } diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index de76313e5d50..116a2003f19e 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -45,11 +45,6 @@ struct snd_tscm_spec { #define TSCM_MIDI_IN_PORT_MAX 4 #define TSCM_MIDI_OUT_PORT_MAX 4 -struct snd_fw_async_midi_port; -typedef int (*snd_fw_async_midi_port_fill)( - struct snd_rawmidi_substream *substream, - u8 *buf); - struct snd_fw_async_midi_port { struct fw_device *parent; struct work_struct work; @@ -64,7 +59,6 @@ struct snd_fw_async_midi_port { unsigned int len; struct snd_rawmidi_substream *substream; - snd_fw_async_midi_port_fill fill; int consume_bytes; }; @@ -155,8 +149,7 @@ int snd_tscm_stream_lock_try(struct snd_tscm *tscm); void snd_tscm_stream_lock_release(struct snd_tscm *tscm); int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, - struct fw_unit *unit, u64 addr, unsigned int len, - snd_fw_async_midi_port_fill fill); + struct fw_unit *unit, u64 addr, unsigned int len); void snd_fw_async_midi_port_destroy(struct snd_fw_async_midi_port *port); static inline void -- cgit v1.2.3-59-g8ed1b From 38d5826142496a6e0c21fa3fdb48fa910008c159 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 14 Apr 2017 16:46:27 +0900 Subject: ALSA: firewire-tascam: send fixed-length transaction for async midi port TASCAM FireWire series uses asynchronous transactions with fixed length payload for MIDI messaging. On the other hand, ALSA driver for the series has a redundant design to handle different length of payload. This commit removes the redundant abstraction. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/tascam/tascam-transaction.c | 22 +++++++--------------- sound/firewire/tascam/tascam.h | 3 +-- 2 files changed, 8 insertions(+), 17 deletions(-) (limited to 'sound') diff --git a/sound/firewire/tascam/tascam-transaction.c b/sound/firewire/tascam/tascam-transaction.c index dcbc0033a3ad..2f5e20cf4ce5 100644 --- a/sound/firewire/tascam/tascam-transaction.c +++ b/sound/firewire/tascam/tascam-transaction.c @@ -176,7 +176,6 @@ static void midi_port_work(struct work_struct *work) container_of(work, struct snd_fw_async_midi_port, work); struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream); int generation; - int type; /* Under transacting or error state. */ if (!port->idling || port->error) @@ -196,7 +195,7 @@ static void midi_port_work(struct work_struct *work) * Fill the buffer. The callee must use snd_rawmidi_transmit_peek(). * Later, snd_rawmidi_transmit_ack() is called. */ - memset(port->buf, 0, port->len); + memset(port->buf, 0, 4); port->consume_bytes = fill_message(substream, port->buf); if (port->consume_bytes <= 0) { /* Do it in next chance, immediately. */ @@ -210,12 +209,6 @@ static void midi_port_work(struct work_struct *work) return; } - /* Calculate type of transaction. */ - if (port->len == 4) - type = TCODE_WRITE_QUADLET_REQUEST; - else - type = TCODE_WRITE_BLOCK_REQUEST; - /* Set interval to next transaction. */ port->next_ktime = ktime_add_ns(ktime_get(), port->consume_bytes * 8 * NSEC_PER_SEC / 31250); @@ -234,18 +227,18 @@ static void midi_port_work(struct work_struct *work) generation = port->parent->generation; smp_rmb(); - fw_send_request(port->parent->card, &port->transaction, type, + fw_send_request(port->parent->card, &port->transaction, + TCODE_WRITE_QUADLET_REQUEST, port->parent->node_id, generation, port->parent->max_speed, port->addr, - port->buf, port->len, async_midi_port_callback, + port->buf, 4, async_midi_port_callback, port); } int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, - struct fw_unit *unit, u64 addr, unsigned int len) + struct fw_unit *unit, u64 addr) { - port->len = DIV_ROUND_UP(len, 4) * 4; - port->buf = kzalloc(port->len, GFP_KERNEL); + port->buf = kzalloc(4, GFP_KERNEL); if (port->buf == NULL) return -ENOMEM; @@ -344,8 +337,7 @@ int snd_tscm_transaction_register(struct snd_tscm *tscm) for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++) { err = snd_fw_async_midi_port_init( &tscm->out_ports[i], tscm->unit, - TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_RX_QUAD, - 4); + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_RX_QUAD); if (err < 0) goto error; } diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index 116a2003f19e..c89d7afa998f 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -56,7 +56,6 @@ struct snd_fw_async_midi_port { struct fw_transaction transaction; u8 *buf; - unsigned int len; struct snd_rawmidi_substream *substream; int consume_bytes; @@ -149,7 +148,7 @@ int snd_tscm_stream_lock_try(struct snd_tscm *tscm); void snd_tscm_stream_lock_release(struct snd_tscm *tscm); int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, - struct fw_unit *unit, u64 addr, unsigned int len); + struct fw_unit *unit, u64 addr); void snd_fw_async_midi_port_destroy(struct snd_fw_async_midi_port *port); static inline void -- cgit v1.2.3-59-g8ed1b From 27badc4c10da29b884f1ac7cc9ce13e849ce8be4 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 14 Apr 2017 16:46:28 +0900 Subject: ALSA: firewire-tascam: use the same address for asynchronous transaction for MIDI message Units on TASCAM FireWire series receive MIDI messages by asynchronous transactions on IEEE 1394 bus. Although the transaction is sent to a certain register, current ALSA driver for this series has a redundant design. This commit use the same address for the transaction. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/tascam/tascam-transaction.c | 9 ++++----- sound/firewire/tascam/tascam.h | 3 +-- 2 files changed, 5 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/firewire/tascam/tascam-transaction.c b/sound/firewire/tascam/tascam-transaction.c index 2f5e20cf4ce5..4e362b83d8e8 100644 --- a/sound/firewire/tascam/tascam-transaction.c +++ b/sound/firewire/tascam/tascam-transaction.c @@ -230,20 +230,20 @@ static void midi_port_work(struct work_struct *work) fw_send_request(port->parent->card, &port->transaction, TCODE_WRITE_QUADLET_REQUEST, port->parent->node_id, generation, - port->parent->max_speed, port->addr, + port->parent->max_speed, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_RX_QUAD, port->buf, 4, async_midi_port_callback, port); } int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, - struct fw_unit *unit, u64 addr) + struct fw_unit *unit) { port->buf = kzalloc(4, GFP_KERNEL); if (port->buf == NULL) return -ENOMEM; port->parent = fw_parent_device(unit); - port->addr = addr; port->idling = true; port->next_ktime = 0; port->error = false; @@ -336,8 +336,7 @@ int snd_tscm_transaction_register(struct snd_tscm *tscm) for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++) { err = snd_fw_async_midi_port_init( - &tscm->out_ports[i], tscm->unit, - TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_RX_QUAD); + &tscm->out_ports[i], tscm->unit); if (err < 0) goto error; } diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index c89d7afa998f..de0aefe2d690 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -52,7 +52,6 @@ struct snd_fw_async_midi_port { ktime_t next_ktime; bool error; - u64 addr; struct fw_transaction transaction; u8 *buf; @@ -148,7 +147,7 @@ int snd_tscm_stream_lock_try(struct snd_tscm *tscm); void snd_tscm_stream_lock_release(struct snd_tscm *tscm); int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, - struct fw_unit *unit, u64 addr); + struct fw_unit *unit); void snd_fw_async_midi_port_destroy(struct snd_fw_async_midi_port *port); static inline void -- cgit v1.2.3-59-g8ed1b From 98a00d3602a1c50a397893c5de26dbbe7d77804a Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 14 Apr 2017 16:46:29 +0900 Subject: ALSA: firewire-tascam: use fixed-length array for message cache to async midi port ALSA driver for TASCAM FireWire series internally allocates 4 byte buffer for asynchronous transaction to transfer MIDI messages. However, the buffer can be allocated with memory object of parent structure. This commit adds 4 byte array as a member of the structure and obsoletes the redundant allocation. This is deallocated with memory object of parent structure. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/tascam/tascam-transaction.c | 15 --------------- sound/firewire/tascam/tascam.h | 4 ++-- 2 files changed, 2 insertions(+), 17 deletions(-) (limited to 'sound') diff --git a/sound/firewire/tascam/tascam-transaction.c b/sound/firewire/tascam/tascam-transaction.c index 4e362b83d8e8..248afe663387 100644 --- a/sound/firewire/tascam/tascam-transaction.c +++ b/sound/firewire/tascam/tascam-transaction.c @@ -239,10 +239,6 @@ static void midi_port_work(struct work_struct *work) int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, struct fw_unit *unit) { - port->buf = kzalloc(4, GFP_KERNEL); - if (port->buf == NULL) - return -ENOMEM; - port->parent = fw_parent_device(unit); port->idling = true; port->next_ktime = 0; @@ -253,13 +249,6 @@ int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, return 0; } -void snd_fw_async_midi_port_destroy(struct snd_fw_async_midi_port *port) -{ - snd_fw_async_midi_port_finish(port); - cancel_work_sync(&port->work); - kfree(port->buf); -} - static void handle_midi_tx(struct fw_card *card, struct fw_request *request, int tcode, int destination, int source, int generation, unsigned long long offset, @@ -389,7 +378,6 @@ int snd_tscm_transaction_reregister(struct snd_tscm *tscm) void snd_tscm_transaction_unregister(struct snd_tscm *tscm) { __be32 reg; - unsigned int i; if (tscm->async_handler.callback_data == NULL) return; @@ -416,7 +404,4 @@ void snd_tscm_transaction_unregister(struct snd_tscm *tscm) fw_core_remove_address_handler(&tscm->async_handler); tscm->async_handler.callback_data = NULL; - - for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++) - snd_fw_async_midi_port_destroy(&tscm->out_ports[i]); } diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index de0aefe2d690..fdf352bc303f 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -54,7 +54,7 @@ struct snd_fw_async_midi_port { struct fw_transaction transaction; - u8 *buf; + u8 buf[4]; struct snd_rawmidi_substream *substream; int consume_bytes; @@ -148,7 +148,6 @@ void snd_tscm_stream_lock_release(struct snd_tscm *tscm); int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, struct fw_unit *unit); -void snd_fw_async_midi_port_destroy(struct snd_fw_async_midi_port *port); static inline void snd_fw_async_midi_port_run(struct snd_fw_async_midi_port *port, @@ -164,6 +163,7 @@ static inline void snd_fw_async_midi_port_finish(struct snd_fw_async_midi_port *port) { port->substream = NULL; + cancel_work_sync(&port->work); port->error = false; } -- cgit v1.2.3-59-g8ed1b From 3e7dc65ca001a6e40e78b912c6146ec013a3b49a Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 14 Apr 2017 16:46:30 +0900 Subject: ALSA: firewire-tascam: initialize parameters at open of rawmidi character devices In current design of ALSA driver for TASCAM FireWire series, initialization of members in asymc midi port structure is done at device probing. Some of the members should be initialized every time to use rawmidi devices because they're changed in sequence of transmission for MIDI messages. This commit adds a new function to initialize them. Invariant parameters during object lifetime are kept as is. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/tascam/tascam-midi.c | 2 ++ sound/firewire/tascam/tascam-transaction.c | 16 ++++------------ sound/firewire/tascam/tascam.h | 3 +-- 3 files changed, 7 insertions(+), 14 deletions(-) (limited to 'sound') diff --git a/sound/firewire/tascam/tascam-midi.c b/sound/firewire/tascam/tascam-midi.c index df4f95d65925..901df81129bb 100644 --- a/sound/firewire/tascam/tascam-midi.c +++ b/sound/firewire/tascam/tascam-midi.c @@ -18,6 +18,8 @@ static int midi_playback_open(struct snd_rawmidi_substream *substream) { struct snd_tscm *tscm = substream->rmidi->private_data; + snd_fw_async_midi_port_init(&tscm->out_ports[substream->number]); + /* Initialize internal status. */ tscm->running_status[substream->number] = 0; tscm->on_sysex[substream->number] = 0; diff --git a/sound/firewire/tascam/tascam-transaction.c b/sound/firewire/tascam/tascam-transaction.c index 248afe663387..a248a4ae0353 100644 --- a/sound/firewire/tascam/tascam-transaction.c +++ b/sound/firewire/tascam/tascam-transaction.c @@ -236,17 +236,10 @@ static void midi_port_work(struct work_struct *work) port); } -int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, - struct fw_unit *unit) +void snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port) { - port->parent = fw_parent_device(unit); port->idling = true; - port->next_ktime = 0; port->error = false; - - INIT_WORK(&port->work, midi_port_work); - - return 0; } static void handle_midi_tx(struct fw_card *card, struct fw_request *request, @@ -324,10 +317,9 @@ int snd_tscm_transaction_register(struct snd_tscm *tscm) goto error; for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++) { - err = snd_fw_async_midi_port_init( - &tscm->out_ports[i], tscm->unit); - if (err < 0) - goto error; + tscm->out_ports[i].parent = fw_parent_device(tscm->unit); + tscm->out_ports[i].next_ktime = 0; + INIT_WORK(&tscm->out_ports[i].work, midi_port_work); } return err; diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index fdf352bc303f..37ec2016d033 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -146,8 +146,7 @@ void snd_tscm_stream_lock_changed(struct snd_tscm *tscm); int snd_tscm_stream_lock_try(struct snd_tscm *tscm); void snd_tscm_stream_lock_release(struct snd_tscm *tscm); -int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, - struct fw_unit *unit); +void snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port); static inline void snd_fw_async_midi_port_run(struct snd_fw_async_midi_port *port, -- cgit v1.2.3-59-g8ed1b From 1f94205d2225339c77e64d5872ec575ee2815720 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 14 Apr 2017 16:46:31 +0900 Subject: ALSA: firewire-tascam: move message parameters for async midi port Units on TASCAM FireWire series handle MIDI messages with support for running status. Drivers for the series should remember current running status and transfer valid MIDI messages. For this purpose, current ALSA driver for the series has some members in its top-level structure. This is due to better abstraction of async midi port. Nowadays, the abstraction was localized just for the driver. This commit moves the members to structure for async midi port. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/tascam/tascam-midi.c | 3 --- sound/firewire/tascam/tascam-transaction.c | 33 +++++++++++++++--------------- sound/firewire/tascam/tascam.h | 4 ++-- 3 files changed, 19 insertions(+), 21 deletions(-) (limited to 'sound') diff --git a/sound/firewire/tascam/tascam-midi.c b/sound/firewire/tascam/tascam-midi.c index 901df81129bb..760f72a86c27 100644 --- a/sound/firewire/tascam/tascam-midi.c +++ b/sound/firewire/tascam/tascam-midi.c @@ -20,9 +20,6 @@ static int midi_playback_open(struct snd_rawmidi_substream *substream) snd_fw_async_midi_port_init(&tscm->out_ports[substream->number]); - /* Initialize internal status. */ - tscm->running_status[substream->number] = 0; - tscm->on_sysex[substream->number] = 0; return 0; } diff --git a/sound/firewire/tascam/tascam-transaction.c b/sound/firewire/tascam/tascam-transaction.c index a248a4ae0353..8967c52f5032 100644 --- a/sound/firewire/tascam/tascam-transaction.c +++ b/sound/firewire/tascam/tascam-transaction.c @@ -58,39 +58,38 @@ static inline int calculate_message_bytes(u8 status) return -EINVAL; } -static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf) +static int fill_message(struct snd_fw_async_midi_port *port, + struct snd_rawmidi_substream *substream) { - struct snd_tscm *tscm = substream->rmidi->private_data; - unsigned int port = substream->number; int i, len, consume; u8 *label, *msg; u8 status; /* The first byte is used for label, the rest for MIDI bytes. */ - label = buf; - msg = buf + 1; + label = port->buf; + msg = port->buf + 1; consume = snd_rawmidi_transmit_peek(substream, msg, 3); if (consume == 0) return 0; /* On exclusive message. */ - if (tscm->on_sysex[port]) { + if (port->on_sysex) { /* Seek the end of exclusives. */ for (i = 0; i < consume; ++i) { if (msg[i] == 0xf7) { - tscm->on_sysex[port] = false; + port->on_sysex = false; break; } } /* At the end of exclusive message, use label 0x07. */ - if (!tscm->on_sysex[port]) { + if (!port->on_sysex) { consume = i + 1; - *label = (port << 4) | 0x07; + *label = (substream->number << 4) | 0x07; /* During exclusive message, use label 0x04. */ } else if (consume == 3) { - *label = (port << 4) | 0x04; + *label = (substream->number << 4) | 0x04; /* We need to fill whole 3 bytes. Go to next change. */ } else { return 0; @@ -101,12 +100,12 @@ static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf) /* The beginning of exclusives. */ if (msg[0] == 0xf0) { /* Transfer it in next chance in another condition. */ - tscm->on_sysex[port] = true; + port->on_sysex = true; return 0; } else { /* On running-status. */ if ((msg[0] & 0x80) != 0x80) - status = tscm->running_status[port]; + status = port->running_status; else status = msg[0]; @@ -124,18 +123,18 @@ static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf) msg[2] = msg[1]; msg[1] = msg[0]; - msg[0] = tscm->running_status[port]; + msg[0] = port->running_status; } else { /* Enough MIDI bytes were not retrieved. */ if (consume < len) return 0; consume = len; - tscm->running_status[port] = msg[0]; + port->running_status = msg[0]; } } - *label = (port << 4) | (msg[0] >> 4); + *label = (substream->number << 4) | (msg[0] >> 4); } if (len > 0 && len < 3) @@ -196,7 +195,7 @@ static void midi_port_work(struct work_struct *work) * Later, snd_rawmidi_transmit_ack() is called. */ memset(port->buf, 0, 4); - port->consume_bytes = fill_message(substream, port->buf); + port->consume_bytes = fill_message(port, substream); if (port->consume_bytes <= 0) { /* Do it in next chance, immediately. */ if (port->consume_bytes == 0) { @@ -240,6 +239,8 @@ void snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port) { port->idling = true; port->error = false; + port->running_status = 0; + port->on_sysex = false; } static void handle_midi_tx(struct fw_card *card, struct fw_request *request, diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index 37ec2016d033..08ecfae5c584 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -55,6 +55,8 @@ struct snd_fw_async_midi_port { struct fw_transaction transaction; u8 buf[4]; + u8 running_status; + bool on_sysex; struct snd_rawmidi_substream *substream; int consume_bytes; @@ -87,8 +89,6 @@ struct snd_tscm { /* For MIDI message outgoing transactions. */ struct snd_fw_async_midi_port out_ports[TSCM_MIDI_OUT_PORT_MAX]; - u8 running_status[TSCM_MIDI_OUT_PORT_MAX]; - bool on_sysex[TSCM_MIDI_OUT_PORT_MAX]; }; #define TSCM_ADDR_BASE 0xffff00000000ull -- cgit v1.2.3-59-g8ed1b From ae369e559f16c5245d6b987c43799179af16e4d6 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 14 Apr 2017 16:46:32 +0900 Subject: ALSA: firewire-tascam: support drain callback for MIDI playback substream ALSA driver for TASCAM FireWire series transfers MIDI messages in system workqueue. In current design of the driver, applications should wait for sequence of transmission when they close ALSA rawmidi character devices. However, when considering design of rawmidi interface, it's preferable to wait in drain ioctl. This commit adds support for the drain ioctl to wait for the end of the transmission. Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/tascam/tascam-midi.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/firewire/tascam/tascam-midi.c b/sound/firewire/tascam/tascam-midi.c index 760f72a86c27..4a741570d536 100644 --- a/sound/firewire/tascam/tascam-midi.c +++ b/sound/firewire/tascam/tascam-midi.c @@ -30,12 +30,15 @@ static int midi_capture_close(struct snd_rawmidi_substream *substream) } static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static void midi_playback_drain(struct snd_rawmidi_substream *substream) { struct snd_tscm *tscm = substream->rmidi->private_data; snd_fw_async_midi_port_finish(&tscm->out_ports[substream->number]); - - return 0; } static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) @@ -77,6 +80,7 @@ int snd_tscm_create_midi_devices(struct snd_tscm *tscm) static const struct snd_rawmidi_ops playback_ops = { .open = midi_playback_open, .close = midi_playback_close, + .drain = midi_playback_drain, .trigger = midi_playback_trigger, }; struct snd_rawmidi *rmidi; -- cgit v1.2.3-59-g8ed1b From a5f8661df0190f206ec73bd42b756d889c16150d Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Sun, 16 Apr 2017 21:51:11 -0300 Subject: ALSA: usb-audio: replace /proc/bus/usb by /dev/bus/usb The /proc/bus/usb devices don't exist anymore, since when we got rid of usbfs. Those devices are now seen at /dev/bus/usb. Signed-off-by: Mauro Carvalho Chehab Signed-off-by: Takashi Iwai --- sound/usb/usx2y/us122l.c | 2 +- sound/usb/usx2y/usX2Yhwdep.c | 2 +- sound/usb/usx2y/usx2yhwdeppcm.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/usb/usx2y/us122l.c b/sound/usb/usx2y/us122l.c index cf45bf1f7ee0..e118bdca983d 100644 --- a/sound/usb/usx2y/us122l.c +++ b/sound/usb/usx2y/us122l.c @@ -472,7 +472,7 @@ static int usb_stream_hwdep_new(struct snd_card *card) hw->ops.mmap = usb_stream_hwdep_mmap; hw->ops.poll = usb_stream_hwdep_poll; - sprintf(hw->name, "/proc/bus/usb/%03d/%03d/hwdeppcm", + sprintf(hw->name, "/dev/bus/usb/%03d/%03d/hwdeppcm", dev->bus->busnum, dev->devnum); return 0; } diff --git a/sound/usb/usx2y/usX2Yhwdep.c b/sound/usb/usx2y/usX2Yhwdep.c index 605e1047c01d..f4b3cda412fc 100644 --- a/sound/usb/usx2y/usX2Yhwdep.c +++ b/sound/usb/usx2y/usX2Yhwdep.c @@ -258,7 +258,7 @@ int usX2Y_hwdep_new(struct snd_card *card, struct usb_device* device) hw->ops.mmap = snd_us428ctls_mmap; hw->ops.poll = snd_us428ctls_poll; hw->exclusive = 1; - sprintf(hw->name, "/proc/bus/usb/%03d/%03d", device->bus->busnum, device->devnum); + sprintf(hw->name, "/dev/bus/usb/%03d/%03d", device->bus->busnum, device->devnum); return 0; } diff --git a/sound/usb/usx2y/usx2yhwdeppcm.c b/sound/usb/usx2y/usx2yhwdeppcm.c index f95164b91152..d51c7fd7835b 100644 --- a/sound/usb/usx2y/usx2yhwdeppcm.c +++ b/sound/usb/usx2y/usx2yhwdeppcm.c @@ -723,7 +723,7 @@ int usX2Y_hwdep_pcm_new(struct snd_card *card) hw->ops.release = snd_usX2Y_hwdep_pcm_release; hw->ops.mmap = snd_usX2Y_hwdep_pcm_mmap; hw->exclusive = 1; - sprintf(hw->name, "/proc/bus/usb/%03d/%03d/hwdeppcm", dev->bus->busnum, dev->devnum); + sprintf(hw->name, "/dev/bus/usb/%03d/%03d/hwdeppcm", dev->bus->busnum, dev->devnum); err = snd_pcm_new(card, NAME_ALLCAPS" hwdep Audio", 2, 1, 1, &pcm); if (err < 0) { -- cgit v1.2.3-59-g8ed1b From 5cd5b1bdfb0137d0e814a51ff203d72c76b9f375 Mon Sep 17 00:00:00 2001 From: Jerónimo Borque Date: Wed, 19 Apr 2017 12:09:50 -0300 Subject: ALSA: hda - Add HP ZBook 15u G3 Conexant CX20724 GPIO mute leds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HP ZBook 15u G3 has a Conexant CX20724 with mute led on GPIO1 and mic mute led on GPIO2. Adding CXT_FIXUP_MUTE_LED_GPIO inspired on patch_realtek's one. Signed-off-by: Jerónimo Borque Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_conexant.c | 81 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index 69266b8ea2ad..e8253737c47a 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -52,6 +52,12 @@ struct conexant_spec { bool dc_enable; unsigned int dc_input_bias; /* offset into olpc_xo_dc_bias */ struct nid_path *dc_mode_path; + + int mute_led_polarity; + unsigned int gpio_led; + unsigned int gpio_mute_led_mask; + unsigned int gpio_mic_led_mask; + }; @@ -264,6 +270,7 @@ enum { CXT_FIXUP_HP_DOCK, CXT_FIXUP_HP_SPECTRE, CXT_FIXUP_HP_GATE_MIC, + CXT_FIXUP_MUTE_LED_GPIO, }; /* for hda_fixup_thinkpad_acpi() */ @@ -646,6 +653,74 @@ static void cxt_fixup_hp_gate_mic_jack(struct hda_codec *codec, snd_hda_jack_set_gating_jack(codec, 0x19, 0x16); } +/* update LED status via GPIO */ +static void cxt_update_gpio_led(struct hda_codec *codec, unsigned int mask, + bool enabled) +{ + struct conexant_spec *spec = codec->spec; + unsigned int oldval = spec->gpio_led; + + if (spec->mute_led_polarity) + enabled = !enabled; + + if (enabled) + spec->gpio_led &= ~mask; + else + spec->gpio_led |= mask; + if (spec->gpio_led != oldval) + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, + spec->gpio_led); +} + +/* turn on/off mute LED via GPIO per vmaster hook */ +static void cxt_fixup_gpio_mute_hook(void *private_data, int enabled) +{ + struct hda_codec *codec = private_data; + struct conexant_spec *spec = codec->spec; + + cxt_update_gpio_led(codec, spec->gpio_mute_led_mask, enabled); +} + +/* turn on/off mic-mute LED via GPIO per capture hook */ +static void cxt_fixup_gpio_mic_mute_hook(struct hda_codec *codec, + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct conexant_spec *spec = codec->spec; + + if (ucontrol) + cxt_update_gpio_led(codec, spec->gpio_mic_led_mask, + ucontrol->value.integer.value[0] || + ucontrol->value.integer.value[1]); +} + + +static void cxt_fixup_mute_led_gpio(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct conexant_spec *spec = codec->spec; + static const struct hda_verb gpio_init[] = { + { 0x01, AC_VERB_SET_GPIO_MASK, 0x03 }, + { 0x01, AC_VERB_SET_GPIO_DIRECTION, 0x03 }, + {} + }; + codec_info(codec, "action: %d gpio_led: %d\n", action, spec->gpio_led); + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->gen.vmaster_mute.hook = cxt_fixup_gpio_mute_hook; + spec->gen.cap_sync_hook = cxt_fixup_gpio_mic_mute_hook; + spec->gpio_led = 0; + spec->mute_led_polarity = 0; + spec->gpio_mute_led_mask = 0x01; + spec->gpio_mic_led_mask = 0x02; + } + snd_hda_add_verbs(codec, gpio_init); + if (spec->gpio_led) + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, + spec->gpio_led); +} + + /* ThinkPad X200 & co with cxt5051 */ static const struct hda_pintbl cxt_pincfg_lenovo_x200[] = { { 0x16, 0x042140ff }, /* HP (seq# overridden) */ @@ -799,6 +874,10 @@ static const struct hda_fixup cxt_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = cxt_fixup_hp_gate_mic_jack, }, + [CXT_FIXUP_MUTE_LED_GPIO] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_mute_led_gpio, + }, }; static const struct snd_pci_quirk cxt5045_fixups[] = { @@ -851,6 +930,7 @@ static const struct snd_pci_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x103c, 0x8079, "HP EliteBook 840 G3", CXT_FIXUP_HP_DOCK), SND_PCI_QUIRK(0x103c, 0x8174, "HP Spectre x360", CXT_FIXUP_HP_SPECTRE), SND_PCI_QUIRK(0x103c, 0x8115, "HP Z1 Gen3", CXT_FIXUP_HP_GATE_MIC), + SND_PCI_QUIRK(0x103c, 0x814f, "HP ZBook 15u G3", CXT_FIXUP_MUTE_LED_GPIO), SND_PCI_QUIRK(0x1043, 0x138d, "Asus", CXT_FIXUP_HEADPHONE_MIC_PIN), SND_PCI_QUIRK(0x152d, 0x0833, "OLPC XO-1.5", CXT_FIXUP_OLPC_XO), SND_PCI_QUIRK(0x17aa, 0x20f2, "Lenovo T400", CXT_PINCFG_LENOVO_TP410), @@ -882,6 +962,7 @@ static const struct hda_model_fixup cxt5066_fixup_models[] = { { .id = CXT_FIXUP_OLPC_XO, .name = "olpc-xo" }, { .id = CXT_FIXUP_MUTE_LED_EAPD, .name = "mute-led-eapd" }, { .id = CXT_FIXUP_HP_DOCK, .name = "hp-dock" }, + { .id = CXT_FIXUP_MUTE_LED_GPIO, .name = "mute-led-gpio" }, {} }; -- cgit v1.2.3-59-g8ed1b From fa8323bf49f5c92363fe7c3c80b6b342f5430baa Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 19 Apr 2017 19:51:29 +0200 Subject: ALSA: firewire-motu: mark trace helpers as __maybe_unused Two functions were introduced for the purpose of tracing but cause warnings when tracing is disabled: sound/firewire/motu/amdtp-motu.c:284:13: error: 'copy_message' defined but not used [-Werror=unused-function] static void copy_message(u64 *frames, __be32 *buffer, unsigned int data_blocks, sound/firewire/motu/amdtp-motu.c:271:13: error: 'copy_sph' defined but not used [-Werror=unused-function] static void copy_sph(u32 *frames, __be32 *buffer, unsigned int data_blocks, Marking them as __maybe_unused will do the right thing here. Fixes: 17909c1b3058 ("ALSA: firewire-motu: add tracepoints for SPH in IEC 61883-1 fashion") Fixes: c6b0b9e65f09 ("ALSA: firewire-motu: add tracepoints for messages for unique protocol") Signed-off-by: Arnd Bergmann Signed-off-by: Takashi Iwai --- sound/firewire/motu/amdtp-motu.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/firewire/motu/amdtp-motu.c b/sound/firewire/motu/amdtp-motu.c index 996b5f818918..96f0091144bb 100644 --- a/sound/firewire/motu/amdtp-motu.c +++ b/sound/firewire/motu/amdtp-motu.c @@ -268,8 +268,9 @@ static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer, } /* For tracepoints. */ -static void copy_sph(u32 *frames, __be32 *buffer, unsigned int data_blocks, - unsigned int data_block_quadlets) +static void __maybe_unused copy_sph(u32 *frames, __be32 *buffer, + unsigned int data_blocks, + unsigned int data_block_quadlets) { unsigned int i; @@ -281,8 +282,9 @@ static void copy_sph(u32 *frames, __be32 *buffer, unsigned int data_blocks, } /* For tracepoints. */ -static void copy_message(u64 *frames, __be32 *buffer, unsigned int data_blocks, - unsigned int data_block_quadlets) +static void __maybe_unused copy_message(u64 *frames, __be32 *buffer, + unsigned int data_blocks, + unsigned int data_block_quadlets) { unsigned int i; -- cgit v1.2.3-59-g8ed1b From e3a973c69b3e3d877db982ff11f930cf845fe728 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Fri, 14 Apr 2017 23:06:54 +0900 Subject: ALSA: fireface: obsolete usage of ktime_set() for zero assignment In development period for Linux v4.10, ktime_t became an alias of s64, instead of union. I forgot it. We can just assign zero, instead of usage of ktime_set(0, 0). Fixes: 19174295788 ("ALSA: fireface: add transaction support") Signed-off-by: Takashi Sakamoto Signed-off-by: Takashi Iwai --- sound/firewire/fireface/ff-transaction.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/firewire/fireface/ff-transaction.c b/sound/firewire/fireface/ff-transaction.c index d1b098f8ae42..dd6c8e839647 100644 --- a/sound/firewire/fireface/ff-transaction.c +++ b/sound/firewire/fireface/ff-transaction.c @@ -21,7 +21,7 @@ static void finish_transmit_midi_msg(struct snd_ff *ff, unsigned int port, if (rcode != RCODE_COMPLETE) { /* Transfer the message again, immediately. */ - ff->next_ktime[port] = ktime_set(0, 0); + ff->next_ktime[port] = 0; schedule_work(&ff->rx_midi_work[port]); return; } -- cgit v1.2.3-59-g8ed1b From fc7438b1eb12b6c93d7b7a62423779eb5dfc673c Mon Sep 17 00:00:00 2001 From: Mikhail Paulyshka Date: Fri, 21 Apr 2017 08:52:42 +0200 Subject: ALSA: hda - Fix headset microphone detection for ASUS N551 and N751 Headset microphone does not work out of the box on ASUS Nx51 laptops. This patch fixes it. Patch tested on Asus N551 laptop. Asus N751 part is not tested, but according to [1] this laptop uses the same audiosystem. 1. https://bugzilla.kernel.org/show_bug.cgi?id=117781 Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=195437 Signed-off-by: Mikhail Paulyshka Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 9371f1a95b33..05a26fba5ef2 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -6815,6 +6815,7 @@ enum { ALC668_FIXUP_DELL_DISABLE_AAMIX, ALC668_FIXUP_DELL_XPS13, ALC662_FIXUP_ASUS_Nx50, + ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE, ALC668_FIXUP_ASUS_Nx51, ALC891_FIXUP_HEADSET_MODE, ALC891_FIXUP_DELL_MIC_NO_PRESENCE, @@ -7068,14 +7069,21 @@ static const struct hda_fixup alc662_fixups[] = { .chained = true, .chain_id = ALC662_FIXUP_BASS_1A }, + [ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode_alc668, + .chain_id = ALC662_FIXUP_BASS_CHMAP + }, [ALC668_FIXUP_ASUS_Nx51] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { - {0x1a, 0x90170151}, /* bass speaker */ + { 0x19, 0x03a1913d }, /* use as headphone mic, without its own jack detect */ + { 0x1a, 0x90170151 }, /* bass speaker */ + { 0x1b, 0x03a1113c }, /* use as headset mic, without its own jack detect */ {} }, .chained = true, - .chain_id = ALC662_FIXUP_BASS_CHMAP, + .chain_id = ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE, }, [ALC891_FIXUP_HEADSET_MODE] = { .type = HDA_FIXUP_FUNC, -- cgit v1.2.3-59-g8ed1b From 7beb3a6e9335d13872f24566b451f8371edd238f Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 10 Apr 2017 18:05:52 +0200 Subject: ALSA: hda - Support Gigabyte Gaming board with dual Realtek codecs This patch adds some workarounds to make Gigabyte GA-AX370 Gaming 5 board working without the conflicts of kctls, etc. In general, the dual codec configs result in the conflicts of the following stuff: - Master controls - Capture controls - Analog loopback controls In addition, the auto-mute and the auto-mic can't work well among multiple codecs. The current "solution" is to disable all these features, and use UCM for a better PulseAudio management. For a dedicated UCM profile, the patch overrides the card longname so that the system an get a unique profile path. Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=195305 Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 61 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 9371f1a95b33..e81cf83d2afd 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -1800,6 +1800,7 @@ enum { ALC882_FIXUP_NO_PRIMARY_HP, ALC887_FIXUP_ASUS_BASS, ALC887_FIXUP_BASS_CHMAP, + ALC1220_FIXUP_GB_DUAL_CODECS, }; static void alc889_fixup_coef(struct hda_codec *codec, @@ -1962,6 +1963,61 @@ static void alc882_fixup_no_primary_hp(struct hda_codec *codec, static void alc_fixup_bass_chmap(struct hda_codec *codec, const struct hda_fixup *fix, int action); +/* For dual-codec configuration, we need to disable some features to avoid + * conflicts of kctls and PCM streams + */ +static void alc_fixup_dual_codecs(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + /* disable vmaster */ + spec->gen.suppress_vmaster = 1; + /* auto-mute and auto-mic switch don't work with multiple codecs */ + spec->gen.suppress_auto_mute = 1; + spec->gen.suppress_auto_mic = 1; + /* disable aamix as well */ + spec->gen.mixer_nid = 0; + /* add location prefix to avoid conflicts */ + codec->force_pin_prefix = 1; +} + +static void rename_ctl(struct hda_codec *codec, const char *oldname, + const char *newname) +{ + struct snd_kcontrol *kctl; + + kctl = snd_hda_find_mixer_ctl(codec, oldname); + if (kctl) + strcpy(kctl->id.name, newname); +} + +static void alc1220_fixup_gb_dual_codecs(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + alc_fixup_dual_codecs(codec, fix, action); + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + /* override card longname to provide a unique UCM profile */ + strcpy(codec->card->longname, "HDAudio-Gigabyte-ALC1220DualCodecs"); + break; + case HDA_FIXUP_ACT_BUILD: + /* rename Capture controls depending on the codec */ + rename_ctl(codec, "Capture Volume", + codec->addr == 0 ? + "Rear-Panel Capture Volume" : + "Front-Panel Capture Volume"); + rename_ctl(codec, "Capture Switch", + codec->addr == 0 ? + "Rear-Panel Capture Switch" : + "Front-Panel Capture Switch"); + break; + } +} + static const struct hda_fixup alc882_fixups[] = { [ALC882_FIXUP_ABIT_AW9D_MAX] = { .type = HDA_FIXUP_PINS, @@ -2198,6 +2254,10 @@ static const struct hda_fixup alc882_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_bass_chmap, }, + [ALC1220_FIXUP_GB_DUAL_CODECS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc1220_fixup_gb_dual_codecs, + }, }; static const struct snd_pci_quirk alc882_fixup_tbl[] = { @@ -2267,6 +2327,7 @@ static const struct snd_pci_quirk alc882_fixup_tbl[] = { SND_PCI_QUIRK(0x1462, 0x7350, "MSI-7350", ALC889_FIXUP_CD), SND_PCI_QUIRK_VENDOR(0x1462, "MSI", ALC882_FIXUP_GPIO3), SND_PCI_QUIRK(0x1458, 0xa002, "Gigabyte EP45-DS3/Z87X-UD3H", ALC889_FIXUP_FRONT_HP_NO_PRESENCE), + SND_PCI_QUIRK(0x1458, 0xa0b8, "Gigabyte AZ370-Gaming", ALC1220_FIXUP_GB_DUAL_CODECS), SND_PCI_QUIRK(0x147b, 0x107a, "Abit AW9D-MAX", ALC882_FIXUP_ABIT_AW9D_MAX), SND_PCI_QUIRK_VENDOR(0x1558, "Clevo laptop", ALC882_FIXUP_EAPD), SND_PCI_QUIRK(0x161f, 0x2054, "Medion laptop", ALC883_FIXUP_EAPD), -- cgit v1.2.3-59-g8ed1b From 56798e6b3a2264b58d50b93cfd66586ede77d661 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 11 Apr 2017 08:10:52 +0200 Subject: ALSA: hda - Use a helper function for renaming kctl names Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index e81cf83d2afd..57eeefd36f19 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -4724,7 +4724,6 @@ static void alc282_fixup_asus_tx300(struct hda_codec *codec, { 0x1b, 0x21114000 }, /* dock speaker pin */ {} }; - struct snd_kcontrol *kctl; switch (action) { case HDA_FIXUP_ACT_PRE_PROBE: @@ -4739,12 +4738,10 @@ static void alc282_fixup_asus_tx300(struct hda_codec *codec, /* this is a bit tricky; give more sane names for the main * (tablet) speaker and the dock speaker, respectively */ - kctl = snd_hda_find_mixer_ctl(codec, "Speaker Playback Switch"); - if (kctl) - strcpy(kctl->id.name, "Dock Speaker Playback Switch"); - kctl = snd_hda_find_mixer_ctl(codec, "Bass Speaker Playback Switch"); - if (kctl) - strcpy(kctl->id.name, "Speaker Playback Switch"); + rename_ctl(codec, "Speaker Playback Switch", + "Dock Speaker Playback Switch"); + rename_ctl(codec, "Bass Speaker Playback Switch", + "Speaker Playback Switch"); break; } } -- cgit v1.2.3-59-g8ed1b From ca169cc2f9e1f8ed9c867b197a49d6dd05e5436d Mon Sep 17 00:00:00 2001 From: Kailang Yang Date: Tue, 25 Apr 2017 16:17:40 +0800 Subject: ALSA: hda/realtek - Add Dual Codecs support for Lenovo P520/420 Lenovo P520/420 build with two codecs. ALC233 for front panel. ALC662 for rear panel. This patch will rename capture name for slove conflicts. And create a card longname for UCM profile. Signed-off-by: Kailang Yang Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 93846bf65a23..58df440013c5 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -4824,6 +4824,30 @@ static void alc280_fixup_hp_9480m(struct hda_codec *codec, } } +static void alc233_alc662_fixup_lenovo_dual_codecs(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + alc_fixup_dual_codecs(codec, fix, action); + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + /* override card longname to provide a unique UCM profile */ + strcpy(codec->card->longname, "HDAudio-Lenovo-DualCodecs"); + break; + case HDA_FIXUP_ACT_BUILD: + /* rename Capture controls depending on the codec */ + rename_ctl(codec, "Capture Volume", + codec->addr == 0 ? + "Rear-Panel Capture Volume" : + "Front-Panel Capture Volume"); + rename_ctl(codec, "Capture Switch", + codec->addr == 0 ? + "Rear-Panel Capture Switch" : + "Front-Panel Capture Switch"); + break; + } +} + /* for hda_fixup_thinkpad_acpi() */ #include "thinkpad_helper.c" @@ -4937,6 +4961,7 @@ enum { ALC256_FIXUP_ASUS_AIO_GPIO2, ALC233_FIXUP_ASUS_MIC_NO_PRESENCE, ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE, + ALC233_FIXUP_LENOVO_MULTI_CODECS, }; static const struct hda_fixup alc269_fixups[] = { @@ -5706,6 +5731,10 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC233_FIXUP_ASUS_MIC_NO_PRESENCE }, + [ALC233_FIXUP_LENOVO_MULTI_CODECS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc233_alc662_fixup_lenovo_dual_codecs, + }, }; static const struct snd_pci_quirk alc269_fixup_tbl[] = { @@ -5860,6 +5889,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x144d, 0xc740, "Samsung Ativ book 8 (NP870Z5G)", ALC269_FIXUP_ATIV_BOOK_8), SND_PCI_QUIRK(0x1458, 0xfa53, "Gigabyte BXBT-2807", ALC283_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1462, 0xb120, "MSI Cubi MS-B120", ALC283_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x17aa, 0x1036, "Lenovo P520", ALC233_FIXUP_LENOVO_MULTI_CODECS), SND_PCI_QUIRK(0x17aa, 0x20f2, "Thinkpad SL410/510", ALC269_FIXUP_SKU_IGNORE), SND_PCI_QUIRK(0x17aa, 0x215e, "Thinkpad L512", ALC269_FIXUP_SKU_IGNORE), SND_PCI_QUIRK(0x17aa, 0x21b8, "Thinkpad Edge 14", ALC269_FIXUP_SKU_IGNORE), @@ -6881,6 +6911,7 @@ enum { ALC892_FIXUP_ASROCK_MOBO, ALC662_FIXUP_USI_FUNC, ALC662_FIXUP_USI_HEADSET_MODE, + ALC662_FIXUP_LENOVO_MULTI_CODECS, }; static const struct hda_fixup alc662_fixups[] = { @@ -7186,6 +7217,10 @@ static const struct hda_fixup alc662_fixups[] = { .chained = true, .chain_id = ALC662_FIXUP_USI_FUNC }, + [ALC662_FIXUP_LENOVO_MULTI_CODECS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc233_alc662_fixup_lenovo_dual_codecs, + }, }; static const struct snd_pci_quirk alc662_fixup_tbl[] = { @@ -7223,6 +7258,7 @@ static const struct snd_pci_quirk alc662_fixup_tbl[] = { SND_PCI_QUIRK(0x105b, 0x0cd6, "Foxconn", ALC662_FIXUP_ASUS_MODE2), SND_PCI_QUIRK(0x144d, 0xc051, "Samsung R720", ALC662_FIXUP_IDEAPAD), SND_PCI_QUIRK(0x14cd, 0x5003, "USI", ALC662_FIXUP_USI_HEADSET_MODE), + SND_PCI_QUIRK(0x17aa, 0x1036, "Lenovo P520", ALC662_FIXUP_LENOVO_MULTI_CODECS), SND_PCI_QUIRK(0x17aa, 0x38af, "Lenovo Ideapad Y550P", ALC662_FIXUP_IDEAPAD), SND_PCI_QUIRK(0x17aa, 0x3a0d, "Lenovo Ideapad Y550", ALC662_FIXUP_IDEAPAD), SND_PCI_QUIRK(0x1849, 0x5892, "ASRock B150M", ALC892_FIXUP_ASROCK_MOBO), -- cgit v1.2.3-59-g8ed1b From 0997e378be1d888a5d093eeee55f842cdfe55ebe Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Sat, 29 Apr 2017 22:52:55 +0100 Subject: ALSA: ali5451: fix spelling mistake in "ali_capture_preapre" trivial fix to spelling mistake in dev_warn message, "ali_capture_preapre" should be "ali_capture_prepare" Signed-off-by: Colin Ian King Signed-off-by: Takashi Iwai --- sound/pci/ali5451/ali5451.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/pci/ali5451/ali5451.c b/sound/pci/ali5451/ali5451.c index 92b819e4f729..34bbc2e730a6 100644 --- a/sound/pci/ali5451/ali5451.c +++ b/sound/pci/ali5451/ali5451.c @@ -1339,7 +1339,7 @@ static int snd_ali_prepare(struct snd_pcm_substream *substream) rate = snd_ali_get_spdif_in_rate(codec); if (rate == 0) { dev_warn(codec->card->dev, - "ali_capture_preapre: spdif rate detect err!\n"); + "ali_capture_prepare: spdif rate detect err!\n"); rate = 48000; } spin_lock_irq(&codec->reg_lock); -- cgit v1.2.3-59-g8ed1b