From 2112aa034907c428785e1a5730927181276ee45b Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 17 Nov 2023 13:05:55 +0100 Subject: ALSA: pcm: Introduce MSBITS subformat interface Improve granularity of format selection for S32/U32 formats by adding constants representing 20, 24 and MAX most significant bits. The MAX means the maximum number of significant bits which can the physical format hold. For 32-bit formats, MAX is related to 32 bits. For 8-bit formats, MAX is related to 8 bits etc. As there is only one user currently (format S32_LE), subformat is represented by a simple u32 and stores flags only for that one user alone. The approach of subformat being part of struct snd_pcm_hardware is a compromise between ALSA and ASoC allowing for hw_params-intersection code to be alloc/free-less while not adding any new responsibilities to ASoC runtime structures. Acked-by: Mark Brown Signed-off-by: Jaroslav Kysela Co-developed-by: Cezary Rojewski Signed-off-by: Cezary Rojewski Link: https://lore.kernel.org/r/20231117120610.1755254-2-cezary.rojewski@intel.com Signed-off-by: Takashi Iwai --- include/sound/pcm.h | 7 +++++++ include/uapi/sound/asound.h | 7 +++++-- 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 2a815373dac1..cc175c623dac 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -32,6 +32,7 @@ struct snd_pcm_hardware { unsigned int info; /* SNDRV_PCM_INFO_* */ u64 formats; /* SNDRV_PCM_FMTBIT_* */ + u32 subformats; /* for S32_LE, SNDRV_PCM_SUBFMTBIT_* */ unsigned int rates; /* SNDRV_PCM_RATE_* */ unsigned int rate_min; /* min rate */ unsigned int rate_max; /* max rate */ @@ -217,6 +218,12 @@ struct snd_pcm_ops { #define SNDRV_PCM_FMTBIT_U20 SNDRV_PCM_FMTBIT_U20_BE #endif +#define _SNDRV_PCM_SUBFMTBIT(fmt) BIT((__force int)SNDRV_PCM_SUBFORMAT_##fmt) +#define SNDRV_PCM_SUBFMTBIT_STD _SNDRV_PCM_SUBFMTBIT(STD) +#define SNDRV_PCM_SUBFMTBIT_MSBITS_MAX _SNDRV_PCM_SUBFMTBIT(MSBITS_MAX) +#define SNDRV_PCM_SUBFMTBIT_MSBITS_20 _SNDRV_PCM_SUBFMTBIT(MSBITS_20) +#define SNDRV_PCM_SUBFMTBIT_MSBITS_24 _SNDRV_PCM_SUBFMTBIT(MSBITS_24) + struct snd_pcm_file { struct snd_pcm_substream *substream; int no_compat_mmap; diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index f9939da41122..d5b9cfbd9cea 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -142,7 +142,7 @@ struct snd_hwdep_dsp_image { * * *****************************************************************************/ -#define SNDRV_PCM_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 15) +#define SNDRV_PCM_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 16) typedef unsigned long snd_pcm_uframes_t; typedef signed long snd_pcm_sframes_t; @@ -267,7 +267,10 @@ typedef int __bitwise snd_pcm_format_t; typedef int __bitwise snd_pcm_subformat_t; #define SNDRV_PCM_SUBFORMAT_STD ((__force snd_pcm_subformat_t) 0) -#define SNDRV_PCM_SUBFORMAT_LAST SNDRV_PCM_SUBFORMAT_STD +#define SNDRV_PCM_SUBFORMAT_MSBITS_MAX ((__force snd_pcm_subformat_t) 1) +#define SNDRV_PCM_SUBFORMAT_MSBITS_20 ((__force snd_pcm_subformat_t) 2) +#define SNDRV_PCM_SUBFORMAT_MSBITS_24 ((__force snd_pcm_subformat_t) 3) +#define SNDRV_PCM_SUBFORMAT_LAST SNDRV_PCM_SUBFORMAT_MSBITS_24 #define SNDRV_PCM_INFO_MMAP 0x00000001 /* hardware supports mmap */ #define SNDRV_PCM_INFO_MMAP_VALID 0x00000002 /* period data are valid during transfer */ -- cgit v1.2.3 From a7fc8b862fd5952be791a870ef8ef56016e83ff4 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Fri, 17 Nov 2023 13:05:56 +0100 Subject: ALSA: hda: Honor subformat when querying PCMs Update mechanism for querying supported PCMs to allow for granular format selection when container size is 32 bits. Currently always the highest bit depth is selected, regardless of how many actual formats codec in question supports. Acked-by: Mark Brown Co-developed-by: Jaroslav Kysela Signed-off-by: Jaroslav Kysela Signed-off-by: Cezary Rojewski Link: https://lore.kernel.org/r/20231117120610.1755254-3-cezary.rojewski@intel.com Signed-off-by: Takashi Iwai --- include/sound/hda_codec.h | 5 +++-- include/sound/hdaudio.h | 3 ++- sound/hda/hdac_device.c | 45 ++++++++++++++++++++++++-------------------- sound/pci/hda/hda_codec.c | 2 ++ sound/pci/hda/patch_hdmi.c | 1 + sound/soc/codecs/hdac_hdmi.c | 3 ++- 6 files changed, 35 insertions(+), 24 deletions(-) (limited to 'include') diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h index 5497dc9c396a..9c94ba7c183d 100644 --- a/include/sound/hda_codec.h +++ b/include/sound/hda_codec.h @@ -141,6 +141,7 @@ struct hda_pcm_stream { hda_nid_t nid; /* default NID to query rates/formats/bps, or set up */ u32 rates; /* supported rates */ u64 formats; /* supported formats (SNDRV_PCM_FMTBIT_) */ + u32 subformats; /* for S32_LE format, SNDRV_PCM_SUBFMTBIT_* */ unsigned int maxbps; /* supported max. bit per sample */ const struct snd_pcm_chmap_elem *chmap; /* chmap to override */ struct hda_pcm_ops ops; @@ -448,8 +449,8 @@ void __snd_hda_codec_cleanup_stream(struct hda_codec *codec, hda_nid_t nid, #define snd_hda_codec_cleanup_stream(codec, nid) \ __snd_hda_codec_cleanup_stream(codec, nid, 0) -#define snd_hda_query_supported_pcm(codec, nid, ratesp, fmtsp, bpsp) \ - snd_hdac_query_supported_pcm(&(codec)->core, nid, ratesp, fmtsp, bpsp) +#define snd_hda_query_supported_pcm(codec, nid, ratesp, fmtsp, subfmtp, bpsp) \ + snd_hdac_query_supported_pcm(&(codec)->core, nid, ratesp, fmtsp, subfmtp, bpsp) #define snd_hda_is_supported_format(codec, nid, fmt) \ snd_hdac_is_supported_format(&(codec)->core, nid, fmt) diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index dd7c87bbc613..7cfea9e7f6e4 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -146,7 +146,8 @@ unsigned int snd_hdac_calc_stream_format(unsigned int rate, unsigned int maxbps, unsigned short spdif_ctls); int snd_hdac_query_supported_pcm(struct hdac_device *codec, hda_nid_t nid, - u32 *ratesp, u64 *formatsp, unsigned int *bpsp); + u32 *ratesp, u64 *formatsp, u32 *subformatsp, + unsigned int *bpsp); bool snd_hdac_is_supported_format(struct hdac_device *codec, hda_nid_t nid, unsigned int format); diff --git a/sound/hda/hdac_device.c b/sound/hda/hdac_device.c index bbf7bcdb449a..cde4d5c33771 100644 --- a/sound/hda/hdac_device.c +++ b/sound/hda/hdac_device.c @@ -817,15 +817,17 @@ static unsigned int query_stream_param(struct hdac_device *codec, hda_nid_t nid) * @nid: NID to query * @ratesp: the pointer to store the detected rate bitflags * @formatsp: the pointer to store the detected formats + * @subformatsp: the pointer to store the detected subformats for S32_LE format * @bpsp: the pointer to store the detected format widths * - * Queries the supported PCM rates and formats. The NULL @ratesp, @formatsp - * or @bsps argument is ignored. + * Queries the supported PCM rates and formats. The NULL @ratesp, @formatsp, + * @subformatsp or @bpsp argument is ignored. * * Returns 0 if successful, otherwise a negative error code. */ int snd_hdac_query_supported_pcm(struct hdac_device *codec, hda_nid_t nid, - u32 *ratesp, u64 *formatsp, unsigned int *bpsp) + u32 *ratesp, u64 *formatsp, u32 *subformatsp, + unsigned int *bpsp) { unsigned int i, val, wcaps; @@ -848,9 +850,10 @@ int snd_hdac_query_supported_pcm(struct hdac_device *codec, hda_nid_t nid, *ratesp = rates; } - if (formatsp || bpsp) { - u64 formats = 0; + if (formatsp || subformatsp || bpsp) { unsigned int streams, bps; + u32 subformats = 0; + u64 formats = 0; streams = query_stream_param(codec, nid); if (!streams) @@ -866,24 +869,24 @@ int snd_hdac_query_supported_pcm(struct hdac_device *codec, hda_nid_t nid, formats |= SNDRV_PCM_FMTBIT_S16_LE; bps = 16; } - if (wcaps & AC_WCAP_DIGITAL) { - if (val & AC_SUPPCM_BITS_32) + if (val & AC_SUPPCM_BITS_20) { + formats |= SNDRV_PCM_FMTBIT_S32_LE; + subformats |= SNDRV_PCM_SUBFMTBIT_MSBITS_20; + bps = 20; + } + if (val & AC_SUPPCM_BITS_24) { + formats |= SNDRV_PCM_FMTBIT_S32_LE; + subformats |= SNDRV_PCM_SUBFMTBIT_MSBITS_24; + bps = 24; + } + if (val & AC_SUPPCM_BITS_32) { + if (wcaps & AC_WCAP_DIGITAL) { formats |= SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE; - if (val & (AC_SUPPCM_BITS_20|AC_SUPPCM_BITS_24)) + } else { formats |= SNDRV_PCM_FMTBIT_S32_LE; - if (val & AC_SUPPCM_BITS_24) - bps = 24; - else if (val & AC_SUPPCM_BITS_20) - bps = 20; - } else if (val & (AC_SUPPCM_BITS_20|AC_SUPPCM_BITS_24| - AC_SUPPCM_BITS_32)) { - formats |= SNDRV_PCM_FMTBIT_S32_LE; - if (val & AC_SUPPCM_BITS_32) + subformats |= SNDRV_PCM_SUBFMTBIT_MSBITS_MAX; bps = 32; - else if (val & AC_SUPPCM_BITS_24) - bps = 24; - else if (val & AC_SUPPCM_BITS_20) - bps = 20; + } } } #if 0 /* FIXME: CS4206 doesn't work, which is the only codec supporting float */ @@ -911,6 +914,8 @@ int snd_hdac_query_supported_pcm(struct hdac_device *codec, hda_nid_t nid, } if (formatsp) *formatsp = formats; + if (subformatsp) + *subformatsp = subformats; if (bpsp) *bpsp = bps; } diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 01718b1fc9a7..12f02cdc9659 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -3163,6 +3163,7 @@ static int set_pcm_default_values(struct hda_codec *codec, err = snd_hda_query_supported_pcm(codec, info->nid, info->rates ? NULL : &info->rates, info->formats ? NULL : &info->formats, + info->subformats ? NULL : &info->subformats, info->maxbps ? NULL : &info->maxbps); if (err < 0) return err; @@ -3757,6 +3758,7 @@ int snd_hda_multi_out_analog_open(struct hda_codec *codec, snd_hda_query_supported_pcm(codec, mout->dig_out_nid, &mout->spdif_rates, &mout->spdif_formats, + NULL, &mout->spdif_maxbps); } mutex_lock(&codec->spdif_mutex); diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index 1cde2a69bdb4..687b8b8fd7ac 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -1977,6 +1977,7 @@ static int hdmi_add_cvt(struct hda_codec *codec, hda_nid_t cvt_nid) err = snd_hda_query_supported_pcm(codec, cvt_nid, &per_cvt->rates, &per_cvt->formats, + NULL, &per_cvt->maxbps); if (err < 0) return err; diff --git a/sound/soc/codecs/hdac_hdmi.c b/sound/soc/codecs/hdac_hdmi.c index b9c5ffbfb5ba..3e4f632d8665 100644 --- a/sound/soc/codecs/hdac_hdmi.c +++ b/sound/soc/codecs/hdac_hdmi.c @@ -670,6 +670,7 @@ hdac_hdmi_query_cvt_params(struct hdac_device *hdev, struct hdac_hdmi_cvt *cvt) err = snd_hdac_query_supported_pcm(hdev, cvt->nid, &cvt->params.rates, &cvt->params.formats, + NULL, &cvt->params.maxbps); if (err < 0) dev_err(&hdev->dev, @@ -1577,7 +1578,7 @@ static int hdac_hdmi_create_dais(struct hdac_device *hdev, list_for_each_entry(cvt, &hdmi->cvt_list, head) { ret = snd_hdac_query_supported_pcm(hdev, cvt->nid, - &rates, &formats, &bps); + &rates, &formats, NULL, &bps); if (ret) return ret; -- cgit v1.2.3 From 4a6ba09e892aab49fda8464e4f9b244a17fd75b2 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Fri, 17 Nov 2023 13:05:57 +0100 Subject: ASoC: pcm: Honor subformat when configuring runtime Subformat options are ignored when setting up hardware parameters and assigning PCM stream capabilities. Account for them to allow for granular format selection. As there is only one user currently (format S32_LE), subformat is represented by a simple u32 and stores flags only for that one user alone. Such approach allows for alloc/free-less code until there are more users on the horizon. Acked-by: Mark Brown Signed-off-by: Cezary Rojewski Acked-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20231117120610.1755254-4-cezary.rojewski@intel.com Signed-off-by: Takashi Iwai --- include/sound/soc.h | 1 + sound/soc/soc-pcm.c | 10 ++++++++++ 2 files changed, 11 insertions(+) (limited to 'include') diff --git a/include/sound/soc.h b/include/sound/soc.h index 7792c393e238..ccc31bc41e92 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -620,6 +620,7 @@ enum snd_soc_trigger_order { struct snd_soc_pcm_stream { const char *stream_name; u64 formats; /* SNDRV_PCM_FMTBIT_* */ + u32 subformats; /* for S32_LE format, SNDRV_PCM_SUBFMTBIT_* */ unsigned int rates; /* SNDRV_PCM_RATE_* */ unsigned int rate_min; /* min rate */ unsigned int rate_max; /* max rate */ diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 323e4d7b6adf..9d688917cce2 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -554,6 +554,12 @@ static void soc_pcm_hw_update_format(struct snd_pcm_hardware *hw, hw->formats &= p->formats; } +static void soc_pcm_hw_update_subformat(struct snd_pcm_hardware *hw, + struct snd_soc_pcm_stream *p) +{ + hw->subformats &= p->subformats; +} + /** * snd_soc_runtime_calc_hw() - Calculate hw limits for a PCM stream * @rtd: ASoC PCM runtime @@ -592,6 +598,7 @@ int snd_soc_runtime_calc_hw(struct snd_soc_pcm_runtime *rtd, soc_pcm_hw_update_chan(hw, cpu_stream); soc_pcm_hw_update_rate(hw, cpu_stream); soc_pcm_hw_update_format(hw, cpu_stream); + soc_pcm_hw_update_subformat(hw, cpu_stream); } cpu_chan_min = hw->channels_min; cpu_chan_max = hw->channels_max; @@ -613,6 +620,7 @@ int snd_soc_runtime_calc_hw(struct snd_soc_pcm_runtime *rtd, soc_pcm_hw_update_chan(hw, codec_stream); soc_pcm_hw_update_rate(hw, codec_stream); soc_pcm_hw_update_format(hw, codec_stream); + soc_pcm_hw_update_subformat(hw, codec_stream); } /* Verify both a valid CPU DAI and a valid CODEC DAI were found */ @@ -1713,6 +1721,7 @@ static void dpcm_runtime_setup_fe(struct snd_pcm_substream *substream) soc_pcm_hw_update_rate(hw, cpu_stream); soc_pcm_hw_update_chan(hw, cpu_stream); soc_pcm_hw_update_format(hw, cpu_stream); + soc_pcm_hw_update_subformat(hw, cpu_stream); } } @@ -1750,6 +1759,7 @@ static void dpcm_runtime_setup_be_format(struct snd_pcm_substream *substream) codec_stream = snd_soc_dai_get_pcm_stream(dai, stream); soc_pcm_hw_update_format(hw, codec_stream); + soc_pcm_hw_update_subformat(hw, codec_stream); } } } -- cgit v1.2.3 From d24f1a090d3f2a5c86a8782afeda71c2d2539966 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Fri, 17 Nov 2023 13:05:58 +0100 Subject: ALSA: hda: Upgrade stream-format infrastructure Introduce a set of functions that ultimately facilite SDxFMT-related calculations in atomic manner: First, introduce snd_pcm_subformat_width() and snd_pcm_hw_params_bits() helpers that separate the base functionality from the HDAudio-specific one. snd_hdac_format_normalize() - format converter. S20_LE, S24_LE and their unsigned and BE friends are invalid from HDAudio perspective but still can be specified as function argument due to compatibility reasons. snd_hdac_stream_format_bits() - obtain just the bits-per-sample value. Does not ignore subformat and msbits parameters. snd_hdac_stream_format() and snd_hdac_spdif_stream_format() - obtain the SDxFMT value given the audio format parameters. The former is stripped away of spdif-related information. Useful for users that do not care about them. Acked-by: Mark Brown Signed-off-by: Cezary Rojewski Acked-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20231117120610.1755254-5-cezary.rojewski@intel.com Signed-off-by: Takashi Iwai --- include/sound/hdaudio.h | 5 ++ include/sound/pcm_params.h | 2 + sound/core/pcm_lib.c | 34 +++++++++++++ sound/hda/hdac_device.c | 124 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 165 insertions(+) (limited to 'include') diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 7cfea9e7f6e4..b00c631c4053 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -140,6 +140,11 @@ int snd_hdac_get_connections(struct hdac_device *codec, hda_nid_t nid, hda_nid_t *conn_list, int max_conns); int snd_hdac_get_sub_nodes(struct hdac_device *codec, hda_nid_t nid, hda_nid_t *start_id); +unsigned int snd_hdac_stream_format_bits(snd_pcm_format_t format, snd_pcm_subformat_t subformat, + unsigned int maxbits); +unsigned int snd_hdac_stream_format(unsigned int channels, unsigned int bits, unsigned int rate); +unsigned int snd_hdac_spdif_stream_format(unsigned int channels, unsigned int bits, + unsigned int rate, unsigned short spdif_ctls); unsigned int snd_hdac_calc_stream_format(unsigned int rate, unsigned int channels, snd_pcm_format_t format, diff --git a/include/sound/pcm_params.h b/include/sound/pcm_params.h index ba184f49f7e1..fbf35df6e5cf 100644 --- a/include/sound/pcm_params.h +++ b/include/sound/pcm_params.h @@ -362,6 +362,8 @@ static inline int params_physical_width(const struct snd_pcm_hw_params *p) return snd_pcm_format_physical_width(params_format(p)); } +int snd_pcm_hw_params_bits(const struct snd_pcm_hw_params *p); + static inline void params_set_format(struct snd_pcm_hw_params *p, snd_pcm_format_t fmt) { diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index a11cd7d6295f..41103e5c43ce 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -1706,6 +1706,40 @@ int snd_pcm_hw_param_last(struct snd_pcm_substream *pcm, } EXPORT_SYMBOL(snd_pcm_hw_param_last); +/** + * snd_pcm_hw_params_bits - Get the number of bits per the sample. + * @p: hardware parameters + * + * Return: The number of bits per sample based on the format, + * subformat and msbits the specified hw params has. + */ +int snd_pcm_hw_params_bits(const struct snd_pcm_hw_params *p) +{ + snd_pcm_subformat_t subformat = params_subformat(p); + snd_pcm_format_t format = params_format(p); + + switch (format) { + case SNDRV_PCM_FORMAT_S32_LE: + case SNDRV_PCM_FORMAT_U32_LE: + case SNDRV_PCM_FORMAT_S32_BE: + case SNDRV_PCM_FORMAT_U32_BE: + switch (subformat) { + case SNDRV_PCM_SUBFORMAT_MSBITS_20: + return 20; + case SNDRV_PCM_SUBFORMAT_MSBITS_24: + return 24; + case SNDRV_PCM_SUBFORMAT_MSBITS_MAX: + case SNDRV_PCM_SUBFORMAT_STD: + default: + break; + } + fallthrough; + default: + return snd_pcm_format_width(format); + } +} +EXPORT_SYMBOL(snd_pcm_hw_params_bits); + static int snd_pcm_lib_ioctl_reset(struct snd_pcm_substream *substream, void *arg) { diff --git a/sound/hda/hdac_device.c b/sound/hda/hdac_device.c index cde4d5c33771..556bd24f4014 100644 --- a/sound/hda/hdac_device.c +++ b/sound/hda/hdac_device.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "local.h" static void setup_fg_nodes(struct hdac_device *codec); @@ -725,6 +726,129 @@ static const struct hda_rate_tbl rate_bits[] = { { 0 } /* terminator */ }; +static snd_pcm_format_t snd_hdac_format_normalize(snd_pcm_format_t format) +{ + switch (format) { + case SNDRV_PCM_FORMAT_S20_LE: + case SNDRV_PCM_FORMAT_S24_LE: + return SNDRV_PCM_FORMAT_S32_LE; + + case SNDRV_PCM_FORMAT_U20_LE: + case SNDRV_PCM_FORMAT_U24_LE: + return SNDRV_PCM_FORMAT_U32_LE; + + case SNDRV_PCM_FORMAT_S20_BE: + case SNDRV_PCM_FORMAT_S24_BE: + return SNDRV_PCM_FORMAT_S32_BE; + + case SNDRV_PCM_FORMAT_U20_BE: + case SNDRV_PCM_FORMAT_U24_BE: + return SNDRV_PCM_FORMAT_U32_BE; + + default: + return format; + } +} + +/** + * snd_hdac_stream_format_bits - obtain bits per sample value. + * @format: the PCM format. + * @subformat: the PCM subformat. + * @maxbits: the maximum bits per sample. + * + * Return: The number of bits per sample. + */ +unsigned int snd_hdac_stream_format_bits(snd_pcm_format_t format, snd_pcm_subformat_t subformat, + unsigned int maxbits) +{ + struct snd_pcm_hw_params params; + unsigned int bits; + + memset(¶ms, 0, sizeof(params)); + + params_set_format(¶ms, snd_hdac_format_normalize(format)); + snd_mask_set(hw_param_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT), + (__force unsigned int)subformat); + + bits = snd_pcm_hw_params_bits(¶ms); + if (maxbits) + return min(bits, maxbits); + return bits; +} +EXPORT_SYMBOL_GPL(snd_hdac_stream_format_bits); + +/** + * snd_hdac_stream_format - convert format parameters to SDxFMT value. + * @channels: the number of channels. + * @bits: bits per sample. + * @rate: the sample rate. + * + * Return: The format bitset or zero if invalid. + */ +unsigned int snd_hdac_stream_format(unsigned int channels, unsigned int bits, unsigned int rate) +{ + unsigned int val = 0; + int i; + + for (i = 0; rate_bits[i].hz; i++) { + if (rate_bits[i].hz == rate) { + val = rate_bits[i].hda_fmt; + break; + } + } + + if (!rate_bits[i].hz) + return 0; + + if (channels == 0 || channels > 8) + return 0; + val |= channels - 1; + + switch (bits) { + case 8: + val |= AC_FMT_BITS_8; + break; + case 16: + val |= AC_FMT_BITS_16; + break; + case 20: + val |= AC_FMT_BITS_20; + break; + case 24: + val |= AC_FMT_BITS_24; + break; + case 32: + val |= AC_FMT_BITS_32; + break; + default: + return 0; + } + + return val; +} +EXPORT_SYMBOL_GPL(snd_hdac_stream_format); + +/** + * snd_hdac_spdif_stream_format - convert format parameters to SDxFMT value. + * @channels: the number of channels. + * @bits: bits per sample. + * @rate: the sample rate. + * @spdif_ctls: HD-audio SPDIF status bits (0 if irrelevant). + * + * Return: The format bitset or zero if invalid. + */ +unsigned int snd_hdac_spdif_stream_format(unsigned int channels, unsigned int bits, + unsigned int rate, unsigned short spdif_ctls) +{ + unsigned int val = snd_hdac_stream_format(channels, bits, rate); + + if (val && spdif_ctls & AC_DIG1_NONAUDIO) + val |= AC_FMT_TYPE_NON_PCM; + + return val; +} +EXPORT_SYMBOL_GPL(snd_hdac_spdif_stream_format); + /** * snd_hdac_calc_stream_format - calculate the format bitset * @rate: the sample rate -- cgit v1.2.3 From dfd6ba6813dd0fdea54de58e7b80ca304ce3fca5 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Fri, 17 Nov 2023 13:06:08 +0100 Subject: ALSA: hda: Drop snd_hdac_calc_stream_format() There are no users of the function. Acked-by: Mark Brown Signed-off-by: Cezary Rojewski Acked-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20231117120610.1755254-15-cezary.rojewski@intel.com Signed-off-by: Takashi Iwai --- include/sound/hdaudio.h | 5 ---- sound/hda/hdac_device.c | 61 ------------------------------------------------- 2 files changed, 66 deletions(-) (limited to 'include') diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index b00c631c4053..62a3cd154ff2 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -145,11 +145,6 @@ unsigned int snd_hdac_stream_format_bits(snd_pcm_format_t format, snd_pcm_subfor unsigned int snd_hdac_stream_format(unsigned int channels, unsigned int bits, unsigned int rate); unsigned int snd_hdac_spdif_stream_format(unsigned int channels, unsigned int bits, unsigned int rate, unsigned short spdif_ctls); -unsigned int snd_hdac_calc_stream_format(unsigned int rate, - unsigned int channels, - snd_pcm_format_t format, - unsigned int maxbps, - unsigned short spdif_ctls); int snd_hdac_query_supported_pcm(struct hdac_device *codec, hda_nid_t nid, u32 *ratesp, u64 *formatsp, u32 *subformatsp, unsigned int *bpsp); diff --git a/sound/hda/hdac_device.c b/sound/hda/hdac_device.c index 556bd24f4014..7f7b67fe1b65 100644 --- a/sound/hda/hdac_device.c +++ b/sound/hda/hdac_device.c @@ -849,67 +849,6 @@ unsigned int snd_hdac_spdif_stream_format(unsigned int channels, unsigned int bi } EXPORT_SYMBOL_GPL(snd_hdac_spdif_stream_format); -/** - * snd_hdac_calc_stream_format - calculate the format bitset - * @rate: the sample rate - * @channels: the number of channels - * @format: the PCM format (SNDRV_PCM_FORMAT_XXX) - * @maxbps: the max. bps - * @spdif_ctls: HD-audio SPDIF status bits (0 if irrelevant) - * - * Calculate the format bitset from the given rate, channels and th PCM format. - * - * Return zero if invalid. - */ -unsigned int snd_hdac_calc_stream_format(unsigned int rate, - unsigned int channels, - snd_pcm_format_t format, - unsigned int maxbps, - unsigned short spdif_ctls) -{ - int i; - unsigned int val = 0; - - for (i = 0; rate_bits[i].hz; i++) - if (rate_bits[i].hz == rate) { - val = rate_bits[i].hda_fmt; - break; - } - if (!rate_bits[i].hz) - return 0; - - if (channels == 0 || channels > 8) - return 0; - val |= channels - 1; - - switch (snd_pcm_format_width(format)) { - case 8: - val |= AC_FMT_BITS_8; - break; - case 16: - val |= AC_FMT_BITS_16; - break; - case 20: - case 24: - case 32: - if (maxbps >= 32 || format == SNDRV_PCM_FORMAT_FLOAT_LE) - val |= AC_FMT_BITS_32; - else if (maxbps >= 24) - val |= AC_FMT_BITS_24; - else - val |= AC_FMT_BITS_20; - break; - default: - return 0; - } - - if (spdif_ctls & AC_DIG1_NONAUDIO) - val |= AC_FMT_TYPE_NON_PCM; - - return val; -} -EXPORT_SYMBOL_GPL(snd_hdac_calc_stream_format); - static unsigned int query_pcm_param(struct hdac_device *codec, hda_nid_t nid) { unsigned int val = 0; -- cgit v1.2.3 From 5ec42bf04d72fd6d0a6855810cc779e0ee31dfd7 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Mon, 4 Dec 2023 15:27:06 -0600 Subject: PCI: add INTEL_HDA_ARL to pci_ids.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PCI ID insertion follows the increasing order in the table, but this hardware follows MTL (MeteorLake). Signed-off-by: Pierre-Louis Bossart Reviewed-by: Péter Ujfalusi Reviewed-by: Kai Vehmanen Acked-by: Mark Brown Link: https://lore.kernel.org/r/20231204212710.185976-2-pierre-louis.bossart@linux.intel.com Signed-off-by: Takashi Iwai --- include/linux/pci_ids.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include') diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 275799b5f535..97cc0baad0f4 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -3065,6 +3065,7 @@ #define PCI_DEVICE_ID_INTEL_82443GX_0 0x71a0 #define PCI_DEVICE_ID_INTEL_82443GX_2 0x71a2 #define PCI_DEVICE_ID_INTEL_82372FB_1 0x7601 +#define PCI_DEVICE_ID_INTEL_HDA_ARL 0x7728 #define PCI_DEVICE_ID_INTEL_HDA_RPL_S 0x7a50 #define PCI_DEVICE_ID_INTEL_HDA_ADL_S 0x7ad0 #define PCI_DEVICE_ID_INTEL_HDA_MTL 0x7e28 -- cgit v1.2.3 From 337b2f0e778f78f61972497994b70a05e8f6447d Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Wed, 20 Dec 2023 04:09:23 +1030 Subject: ALSA: scarlett2: Add skeleton hwdep/ioctl interface Add skeleton hwdep/ioctl interface, beginning with SCARLETT2_IOCTL_PVERSION and SCARLETT2_IOCTL_REBOOT. Signed-off-by: Geoffrey D. Bennett Link: https://lore.kernel.org/r/24ffcd47a8a02ebad3c8b2438104af8f0169164e.1703001053.git.g@b4.vu Signed-off-by: Takashi Iwai --- MAINTAINERS | 1 + include/uapi/sound/scarlett2.h | 34 +++++++++++++++++++++ sound/usb/mixer_scarlett2.c | 67 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 include/uapi/sound/scarlett2.h (limited to 'include') diff --git a/MAINTAINERS b/MAINTAINERS index ae3f72f57854..80c65096538c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8279,6 +8279,7 @@ S: Maintained W: https://github.com/geoffreybennett/scarlett-gen2 B: https://github.com/geoffreybennett/scarlett-gen2/issues T: git https://github.com/geoffreybennett/scarlett-gen2.git +F: include/uapi/sound/scarlett2.h F: sound/usb/mixer_scarlett2.c FORCEDETH GIGABIT ETHERNET DRIVER diff --git a/include/uapi/sound/scarlett2.h b/include/uapi/sound/scarlett2.h new file mode 100644 index 000000000000..ec0b7da335ff --- /dev/null +++ b/include/uapi/sound/scarlett2.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Focusrite Scarlett 2 Protocol Driver for ALSA + * (including Scarlett 2nd Gen, 3rd Gen, Clarett USB, and Clarett+ + * series products) + * + * Copyright (c) 2023 by Geoffrey D. Bennett + */ +#ifndef __UAPI_SOUND_SCARLETT2_H +#define __UAPI_SOUND_SCARLETT2_H + +#include +#include + +#define SCARLETT2_HWDEP_MAJOR 1 +#define SCARLETT2_HWDEP_MINOR 0 +#define SCARLETT2_HWDEP_SUBMINOR 0 + +#define SCARLETT2_HWDEP_VERSION \ + ((SCARLETT2_HWDEP_MAJOR << 16) | \ + (SCARLETT2_HWDEP_MINOR << 8) | \ + SCARLETT2_HWDEP_SUBMINOR) + +#define SCARLETT2_HWDEP_VERSION_MAJOR(v) (((v) >> 16) & 0xFF) +#define SCARLETT2_HWDEP_VERSION_MINOR(v) (((v) >> 8) & 0xFF) +#define SCARLETT2_HWDEP_VERSION_SUBMINOR(v) ((v) & 0xFF) + +/* Get protocol version */ +#define SCARLETT2_IOCTL_PVERSION _IOR('S', 0x60, int) + +/* Reboot */ +#define SCARLETT2_IOCTL_REBOOT _IO('S', 0x61) + +#endif /* __UAPI_SOUND_SCARLETT2_H */ diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index b62fc0038671..d27628e4bda8 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -146,6 +146,9 @@ #include #include +#include + +#include #include "usbaudio.h" #include "mixer.h" @@ -1439,6 +1442,16 @@ static int scarlett2_usb( /* validate the response */ if (err != resp_buf_size) { + + /* ESHUTDOWN and EPROTO are valid responses to a + * reboot request + */ + if (cmd == SCARLETT2_USB_REBOOT && + (err == -ESHUTDOWN || err == -EPROTO)) { + err = 0; + goto unlock; + } + usb_audio_err( mixer->chip, "%s USB response result cmd %x was %d expected %zu\n", @@ -4697,6 +4710,49 @@ static int snd_scarlett2_controls_create( return 0; } +/*** hwdep interface ***/ + +/* Reboot the device. */ +static int scarlett2_reboot(struct usb_mixer_interface *mixer) +{ + return scarlett2_usb(mixer, SCARLETT2_USB_REBOOT, NULL, 0, NULL, 0); +} + +static int scarlett2_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct usb_mixer_interface *mixer = hw->private_data; + + switch (cmd) { + + case SCARLETT2_IOCTL_PVERSION: + return put_user(SCARLETT2_HWDEP_VERSION, + (int __user *)arg) ? -EFAULT : 0; + + case SCARLETT2_IOCTL_REBOOT: + return scarlett2_reboot(mixer); + + default: + return -ENOIOCTLCMD; + } +} + +static int scarlett2_hwdep_init(struct usb_mixer_interface *mixer) +{ + struct snd_hwdep *hw; + int err; + + err = snd_hwdep_new(mixer->chip->card, "Focusrite Control", 0, &hw); + if (err < 0) + return err; + + hw->private_data = mixer; + hw->exclusive = 1; + hw->ops.ioctl = scarlett2_hwdep_ioctl; + + return 0; +} + int snd_scarlett2_init(struct usb_mixer_interface *mixer) { struct snd_usb_audio *chip = mixer->chip; @@ -4738,11 +4794,20 @@ int snd_scarlett2_init(struct usb_mixer_interface *mixer) USB_ID_PRODUCT(chip->usb_id)); err = snd_scarlett2_controls_create(mixer, entry); - if (err < 0) + if (err < 0) { usb_audio_err(mixer->chip, "Error initialising %s Mixer Driver: %d", entry->series_name, err); + return err; + } + + err = scarlett2_hwdep_init(mixer); + if (err < 0) + usb_audio_err(mixer->chip, + "Error creating %s hwdep device: %d", + entry->series_name, + err); return err; } -- cgit v1.2.3 From 6a7508e64ee3e8320c886020bcdcd70f7fcbff2c Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Wed, 20 Dec 2023 04:20:29 +1030 Subject: ALSA: scarlett2: Add ioctl commands to erase flash segments Add ioctls: - SCARLETT2_IOCTL_SELECT_FLASH_SEGMENT - SCARLETT2_IOCTL_ERASE_FLASH_SEGMENT - SCARLETT2_IOCTL_GET_ERASE_PROGRESS The settings or the firmware flash segment can be selected and then erased (asynchronous operation), and the erase progress can be monitored. If the erase progress is not monitored, then subsequent hwdep operations will block until the erase is complete. Once the erase is started, ALSA controls that communicate with the device will all return -EBUSY, and the device must be rebooted. Signed-off-by: Geoffrey D. Bennett Link: https://lore.kernel.org/r/227409adb672f174bf3db211e9bda016fb4646ea.1703001053.git.g@b4.vu Signed-off-by: Takashi Iwai --- include/uapi/sound/scarlett2.h | 20 ++ sound/usb/mixer_scarlett2.c | 428 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 442 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/include/uapi/sound/scarlett2.h b/include/uapi/sound/scarlett2.h index ec0b7da335ff..d0ff38ffa154 100644 --- a/include/uapi/sound/scarlett2.h +++ b/include/uapi/sound/scarlett2.h @@ -31,4 +31,24 @@ /* Reboot */ #define SCARLETT2_IOCTL_REBOOT _IO('S', 0x61) +/* Select flash segment */ +#define SCARLETT2_SEGMENT_ID_SETTINGS 0 +#define SCARLETT2_SEGMENT_ID_FIRMWARE 1 +#define SCARLETT2_SEGMENT_ID_COUNT 2 + +#define SCARLETT2_IOCTL_SELECT_FLASH_SEGMENT _IOW('S', 0x62, int) + +/* Erase selected flash segment */ +#define SCARLETT2_IOCTL_ERASE_FLASH_SEGMENT _IO('S', 0x63) + +/* Get selected flash segment erase progress + * 1 through to num_blocks, or 255 for complete + */ +struct scarlett2_flash_segment_erase_progress { + unsigned char progress; + unsigned char num_blocks; +}; +#define SCARLETT2_IOCTL_GET_ERASE_PROGRESS \ + _IOR('S', 0x64, struct scarlett2_flash_segment_erase_progress) + #endif /* __UAPI_SOUND_SCARLETT2_H */ diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index d27628e4bda8..2d60fa607bd8 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -193,11 +193,6 @@ static const u16 scarlett2_mixer_values[SCARLETT2_MIXER_VALUE_COUNT] = { 16345 }; -/* Flash segments that we may manipulate */ -#define SCARLETT2_SEGMENT_ID_SETTINGS 0 -#define SCARLETT2_SEGMENT_ID_FIRMWARE 1 -#define SCARLETT2_SEGMENT_ID_COUNT 2 - /* Maximum number of analogue outputs */ #define SCARLETT2_ANALOGUE_MAX 10 @@ -267,6 +262,13 @@ enum { SCARLETT2_DIM_MUTE_COUNT = 2, }; +/* Flash Write State */ +enum { + SCARLETT2_FLASH_WRITE_STATE_IDLE = 0, + SCARLETT2_FLASH_WRITE_STATE_SELECTED = 1, + SCARLETT2_FLASH_WRITE_STATE_ERASING = 2 +}; + static const char *const scarlett2_dim_mute_names[SCARLETT2_DIM_MUTE_COUNT] = { "Mute Playback Switch", "Dim Playback Switch" }; @@ -427,6 +429,9 @@ struct scarlett2_data { struct usb_mixer_interface *mixer; struct mutex usb_mutex; /* prevent sending concurrent USB requests */ struct mutex data_mutex; /* lock access to this data */ + u8 hwdep_in_use; + u8 selected_flash_segment_id; + u8 flash_write_state; struct delayed_work work; const struct scarlett2_device_info *info; const char *series_name; @@ -2137,6 +2142,11 @@ static int scarlett2_sync_ctl_get(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->sync_updated) { err = scarlett2_update_sync(mixer); if (err < 0) @@ -2233,6 +2243,11 @@ static int scarlett2_master_volume_ctl_get(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->vol_updated) { err = scarlett2_update_volumes(mixer); if (err < 0) @@ -2272,6 +2287,11 @@ static int scarlett2_volume_ctl_get(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->vol_updated) { err = scarlett2_update_volumes(mixer); if (err < 0) @@ -2295,6 +2315,11 @@ static int scarlett2_volume_ctl_put(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->vol[index]; val = ucontrol->value.integer.value[0]; @@ -2352,6 +2377,11 @@ static int scarlett2_mute_ctl_get(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->vol_updated) { err = scarlett2_update_volumes(mixer); if (err < 0) @@ -2375,6 +2405,11 @@ static int scarlett2_mute_ctl_put(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->mute_switch[index]; val = !!ucontrol->value.integer.value[0]; @@ -2514,6 +2549,11 @@ static int scarlett2_sw_hw_enum_ctl_put(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->vol_sw_hw_switch[index]; val = !!ucontrol->value.enumerated.item[0]; @@ -2611,6 +2651,11 @@ static int scarlett2_level_enum_ctl_get(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->input_other_updated) { err = scarlett2_update_input_other(mixer); if (err < 0) @@ -2636,6 +2681,11 @@ static int scarlett2_level_enum_ctl_put(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->level_switch[index]; val = !!ucontrol->value.enumerated.item[0]; @@ -2675,6 +2725,11 @@ static int scarlett2_pad_ctl_get(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->input_other_updated) { err = scarlett2_update_input_other(mixer); if (err < 0) @@ -2700,6 +2755,11 @@ static int scarlett2_pad_ctl_put(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->pad_switch[index]; val = !!ucontrol->value.integer.value[0]; @@ -2739,6 +2799,11 @@ static int scarlett2_air_ctl_get(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->input_other_updated) { err = scarlett2_update_input_other(mixer); if (err < 0) @@ -2763,6 +2828,11 @@ static int scarlett2_air_ctl_put(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->air_switch[index]; val = !!ucontrol->value.integer.value[0]; @@ -2802,6 +2872,11 @@ static int scarlett2_phantom_ctl_get(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->input_other_updated) { err = scarlett2_update_input_other(mixer); if (err < 0) @@ -2827,6 +2902,11 @@ static int scarlett2_phantom_ctl_put(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->phantom_switch[index]; val = !!ucontrol->value.integer.value[0]; @@ -2878,6 +2958,11 @@ static int scarlett2_phantom_persistence_ctl_put( mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->phantom_persistence; val = !!ucontrol->value.integer.value[0]; @@ -2988,6 +3073,11 @@ static int scarlett2_direct_monitor_ctl_get( mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->monitor_other_updated) { err = scarlett2_update_monitor_other(mixer); if (err < 0) @@ -3012,6 +3102,11 @@ static int scarlett2_direct_monitor_ctl_put( mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->direct_monitor_switch; val = min(ucontrol->value.enumerated.item[0], 2U); @@ -3101,6 +3196,11 @@ static int scarlett2_speaker_switch_enum_ctl_get( mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->monitor_other_updated) { err = scarlett2_update_monitor_other(mixer); if (err < 0) @@ -3181,6 +3281,11 @@ static int scarlett2_speaker_switch_enum_ctl_put( mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->speaker_switching_switch; val = min(ucontrol->value.enumerated.item[0], 2U); @@ -3262,6 +3367,11 @@ static int scarlett2_talkback_enum_ctl_get( mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->monitor_other_updated) { err = scarlett2_update_monitor_other(mixer); if (err < 0) @@ -3285,6 +3395,11 @@ static int scarlett2_talkback_enum_ctl_put( mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->talkback_switch; val = min(ucontrol->value.enumerated.item[0], 2U); @@ -3349,6 +3464,11 @@ static int scarlett2_talkback_map_ctl_put( mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->talkback_map[index]; val = !!ucontrol->value.integer.value[0]; @@ -3423,6 +3543,11 @@ static int scarlett2_dim_mute_ctl_get(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->vol_updated) { err = scarlett2_update_volumes(mixer); if (err < 0) @@ -3451,6 +3576,11 @@ static int scarlett2_dim_mute_ctl_put(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->dim_mute[index]; val = !!ucontrol->value.integer.value[0]; @@ -3695,6 +3825,11 @@ static int scarlett2_mixer_ctl_put(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->mix[index]; val = clamp(ucontrol->value.integer.value[0], 0L, (long)SCARLETT2_MIXER_MAX_VALUE); @@ -3808,6 +3943,11 @@ static int scarlett2_mux_src_enum_ctl_get(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->mux_updated) { err = scarlett2_usb_get_mux(mixer); if (err < 0) @@ -3831,6 +3971,11 @@ static int scarlett2_mux_src_enum_ctl_put(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->mux[index]; val = min(ucontrol->value.enumerated.item[0], private->num_mux_srcs - 1U); @@ -3915,6 +4060,11 @@ static int scarlett2_meter_ctl_get(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + err = scarlett2_usb_get_meter_levels(elem->head.mixer, elem->channels, meter_levels); if (err < 0) @@ -3983,6 +4133,11 @@ static int scarlett2_msd_ctl_put(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->msd_switch; val = !!ucontrol->value.integer.value[0]; @@ -4050,6 +4205,11 @@ static int scarlett2_standalone_ctl_put(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex); + if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->standalone_switch; val = !!ucontrol->value.integer.value[0]; @@ -4712,12 +4872,241 @@ static int snd_scarlett2_controls_create( /*** hwdep interface ***/ -/* Reboot the device. */ +/* Set private->hwdep_in_use; prevents access to the ALSA controls + * while doing a config erase/firmware upgrade. + */ +static void scarlett2_lock(struct scarlett2_data *private) +{ + mutex_lock(&private->data_mutex); + private->hwdep_in_use = 1; + mutex_unlock(&private->data_mutex); +} + +/* Call SCARLETT2_USB_GET_ERASE to get the erase progress */ +static int scarlett2_get_erase_progress(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + int segment_id, segment_num, err; + u8 erase_resp; + + struct { + __le32 segment_num; + __le32 pad; + } __packed erase_req; + + segment_id = private->selected_flash_segment_id; + segment_num = private->flash_segment_nums[segment_id]; + + if (segment_num < SCARLETT2_SEGMENT_NUM_MIN || + segment_num > SCARLETT2_SEGMENT_NUM_MAX) + return -EFAULT; + + /* Send the erase progress request */ + erase_req.segment_num = cpu_to_le32(segment_num); + erase_req.pad = 0; + + err = scarlett2_usb(mixer, SCARLETT2_USB_GET_ERASE, + &erase_req, sizeof(erase_req), + &erase_resp, sizeof(erase_resp)); + if (err < 0) + return err; + + return erase_resp; +} + +/* Repeatedly call scarlett2_get_erase_progress() until it returns + * 0xff (erase complete) or we've waited 10 seconds (it usually takes + * <3 seconds). + */ +static int scarlett2_wait_for_erase(struct usb_mixer_interface *mixer) +{ + int i, err; + + for (i = 0; i < 100; i++) { + err = scarlett2_get_erase_progress(mixer); + if (err < 0) + return err; + + if (err == 0xff) + return 0; + + msleep(100); + } + + return -ETIMEDOUT; +} + +/* Reboot the device; wait for the erase to complete if one is in + * progress. + */ static int scarlett2_reboot(struct usb_mixer_interface *mixer) { + struct scarlett2_data *private = mixer->private_data; + + if (private->flash_write_state == + SCARLETT2_FLASH_WRITE_STATE_ERASING) { + int err = scarlett2_wait_for_erase(mixer); + + if (err < 0) + return err; + } + return scarlett2_usb(mixer, SCARLETT2_USB_REBOOT, NULL, 0, NULL, 0); } +/* Select a flash segment for erasing (and possibly writing to) */ +static int scarlett2_ioctl_select_flash_segment( + struct usb_mixer_interface *mixer, + unsigned long arg) +{ + struct scarlett2_data *private = mixer->private_data; + int segment_id, segment_num; + + if (get_user(segment_id, (int __user *)arg)) + return -EFAULT; + + /* Check the segment ID and segment number */ + if (segment_id < 0 || segment_id >= SCARLETT2_SEGMENT_ID_COUNT) + return -EINVAL; + + segment_num = private->flash_segment_nums[segment_id]; + if (segment_num < SCARLETT2_SEGMENT_NUM_MIN || + segment_num > SCARLETT2_SEGMENT_NUM_MAX) { + usb_audio_err(mixer->chip, + "%s: invalid segment number %d\n", + __func__, segment_id); + return -EFAULT; + } + + /* If erasing, wait for it to complete */ + if (private->flash_write_state == SCARLETT2_FLASH_WRITE_STATE_ERASING) { + int err = scarlett2_wait_for_erase(mixer); + + if (err < 0) + return err; + } + + /* Save the selected segment ID and set the state to SELECTED */ + private->selected_flash_segment_id = segment_id; + private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_SELECTED; + + return 0; +} + +/* Erase the previously-selected flash segment */ +static int scarlett2_ioctl_erase_flash_segment( + struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + int segment_id, segment_num, err; + + struct { + __le32 segment_num; + __le32 pad; + } __packed erase_req; + + if (private->flash_write_state != SCARLETT2_FLASH_WRITE_STATE_SELECTED) + return -EINVAL; + + segment_id = private->selected_flash_segment_id; + segment_num = private->flash_segment_nums[segment_id]; + + if (segment_num < SCARLETT2_SEGMENT_NUM_MIN || + segment_num > SCARLETT2_SEGMENT_NUM_MAX) + return -EFAULT; + + /* Prevent access to ALSA controls that access the device from + * here on + */ + scarlett2_lock(private); + + /* Send the erase request */ + erase_req.segment_num = cpu_to_le32(segment_num); + erase_req.pad = 0; + + err = scarlett2_usb(mixer, SCARLETT2_USB_ERASE_SEGMENT, + &erase_req, sizeof(erase_req), + NULL, 0); + if (err < 0) + return err; + + /* On success, change the state from SELECTED to ERASING */ + private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_ERASING; + + return 0; +} + +/* Get the erase progress from the device */ +static int scarlett2_ioctl_get_erase_progress( + struct usb_mixer_interface *mixer, + unsigned long arg) +{ + struct scarlett2_data *private = mixer->private_data; + struct scarlett2_flash_segment_erase_progress progress; + int segment_id, segment_num, err; + u8 erase_resp; + + struct { + __le32 segment_num; + __le32 pad; + } __packed erase_req; + + /* Check that we're erasing */ + if (private->flash_write_state != SCARLETT2_FLASH_WRITE_STATE_ERASING) + return -EINVAL; + + segment_id = private->selected_flash_segment_id; + segment_num = private->flash_segment_nums[segment_id]; + + if (segment_num < SCARLETT2_SEGMENT_NUM_MIN || + segment_num > SCARLETT2_SEGMENT_NUM_MAX) + return -EFAULT; + + /* Send the erase progress request */ + erase_req.segment_num = cpu_to_le32(segment_num); + erase_req.pad = 0; + + err = scarlett2_usb(mixer, SCARLETT2_USB_GET_ERASE, + &erase_req, sizeof(erase_req), + &erase_resp, sizeof(erase_resp)); + if (err < 0) + return err; + + progress.progress = erase_resp; + progress.num_blocks = private->flash_segment_blocks[segment_id]; + + if (copy_to_user((void __user *)arg, &progress, sizeof(progress))) + return -EFAULT; + + /* If the erase is complete, change the state from ERASING to + * IDLE. + */ + if (progress.progress == 0xff) + private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_IDLE; + + return 0; +} + +static int scarlett2_hwdep_open(struct snd_hwdep *hw, struct file *file) +{ + struct usb_mixer_interface *mixer = hw->private_data; + struct scarlett2_data *private = mixer->private_data; + + /* If erasing, wait for it to complete */ + if (private->flash_write_state == + SCARLETT2_FLASH_WRITE_STATE_ERASING) { + int err = scarlett2_wait_for_erase(mixer); + + if (err < 0) + return err; + } + + /* Set the state to IDLE */ + private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_IDLE; + + return 0; +} + static int scarlett2_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, unsigned int cmd, unsigned long arg) { @@ -4732,11 +5121,36 @@ static int scarlett2_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, case SCARLETT2_IOCTL_REBOOT: return scarlett2_reboot(mixer); + case SCARLETT2_IOCTL_SELECT_FLASH_SEGMENT: + return scarlett2_ioctl_select_flash_segment(mixer, arg); + + case SCARLETT2_IOCTL_ERASE_FLASH_SEGMENT: + return scarlett2_ioctl_erase_flash_segment(mixer); + + case SCARLETT2_IOCTL_GET_ERASE_PROGRESS: + return scarlett2_ioctl_get_erase_progress(mixer, arg); + default: return -ENOIOCTLCMD; } } +static int scarlett2_hwdep_release(struct snd_hwdep *hw, struct file *file) +{ + struct usb_mixer_interface *mixer = hw->private_data; + struct scarlett2_data *private = mixer->private_data; + + /* Return from the SELECTED or WRITE state to IDLE. + * The ERASING state is left as-is, and checked on next open. + */ + if (private && + private->hwdep_in_use && + private->flash_write_state != SCARLETT2_FLASH_WRITE_STATE_ERASING) + private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_IDLE; + + return 0; +} + static int scarlett2_hwdep_init(struct usb_mixer_interface *mixer) { struct snd_hwdep *hw; @@ -4748,7 +5162,9 @@ static int scarlett2_hwdep_init(struct usb_mixer_interface *mixer) hw->private_data = mixer; hw->exclusive = 1; + hw->ops.open = scarlett2_hwdep_open; hw->ops.ioctl = scarlett2_hwdep_ioctl; + hw->ops.release = scarlett2_hwdep_release; return 0; } -- cgit v1.2.3 From 4e809a299677b8a9a239574a787620cb4f6c086a Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Wed, 27 Dec 2023 04:38:58 +1030 Subject: ALSA: scarlett2: Add support for Solo, 2i2, and 4i4 Gen 4 Add new Focusrite Scarlett Gen 4 USB IDs, notification arrays, config sets, and device info data. Signed-off-by: Geoffrey D. Bennett Signed-off-by: Takashi Iwai Link: https://lore.kernel.org/r/b33526d3b7a56bb2c86aa4eb2137a415bd23f1ce.1703612638.git.g@b4.vu --- include/uapi/sound/scarlett2.h | 4 +- sound/usb/mixer_quirks.c | 3 + sound/usb/mixer_scarlett2.c | 361 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 351 insertions(+), 17 deletions(-) (limited to 'include') diff --git a/include/uapi/sound/scarlett2.h b/include/uapi/sound/scarlett2.h index d0ff38ffa154..91467ab0ed70 100644 --- a/include/uapi/sound/scarlett2.h +++ b/include/uapi/sound/scarlett2.h @@ -1,8 +1,8 @@ /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ /* * Focusrite Scarlett 2 Protocol Driver for ALSA - * (including Scarlett 2nd Gen, 3rd Gen, Clarett USB, and Clarett+ - * series products) + * (including Scarlett 2nd Gen, 3rd Gen, 4th Gen, Clarett USB, and + * Clarett+ series products) * * Copyright (c) 2023 by Geoffrey D. Bennett */ diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index c8d48566e175..065a4be0d771 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -3447,6 +3447,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) case USB_ID(0x1235, 0x8213): /* Focusrite Scarlett 8i6 3rd Gen */ case USB_ID(0x1235, 0x8214): /* Focusrite Scarlett 18i8 3rd Gen */ case USB_ID(0x1235, 0x8215): /* Focusrite Scarlett 18i20 3rd Gen */ + case USB_ID(0x1235, 0x8218): /* Focusrite Scarlett Solo 4th Gen */ + case USB_ID(0x1235, 0x8219): /* Focusrite Scarlett 2i2 4th Gen */ + case USB_ID(0x1235, 0x821a): /* Focusrite Scarlett 4i4 4th Gen */ case USB_ID(0x1235, 0x8206): /* Focusrite Clarett 2Pre USB */ case USB_ID(0x1235, 0x8207): /* Focusrite Clarett 4Pre USB */ case USB_ID(0x1235, 0x8208): /* Focusrite Clarett 8Pre USB */ diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index 94413fca2b6c..743054472184 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -1,12 +1,13 @@ // SPDX-License-Identifier: GPL-2.0 /* * Focusrite Scarlett 2 Protocol Driver for ALSA - * (including Scarlett 2nd Gen, 3rd Gen, Clarett USB, and Clarett+ - * series products) + * (including Scarlett 2nd Gen, 3rd Gen, 4th Gen, Clarett USB, and + * Clarett+ series products) * * Supported models: * - 6i6/18i8/18i20 Gen 2 * - Solo/2i2/4i4/8i6/18i8/18i20 Gen 3 + * - Solo/2i2/4i4 Gen 4 * - Clarett 2Pre/4Pre/8Pre USB * - Clarett+ 2Pre/4Pre/8Pre * @@ -68,6 +69,12 @@ * * Support for Clarett 2Pre and 4Pre USB added in Oct 2023. * + * Support for firmware updates added in Dec 2023. + * + * Support for Scarlett Solo/2i2/4i4 Gen 4 added in Dec 2023 (thanks + * to many LinuxMusicians people and to Focusrite for hardware + * donations). + * * This ALSA mixer gives access to (model-dependent): * - input, output, mixer-matrix muxes * - mixer-matrix gain stages @@ -78,6 +85,8 @@ * controls * - disable/enable MSD mode * - disable/enable standalone mode + * - input gain, autogain, safe mode + * - direct monitor mixes * * * /--------------\ 18chn 20chn /--------------\ @@ -130,7 +139,7 @@ * \--------------/ * * - * Gen 3 devices have a Mass Storage Device (MSD) mode where a small + * Gen 3/4 devices have a Mass Storage Device (MSD) mode where a small * disk with registration and driver download information is presented * to the host. To access the full functionality of the device without * proprietary software, MSD mode can be disabled by: @@ -302,9 +311,19 @@ struct scarlett2_notification { static void scarlett2_notify_sync(struct usb_mixer_interface *mixer); static void scarlett2_notify_dim_mute(struct usb_mixer_interface *mixer); static void scarlett2_notify_monitor(struct usb_mixer_interface *mixer); +static void scarlett2_notify_volume(struct usb_mixer_interface *mixer); +static void scarlett2_notify_input_level(struct usb_mixer_interface *mixer); +static void scarlett2_notify_input_pad(struct usb_mixer_interface *mixer); +static void scarlett2_notify_input_air(struct usb_mixer_interface *mixer); +static void scarlett2_notify_input_phantom(struct usb_mixer_interface *mixer); static void scarlett2_notify_input_other(struct usb_mixer_interface *mixer); +static void scarlett2_notify_input_select(struct usb_mixer_interface *mixer); +static void scarlett2_notify_input_gain(struct usb_mixer_interface *mixer); +static void scarlett2_notify_autogain(struct usb_mixer_interface *mixer); +static void scarlett2_notify_input_safe(struct usb_mixer_interface *mixer); static void scarlett2_notify_monitor_other(struct usb_mixer_interface *mixer); static void scarlett2_notify_direct_monitor(struct usb_mixer_interface *mixer); +static void scarlett2_notify_power_status(struct usb_mixer_interface *mixer); /* Arrays of notification callback functions */ @@ -325,6 +344,48 @@ static const struct scarlett2_notification scarlett3a_notifications[] = { { 0, NULL } }; +static const struct scarlett2_notification scarlett4_solo_notifications[] = { + { 0x00000001, NULL }, /* ack, gets ignored */ + { 0x00000008, scarlett2_notify_sync }, + { 0x00400000, scarlett2_notify_input_air }, + { 0x00800000, scarlett2_notify_direct_monitor }, + { 0x01000000, scarlett2_notify_input_level }, + { 0x02000000, scarlett2_notify_input_phantom }, + { 0, NULL } +}; + +static const struct scarlett2_notification scarlett4_2i2_notifications[] = { + { 0x00000001, NULL }, /* ack, gets ignored */ + { 0x00000008, scarlett2_notify_sync }, + { 0x00200000, scarlett2_notify_input_safe }, + { 0x00400000, scarlett2_notify_autogain }, + { 0x00800000, scarlett2_notify_input_air }, + { 0x01000000, scarlett2_notify_direct_monitor }, + { 0x02000000, scarlett2_notify_input_select }, + { 0x04000000, scarlett2_notify_input_level }, + { 0x08000000, scarlett2_notify_input_phantom }, + { 0x10000000, NULL }, /* power status, ignored */ + { 0x40000000, scarlett2_notify_input_gain }, + { 0x80000000, NULL }, /* power status, ignored */ + { 0, NULL } +}; + +static const struct scarlett2_notification scarlett4_4i4_notifications[] = { + { 0x00000001, NULL }, /* ack, gets ignored */ + { 0x00000008, scarlett2_notify_sync }, + { 0x00200000, scarlett2_notify_input_safe }, + { 0x00400000, scarlett2_notify_autogain }, + { 0x00800000, scarlett2_notify_input_air }, + { 0x01000000, scarlett2_notify_input_select }, + { 0x02000000, scarlett2_notify_input_level }, + { 0x04000000, scarlett2_notify_input_phantom }, + { 0x08000000, scarlett2_notify_power_status }, /* power external */ + { 0x20000000, scarlett2_notify_input_gain }, + { 0x40000000, scarlett2_notify_power_status }, /* power status */ + { 0x80000000, scarlett2_notify_volume }, + { 0, NULL } +}; + /* Configuration parameters that can be read and written */ enum { SCARLETT2_CONFIG_DIM_MUTE, @@ -543,6 +604,123 @@ static const struct scarlett2_config_set scarlett2_config_set_gen3c = { } }; +/* Solo Gen 4 */ +static const struct scarlett2_config_set scarlett2_config_set_gen4_solo = { + .notifications = scarlett4_solo_notifications, + .gen4_write_addr = 0xd8, + .items = { + [SCARLETT2_CONFIG_MSD_SWITCH] = { + .offset = 0x47, .size = 8, .activate = 4 }, + + [SCARLETT2_CONFIG_DIRECT_MONITOR] = { + .offset = 0x108, .activate = 12 }, + + [SCARLETT2_CONFIG_PHANTOM_SWITCH] = { + .offset = 0x46, .activate = 9, .mute = 1 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x3d, .activate = 10, .mute = 1 }, + + [SCARLETT2_CONFIG_AIR_SWITCH] = { + .offset = 0x3e, .activate = 11 }, + + [SCARLETT2_CONFIG_DIRECT_MONITOR_GAIN] = { + .offset = 0x232, .size = 16, .activate = 26 } + } +}; + +/* 2i2 Gen 4 */ +static const struct scarlett2_config_set scarlett2_config_set_gen4_2i2 = { + .notifications = scarlett4_2i2_notifications, + .gen4_write_addr = 0xfc, + .items = { + [SCARLETT2_CONFIG_MSD_SWITCH] = { + .offset = 0x49, .size = 8, .activate = 4 }, // 0x41 ?? + + [SCARLETT2_CONFIG_DIRECT_MONITOR] = { + .offset = 0x14a, .activate = 16 }, + + [SCARLETT2_CONFIG_AUTOGAIN_SWITCH] = { + .offset = 0x135, .activate = 10 }, + + [SCARLETT2_CONFIG_AUTOGAIN_STATUS] = { + .offset = 0x137 }, + + [SCARLETT2_CONFIG_PHANTOM_SWITCH] = { + .offset = 0x48, .activate = 11, .mute = 1 }, + + [SCARLETT2_CONFIG_INPUT_GAIN] = { + .offset = 0x4b, .activate = 12 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x3c, .activate = 13, .mute = 1 }, + + [SCARLETT2_CONFIG_SAFE_SWITCH] = { + .offset = 0x147, .activate = 14 }, + + [SCARLETT2_CONFIG_AIR_SWITCH] = { + .offset = 0x3e, .activate = 15 }, + + [SCARLETT2_CONFIG_INPUT_SELECT_SWITCH] = { + .offset = 0x14b, .activate = 17 }, + + [SCARLETT2_CONFIG_INPUT_LINK_SWITCH] = { + .offset = 0x14e, .activate = 18 }, + + [SCARLETT2_CONFIG_DIRECT_MONITOR_GAIN] = { + .offset = 0x2a0, .size = 16, .activate = 36 } + } +}; + +/* 4i4 Gen 4 */ +static const struct scarlett2_config_set scarlett2_config_set_gen4_4i4 = { + .notifications = scarlett4_4i4_notifications, + .gen4_write_addr = 0x130, + .items = { + [SCARLETT2_CONFIG_MSD_SWITCH] = { + .offset = 0x5c, .size = 8, .activate = 4 }, + + [SCARLETT2_CONFIG_AUTOGAIN_SWITCH] = { + .offset = 0x13e, .activate = 10 }, + + [SCARLETT2_CONFIG_AUTOGAIN_STATUS] = { + .offset = 0x140 }, + + [SCARLETT2_CONFIG_PHANTOM_SWITCH] = { + .offset = 0x5a, .activate = 11, .mute = 1 }, + + [SCARLETT2_CONFIG_INPUT_GAIN] = { + .offset = 0x5e, .activate = 12 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x4e, .activate = 13, .mute = 1 }, + + [SCARLETT2_CONFIG_SAFE_SWITCH] = { + .offset = 0x150, .activate = 14 }, + + [SCARLETT2_CONFIG_AIR_SWITCH] = { + .offset = 0x50, .activate = 15 }, + + [SCARLETT2_CONFIG_INPUT_SELECT_SWITCH] = { + .offset = 0x153, .activate = 16 }, + + [SCARLETT2_CONFIG_INPUT_LINK_SWITCH] = { + .offset = 0x156, .activate = 17 }, + + [SCARLETT2_CONFIG_MASTER_VOLUME] = { + .offset = 0x32, .size = 16 }, + + [SCARLETT2_CONFIG_HEADPHONE_VOLUME] = { + .offset = 0x3a, .size = 16 }, + + [SCARLETT2_CONFIG_POWER_EXT] = { + .offset = 0x168 }, + + [SCARLETT2_CONFIG_POWER_STATUS] = { + .offset = 0x66 } + } +}; + /* Clarett USB and Clarett+ devices: 2Pre, 4Pre, 8Pre */ static const struct scarlett2_config_set scarlett2_config_set_clarett = { .notifications = scarlett2_notifications, @@ -1274,6 +1452,160 @@ static const struct scarlett2_device_info s18i20_gen3_info = { } }; +static const struct scarlett2_device_info solo_gen4_info = { + .config_set = &scarlett2_config_set_gen4_solo, + .min_firmware_version = 2115, + + .level_input_count = 1, + .air_input_count = 1, + .air_input_first = 1, + .air_option = 1, + .phantom_count = 1, + .phantom_first = 1, + .inputs_per_phantom = 1, + .direct_monitor = 1, + .dsp_count = 2, + + .port_count = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 2, 2 }, + [SCARLETT2_PORT_TYPE_MIX] = { 8, 6 }, + [SCARLETT2_PORT_TYPE_PCM] = { 2, 4 }, + }, + + .mux_assignment = { { + { SCARLETT2_PORT_TYPE_MIX, 4, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 2, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 0, 4 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 2 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_MIX, 4, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 2, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 0, 4 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 2 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_MIX, 4, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 2, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 0, 4 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 2 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 }, + { 0, 0, 0 }, + } }, + + .meter_map = { + { 6, 2 }, + { 4, 2 }, + { 8, 4 }, + { 2, 2 }, + { 0, 2 }, + { 0, 0 } + } +}; + +static const struct scarlett2_device_info s2i2_gen4_info = { + .config_set = &scarlett2_config_set_gen4_2i2, + .min_firmware_version = 2115, + + .level_input_count = 2, + .air_input_count = 2, + .air_option = 1, + .phantom_count = 1, + .inputs_per_phantom = 2, + .gain_input_count = 2, + .direct_monitor = 2, + .dsp_count = 2, + + .port_count = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 2, 2 }, + [SCARLETT2_PORT_TYPE_MIX] = { 6, 6 }, + [SCARLETT2_PORT_TYPE_PCM] = { 2, 4 }, + }, + + .mux_assignment = { { + { SCARLETT2_PORT_TYPE_MIX, 4, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 2, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 0, 4 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 2 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_MIX, 4, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 2, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 0, 4 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 2 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_MIX, 4, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 2, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 0, 4 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 2 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 }, + { 0, 0, 0 }, + } }, + + .meter_map = { + { 6, 2 }, + { 4, 2 }, + { 8, 4 }, + { 2, 2 }, + { 0, 2 }, + { 0, 0 } + } +}; + +static const struct scarlett2_device_info s4i4_gen4_info = { + .config_set = &scarlett2_config_set_gen4_4i4, + .min_firmware_version = 2089, + + .level_input_count = 2, + .air_input_count = 2, + .air_option = 1, + .phantom_count = 2, + .inputs_per_phantom = 1, + .gain_input_count = 2, + .dsp_count = 2, + + .port_count = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 4, 6 }, + [SCARLETT2_PORT_TYPE_MIX] = { 8, 12 }, + [SCARLETT2_PORT_TYPE_PCM] = { 6, 6 }, + }, + + .mux_assignment = { { + { SCARLETT2_PORT_TYPE_MIX, 10, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 0, 6 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 10 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_MIX, 10, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 0, 6 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 10 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_MIX, 10, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 0, 6 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 10 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 }, + { 0, 0, 0 }, + } }, + + .meter_map = { + { 16, 8 }, + { 6, 10 }, + { 0, 6 }, + { 0, 0 } + } +}; + static const struct scarlett2_device_info clarett_2pre_info = { .config_set = &scarlett2_config_set_clarett, .level_input_count = 2, @@ -1451,6 +1783,11 @@ static const struct scarlett2_device_entry scarlett2_devices[] = { { USB_ID(0x1235, 0x8214), &s18i8_gen3_info, "Scarlett Gen 3" }, { USB_ID(0x1235, 0x8215), &s18i20_gen3_info, "Scarlett Gen 3" }, + /* Supported Gen 4 devices */ + { USB_ID(0x1235, 0x8218), &solo_gen4_info, "Scarlett Gen 4" }, + { USB_ID(0x1235, 0x8219), &s2i2_gen4_info, "Scarlett Gen 4" }, + { USB_ID(0x1235, 0x821a), &s4i4_gen4_info, "Scarlett Gen 4" }, + /* Supported Clarett USB/Clarett+ devices */ { USB_ID(0x1235, 0x8206), &clarett_2pre_info, "Clarett USB" }, { USB_ID(0x1235, 0x8207), &clarett_4pre_info, "Clarett USB" }, @@ -6353,8 +6690,7 @@ static void scarlett2_notify_monitor(struct usb_mixer_interface *mixer) } /* Notify on volume change (Gen 4) */ -static __always_unused void scarlett2_notify_volume( - struct usb_mixer_interface *mixer) +static void scarlett2_notify_volume(struct usb_mixer_interface *mixer) { struct scarlett2_data *private = mixer->private_data; @@ -6460,8 +6796,7 @@ static void scarlett2_notify_input_other(struct usb_mixer_interface *mixer) } /* Notify on input select change */ -static __always_unused void scarlett2_notify_input_select( - struct usb_mixer_interface *mixer) +static void scarlett2_notify_input_select(struct usb_mixer_interface *mixer) { struct snd_card *card = mixer->chip->card; struct scarlett2_data *private = mixer->private_data; @@ -6483,8 +6818,7 @@ static __always_unused void scarlett2_notify_input_select( } /* Notify on input gain change */ -static __always_unused void scarlett2_notify_input_gain( - struct usb_mixer_interface *mixer) +static void scarlett2_notify_input_gain(struct usb_mixer_interface *mixer) { struct snd_card *card = mixer->chip->card; struct scarlett2_data *private = mixer->private_data; @@ -6502,8 +6836,7 @@ static __always_unused void scarlett2_notify_input_gain( } /* Notify on autogain change */ -static __always_unused void scarlett2_notify_autogain( - struct usb_mixer_interface *mixer) +static void scarlett2_notify_autogain(struct usb_mixer_interface *mixer) { struct snd_card *card = mixer->chip->card; struct scarlett2_data *private = mixer->private_data; @@ -6526,8 +6859,7 @@ static __always_unused void scarlett2_notify_autogain( } /* Notify on input safe switch change */ -static __always_unused void scarlett2_notify_input_safe( - struct usb_mixer_interface *mixer) +static void scarlett2_notify_input_safe(struct usb_mixer_interface *mixer) { struct snd_card *card = mixer->chip->card; struct scarlett2_data *private = mixer->private_data; @@ -6603,8 +6935,7 @@ static void scarlett2_notify_direct_monitor(struct usb_mixer_interface *mixer) } /* Notify on power change */ -static __always_unused void scarlett2_notify_power_status( - struct usb_mixer_interface *mixer) +static void scarlett2_notify_power_status(struct usb_mixer_interface *mixer) { struct snd_card *card = mixer->chip->card; struct scarlett2_data *private = mixer->private_data; -- cgit v1.2.3 From 66e82d219924f6112509f7e8f8e687fcc81a16e3 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 19 Dec 2023 14:34:46 +0100 Subject: ALSA: mark all struct bus_type as const Now that the driver core can properly handle constant struct bus_type, move all of the sound subsystem struct bus_type structures as const, placing them into read-only memory which can not be modified at runtime. Note, this fixes a duplicate definition of ac97_bus_type, which somehow was declared extern in a .h file, and then static as a prototype in a .c file, and then properly later on in the same .c file. Amazing that no compiler warning ever showed up for this. Cc: Jaroslav Kysela Cc: Takashi Iwai Cc: Dawei Li Cc: Yu Liao Cc: "Rafael J. Wysocki" Cc: Hans de Goede Cc: linux-sound@vger.kernel.org Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/2023121945-immersion-budget-d0aa@gregkh Signed-off-by: Takashi Iwai --- include/sound/ac97_codec.h | 2 +- include/sound/hdaudio.h | 2 +- sound/ac97/bus.c | 2 -- sound/ac97_bus.c | 2 +- sound/hda/hda_bus_type.c | 2 +- 5 files changed, 4 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/include/sound/ac97_codec.h b/include/sound/ac97_codec.h index c495c6d5fbe0..4bd3be3a3192 100644 --- a/include/sound/ac97_codec.h +++ b/include/sound/ac97_codec.h @@ -410,7 +410,7 @@ int snd_ac97_pcm_close(struct ac97_pcm *pcm); int snd_ac97_pcm_double_rate_rules(struct snd_pcm_runtime *runtime); /* ad hoc AC97 device driver access */ -extern struct bus_type ac97_bus_type; +extern const struct bus_type ac97_bus_type; /* AC97 platform_data adding function */ static inline void snd_ac97_dev_add_pdata(struct snd_ac97 *ac97, void *data) diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 62a3cd154ff2..a73d7f34f4e5 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -33,7 +33,7 @@ struct hda_device_id; /* * exported bus type */ -extern struct bus_type snd_hda_bus_type; +extern const struct bus_type snd_hda_bus_type; /* * generic arrays diff --git a/sound/ac97/bus.c b/sound/ac97/bus.c index 3173e9d98927..1dc7965eb14b 100644 --- a/sound/ac97/bus.c +++ b/sound/ac97/bus.c @@ -28,8 +28,6 @@ static DEFINE_MUTEX(ac97_controllers_mutex); static DEFINE_IDR(ac97_adapter_idr); static LIST_HEAD(ac97_controllers); -static struct bus_type ac97_bus_type; - static inline struct ac97_controller* to_ac97_controller(struct device *ac97_adapter) { diff --git a/sound/ac97_bus.c b/sound/ac97_bus.c index c7aee8c42c55..7ea274c55900 100644 --- a/sound/ac97_bus.c +++ b/sound/ac97_bus.c @@ -75,7 +75,7 @@ int snd_ac97_reset(struct snd_ac97 *ac97, bool try_warm, unsigned int id, } EXPORT_SYMBOL_GPL(snd_ac97_reset); -struct bus_type ac97_bus_type = { +const struct bus_type ac97_bus_type = { .name = "ac97", }; diff --git a/sound/hda/hda_bus_type.c b/sound/hda/hda_bus_type.c index 4cd94178df9f..cce2c30511a2 100644 --- a/sound/hda/hda_bus_type.c +++ b/sound/hda/hda_bus_type.c @@ -76,7 +76,7 @@ static int hda_uevent(const struct device *dev, struct kobj_uevent_env *env) return 0; } -struct bus_type snd_hda_bus_type = { +const struct bus_type snd_hda_bus_type = { .name = "hdaudio", .match = hda_bus_match, .uevent = hda_uevent, -- cgit v1.2.3 From 76f5f55c45b906710c9565a7e68c8d782c46b394 Mon Sep 17 00:00:00 2001 From: Gergo Koteles Date: Sat, 30 Dec 2023 01:09:42 +0100 Subject: ALSA: hda/tas2781: add ptrs to calibration functions Make calibration functions configurable to support different calibration data storage modes. Signed-off-by: Gergo Koteles Link: https://lore.kernel.org/r/5859c77ffef752b8a9784713b412d815d7e2688c.1703891777.git.soyer@irl.hu Signed-off-by: Takashi Iwai --- include/sound/tas2781.h | 5 +++++ sound/pci/hda/tas2781_hda_i2c.c | 25 +++++++++++-------------- sound/soc/codecs/tas2781-comlib.c | 15 +++++++++++++++ 3 files changed, 31 insertions(+), 14 deletions(-) (limited to 'include') diff --git a/include/sound/tas2781.h b/include/sound/tas2781.h index a6c808b22318..e17ceab4fead 100644 --- a/include/sound/tas2781.h +++ b/include/sound/tas2781.h @@ -131,6 +131,9 @@ struct tasdevice_priv { const struct firmware *fmw, int offset); int (*tasdevice_load_block)(struct tasdevice_priv *tas_priv, struct tasdev_blk *block); + + int (*save_calibration)(struct tasdevice_priv *tas_priv); + void (*apply_calibration)(struct tasdevice_priv *tas_priv); }; void tas2781_reset(struct tasdevice_priv *tas_dev); @@ -139,6 +142,8 @@ int tascodec_init(struct tasdevice_priv *tas_priv, void *codec, struct tasdevice_priv *tasdevice_kzalloc(struct i2c_client *i2c); int tasdevice_init(struct tasdevice_priv *tas_priv); void tasdevice_remove(struct tasdevice_priv *tas_priv); +int tasdevice_save_calibration(struct tasdevice_priv *tas_priv); +void tasdevice_apply_calibration(struct tasdevice_priv *tas_priv); int tasdevice_dev_read(struct tasdevice_priv *tas_priv, unsigned short chn, unsigned int reg, unsigned int *value); int tasdevice_dev_write(struct tasdevice_priv *tas_priv, diff --git a/sound/pci/hda/tas2781_hda_i2c.c b/sound/pci/hda/tas2781_hda_i2c.c index c8523df4105f..4b639120c981 100644 --- a/sound/pci/hda/tas2781_hda_i2c.c +++ b/sound/pci/hda/tas2781_hda_i2c.c @@ -479,7 +479,7 @@ static int tas2781_save_calibration(struct tasdevice_priv *tas_priv) dev_dbg(tas_priv->dev, "%4ld-%2d-%2d, %2d:%2d:%2d\n", tm->tm_year, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); - tas2781_apply_calib(tas_priv); + tasdevice_apply_calibration(tas_priv); } else tas_priv->cali_data.total_sz = 0; @@ -582,7 +582,7 @@ static void tasdev_fw_ready(const struct firmware *fmw, void *context) /* If calibrated data occurs error, dsp will still works with default * calibrated data inside algo. */ - tas2781_save_calibration(tas_priv); + tasdevice_save_calibration(tas_priv); tasdevice_tuning_switch(tas_hda->priv, 0); @@ -685,10 +685,6 @@ static int tas2781_hda_i2c_probe(struct i2c_client *clt) const char *device_name; int ret; - if (strstr(dev_name(&clt->dev), "TIAS2781")) - device_name = "TIAS2781"; - else - return -ENODEV; tas_hda = devm_kzalloc(&clt->dev, sizeof(*tas_hda), GFP_KERNEL); if (!tas_hda) @@ -701,6 +697,13 @@ static int tas2781_hda_i2c_probe(struct i2c_client *clt) if (!tas_hda->priv) return -ENOMEM; + if (strstr(dev_name(&clt->dev), "TIAS2781")) { + device_name = "TIAS2781"; + tas_hda->priv->save_calibration = tas2781_save_calibration; + tas_hda->priv->apply_calibration = tas2781_apply_calib; + } else + return -ENODEV; + tas_hda->priv->irq_info.irq = clt->irq; ret = tas2781_read_acpi(tas_hda->priv, device_name); if (ret) @@ -767,8 +770,6 @@ static int tas2781_runtime_suspend(struct device *dev) static int tas2781_runtime_resume(struct device *dev) { struct tas2781_hda *tas_hda = dev_get_drvdata(dev); - unsigned long calib_data_sz = - tas_hda->priv->ndev * TASDEVICE_SPEAKER_CALIBRATION_SIZE; dev_dbg(tas_hda->dev, "Runtime Resume\n"); @@ -779,8 +780,7 @@ static int tas2781_runtime_resume(struct device *dev) /* If calibrated data occurs error, dsp will still works with default * calibrated data inside algo. */ - if (tas_hda->priv->cali_data.total_sz > calib_data_sz) - tas2781_apply_calib(tas_hda->priv); + tasdevice_apply_calibration(tas_hda->priv); mutex_unlock(&tas_hda->priv->codec_lock); @@ -811,8 +811,6 @@ static int tas2781_system_suspend(struct device *dev) static int tas2781_system_resume(struct device *dev) { struct tas2781_hda *tas_hda = dev_get_drvdata(dev); - unsigned long calib_data_sz = - tas_hda->priv->ndev * TASDEVICE_SPEAKER_CALIBRATION_SIZE; int i, ret; dev_info(tas_hda->priv->dev, "System Resume\n"); @@ -834,8 +832,7 @@ static int tas2781_system_resume(struct device *dev) /* If calibrated data occurs error, dsp will still work with default * calibrated data inside algo. */ - if (tas_hda->priv->cali_data.total_sz > calib_data_sz) - tas2781_apply_calib(tas_hda->priv); + tasdevice_apply_calibration(tas_hda->priv); mutex_unlock(&tas_hda->priv->codec_lock); return 0; diff --git a/sound/soc/codecs/tas2781-comlib.c b/sound/soc/codecs/tas2781-comlib.c index 00e35169ae49..b7e56ceb1acf 100644 --- a/sound/soc/codecs/tas2781-comlib.c +++ b/sound/soc/codecs/tas2781-comlib.c @@ -412,6 +412,21 @@ void tasdevice_remove(struct tasdevice_priv *tas_priv) } EXPORT_SYMBOL_GPL(tasdevice_remove); +int tasdevice_save_calibration(struct tasdevice_priv *tas_priv) +{ + if (tas_priv->save_calibration) + return tas_priv->save_calibration(tas_priv); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(tasdevice_save_calibration); + +void tasdevice_apply_calibration(struct tasdevice_priv *tas_priv) +{ + if (tas_priv->apply_calibration && tas_priv->cali_data.total_sz) + tas_priv->apply_calibration(tas_priv); +} +EXPORT_SYMBOL_GPL(tasdevice_apply_calibration); + static int tasdevice_clamp(int val, int max, unsigned int invert) { if (val > max) -- cgit v1.2.3 From c021ca729fe870d25a4798491c383c89b3091650 Mon Sep 17 00:00:00 2001 From: Gergo Koteles Date: Sat, 30 Dec 2023 01:09:43 +0100 Subject: ALSA: hda/tas2781: add configurable global i2c address Make the global i2c address configurable to support compatible amplifiers with different global i2c address. Signed-off-by: Gergo Koteles Link: https://lore.kernel.org/r/a252f1efeed5049f027f01e699c9e10e1e05bf9e.1703891777.git.soyer@irl.hu Signed-off-by: Takashi Iwai --- include/sound/tas2781.h | 2 ++ sound/pci/hda/tas2781_hda_i2c.c | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/sound/tas2781.h b/include/sound/tas2781.h index e17ceab4fead..dde9f8120d4c 100644 --- a/include/sound/tas2781.h +++ b/include/sound/tas2781.h @@ -121,6 +121,8 @@ struct tasdevice_priv { bool force_fwload_status; bool playback_started; bool isacpi; + unsigned int global_addr; + int (*fw_parse_variable_header)(struct tasdevice_priv *tas_priv, const struct firmware *fmw, int offset); int (*fw_parse_program_data)(struct tasdevice_priv *tas_priv, diff --git a/sound/pci/hda/tas2781_hda_i2c.c b/sound/pci/hda/tas2781_hda_i2c.c index 4b639120c981..503be3fbce56 100644 --- a/sound/pci/hda/tas2781_hda_i2c.c +++ b/sound/pci/hda/tas2781_hda_i2c.c @@ -81,7 +81,7 @@ static int tas2781_get_i2c_res(struct acpi_resource *ares, void *data) if (i2c_acpi_get_i2c_resource(ares, &sb)) { if (tas_priv->ndev < TASDEVICE_MAX_CHANNELS && - sb->slave_address != TAS2781_GLOBAL_ADDR) { + sb->slave_address != tas_priv->global_addr) { tas_priv->tasdevice[tas_priv->ndev].dev_addr = (unsigned int)sb->slave_address; tas_priv->ndev++; @@ -701,6 +701,7 @@ static int tas2781_hda_i2c_probe(struct i2c_client *clt) device_name = "TIAS2781"; tas_hda->priv->save_calibration = tas2781_save_calibration; tas_hda->priv->apply_calibration = tas2781_apply_calib; + tas_hda->priv->global_addr = TAS2781_GLOBAL_ADDR; } else return -ENODEV; -- cgit v1.2.3 From c3ca4458cc2f719b1652abaa8d2fbf7f3d4311cb Mon Sep 17 00:00:00 2001 From: Gergo Koteles Date: Sat, 30 Dec 2023 01:09:44 +0100 Subject: ALSA: hda/tas2781: add TAS2563 support for 14ARB7 The INT8866 belongs to the Lenovo Yoga 7 Gen 7 AMD 14ARB7 laptop. It has two TAS2563 amplifier. Add the PNP ID and calibration functions to handle them. ACPI excerpt: Scope (_SB.I2CD) { Device (TAS) { Name (_HID, "INT8866") // _HID: Hardware ID Name (_UID, Zero) // _UID: Unique ID Method (_CRS, 0, NotSerialized) // _CRS: Current Resource Settings { Name (RBUF, ResourceTemplate () { I2cSerialBusV2 (0x004C, ControllerInitiated, 0x00061A80, AddressingMode7Bit, "\\_SB.I2CD", 0x00, ResourceConsumer, , Exclusive, ) I2cSerialBusV2 (0x004D, ControllerInitiated, 0x00061A80, AddressingMode7Bit, "\\_SB.I2CD", 0x00, ResourceConsumer, , Exclusive, ) GpioInt (Edge, ActiveLow, SharedAndWake, PullNone, 0x0000, "\\_SB.GPIO", 0x00, ResourceConsumer, , ) { // Pin list 0x0020 } }) Return (RBUF) /* \_SB_.I2CD.TAS_._CRS.RBUF */ } Method (_STA, 0, NotSerialized) // _STA: Status { Return (0x0F) } } } Signed-off-by: Gergo Koteles Link: https://lore.kernel.org/r/3b8d4c602e1a46922f53bc9afc8b705d55aa4872.1703891777.git.soyer@irl.hu Signed-off-by: Takashi Iwai --- include/sound/tas2781.h | 1 + sound/pci/hda/tas2781_hda_i2c.c | 87 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) (limited to 'include') diff --git a/include/sound/tas2781.h b/include/sound/tas2781.h index dde9f8120d4c..0a86ab8d47b9 100644 --- a/include/sound/tas2781.h +++ b/include/sound/tas2781.h @@ -22,6 +22,7 @@ #define TAS2781_DRV_VER 1 #define SMARTAMP_MODULE_NAME "tas2781" #define TAS2781_GLOBAL_ADDR 0x40 +#define TAS2563_GLOBAL_ADDR 0x48 #define TASDEVICE_RATES (SNDRV_PCM_RATE_44100 |\ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |\ SNDRV_PCM_RATE_88200) diff --git a/sound/pci/hda/tas2781_hda_i2c.c b/sound/pci/hda/tas2781_hda_i2c.c index 503be3fbce56..4805cf0b6480 100644 --- a/sound/pci/hda/tas2781_hda_i2c.c +++ b/sound/pci/hda/tas2781_hda_i2c.c @@ -65,6 +65,24 @@ enum calib_data { CALIB_MAX }; +#define TAS2563_MAX_CHANNELS 4 + +#define TAS2563_CAL_POWER TASDEVICE_REG(0, 0x0d, 0x3c) +#define TAS2563_CAL_R0 TASDEVICE_REG(0, 0x0f, 0x34) +#define TAS2563_CAL_INVR0 TASDEVICE_REG(0, 0x0f, 0x40) +#define TAS2563_CAL_R0_LOW TASDEVICE_REG(0, 0x0f, 0x48) +#define TAS2563_CAL_TLIM TASDEVICE_REG(0, 0x10, 0x14) +#define TAS2563_CAL_N 5 +#define TAS2563_CAL_DATA_SIZE 4 +#define TAS2563_CAL_CH_SIZE 20 +#define TAS2563_CAL_ARRAY_SIZE 80 + +static unsigned int cal_regs[TAS2563_CAL_N] = { + TAS2563_CAL_POWER, TAS2563_CAL_R0, TAS2563_CAL_INVR0, + TAS2563_CAL_R0_LOW, TAS2563_CAL_TLIM, +}; + + struct tas2781_hda { struct device *dev; struct tasdevice_priv *priv; @@ -404,6 +422,69 @@ static const struct snd_kcontrol_new tas2781_dsp_conf_ctrl = { .put = tasdevice_config_put, }; +static void tas2563_apply_calib(struct tasdevice_priv *tas_priv) +{ + unsigned int data; + int offset = 0; + int ret; + + for (int i = 0; i < tas_priv->ndev; i++) { + for (int j = 0; j < TAS2563_CAL_N; ++j) { + data = cpu_to_be32( + *(uint32_t *)&tas_priv->cali_data.data[offset]); + ret = tasdevice_dev_bulk_write(tas_priv, i, cal_regs[j], + (unsigned char *)&data, TAS2563_CAL_DATA_SIZE); + if (ret) + dev_err(tas_priv->dev, + "Error writing calib regs\n"); + offset += TAS2563_CAL_DATA_SIZE; + } + } +} + +static int tas2563_save_calibration(struct tasdevice_priv *tas_priv) +{ + static efi_guid_t efi_guid = EFI_GUID(0x1f52d2a1, 0xbb3a, 0x457d, 0xbc, + 0x09, 0x43, 0xa3, 0xf4, 0x31, 0x0a, 0x92); + + static efi_char16_t *efi_vars[TAS2563_MAX_CHANNELS][TAS2563_CAL_N] = { + { L"Power_1", L"R0_1", L"InvR0_1", L"R0_Low_1", L"TLim_1" }, + { L"Power_2", L"R0_2", L"InvR0_2", L"R0_Low_2", L"TLim_2" }, + { L"Power_3", L"R0_3", L"InvR0_3", L"R0_Low_3", L"TLim_3" }, + { L"Power_4", L"R0_4", L"InvR0_4", L"R0_Low_4", L"TLim_4" }, + }; + + unsigned long max_size = TAS2563_CAL_DATA_SIZE; + unsigned int offset = 0; + efi_status_t status; + unsigned int attr; + + tas_priv->cali_data.data = devm_kzalloc(tas_priv->dev, + TAS2563_CAL_ARRAY_SIZE, GFP_KERNEL); + if (!tas_priv->cali_data.data) + return -ENOMEM; + + for (int i = 0; i < tas_priv->ndev; ++i) { + for (int j = 0; j < TAS2563_CAL_N; ++j) { + status = efi.get_variable(efi_vars[i][j], + &efi_guid, &attr, &max_size, + &tas_priv->cali_data.data[offset]); + if (status != EFI_SUCCESS || + max_size != TAS2563_CAL_DATA_SIZE) { + dev_warn(tas_priv->dev, + "Calibration data read failed %ld\n", status); + return -EINVAL; + } + offset += TAS2563_CAL_DATA_SIZE; + } + } + + tas_priv->cali_data.total_sz = offset; + tasdevice_apply_calibration(tas_priv); + + return 0; +} + static void tas2781_apply_calib(struct tasdevice_priv *tas_priv) { static const unsigned char page_array[CALIB_MAX] = { @@ -702,6 +783,11 @@ static int tas2781_hda_i2c_probe(struct i2c_client *clt) tas_hda->priv->save_calibration = tas2781_save_calibration; tas_hda->priv->apply_calibration = tas2781_apply_calib; tas_hda->priv->global_addr = TAS2781_GLOBAL_ADDR; + } else if (strstr(dev_name(&clt->dev), "INT8866")) { + device_name = "INT8866"; + tas_hda->priv->save_calibration = tas2563_save_calibration; + tas_hda->priv->apply_calibration = tas2563_apply_calib; + tas_hda->priv->global_addr = TAS2563_GLOBAL_ADDR; } else return -ENODEV; @@ -851,6 +937,7 @@ static const struct i2c_device_id tas2781_hda_i2c_id[] = { static const struct acpi_device_id tas2781_acpi_hda_match[] = { {"TIAS2781", 0 }, + {"INT8866", 0 }, {} }; MODULE_DEVICE_TABLE(acpi, tas2781_acpi_hda_match); -- cgit v1.2.3